栈
栈是一种十分常见的数据结构,例如在函数中的递归就和栈有关,当当前函数执行完毕时要回到上一个状态就是通过栈去获取的。栈本身就是数组或者链表,只不过去定义了他取和放数据的方式。
概念
栈的定义就是后进先出,当一个元素进入栈时,越晚进入的元素越先被取出。
例如:有一种数据为1,2,3,4,5。然后将他们一次进栈,那么栈内元素顺序则为5,4,3,2,1。然后让他们按顺序出栈则得到结果5,4,3,2,1。
实现
栈实现一般有两种方式,一种是使用数组,一种是使用链表。栈中有个比较常说的概念就是栈顶指针,栈顶指针就是记录指栈内第一个出栈的元素的位置。
链表实现可以之间像普通链表方式一样存储元素,只不过每个元素的指针应该指向他的前一个结点而不是后一个结点。然后只要用一个指针去指向最后一个元素(此时该指针便是栈顶指针),那么进栈时就可以直接将元素插入到栈顶元素之后,在更新栈顶元素为新元素即可,出栈则是将栈顶指针元素输出,然后让其指向前一个元素。
数组实现思路实际上和链表类似,只不过通过下标方式去代替指针。只不过数组中栈顶元素一般指向的是下一个元素进栈的位置,而不是当前元素中栈顶元素的位置。这样设计主要是方便插入元素,与链表不同,链表插入元素必须指定上一个元素位置,然后让新元素指向它,而数组不需要因为是连续存储的,所有他的位置直接放入其对应下标即可。因为栈顶指针是指向栈顶元素的后一位,所有取栈顶元素时就要取栈顶元素指针减一的下标。
代码实现
链表实现:
public class LinkedNode<T>
{
public T Value { get; set; }
public LinkedNode<T> Next { get; set; }
public LinkedNode() { }
public LinkedNode(T value) => Value = value;
}
public class MyLinkedStack<T>
{
public LinkedNode<T> bottom;
public LinkedNode<T> top;
private int count = 0;
public int Count => count;
public MyLinkedStack()
{
bottom = new LinkedNode<T>();
top = bottom;
}
public void Push(T value)
{
LinkedNode<T> node = new LinkedNode<T>(value);
node.Next = top;
top = node;
count++;
}
public T Pop()
{
if (top == bottom)
throw new Exception("Stack is empty");
T value = top.Value;
top = top.Next;
count--;
return value;
}
public T Peek()
{
if (top == bottom)
throw new Exception("Stack is empty");
return top.Value;
}
}
这里我将第一个结点(栈底元素)作为哨兵结点,即给他初始化,但他只是用来方便后续结点连接的,本身元素没有意义。这么写可以免去栈内没有元素时插入的麻烦处理。
数组实现:
public class MyStack<T>
{
private T[] items;
private int top;
private int count;
public int Count => count;
public int Capacity => items == null ? 0 : items.Length;
public MyStack(int capacity = 10)
{
items = new T[capacity + 1];
top = 0;
count = 0;
}
public void Push(T item)
{
if (count == Capacity)
Expansion();
items[top] = item;
count++;
top++;
}
public T Pop()
{
if (count == 0)
throw new Exception("Stack is empty");
top--;
T value = items[top];
count--;
return value;
}
public T Peek()
{
if (count == 0)
throw new Exception("Stack is empty");
return items[top - 1];
}
private void Expansion()
{
int newCapacity = Capacity * 2;
T[] newItems = new T[newCapacity];
for (int i = 0; i < top; i++)
newItems[i] = items[i];
items = newItems;
}
}
这里可以不使用count取记录元素数量,top可以完全代替他。当top为0时,说明栈空,当top为capacity时,说明栈满。
这里要注意的一点是取栈顶元素时取地是top-1的值,不是top的值;
测试
这里选用leetcode的困难题做测试,实现代码选择第二个官方题解。32. 最长有效括号 - 力扣(LeetCode)
官方栈
自己实现链表栈
自己实现数组栈
测试结果为都能通过,可以看到的是数组栈和官方结果比较类似,是因为官方提供的栈是使用数组实现的,所有差不多。链表栈在空间消耗上比较大,是因为结点我使用一个类取存储一个结点,比起数组要多存储了一个引用,所有空间消耗会更大,但是链表不用考虑空间不足扩容,各有各的优势。