1.定义
栈(
Stack
):栈应用非常广泛的数据结构,来自线性表数据结构,都是“操作受限”
的线性表。
限制在表的一端进行插入和删除操作的线性表。
称为后进先出
LIFO
(
Last In First Out
)或先进后出
FILO
(
First In Last Out
)线性表。
栈:限制在表的一端进行插入和删除操作的线性表。
栈顶(
Top
):允许进行插入、删除操作的一端,又称为表尾。
用栈顶指针(top
)来 指示栈顶元素。
栈底(
Bottom
):是固定端,又称为表头。
n 个不同元素进栈,出栈元素不同排列的个数为(卡特兰数)
2.顺序栈
栈的顺序存储结构简称为顺序栈,用一维数组来存储栈。
根据数组是否可以根据需要增大,可分为静态顺序栈和动态顺序栈。
静态顺序栈实现简单,但不能根据需要增大栈的存储空间;
动态顺序栈可以根据需要增大栈的存储空间,但实现稍为复杂。
2.1.动态顺序存储
采用动态一维数组来存储栈。动态,指的是栈的大小可以根据需要增加。
用
bottom
表示栈底指针,栈底固定不变的;
用
top
表示栈顶指针,栈顶则随着进栈和退栈操作而变化。
结点进栈:首先将数据元素保存到栈顶(
top
所指的当前位置),然后执行
top
加
1
,
使
top
指向栈顶的下一个存储位置;
结点出栈:执行
top
减
1
,使
top
指向栈顶元素的存储位置,然后将栈顶元素取出。
#define STACK_DEFAULT_SIZE 100 /* 栈初始向量大小 */
#define STACKINCREMENT 10 /* 存储空间分配增量 */
typedef struct sqstack {
int* bottom; /* 栈不存在时值为 NULL */
int* top; /* 栈顶指针 */
int stacksize; /* 当前已分配空间,以元素为单位 */
}SqStack;
/**
* 栈初始化
*/
bool init_stack(SqStack* stack) {
if (stack == nullptr)
return false;
stack->stacksize = STACK_DEFAULT_SIZE;
//【1】动态分配栈内存空间
stack->bottom = (int*)malloc(sizeof(int) * stack->stacksize);
if (stack->bottom == nullptr) {
return false;
}
//【2】初始条件top=bottom
stack->top = stack->bottom;
return true;
}
bool free_stack(SqStack* stack) {
if (stack == nullptr || stack->bottom == nullptr)
return false;
free(stack->bottom);
return true;
}
bool push(SqStack* stack, int e) {
if (stack == nullptr)
return false;
if (stack->top - stack->bottom > stack->stacksize) {
//栈满时,则重新开辟更大的空间
}
//栈顶指针top指向栈顶的下一个空位置
//所以,top位置先存数据,再自增
*stack->top = e;
stack->top += 1;
return true;
}
bool pop(SqStack* stack, int *e) {
if (stack == nullptr) {
return false;
}
if (stack->bottom == stack->top) {//栈空,返回错误
return false;
}
//栈顶指针top指向栈顶的下一个空位置
//所以先自减,再弹出数据
stack->top--;
*e = *stack->top;
return true;
}
int pop(SqStack* stack) {
int e;
bool ok = pop(stack, &e);
return e;
}
void test1() {
SqStack s;
init_stack(&s);
push(&s, 1);
push(&s, 6);
push(&s, 7);
printf("%d\n", pop(&s));//7
printf("%d\n", pop(&s));//6
printf("%d\n", pop(&s));//1
free_stack(&s);
}
2.2.静态顺序存储
采用静态一维数组来存储栈。
栈底固定不变的;
栈顶则随着进栈和退栈操作而变化,用一个整型变量
top
(称为栈顶指针)来指示当
前栈顶位置。
用
top=0
表示栈空的初始状态
每次
top
指向栈顶在数组中的存储位置。
#define MAX_STACK_SIZE 100 /* 栈初始向量大小 */
typedef struct sqstack {
int data[MAX_STACK_SIZE];
int top;
}SqStack;
/**
* 栈初始化
*/
SqStack create_stack() {
SqStack s;
s.top = 0;
return s;
}
bool push(SqStack* stack, int e) {
if (stack->top >= MAX_STACK_SIZE) {
return false;
}
stack->data[stack->top++] = e;
return true;
}
bool pop(SqStack* stack, int* e) {
if (stack->top <= 0) {
return false;
}
stack->top--;
*e = stack->data[stack->top];
return true;
}
int pop(SqStack* stack) {
int e;
bool ok = pop(stack, &e);
return e;
}
void test1() {
SqStack s = create_stack();
push(&s, 1);
push(&s, 6);
push(&s, 7);
printf("%d\n", pop(&s));//7
printf("%d\n", pop(&s));//6
printf("%d\n", pop(&s));//1
}
3.链式栈
栈的链式存储结构称为链栈,是运算受限的单链表。
其插入和删除操作只能在表头位置上进行.
链栈附加头结点,栈顶指针
top
就是链表的头指针。
图是栈的链式存储表示形式。
typedef struct LStackNode {
int data;
struct LStackNode* next;
}LStackNode,*LinkStack;
/**
* 栈初始化
*/
LinkStack create_stack() {
LStackNode* top = (LStackNode*)malloc(sizeof(LStackNode));
top->next = nullptr;
return(top);
}
/**
* 压栈(头插)
*/
bool push(LinkStack stack, int e) {
LStackNode *p = (LStackNode*)malloc(sizeof(LStackNode));
if (!p) {
return false; /* 申请新结点失败,返回错误标志 */
}
p->data = e;
p->next = stack->next;
stack->next = p;
return true;
}
/**
* 弹栈(头出)
*/
bool pop(LinkStack stack, int* e) {
LStackNode* top = stack;
if (top->next == nullptr)
return false; /* 栈空,返回错误标志 */
LStackNode* node = top->next;
*e = node->data;
top->next = node->next;
return true;
}
int pop(LinkStack stack) {
int e;
bool ok = pop(stack, &e);
return e;
}
void test1() {
LinkStack s = create_stack();
push(s, 1);
push(s, 6);
push(s, 7);
printf("%d\n", pop(s));//7
printf("%d\n", pop(s));//6
printf("%d\n", pop(s));//1
}
4.栈的应用
4.1.括号匹配
算法思想:
设置一个栈,
1)当读到左括号时,左括号进栈。
2)当读到右括号时,则从栈中弹出一个元素,与读到的左括号进行匹配,
若匹配成功, 继续读入;
否则匹配失败,返回
FLASE
。
bool match(const char* s) {
LinkStack stack = create_stack();
for (const char* p = s; *p != '\0'; p++) {
//【1】遇到左括号,直接进栈
if (*p == '[' || *p == '(') {
push(stack, *p);
}
//【2】遇到右括号,先弹栈,与右括号进行匹配判断
//匹配失败,则返回false
else if (*p == ']') {
if ((char)pop(stack) != '[') {
return false;
}
}
else if (*p == ')') {
if ((char)pop(stack) != '(') {
return false;
}
}
}
//【3】栈非空,说明匹配失败
if (!is_empty(stack))
return false;
return true;
}
void main() {
printf("%s\n", match("[][]()") ? "true" : "false");//true
printf("%s\n", match("[()()][]") ? "true" : "false");//true
printf("%s\n", match("[][]()[") ? "true" : "false");//false
printf("%s\n", match("[][]()]") ? "true" : "false");//false
printf("%s\n", match("[][]()[[") ? "true" : "false");//false
}
4.2.进制转换
算法思想:
1.
初始化空栈
2.
十进制数
N
非零时,循环以下操作:
(
1
)
N
与
8
求余,余数入栈;
(
2
)
N
更新为
N
与
8
的商
3.
栈非空时,循环以下操作:
(
1
)弹出栈顶元素;
(
2
)输出
e
。
/**
* 将十进制整数 N 转换为 d(2 或 8)进制数
*/
void conversion(int n, int d) {
SqStack s = create_stack();
for (; n > 0; n /= d) {//【1】将余数压栈
push(&s, n % d);
}
while (s.top > 0){//【2】栈非空,弹栈打印
printf("%d", pop(&s));
}
printf("\n");
}
void main() {
conversion(10,8);//12
conversion(10, 2);//1010
}
4.3.递归调用
递归调用:一个函数(或过程)直接或间接地调用自己本身,简称递归(
Recursive
)。
栈的另一个重要应用是在程序设计语言中实现递归和函数调用。
4.4.表达式求值
中缀表达式:
b -
(
a+5
)
* 3
后缀表达式
(Reverse Polish notation)
: b a5+ 3 * -
前缀表达式
(Polish notation)
: - b * +a5 3
注意:
1、数据的相对先后顺序不变
2、变化的是运算符号的位置和顺序
【表达式求值算法思想】0)构建两个栈, 数栈S1 (存储数字)和 符号栈S2 (存储运算符号和括号)1)从左->右扫描表达式,若 遇到数字直接,直接S1.push(e)2)若 遇到符号,设符号为CHa.如果 S2为空栈 ,直接S2.push(CH)b.如果 CH是左括号 ,直接S2.push(CH)c.如果 S2栈顶元素是左括号 ,直接S2.push(CH)d.如果 CH优先级大于S2栈顶符号优先级 ,直接S2.push(CH);如果 CH优先级小于或等于S2栈顶符号优先级 ,从S2中弹出栈顶元素,从S1中弹出两个数后完成运算,并将结果压回S1,回到步骤2,继续扫描
表达式求值功能代码
/**
* 运算符优先级函数
*/
int priority(char ch) {
if (ch == '(' || ch == ')')
return 0;
else if (ch == '+' || ch == '-')
return 1;
else if (ch == '*' || ch == '/' || ch == '%')
return 2;
}
int calc(int d1, int d2, char ch) {
switch (ch)
{
case '+':return d1 + d2;
case '-':return d1 - d2;
case '*':return d1 * d2;
case '/':return d1 / d2;
case '%':return d1 % d2;
default:return INT_MIN;
}
return INT_MIN;
}
/**
* 从数字栈种弹出两个元素,从符号栈中弹出栈顶符号,
完成运算,并将结果压回数字栈
*/
void calc_stack_num(SqStack *stack_num, SqStack* stack_ch) {
int d1 = pop(stack_num);
int d2 = pop(stack_num);
int n = calc(d2, d1, pop(stack_ch));
push(stack_num, n);
}
/**
* 计算表达式exp的值,并返回计算结果
*/
int calculate(const char* exp) {
const char* ch = exp;
//【1】创建符号栈和数栈
SqStack stack_num = create_stack();
SqStack stack_ch = create_stack();
while (*ch != '\0') {
if (isdigit(*ch)) {
int n = 0;
while (isdigit(*ch)) {
n = n * 10 + (*ch - '0');
ch++;
}
push(&stack_num, n);
continue;
}
else {
//[1]符号栈空或栈顶元素是左括号,或当前ch是左括号,直接压栈
if(empty(&stack_ch) || *ch == '('|| top(&stack_ch) == '(')
push(&stack_ch, *ch);
//[2]当前扫描到的符号是右括号
else if (*ch == ')') {
while (top(&stack_ch) != '(') {
calc_stack_num(&stack_num, &stack_ch);
}
pop(&stack_ch);//从符号栈弹出左括号,丢弃
}
//[3]当前符号优先级大于符号栈顶元素优先级,直接压栈
else if(priority(*ch) > priority(top(&stack_ch)))
push(&stack_ch, *ch);
//[4]当前符号优先级小于或等于符号栈顶元素优先级
//数字栈弹出两个元素,从符号栈中弹出栈顶元素,完成运算,
//并将结果压回数字栈,继续检测ch与栈顶符号优先级
else {
while (priority(*ch) <= priority(top(&stack_ch))
&& top(&stack_ch) != '(') {
calc_stack_num(&stack_num, &stack_ch);
}
}
}
//继续扫描下一个字符
ch += 1;
}
//[5]扫描结束后,符号栈不空时,继续计算结果
while(!empty(&stack_ch)) {
calc_stack_num(&stack_num, &stack_ch);
}
return top(&stack_num);
}
void main() {
printf("%d\n", calculate("3+5"));//8
printf("%d\n", calculate("3+5*2"));//13
printf("%d\n", calculate("(3+5)*2"));//16
printf("%d\n", calculate("20-(3+5)*2"));//4
printf("%d\n", calculate("(20-(3+5)*2)*2"));//8
}
运算结果:
例题:假设栈初始为空,将中缀表达式
6/3+
(
2*4-3*2
)
/4
转换为等价后缀表达式的
过程中,栈的变化.
5.附件
5.1公共头文件
/**
* common.h 默认包含头文件
*/
#ifndef __IOSTREAM_H__
#define __IOSTREAM_H__
#include <iostream>
#endif
#ifndef __STDIO_H__
#define __STDIO_H__
#include <stdio.h>
#endif
#ifndef __STDARG_H__
#define __STDARG_H__
#include <stdarg.h>
#endif
#ifndef __CTYPE_H__
#define __CTYPE_H__
#include <ctype.h>
#endif
#ifndef __INITIALIZER_LIST__
#define __INITIALIZER_LIST__
#include <initializer_list>
#endif