数据结构与算法(六)栈的链式存储结构

简介: 栈的链式存储结构,简称为链栈。栈的链式存储结构与线性表的链式存储结构差不多,但是其相对比较简单,只在表尾进行操作。

QQ图片20220425220708.jpg

栈的链式存储结构,简称为链栈。


想想看,栈只是栈顶来做插入和删除操作,栈顶放在链表的头部还是尾部呢?


由于单链表有头指针,而栈顶指针也是必须的,那么干嘛不让他们合二为一呢,所以比较好的办法是把栈顶放到单链表的头部。


另外栈顶在头部了,那么单链表的头结点也就失去了意义,通常对于链栈来说,是不需要头结点的。


同样对于链栈来说,基本不存在栈满的情况,除非内存已经没有可用的空间了。


栈的链式存储结构与线性表的链式存储结构差不多,但是其相对比较简单,只在表尾进行操作。


刚好复习下之前学习的线性表中的链表。


但是链栈和线性表的链式存储结构是有区别的,链栈不需要头结点,有栈顶就可以了。


首先定义一个链栈的接口:


/// <summary>
    /// 链栈接口
    /// </summary>
    public interface IChainStack<T>
    {
        /// <summary>
        /// 清空栈
        /// </summary>
        /// <returns></returns>
        bool ClearStack();
        /// <summary>
        /// 判断栈是否为空
        /// </summary>
        /// <returns>true:空、false:不空</returns>
        bool IsEmptyStack();
        /// <summary>
        /// 获取栈顶元素
        /// </summary>
        /// <returns></returns>
        T GetTopStack();
        /// <summary>
        /// 入栈
        /// </summary>
        /// <returns></returns>
        bool PushStack(T val);
        /// <summary>
        /// 出栈
        /// </summary>
        /// <returns></returns>
        bool PopStack();
        /// <summary>
        /// 获取栈长度
        /// </summary>
        /// <returns></returns>
        int LengthStack();
        /// <summary>
        /// 判断栈是否已满
        /// </summary>
        /// <returns></returns>
        bool IsFull();
        /// <summary>
        /// 打印栈
        /// </summary>
        void paint();
    }

 

然后定义一个链栈的节点


/// <summary>
    /// 链栈节点类
    /// </summary>
    public class ChainStackNode<T>
    {
        /// <summary>
        /// 链表中的数据域
        /// </summary>
        public T data;
        /// <summary>
        /// 链表中的下一个节点的指针域
        /// </summary>
        public ChainStackNode<T> Next;
        // 每个结点有两部分组成,指针+数据,第一个节点没有数据,只有指针,最后一个节点只有数据,没有指针。
        // 那么构造函数需要有三次重载。
        /// <summary>
        /// 中间节点构造函数
        /// </summary>
        /// <param name="val"></param>
        /// <param name="index"></param>
        public ChainStackNode(T val, ChainStackNode<T> index)
        {
            this.data = val;
            this.Next = index;
        }
        /// <summary>
        /// 最后一个节点构造函数
        /// </summary>
        /// <param name="val"></param>
        public ChainStackNode(T val)
        {
            this.data = val;
            this.Next = null;
        }
        /// <summary>
        /// 第一个节点构造函数
        /// </summary>
        /// <param name="val"></param>
        /// <param name="index"></param>
        public ChainStackNode(ChainStackNode<T> index)
        {
            // 默认值
            this.data = default(T);
            this.Next = index;
        }
        /// <summary>
        /// 定义一个空节点
        /// </summary>
        /// <param name="val"></param>
        /// <param name="index"></param>
        public ChainStackNode()
        {
            this.data = default(T);
            this.Next = null;
        }
    }

 

最后我们定义一个类来实现上边定义的接口;实现链栈的操作功能。


