浅谈算法和数据结构: 一 栈和队列

简介: 浅谈算法和数据结构: 一 栈和队列

1. 基本概念

概念很简单,栈 (Stack)是一种后进先出(last in first off,LIFO)的数据结构,而队列(Queue)则是一种先进先出 (fisrt in first out,FIFO)的结构,如下图

d8228050cd331168c52f8c0176919061.png

2. 实现

现在来看如何实现以上的两个数据结构。在动手之前,Framework Design Guidelines这本书告诉我们,在设计API或者实体类的时候,应当围绕场景编写API规格说明书。


1.1 Stack的实现


栈是一种后进先出的数据结构,对于Stack 我们希望至少要对外提供以下几个方法:



b935e102d4e89fdee1a638cf8660e075.jpg

Stack<T>()

创建一个空的栈

void Push(T s)

往栈中添加一个新的元素

T Pop()

移除并返回最近添加的元素

boolean IsEmpty()

栈是否为空

int Size()

栈中元素的个数

要实现这些功能,我们有两中方法,数组和链表,先看链表实现:

栈的链表实现:

我们首先定义一个内部类来保存每个链表的节点,该节点包括当前的值以及指向下一个的值,然后建立一个节点保存位于栈顶的值以及记录栈的元素个数;

class Node
{
    public T Item{get;set;}
    public Node Next { get; set; }
}
private Node first = null;
private int number = 0;

现在来实现Push方法,即向栈顶压入一个元素,首先保存原先的位于栈顶的元素,然后新建一个新的栈顶元素,然后将该元素的下一个指向原先的栈顶元素。整个Pop过程如下:a108a9d672808ae1e33e87874f4e2196.png

实现代码如下:

void Push(T node)
{
    Node oldFirst = first;
    first = new Node();
    first.Item= node;
    first.Next = oldFirst;
    number++;
}

Pop方法也很简单,首先保存栈顶元素的值,然后将栈顶元素设置为下一个元素:

39271325e7cc791fcb79643889e0d581.png

T Pop()
{
    T item = first.Item;
    first = first.Next;
    number--;
    return item;
}

基于链表的Stack实现,在最坏的情况下只需要常量的时间来进行Push和Pop操作。

栈的数组实现:

我们可以使用数组来存储栈中的元素Push的时候,直接添加一个元素S[N]到数组中,Pop的时候直接返回S[N-1].

fe3ded7a64ea5bcd01e30f3979563fc2.png

首先,我们定义一个数组,然后在构造函数中给定初始化大小,Push方法实现如下,就是集合里添加一个元素:

T[] item;
int number = 0;
public StackImplementByArray(int capacity)
{
    item = new T[capacity];
}
public void Push(T _item)
{
    if (number == item.Length) Resize(2 * item.Length);
    item[number++] = _item;
}

Pop方法:

public T Pop()
{
    T temp = item[--number];
    item[number] = default(T);
    if (number > 0 && number == item.Length / 4) Resize(item.Length / 2);
    return temp;
}

在Push和Pop方法中,为了节省内存空间,我们会对数组进行整理。Push的时候,当元素的个数达到数组的Capacity的时候,我们开辟2倍于当前元素的新数组,然后将原数组中的元素拷贝到新数组中。Pop的时候,当元素的个数小于当前容量的1/4的时候,我们将原数组的大小容量减少1/2。


Resize方法基本就是数组复制:

private void Resize(int capacity)
{
    T[] temp = new T[capacity];
    for (int i = 0; i < item.Length; i++)
    {
        temp[i] = item[i];
    }
    item = temp;
}

当我们缩小数组的时候,采用的是判断1/4的情况,这样效率要比1/2要高,因为可以有效避免在1/2附件插入,删除,插入,删除,从而频繁的扩大和缩小数组的情况。下图展示了在插入和删除的情况下数组中的元素以及数组大小的变化情况:

分析:1. Pop和Push操作在最坏的情况下与元素个数成比例的N的时间,时间主要花费在扩大或者缩小数组的个数时,数组拷贝上。


2. 元素在内存中分布紧凑,密度高,便于利用内存的时间和空间局部性,便于CPU进行缓存,较LinkList内存占用小,效率高。


2.2 Queue的实现


Queue是一种先进先出的数据结构,和Stack一样,他也有链表和数组两种实现,理解了Stack的实现后,Queue的实现就比较简单了。



c386dea0ed3deefc5c13f74352e4ca42.png

Stack<T>()

创建一个空的队列

void Enqueue(T s)

往队列中添加一个新的元素

T Dequeue()

移除队列中最早添加的元素

boolean IsEmpty()

队列是否为空

int Size()

队列中元素的个数

首先看链表的实现:

Dequeue方法就是返回链表中的第一个元素,这个和Stack中的Pop方法相似:

public T Dequeue()
{
    T temp = first.Item;
    first = first.Next;
    number--;
    if (IsEmpety())
        last = null;
    return temp;
}

Enqueue和Stack的Push方法不同,他是在链表的末尾增加新的元素:

public void Enqueue(T item)
{
    Node oldLast = last;
    last = new Node();
    last.Item = item;
    if (IsEmpety())
    {
        first = last;
    }
    else
    {
        oldLast.Next = last;
    }
    number++;
}

同样地,现在再来看如何使用数组来实现Queue,首先我们使用数组来保存数据,并定义变量head和tail来记录Queue的首尾元素。


0508f5fee9c1872933f8563d6cb88161.png

和Stack的实现方式不同,在Queue中,我们定义了head和tail来记录头元素和尾元素。当enqueue的时候,tial加1,将元素放在尾部,当dequeue的时候,head减1,并返回。

public void Enqueue(T _item)
{
    if ((head - tail + 1) == item.Length) Resize(2 * item.Length);
    item[tail++] = _item;
}
public T Dequeue()
{
    T temp = item[--head];
    item[head] = default(T);
    if (head > 0 && (tail - head + 1) == item.Length / 4) Resize(item.Length / 2);
    return temp;
}
private void Resize(int capacity)
{
    T[] temp = new T[capacity];
    int index = 0;
    for (int i = head; i < tail; i++)
    {
        temp[++index] = item[i];
    }
    item = temp;
}


目录
相关文章
|
14天前
|
C语言
【数据结构】栈和队列(c语言实现)(附源码)
本文介绍了栈和队列两种数据结构。栈是一种只能在一端进行插入和删除操作的线性表,遵循“先进后出”原则;队列则在一端插入、另一端删除,遵循“先进先出”原则。文章详细讲解了栈和队列的结构定义、方法声明及实现,并提供了完整的代码示例。栈和队列在实际应用中非常广泛,如二叉树的层序遍历和快速排序的非递归实现等。
90 9
|
5天前
|
存储 算法
非递归实现后序遍历时,如何避免栈溢出?
后序遍历的递归实现和非递归实现各有优缺点,在实际应用中需要根据具体的问题需求、二叉树的特点以及性能和空间的限制等因素来选择合适的实现方式。
14 1
|
8天前
|
存储 算法 Java
数据结构的栈
栈作为一种简单而高效的数据结构,在计算机科学和软件开发中有着广泛的应用。通过合理地使用栈,可以有效地解决许多与数据存储和操作相关的问题。
|
11天前
|
存储 JavaScript 前端开发
执行上下文和执行栈
执行上下文是JavaScript运行代码时的环境,每个执行上下文都有自己的变量对象、作用域链和this值。执行栈用于管理函数调用,每当调用一个函数,就会在栈中添加一个新的执行上下文。
|
13天前
|
存储
系统调用处理程序在内核栈中保存了哪些上下文信息?
【10月更文挑战第29天】系统调用处理程序在内核栈中保存的这些上下文信息对于保证系统调用的正确执行和用户程序的正常恢复至关重要。通过准确地保存和恢复这些信息,操作系统能够实现用户模式和内核模式之间的无缝切换,为用户程序提供稳定、可靠的系统服务。
40 4
|
17天前
|
算法 安全 NoSQL
2024重生之回溯数据结构与算法系列学习之栈和队列精题汇总(10)【无论是王道考研人还是IKUN都能包会的;不然别给我家鸽鸽丢脸好嘛?】
数据结构王道第3章之IKUN和I原达人之数据结构与算法系列学习栈与队列精题详解、数据结构、C++、排序算法、java、动态规划你个小黑子;这都学不会;能不能不要给我家鸽鸽丢脸啊~除了会黑我家鸽鸽还会干嘛?!!!
|
29天前
|
存储 算法 Java
Set接口及其主要实现类(如HashSet、TreeSet)如何通过特定数据结构和算法确保元素唯一性
Java Set因其“无重复”特性在集合框架中独树一帜。本文解析了Set接口及其主要实现类(如HashSet、TreeSet)如何通过特定数据结构和算法确保元素唯一性,并提供了最佳实践建议,包括选择合适的Set实现类和正确实现自定义对象的hashCode()与equals()方法。
32 4
|
30天前
数据结构(栈与列队)
数据结构(栈与列队)
17 1
|
1月前
|
算法 程序员 索引
数据结构与算法学习七:栈、数组模拟栈、单链表模拟栈、栈应用实例 实现 综合计算器
栈的基本概念、应用场景以及如何使用数组和单链表模拟栈,并展示了如何利用栈和中缀表达式实现一个综合计算器。
30 1
数据结构与算法学习七:栈、数组模拟栈、单链表模拟栈、栈应用实例 实现 综合计算器
|
1月前
初步认识栈和队列
初步认识栈和队列
58 10

热门文章

最新文章