C#实现栈

本文介绍了栈这一基本数据结构,详细阐述了栈的后进先出(LIFO)原理,并通过链表和数组两种方式展示了栈的实现。栈在函数递归、数据处理等方面的应用被提及,同时提供了链表栈和数组栈的代码示例。文章最后对比了两者的优缺点,并通过实际测试验证了实现的正确性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

栈是一种十分常见的数据结构,例如在函数中的递归就和栈有关,当当前函数执行完毕时要回到上一个状态就是通过栈去获取的。栈本身就是数组或者链表,只不过去定义了他取和放数据的方式。

概念

栈的定义就是后进先出,当一个元素进入栈时,越晚进入的元素越先被取出。

例如:有一种数据为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)

官方栈

自己实现链表栈

 

自己实现数组栈

 测试结果为都能通过,可以看到的是数组栈和官方结果比较类似,是因为官方提供的栈是使用数组实现的,所有差不多。链表栈在空间消耗上比较大,是因为结点我使用一个类取存储一个结点,比起数组要多存储了一个引用,所有空间消耗会更大,但是链表不用考虑空间不足扩容,各有各的优势。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值