public class ChainStackList<T> : IChainStack<T>
    {
        /// <summary>
        /// 存储栈顶指针,其是有指针,没有数据
        /// </summary>
        public ChainStackNode<T> top = null;
        /// <summary>
        /// 栈数量
        /// </summary>
        public int count = 0;
        public ChainStackList()
        {
            top = new ChainStackNode<T>();
            count = 0;
        }
        /// <summary>
        /// 清空栈
        /// </summary>
        /// <returns></returns>
        public bool ClearStack()
        {
            count = 0;
            top = null;
            return true;
        }
        /// <summary>
        /// 获取栈顶数据
        /// </summary>
        /// <returns></returns>
        public T GetTopStack()
        {
            return top.Next.data;
        }
        /// <summary>
        /// 栈是否为空
        /// </summary>
        /// <returns></returns>
        public bool IsEmptyStack()
        {
            if (top.Next == null || count == 0)
            {
                return true;
            }
            else
            {
                return false;
            }
        }
        /// <summary>
        /// 栈是已满,链接基本上不存在满栈的情况,因为满栈基本上意味着系统崩溃
        /// </summary>
        /// <returns></returns>
        public bool IsFull()
        {
            if (top == null)
            {
                return true;
            }
            else
            {
                return false;
            }
        }
        /// <summary>
        /// 获取栈长度
        /// </summary>
        /// <returns></returns>
        public int LengthStack()
        {
            return count;
        }
        /// <summary>
        /// 打印栈
        /// </summary>
        public void paint()
        {
            ChainStackNode<T> tempTop = top;
            ChainStackNode<T> temp = new ChainStackNode<T>();
            for (int i = 0; i < count+1; i++)
            {
                temp = tempTop.Next;
                Console.WriteLine("data:"+ tempTop.data+"index:"+top.Next);
                tempTop = temp;
            }
        }
        /// <summary>
        /// 出栈
        /// </summary>
        /// <returns></returns>
        public bool PopStack()
        {
            if (IsEmptyStack())
            {
                Console.WriteLine("链栈为空,无法弹出");
                return false;
            }
            // 定义一个节点存储栈顶的下一个节点
            ChainStackNode<T> temp = top.Next;
            // 现在栈顶的下一个节点等于原来栈顶下一个节点的先一个节点
            top.Next = temp.Next;
            count--;
            return true;
        }
        /// <summary>
        /// 入栈
        /// </summary>
        /// <param name="val">入栈数据</param>
        /// <returns></returns>
        public bool PushStack(T val)
        {
            ChainStackNode<T> temp = new ChainStackNode<T>(val);
            if (temp == null)
            {
                Console.WriteLine("栈已满");
                return false;
            }
            // 将栈顶的下一个节点赋值给新节点
            temp.Next = top.Next;
            // 将新节点赋值给栈顶的下一个值
            top.Next = temp;
            count++;
            return true;
        }
    }

 

调用示例:


static void Main(string[] args)
        {
            ChainStackList<string> chainStrackList = new ChainStackList<string>();
            chainStrackList.PushStack("11111");
            chainStrackList.PushStack("22222");
            chainStrackList.PushStack("33333");
            chainStrackList.PushStack("44444");
            chainStrackList.PushStack("55555");
            Console.WriteLine("添加数据");
            Console.WriteLine("=========================================");
            Console.WriteLine("打印数据");
            chainStrackList.paint();
            Console.WriteLine("=========================================");
            chainStrackList.PopStack();
            chainStrackList.PopStack();
            Console.WriteLine("弹出栈");
            Console.WriteLine("=========================================");
            Console.WriteLine("打印数据");
            chainStrackList.paint();
            Console.WriteLine("=========================================");
            Console.WriteLine("获取栈顶数据:"+ chainStrackList.GetTopStack());
            Console.WriteLine("=========================================");
            Console.WriteLine("获取栈的长度:" + chainStrackList.LengthStack());
            Console.WriteLine("=========================================");
            Console.ReadKey();
        }


QQ图片20220425220710.png


如上图所示:


在写链栈的时候,只需要搞清楚一件事情就可以了,栈顶top存储的是事实上的栈顶的指针。


Top.Next = 真正的栈顶。


那么添加数据时:


newNode.Next = top.Next;// 将栈顶的下一个节点赋值给新节点
top.Next = newNode;// 将新节点赋值给栈顶的下一个节点

 

删除数据时:


Temp = top.Next;// 定义一个节点存储栈顶的下一个节点
Top.next = temp.next;// 现在栈顶的下一个节点等于原来栈顶下一个节点的先一个节点

 

以上基本上就是链栈的精髓所在,最后放上一张上边代码最终运行的效果图:代码实例在文末,可下载。


QQ图片20220425220712.png

 



