大家好,本篇的内容: 栈的认识,栈的使用,栈的实现!栈的实现源码附在最后!
首先,先让我们来认识一下栈:
一.栈(Stack):
一、栈的概念
栈,一种特殊的线性表,其只允许在固定的一端进行插入和删除元素操作。进行数据插入和删除操作的一端称为栈顶,另一端称为栈底。栈中的数据元素遵守后进先出LIFO(Last In First Out)的原则。
压栈:栈的插入操作叫做进栈/压栈/入栈,入数据在栈顶。
出栈:栈的删除操作叫做出栈。出数据在栈顶。
那么,如何实现栈呢?
通过我们c语言的基础和前篇链表的知识,我们知道可以使用数组或者链表来实现栈。
数组栈:
链表栈:
需要注意,链表存在单链表和双向链表的区别,单链表实现时,栈顶只能是头;而双向链表实现时,栈顶可以是头,也可以是尾。
所以实现栈的方法是多样可选的,看大家喜欢用哪种方法来实现,这里我使用数组栈。
但当我们定义一个栈的时候,就会有一些需要注意的地方:
大家看这俩种思路,当top==0时表示栈为空或者top==0时表示存进去一个数据,那么就存在了一个歧义,top==0时到底表示的时空栈还是表示一个数据?
如果你定义top==0表示一个数据,那么你在定义空栈的时候就要定义成top==-1;但是这种写法会很不方便,给后面带来许多的麻烦,当你要忘压栈的时候,就需要先++top,再进行存数据。
另一种写法:
top==0时表示栈为空,当压栈进入一个数据时,top++,top==1,这时top指向元素的下一个位置,我认为这种写法是比较好的。
还有一个需要注意的地方:
我们知道栈是后进先出的,那么如果有一个栈,进栈序列是1,2,3,4,进栈过程中可以出栈,那么它的出栈序列就不是单一的4,3,2,1了,它可以在3进栈之前让2出栈,4进栈结束后再出栈,那么出栈序列就变成了2,4,3,1;
二、栈的使用:
这里模拟几个功能:
入栈:
public class MyStack {
public static void main(String[] args) {
Stack<Integer> s1 = new Stack<>();
s1.push(1);
s1.push(2);
s1.push(3);
s1.push(4);
s1.push(5);
System.out.println(s1);
}
}
出栈:
public class MyStack {
public static void main(String[] args) {
Stack<Integer> s1 = new Stack<>();
s1.push(1);
s1.push(2);
s1.push(3);
s1.push(4);
s1.push(5);
System.out.println(s1);
s1.pop();
s1.pop();
System.out.println(s1);
}
}
获取栈顶元素:
public class MyStack {
public static void main(String[] args) {
Stack<Integer> s1 = new Stack<>();
s1.push(1);
s1.push(2);
s1.push(3);
s1.push(4);
s1.push(5);
System.out.println(s1.peek());
}
}
判断栈是否为空:
public class MyStack {
public static void main(String[] args) {
Stack<Integer> s1 = new Stack<>();
s1.push(1);
s1.push(2);
s1.push(3);
s1.push(4);
s1.push(5);
System.out.println(s1.empty());
}
}
三、栈的应用场景
1. 若进栈序列为 1,2,3,4 ,进栈过程中可以出栈,则下列不可能的一个出栈序列是(C)
A: 1,4,3,2 B: 2,3,4,1 C: 3,1,4,2 D: 3,4,2,1
问题分析:类似于这种栈的选择题,如果元素较少,我们直接心算就可以,元素较多的话我们可以画图来解决,本题c选项,先出的是3,那么就是1,2,3进栈,然后3出栈,第二个出栈选项给的是1,我们知道1是第一个进栈的,那么想出1,2必须先出,所以C选项错误!
2.一个栈的初始状态为空。现将元素1、2、3、4、5、A、B、C、D、E依次入栈,然后再依次出栈,则元素出栈的顺序是( B)。
A: 12345ABCDE B: EDCBA54321 C: ABCDE12345 D: 54321EDCBA
问题分析:简单明了,栈的结构先进后出,直接选B。
栈我们认识的差不多了,那让我们来做几个题感受一下吧:
有效的括号
给定一个只包括 '(',')','{','}','[',']' 的字符串 s ,判断字符串是否有效。
有效字符串需满足:
左括号必须用相同类型的右括号闭合。
左括号必须以正确的顺序闭合。
每个右括号都有一个对应的相同类型的左括号。
示例 1:
输入:s = "()"
输出:true
示例 2:
输入:s = "()[]{}"
输出:true
示例 3:
输入:s = "(]"
输出:false
解题思路:本题我们可以利用栈来解决,首先我们遍历字符串,把我们遇到的左括号先进栈,遇到右括号不进栈,这个时候先判断栈是否为空,若为空,那么就是右括号多了,直接返回false;否则这时栈顶元素弹出,看这两个括号是否匹配,匹配循环继续,直到最后栈为空,不匹配,直接返回false。
代码实现:
class Solution {
public boolean isValid(String s) {
Stack<Character> stack = new Stack();
for(int i=0;i<s.length;i++){
char ch = s.charAt(i);
//判断是不是左括号
if(ch=='('|| ch=='['|| ch=='{'){
stack.push(ch);
}else{
if(stack.empty()){
//遇到了右括号,此时不匹配!
return false;
}
}
char ch2 = stack.peek();
if(ch2 == '[' &&ch == ']'||ch2 == '(' &&ch == ')'||ch2 == '{' &&ch == '}'){
stack.pop();
}else{
return false;
}
}
}
//当字符串遍历完成了,但是栈不为空,说明左括号还在栈当中没有匹配完成
if(!stack.empty()){
return false;
}
return true;
}
再来做一个吧:
输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否可能为该栈的弹出顺序。假设压入栈的所有数字均不相等。例如序列1,2,3,4,5是某栈的压入顺序,序列4,5,3,2,1是该压栈序列对应的一个弹出序列,但4,3,5,1,2就不可能是该压栈序列的弹出序列。
1. 0<=pushV.length == popV.length <=1000
2. -1000<=pushV[i]<=1000
3. pushV 的所有数字均不相同
示例1
输入:
[1,2,3,4,5],[4,5,3,2,1]
返回值:
true
说明:
可以通过push(1)=>push(2)=>push(3)=>push(4)=>pop()=>push(5)=>pop()=>pop()=>pop()=>pop()
这样的顺序得到[4,5,3,2,1]这个序列,返回true 。
示例2
输入:
[1,2,3,4,5],[4,3,5,1,2]
返回值:
false
说明:
由于是[1,2,3,4,5]的压入顺序,[4,3,5,1,2]的弹出顺序,要求4,3,5必须在1,2前压入,且1,2不能弹出,但是这样压入的顺序,1又不能在2之前弹出,所以无法形成的,返回false 。
解题思路:
因为题目给了两个序列,我们首先遍历第一个序列,让其元素进栈,在这个循环中如果栈顶元素等于给定序列,那么栈顶元素出栈,j++,函数返回值直接判断栈是否为空,为空说明我们的次序是匹配的,不为空,说明这个序列不合法!
代码实现:
import java.util.*;
public class Solution {
public boolean IsPopOrder(int [] pushA, int [] popA) {
Stack<Integer> stack = new Stack<>();
int j = 0;
for (int i = 0; i < pushA.length; i++) {
stack.push(pushA[i]);
while (j < popA.length && !stack.empty()
&& stack.peek().equals(popA[j])) {
//用equals是因为Integer的范围是-128~127 如果超过这个范围肯定会报错 系统给的测试用例应该没有超过这个范围 所以测试可以通过
stack.pop();
j++;
}
}
return stack.empty();
}
}
栈的知识就到此结束了,希望对大家有所帮助!
栈的源码:
Stack.h
#pragma once
#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
#include<stdbool.h>
typedef int STDataType;
typedef struct Stack
{
STDataType* a;
int top; // 标识栈顶位置的
int capacity;
}ST;
void STInit(ST* pst);
void STDestroy(ST* pst);
// 栈顶插入删除
void STPush(ST* pst, STDataType x);
void STPop(ST* pst);
STDataType STTop(ST* pst);
bool STEmpty(ST* pst);
int STSize(ST* pst);
Stack.c
#include"Stack.h"
void STInit(ST* pst)
{
assert(pst);
pst->a = NULL;
pst->capacity = 0;
// 表示top指向栈顶元素的下一个位置
pst->top = 0;
// 表示top指向栈顶元素
//pst->top = -1;
}
void STDestroy(ST* pst)
{
assert(pst);
free(pst->a);
pst->a = NULL;
pst->top = pst->capacity = 0;
}
// 栈顶插入删除
void STPush(ST* pst, STDataType x)
{
assert(pst);
if (pst->top == pst->capacity)
{
int newcapacity = pst->capacity == 0 ? 4 : pst->capacity * 2;
STDataType* tmp = (STDataType*)realloc(pst->a, sizeof(STDataType) * newcapacity);
if (tmp == NULL)
{
perror("realloc fail");
return;
}
pst->a = tmp;
pst->capacity = newcapacity;
}
pst->a[pst->top] = x;
pst->top++;
}
void STPop(ST* pst)
{
assert(pst);
// 不为空
assert(pst->top > 0);
pst->top--;
}
STDataType STTop(ST* pst)
{
assert(pst);
// 不为空
assert(pst->top > 0);
return pst->a[pst->top - 1];
}
bool STEmpty(ST* pst)
{
assert(pst);
/*if (pst->top == 0)
{
return true;
}
else
{
return false;
}*/
return pst->top == 0;
}
int STSize(ST* pst)
{
assert(pst);
return pst->top;
}
Tast.c
#include"Stack.h"
int main()
{
ST s;
STInit(&s);
STPush(&s, 1);
STPush(&s, 2);
STPush(&s, 3);
printf("%d ", STTop(&s));
STPop(&s);
printf("%d ", STTop(&s));
STPop(&s);
STPush(&s, 4);
STPush(&s, 5);
// 一 对 多
// 入栈顺序 -- 出栈顺序
while (!STEmpty(&s))
{
printf("%d ", STTop(&s));
STPop(&s);
}
printf("\n");
return 0;
}