栈的定义
栈(stack) 是限定仅在表尾进行插入和删除操作的线性表,允许插入和删除的一端称为栈顶,另一端称为栈底,不含任何数据元素的栈称为空栈。
栈的特性:(LIFO)先进后出,后进先出。
队列的特性:(FIFO) 先进先出,后进后出。
理解:可以将栈的数据结构联想成羽毛球桶,一打羽毛球,最先放进去的是在桶的最下面,我们想使用羽毛球,必须从最上面的开始拿,依次往下,羽毛球的使用顺序也是后放先使用。
队列,就联想成坐滑滑梯,第一个先滑的必然第一个先出,后面的都需要按照先后顺序滑出。
栈的存储结构
1. 顺序栈: 顺序存储结构,顺序栈的本质是顺序表的简化,唯一需要确定的是用数组的哪一端表示栈底。通常来说,把数组下标为0的一端作为栈底,同时把指针top指示栈顶元素在数组中的位置。
时间复杂度:O(1)
2. 两栈共享空间
在一个程序中如果需要同时使用具有相同数据类型的两个栈时,最直接的方法是为每个栈开辟一个数组空间,不过这样做的结果可能出现一个栈的空间已被占满而无法再进行插入操作,同时另一个栈的空间仍有大量剩余而没有得到利用的情况,从而造成存储空间的浪费。一种可取的方法是充分利用顺序栈单向延伸的特性,使用一个数组来存储两个栈,让一个栈的栈底为该数组的始端,另一个栈的栈底为该数组的末端,每个栈从各自的端点向中间延伸
3. 链栈: 链接存储结构,通常链栈用单链表表示,因此其结点结构和单链表的结点结构相同。所以只能在栈顶进行插入和删除操作,显然,以单链表的头部做栈顶是最方便的,而且没有必要像单链表那样为了方便运算附加一个头结点。
顺序栈和链栈的比较
实现顺序栈和链栈的所有基本操作的算法都只需要常数时间,因此唯一可以比较的是空间性能。初始时顺序栈必须确定一个固定的长度,所以有存储元素个数的限制和空间浪费的问题。链栈没有栈满的问题,只有有当内存没有可用空间时才会出现栈满,但是每个元素都需要一个指针域,从而产生了结松勾性开销。所以当栈的使用过程中元素个数变化较大时,用链栈是适宜的;反之,应该采用用顺序栈。
栈的算法实战
这是我在LeetCode刷题遇到的一条有关栈算法的问题,希望对大家理解和使用栈有帮助!
题目一:有效的括号
给定一个只包括 ‘(’,‘)’,‘{’,‘}’,‘[’,‘]’ 的字符串 s ,判断字符串是否有效。
有效字符串需满足:
左括号必须用相同类型的右括号闭合。
左括号必须以正确的顺序闭合。
示例1:
输入:s = “()”
输出:true
示例2:
输入:s = “([)]”
输出:false
解题思路:
判断括号的有效性可以使用「栈」这一数据结构来解决。
我们遍历给定的字符串 ss。当我们遇到一个左括号时,我们会期望在后续的遍历中,有一个相同类型的右括号将其闭合。由于后遇到的左括号要先闭合,因此我们可以将这个左括号放入栈顶。
当我们遇到一个右括号时,我们需要将一个相同类型的左括号闭合。此时,我们可以取出栈顶的左括号并判断它们是否是相同类型的括号。如果不是相同的类型,或者栈中并没有左括号,那么字符串 s 无效,返回False。为了快速判断括号的类型,我们可以使用哈希表存储每一种括号。哈希表的键为右括号,值为相同类型的左括号。
在遍历结束后,如果栈中没有左括号,说明我们将字符串 s 中的所有左括号闭合,返回 True,否则返回 False。
注意到有效字符串的长度一定为偶数,因此如果字符串的长度为奇数,我们可以直接返回False,省去后续的遍历判断过程。
下面给出多种编程语言的解法
python:
class Solution:
def isValid(self, s: str) -> bool:
if len(s) % 2 == 1:
return False
pairs = {
")": "(",
"]": "[",
"}": "{",
}
stack = list()
for ch in s:
if ch in pairs:
if not stack or stack[-1] != pairs[ch]:
return False
stack.pop()
else:
stack.append(ch)
return not stack
C++:
class Solution {
public:
bool isValid(string s) {
int n = s.size();
if (n % 2 == 1) {
return false;
}
unordered_map<char, char> pairs = {
{')', '('},
{']', '['},
{'}', '{'}
};
stack<char> stk;
for (char ch: s) {
if (pairs.count(ch)) {
if (stk.empty() || stk.top() != pairs[ch]) {
return false;
}
stk.pop();
}
else {
stk.push(ch);
}
}
return stk.empty();
}
};
Java:
class Solution {
public boolean isValid(String s) {
int n = s.length();
if (n % 2 == 1) {
return false;
}
Map<Character, Character> pairs = new HashMap<Character, Character>() {{
put(')', '(');
put(']', '[');
put('}', '{');
}};
Deque<Character> stack = new LinkedList<Character>();
for (int i = 0; i < n; i++) {
char ch = s.charAt(i);
if (pairs.containsKey(ch)) {
if (stack.isEmpty() || stack.peek() != pairs.get(ch)) {
return false;
}
stack.pop();
} else {
stack.push(ch);
}
}
return stack.isEmpty();
}
}
JavaScript:
var isValid = function(s) {
const n = s.length;
if (n % 2 === 1) {
return false;
}
const pairs = new Map([
[')', '('],
[']', '['],
['}', '{']
]);
const stk = [];
for (let ch of s){
if (pairs.has(ch)) {
if (!stk.length || stk[stk.length - 1] !== pairs.get(ch)) {
return false;
}
stk.pop();
}
else {
stk.push(ch);
}
};
return !stk.length;
};
复杂度分析
时间复杂度:O(n)O(n),其中 nn 是字符串 ss 的长度。
空间复杂度:O(n + |\Sigma|)O(n+∣Σ∣),其中 \SigmaΣ 表示字符集,本题中字符串只包含 66 种括号,|\Sigma| = 6∣Σ∣=6。栈中的字符数量为 O(n)O(n),而哈希表使用的空间为 O(|\Sigma|)O(∣Σ∣),相加即可得到总空间复杂度。
最后,更多优质的数据结构和算法内容,请查阅我的博客更多文章!!!
更多关于数据结构和算法总结