目录
相关文章
|
2月前
|
存储 监控 算法
基于 Go 语言跳表结构的局域网控制桌面软件进程管理算法研究
针对企业局域网控制桌面软件对海量进程实时监控的需求,本文提出基于跳表的高效管理方案。通过多级索引实现O(log n)的查询、插入与删除性能,结合Go语言实现并发安全的跳表结构,显著提升进程状态处理效率,适用于千级进程的毫秒级响应场景。
153 15
|
2月前
|
分布式计算 并行计算 算法
《数据之美》:图结构的精妙世界与算法实践
图是表示多对多关系的非线性数据结构,由顶点和边组成,可建模社交网络、路径导航等复杂系统。核心算法包括BFS/DFS遍历、Dijkstra最短路径、Floyd-Warshall全源最短路径,以及Prim和Kruskal最小生成树算法,广泛应用于推荐系统、社交分析与路径规划。
|
3月前
|
运维 监控 JavaScript
基于 Node.js 图结构的局域网设备拓扑分析算法在局域网内监控软件中的应用研究
本文探讨图结构在局域网监控系统中的应用,通过Node.js实现设备拓扑建模、路径分析与故障定位,提升网络可视化、可追溯性与运维效率,结合模拟实验验证其高效性与准确性。
239 3
|
3月前
|
存储 监控 算法
企业电脑监控系统中基于 Go 语言的跳表结构设备数据索引算法研究
本文介绍基于Go语言的跳表算法在企业电脑监控系统中的应用,通过多层索引结构将数据查询、插入、删除操作优化至O(log n),显著提升海量设备数据管理效率,解决传统链表查询延迟问题,实现高效设备状态定位与异常筛选。
127 3
|
6月前
|
编译器 C语言 C++
栈区的非法访问导致的死循环(x64)
这段内容主要分析了一段C语言代码在VS2022中形成死循环的原因,涉及栈区内存布局和数组越界问题。代码中`arr[15]`越界访问,修改了变量`i`的值,导致`for`循环条件始终为真,形成死循环。原因是VS2022栈区从低地址到高地址分配内存,`arr`数组与`i`相邻,`arr[15]`恰好覆盖`i`的地址。而在VS2019中,栈区先分配高地址再分配低地址,因此相同代码表现不同。这说明编译器对栈区内存分配顺序的实现差异会导致程序行为不一致,需避免数组越界以确保代码健壮性。
131 0
栈区的非法访问导致的死循环(x64)
232.用栈实现队列,225. 用队列实现栈
在232题中,通过两个栈(`stIn`和`stOut`)模拟队列的先入先出(FIFO)行为。`push`操作将元素压入`stIn`,`pop`和`peek`操作则通过将`stIn`的元素转移到`stOut`来实现队列的顺序访问。 225题则是利用单个队列(`que`)模拟栈的后入先出(LIFO)特性。通过多次调整队列头部元素的位置,确保弹出顺序符合栈的要求。`top`操作直接返回队列尾部元素,`empty`判断队列是否为空。 两题均仅使用基础数据结构操作,展示了栈与队列之间的转换逻辑。
|
10月前
|
算法 调度 C++
STL——栈和队列和优先队列
通过以上对栈、队列和优先队列的详细解释和示例,希望能帮助读者更好地理解和应用这些重要的数据结构。
244 11
|
10月前
|
DataX
☀☀☀☀☀☀☀有关栈和队列应用的oj题讲解☼☼☼☼☼☼☼
### 简介 本文介绍了三种数据结构的实现方法:用两个队列实现栈、用两个栈实现队列以及设计循环队列。具体思路如下: 1. **用两个队列实现栈**: - 插入元素时,选择非空队列进行插入。 - 移除栈顶元素时,将非空队列中的元素依次转移到另一个队列,直到只剩下一个元素,然后弹出该元素。 - 判空条件为两个队列均为空。 2. **用两个栈实现队列**: - 插入元素时,选择非空栈进行插入。 - 移除队首元素时,将非空栈中的元素依次转移到另一个栈,再将这些元素重新放回原栈以保持顺序。 - 判空条件为两个栈均为空。
|
C语言
【数据结构】栈和队列(c语言实现)(附源码)
本文介绍了栈和队列两种数据结构。栈是一种只能在一端进行插入和删除操作的线性表,遵循“先进后出”原则;队列则在一端插入、另一端删除,遵循“先进先出”原则。文章详细讲解了栈和队列的结构定义、方法声明及实现,并提供了完整的代码示例。栈和队列在实际应用中非常广泛,如二叉树的层序遍历和快速排序的非递归实现等。
1034 9
|
存储 算法
非递归实现后序遍历时,如何避免栈溢出?
后序遍历的递归实现和非递归实现各有优缺点,在实际应用中需要根据具体的问题需求、二叉树的特点以及性能和空间的限制等因素来选择合适的实现方式。
297 59

热门文章

最新文章