数据结构与算法(栈)

栈:实现浏览器的前进与后退功能

场景:当我们进入浏览器之后,会发现,浏览器的前进后退功能是一串页面,比如从a-b-c的页面,进入到c页面后可以回退到吧b再回退到a,还可以再前进到b再到c。
那么这个功能用什么数据结构设计比较好呢?下面就来学习一下栈的数据结构。

理解“栈”

栈:就是叠在一起的盘子,平时放盘子的时候,都是从下往上一个一个放;取出的时候,我们也是从上往下一个一个地依次取,不能从中间任意取出。后进者先出,先进者后出,这就是典型的“栈”结构。
数据结构与算法(栈)_第1张图片
所以,栈是一种“操作受限”的线性表,只允许在一端插入和删除数据。
相比数组和链表,栈带来的只有限制,并没有任何优势,事实真的如此吗?
其实,从功能上,数组或链表确实可以替代栈,但,特定的数据结构是对特定场景的抽象,而且,数组或链表暴露了太多的操作接口。操作上是灵活自由,但使用时就比较不可控。自然也就容易出错。
当某个数据集合涉及在一端插入和删除数据,并且满足后进先出、先进后出的特性,我们首选“栈”。

如何实现一个“栈”

两种操作:入栈、出栈
也就是在栈顶插入一个数据和从栈顶删除一个数据。实际上栈可以用数组来实现,也可以用链表来实现。用数组实现的栈,叫顺序栈;用链表实现的叫链式栈。
顺序栈:

//基于数组实现的顺序栈
public class ArrayStack{
 private String[] items;//数组
 private int count;//栈中元素个数
 private int n;//栈的大小

//初始化数组,申请一个大小为a的数组空间
public ArrayStack(int n){
	this.items = new String[n];
	this.n = n;
	this.count = 0;
}

//入栈操作
public boolean push(String item){
//数组空间不够了,直接返回false,入栈失败
if(count == n)return false;
//将item放到下标为count的位置,并且count加一
items[count] = item;
++count;
return true;
}

//出栈操作
public String pop(){
//栈为空,则直接返回null
if(count == 0)return null;
//返回下标为count-1的数组元素,并且栈中元素个数count减一
String tmp = items[count-1];
--count;
return tmp;
}
}

所以它的操作时间、空间复杂度是多少呢?
不管是顺序栈还是链式栈,我们存储数据只需要一个大小为n的数组就够了。在入栈和出栈过程中,只需要一两个临时变量存储空间,所以空间复杂度是O(1)。
注意:这里存储数据需要一个大小为n的数组,并不是说空间复杂度就是O(n)。因为,这个n空间是必须的,不能省,所以,我们说的空间复杂度是指算法运行还需要的存储空间。
时间复杂度,不管是顺序栈还是链式栈,入栈、出栈只涉及栈顶个别数据的操作,所以时间复杂度都是O(1)。

支持动态扩容的顺序栈

由于基于数组实现的栈是一个固定大小的栈,也就是在初始化栈时需要事先指定栈的大小。当栈满了之后就无法再向栈里添加数据了。尽管链式栈的大小受限,但要存储next指针,内存消耗相对较多。所以,如果要实现一个动态扩容的栈,只需要底层依赖一个支持动态扩容的数组就可以了。当栈满了之后,就申请一个更大的数组,将原来的数据搬移到新数组中。

你可能感兴趣的:(java,数据结构,jvm)