栈
栈(Stack)又名堆栈,它是一种运算受限的 线性表。限定仅在表尾进行插入和删除操作的线性表。这一端被称为 栈顶,相对地,把另一端称为 栈底。向一个栈插入新元素又称作进栈、入栈 或 压栈 ,它是把新元素放到栈顶元素的上面,使之成为新的栈顶元素;从一个栈删除元素又称作 出栈 或 退栈,它是把栈顶元素删除掉,使其相邻的元素成为新的栈顶元素。
栈是一种具有 「先入后出」 特点的抽象数据结构,可使用数组或链表实现。使用数组实现的栈也成为 顺序栈,使用链表实现的栈成为 链栈
「先入后出」 可以用手枪弹夹比喻,弹夹中最先放入的子弹最后才会打出去,而最后放入的子弹第一枪就打出去了。
压栈 方法名一般叫做 push ,出栈 方法名一般叫做 pop。
我们先来画图看看入栈和出栈的过程
stack.push(1); // 元素 1 入栈
stack.push(2); // 元素 2 入栈
stack.pop(); // 出栈 -> 元素 2
stack.pop(); // 出栈 -> 元素 1
没什么问题,这看起来非常好理解。所谓先入后出就像你上班坐公交车一样,先上车的乘客司机总会让你往里走走,因为里面 “还有位置”,但是下车时总是后上车的乘客先下车,因为离车门近,反而先上车的乘客因为在里面的“位置上坐着”,离车门远,总是后下车。
顺序栈和链栈的优缺点
上面有提到过,栈可使用数组或链表实现。使用数组实现的栈也称为 循序栈,使用链表实现的栈称为 链栈
但是这两种栈的优缺点是什么,我们应该如何选择呢?首先我们需要知道数组和链表的优缺点 数组的优缺点:
优点: 数组拥有非常高效的随机访问能力,只要给出下标,就可以用常量时间找到对应元素。
缺点: 数组的劣势体现在插入和删除元素方面。由于数组元素连续紧密地存储在内存中,插入、删除元素都会导致大量元素被迫移动或者重新开辟内存扩容,影响效率。
总结: 数组所适合的是读操作多、写操作少的场景!
链表的优缺点:
优点:插入和删除速度快,保留原有的逻辑顺序,在插入或者删除一个元素的时候,只需要改变指针指向即可。没有空间限制,存储元素无上限,只与内存空间大小有关.
缺点:查找速度比较慢,因为在查找时,需要循环遍历链表。
总结:链表适合的是读操作少、写操作多的场景!
栈的特点:
- 元素数量不固定
- 对线性表尾部有频繁的插入和删除操作(push和pop)
无论从那个方面看,使用链表来实现栈都是一个更好的选择,但是如果我们能避开数组的缺点来使用栈的话,数组的顺序存储也带来不少好处。
顺序栈
顺序栈使用数组来实现,将数组的第一项作为栈低,然后维护一个指针来表示栈顶。由于数组的缺点我们应该尽量使用固定的栈大小,也就是限制栈用元素的数量。下图是一个大小为5的栈
当栈顶指针为 -1 时表示栈中没有元素,即 空栈
我们来看一下简单的模拟实现
class Stsck {
constructor() {
this.items = []//栈的数据存储在数组中
}
push(item) {
this.items.push(item)//入栈操作
}
pop() {
if (this.isEmpty()) {
return null //栈为空时返回null
}
return this.items.pop()//出栈操作 从数组末尾删除并返回元素
}
peek() {
if (this.isEmpty()) {
return null //栈为空时返回null
}
return this.items[this.items.length - 1] //返回栈顶元素但不删除
}
isEmpty() {
return this.items.length === 0//判断栈是否为空
}
clear() {
this.items = []//清空栈
}
size() {
return this.items.length//返回栈的长度
}
}
// 使用Stack
const stsck = new Stsck()
stsck.push(1)
stsck.push(2)
stsck.push(3)
console.log(stsck.peek());//返回最顶端的元素
console.log(stsck.pop());//删除顶端的元素
console.log(stsck.size());//栈的长度
stsck.clear()//清空栈
console.log(stsck.isEmpty());//为空返回true