现实生活中的事情往往都能总结归纳成一定的数据结构,例如餐馆中餐盘的堆叠和使用,羽毛球筒里装的羽毛球等都是典型的栈结构。而在.NET中,值类型在线程栈上进行分配,引用类型在托管堆上进行分配,本文所说的“栈”正是这种数据结构。栈和队列都是常用的数据结构,它们的逻辑结构与线性表相通,不同之处则在于操作受某种特殊限制。因此,栈和队列也被称为操作受限的线性表。这里,我们首先来了解一下栈。

1.1 栈的基本特征

数据结构基础温故-2.栈

(stack)是限定仅在表尾进行插入和删除操作的线性表。其特点是:”后进先出“或”先进后出“。

1.2 栈的基本操作

  (1)栈的插入操作,叫作进栈,也称压栈、入栈:

数据结构基础温故-2.栈

  (2)栈的删除操作,叫作出栈,也有的叫作弹栈:

数据结构基础温故-2.栈

二、栈的基本实现

  既然栈属于特殊的线性表,那么其实现也会有两种形式:顺序存储结构和链式存储结构。首先,对于Stack,我们希望能够提供以下几个方法供调用:

Stack<T>()

创建一个空的栈

void Push(T s)

往栈中添加一个新的元素

T Pop()

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

bool IsEmpty()

栈是否为空

int Size()

栈中元素的个数

2.1 栈的顺序存储实现

  对于顺序存储,我们可以参照顺序表的实现方式,借助数组来存储各个数据元素,然后对这个数组进行一定的封装,提供指定的操作对数据元素进行插入和删除即可。

  (1)入栈操作实现

数据结构基础温故-2.栈

        /// <summary>
        /// 入栈
        /// </summary>
        /// <param name="node">节点元素</param>
        public void Push(T node)
        {
            if (index == nodes.Length)
            {
                // 增大数组容量
                ResizeCapacity(nodes.Length * 2);
            }

            nodes[index] = node;
            index++;
        }

  借助数组来实现入栈操作,其关键之处就在于top指针的移动。这里index初始值为0,每次入栈一个则将index加1,即指向下一个即将入栈的位置。由于这里采用了动态扩容的机制,所以没有判断栈中元素个数是否达到了最大值。

  (2)出栈操作实现

  出栈操作需要先去的要出栈的元素,然后将index减1,即指向下一个即将出栈的元素的位置。

        /// <summary>
        /// 出栈
        /// </summary>
        /// <returns>出栈节点元素</returns>
        public T Pop()
        {
            if(index == 0)
            {
                return default(T);
            }

            T node = nodes[index - 1];
            index--;
            nodes[index] = default(T);

            if (index > 0 && index == nodes.Length / 4)
            {
                // 缩小数组容量
                ResizeCapacity(nodes.Length / 2);
            }
            return node;
        }

  这里首先需要判断index是否已经到达了最小值,出栈的元素位置需要置为默认值(如果是int数组,那么会重置为0),最后返回出栈的元素对象。这里当元素个数小于数组的四分之一时会进行容量收缩操作。

  (3)完整的类实现

    /// <summary>
    /// 基于数组的栈实现
    /// </summary>
    /// <typeparam name="T">类型</typeparam>
    public class MyArrayStack<T>
    {
        private T[] nodes;
        private int index;

        public MyArrayStack(int capacity)
        {
            this.nodes = new T[capacity];
            this.index = 0;
        }

        /// <summary>
        /// 入栈
        /// </summary>
        /// <param name="node">节点元素</param>
        public void Push(T node)
        {
            if (index == nodes.Length)
            {
                // 增大数组容量
                ResizeCapacity(nodes.Length * 2);
            }

            nodes[index] = node;
            index++;
        }

        /// <summary>
        /// 出栈
        /// </summary>
        /// <returns>出栈节点元素</returns>
        public T Pop()
        {
            if(index == 0)
            {
                return default(T);
            }

            T node = nodes[index - 1];
            index--;
            nodes[index] = default(T);

            if (index > 0 && index == nodes.Length / 4)
            {
                // 缩小数组容量
                ResizeCapacity(nodes.Length / 2);
            }
            return node;
        }

        /// <summary>
        /// 重置数组大小
        /// </summary>
        /// <param name="newCapacity">新的容量</param>
        private void ResizeCapacity(int newCapacity)
        {
            T[] newNodes = new T[newCapacity];
            if(newCapacity > nodes.Length)
            {
                for (int i = 0; i < nodes.Length; i++)
                {
                    newNodes[i] = nodes[i];
                }
            }
            else
            {
                for (int i = 0; i < newCapacity; i++)
                {
                    newNodes[i] = nodes[i];
                }
            }

            nodes = newNodes;
        }

        /// <summary>
        /// 栈是否为空
        /// </summary>
        /// <returns>true/false</returns>
        public bool IsEmpty()
        {
            return this.index == 0;
        }

        /// <summary>
        /// 栈中节点个数
        /// </summary>
        public int Size
        {
            get
            {
                return this.index;
            }
        }
    }
View Code

相关文章: