数据结构与算法之美-栈
什么是栈
1.后进者先出,先进者后出,这就是典型的“栈”结构。
2.从栈的操作特性来看,是一种“操作受限”的线性表,只允许在端插入和删除数据。
为什么需要栈
1.栈是一种操作受限的数据结构,其操作特性用数组和链表均可实现。
2.但任何数据结构都是对特定应用场景的抽象,数组和链表虽然使用起来更加灵活,但却暴露了几乎所有的操作,难免会引发错误操作的风险。
3.所以,当某个数据集合只涉及在某端插入和删除数据,且满足后进者先出,先进者后出的操作特性时,我们应该首选栈这种数据结构。
如何实现栈
数组实现
使用数组实现固定容量的栈
public sealed class StackDS<T> { private T[] data;//泛型数据 private int count;//栈内数据数量 private int maxSize;//栈的最大容量 //公有属性,栈内数据数量 public int Count { get { return count; } } //初始化数组大小 public StackDS(int n) { maxSize = n; data = new T[n]; count = 0; } //入栈方法 public Boolean Push(T item) { //栈内数据数量小于最大容量就将数据放入数组 if(count < maxSize) { data[count] = item; count++; return true; } return false; } //出栈方法 public T Pop() { //栈内不为空就将数量减一,返回出栈的数据 if (count != 0) { count--; return data[count]; } //否则就返回泛型的默认值 return default(T); } }
数组实现(自动扩容)
将上述数组实现的栈的入栈代码修改为以下可以自动扩容的代码
//入栈方法 public void Push(T item) { //如果栈内空间不足,将栈大小扩容到原来二倍大小 if(count >= maxSize) { maxSize *= 2; T[] newData = new T[maxSize]; //将原数组值逐一赋值到新数组 for(int i = 0; i < data.Length; i++) newData[i] = data[i]; //将新数组赋值给data data = newData; } //将数据压入栈中,栈中数据数量加一 data[count] = item; count++; }
链表实现
单链表栈结点类
public sealed class Node<T> { private T data; private Node<T> next; public T Data { get{return data;} set{data=value;} } public Node<T> Next { get{return next;} set{next=value;} } public Node(T data) { this.data=data; next=null; } }
单链表栈类
使用了以最新的节点在头结点后的方法来实现入栈出栈,相比将结点放在尾部,好处是不需要遍历整个链表
public sealed class SinglyLinkedStack { private Node<T> head; private Node<T> firstNode; private int count; //实例构造器初始化字段 public SinglyLinkedStack() { firstNode=null; head=firstNode; count=0; } //入栈方法 public void Push(T item) { Node<T> newNode=new Node<T>(item); Node<T> p=head; newNode.next=p.next; p.next=newNode; } //出栈方法 public T Pop() { Node<T> p=head; if(p.next!=null) { T temp=p.next.Data; p.next=p.next.next; return temp; } return default(T); } }
栈的应用
有一些leetcode关于栈的题,即栈的应用的具体代码实现在下面链接的博文中有详细展示。
https://www.cnblogs.com/errornull/p/9862709.html
栈在函数调用中的应用
操作系统给每个线程分配了一块独立的内存空间,这块内存被组织成“栈”这种结构,用来存储函数调用时的临时变量。
每进入一个函数,就会将其中的临时变量作为栈帧入栈,当被调用函数执行完成,返回之后,将这个函数对应的栈帧出栈。
栈在表达式求值中的应用
利用两个栈,其中一个用来保存操作数,另一个用来保存运算符。
我们从左向右遍历表达式,当遇到数字,我们就直接压入操作数栈
当遇到运算符,就与运算符栈的栈顶元素进行比较,若比运算符栈顶元素优先级高,就将当前运算符压入栈
若比运算符栈顶元素的优先级低或者相同,从运算符栈中取出栈顶运算符,从操作数栈顶取出2个操作数,
然后进行计算,把计算完的结果压入操作数栈,继续比较。
栈在括号匹配中的应用
用栈保存为匹配的左括号,从左到右一次扫描字符串,当扫描到左括号时,则将其压入栈中;
当扫描到右括号时,从栈顶取出一个左括号,如果能匹配上,则继续扫描剩下的字符串。
如果扫描过程中,遇到不能配对的右括号,或者栈中没有数据,则说明为非法格式。
当所有的括号都扫描完成之后,如果栈为空,则说明字符串为合法格式;否则,说明未匹配的左括号为非法格式。
实现浏览器的前进后退功能
我们使用两个栈X和Y,我们把首次浏览的页面依次压如栈X,
当点击后退按钮时,再依次从栈X中出栈,并将出栈的数据一次放入Y栈。
当点击前进按钮时,我们依次从栈Y中取出数据,放入栈X中。
当栈X中没有数据时,说明没有页面可以继续后退浏览了。
当Y栈没有数据,那就说明没有页面可以点击前进浏览了。