408线性表【跨考小白学习笔记2】——栈的应用
文章目录
pre:栈数据类型ADT定义及基本操作声明
本文代码:c语言,版本c99
#define Elemtype char
#define Maxsize 50 //定义栈中元素的最大个数
typedef struct {
Elemtype data[Maxsize]; //存放栈中元素
int top; //栈项指针i,数据类型是int
} SqStack;
typedef struct Linknode {
Elemtype data; //数据域
struct Linknode* next; //指针域
} StackNode, *LinkStackPtr;
typedef struct {
LinkStackPtr top;
int count;
} LinkStack;
bool InitStack(SqStack* S);
bool StackEmpty(SqStack S);
int StackLength(SqStack S);
bool GetTop(SqStack S, Elemtype* e);
bool Push(SqStack* S, Elemtype e);
bool Pop(SqStack* S, Elemtype* e);
bool StackTraverse(SqStack S);
bool InitLinkStack(LinkStack* S);
bool ClearLinkStack(LinkStack* S);
bool LinkStackEmpty(LinkStack S);
int LinkStackLength(LinkStack S);
bool GetTop_LinkStack(LinkStack S, Elemtype* e);
bool Push_LinkStack(LinkStack* S, Elemtype e);
bool Pop_LinkStack(LinkStack* S, Elemtype* e);
bool LinkStackTraverse(LinkStack S);
3.3.1栈在括号匹配中的应用
bool bracketcheck(char str[], int length) {
//形参说明:str[]需匹配的字符串;length匹配的长度
//初始条件:栈ADT,此处使用顺序栈
//操作结果:匹配成功返回true(输出为1)匹配失败返回false(输出为0)
//算法设计思想:
/*从左到右扫描一个字符串:
1)凡出现左括弧,则进栈;
2)凡出现右括弧,
首先检查栈是否空
若栈空,则表明该“右括弧”多余
否则(栈非空)和栈顶元素比较,
若相匹配,则“左括弧出栈”
否则表明不匹配
3)表达式检验结束时,
若栈空,则表明表达式中匹配正确
否则表明“左括弧”有余*/
SqStack S;
InitStack(&S); //初始化一个栈
for (int i = 0; i < length; i++) {
if (str[i] == '(' || str[i] == '[' || str[i] == '{')
Push(&S, str[i]); //扫描到左括号,入栈
else if (str[i] == ')' || str[i] == ']' || str[i] == '}') {
if (StackEmpty(S)) //扫描到右括号,且当前栈空
return false; //匹配失败
Elemtype topElem;
Pop(&S, &topElem); //栈顶元素出栈
if (str[i] == ')' && topElem != '(')
return false;
if (str[i] == ']' && topElem != '[')
return false;
if (str[i] == '}' && topElem != '{')
return false;
}
}
return StackEmpty(S); //检索完全部括号后,栈空说明匹配成功
}
调用
int main() {
// 1.括号匹配
char a_group[50] = {'1', '+', '(', '4', '-', '5', ')', '/', '2', '0'};
printf("%d\n",
bracketcheck(a_group, 20)); // 1代表匹配成功,0代表false匹配失败
return 0;
}
3.3.2栈在表达式求值中的应用
1.1)将中缀表达式转成后缀表达式
bool transform(char suffix[], char exp[]) {
//形参说明:
/* *suffix后缀表达式字符串,传入实参&suffix[];
exp[]即expression中缀表达式*/
//初始条件:输入中缀表达式
//操作结果:返回后缀表达式
//算法设计思想:
/*初始化一个栈,用于保存暂时还不能确定运算顺序的运算符。
从左到右处理各个元素,直到末尾。可能遇到三种情况
①遇到操作数。直接加入后缀表达式。
②遇到界限符。遇到“(”直接入栈;遇到“)”则依次弹出栈内运算符并加入后缀表达式,直到弹出“(”为止。注意:“(”不加入后缀表达式。
③遇到运算符。依次弹出栈中优先级高于或等于当前运算符的所有运算符,并加入后缀表达式若碰到“(”或栈空则停止。之后再把当前运算符入栈。
按上述方法处理完所有字符后,将栈中剩余运算符依次弹出,并加入后缀表达式。
*/
SqStack S; //用于保存暂时还不能确定运算顺序的运算符
InitStack(&S);
Elemtype topElem = '\0';
int j = 0;
for (int i = 0; i < strlen(exp); i++) {
if (exp[i] == ')') {
GetTop(S, &topElem);
while (topElem != '(') {
Pop(&S, &topElem);
if (topElem != '(') {
suffix[j++] = topElem;
}
}
} else if (exp[i] == '(') {
Push(&S, exp[i]);
} else if (exp[i] == '+' || exp[i] == '-') {
GetTop(S, &topElem);
if (topElem == '*' || topElem == '/') {
while (!StackEmpty(S) && topElem != '(') { //栈顶元素优先级更高故出栈
Pop(&S, &topElem);
suffix[j++] = topElem;
}
}
Push(&S, exp[i]); //并将该元素压入栈
} else if (exp[i] == '*' || exp[i] == '/') {
Push(&S, exp[i]); //由于乘除优先级最高,直接进栈
} else { //操作数直接输出
suffix[j++] = exp[i];
}
}
while (!StackEmpty(S)) {
Pop(&S, &topElem);
suffix[j++] = topElem;
}
return true;
}
1.2)对后缀表达式求值
int Operate_char2int(char a, char op, char b) {
//输出-9999即为报错
//如果不强转,用char + char 遇到其中一个char 是 0 的时候会出错
//输出2 + '\0'= 98 这里的'\0'显示不出来,但复制的时候是一个换行或终止符
// if (a == '\0')
// a = '0';
// if (b == '\0')
// b = '0';
char a_str[Maxsize] = {0};
char b_str[Maxsize] = {0};
if (isdigit(a)) {
a_str[0] = a;
} else
sprintf(a_str, "%d",a);
// sprintf 是转换整型为字符串!int转不成char,可以转char[]
// 如果是char会发生上溢,overflow
if (isdigit(b)) {
b_str[0] = b;
} else
sprintf(b_str, "%d", b);
switch (op) {
case '+':
return atoi(a_str) + atoi(b_str);
break;
case '-':
return atoi(a_str) - atoi(b_str);
break;
case '*':
return atoi(a_str) * atoi(b_str);
break;
case '/':
if (b == '0')
return -9999; //除数不能为0,报错
return atoi(a_str) / atoi(b_str);
break;
}
return -9999;
}
int calculte_suffix(char suffix[]) {
//形参说明:传入suffix[]后缀表达式,
//初始条件:传入后缀表达式,建立栈ADT,operation_char2int
//操作结果:返回结果(int型)
//算法设计思想:
/*用栈实现后缀表达式的计算:
①从左往右扫描下一个元素,直到处理完所有元素
②若扫描到操作数则压入栈,并回到①;否则执行③
③若扫描到运算符,则弹出两个栈顶元素,执行相应运算,运算结果压回栈顶,回到①
*/
SqStack S;
InitStack(&S);
Elemtype topElem;
Elemtype SecondElem;
int result = 0;
for (int i = 0; i < strlen(suffix); i++) {
if (suffix[i] != '+' && suffix[i] != '-' && suffix[i] != '*' &&
suffix[i] != '/') {
Push(&S, suffix[i]); //压入栈的都是十六进制
} else {
Pop(&S, &topElem);
Pop(&S, &SecondElem);
result = Operate_char2int(SecondElem, suffix[i], topElem);
printf("%c %c %c = %d\n", SecondElem, suffix[i], topElem, result);
//这里result是int压入的是char栈!
//当result=0时,压入栈的被强转成为'\0',当result!=0时,压入栈者强转为十六进制。hoit()可将十六进制字符串强转为十进制整型
Push(&S, result);
}
}
return result;
}
char 与 int 强转方式
sscanf()
atoi() 最方便
Typecasting 输出是ascii码
代码示例:
const char *str = "12345";
char c = 's';
int x, y, z;
sscanf(str, "%d", &x); // Using sscanf
printf("\nThe value of x : %d", x);
y = atoi(str); // Using atoi()
printf("\nThe value of y : %d", y);
z = (int)(c); // Using typecasting
printf("\nThe value of z : %d", z);
c++中,对于signed
char,将其转化为int时,最高位为符号位,那么扩展时,就会对符号位进行扩展,即将整型比字符多出来的位全部设置成与符号位相同。
比如0x80,变成int就会变成0xffffff80;而0x70就会变成0x00000070。
而unsigned char就不会进行扩展,因此想表示byte的时候,最好使用unsigned
char
————————————————
版权声明:该引用段落为CSDN博主「木凡辰」的原创文章,遵循CC 4.0
BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_44844115/article/details/102801953
2)计算中缀表达式
从下面代码也可以看出,直接计算中缀表达式繁琐且易出错
int calculte_exp(char exp[]) {
//形参说明:传入exp中缀表达式
//初始条件:栈ADT,func Operate_char2int
//操作结果:返回计算结果;如果计算失败,返回-9999
//算法设计思想:
/*初始化两个栈,操作数栈和运算符栈
若扫描到操作数,压入操作数栈
若扫描到运算符或界限符,则按照“中缀转后缀”相同的逻辑压入运算符栈
(期间也会弹出运算符每当弹出一个运算符时,就需要再弹出两个操作数栈的栈顶元素并执行相应运算,运算结果再压回操作数栈)
*/
int result = 0;
SqStack S_nums; //操作数栈
SqStack S_op; //运算符栈
InitStack(&S_nums);
InitStack(&S_op);
Elemtype topElem;
Elemtype SecondElem;
Elemtype Operation;
for (int i = 0; i < strlen(exp); i++) {
if (exp[i] == ')') {
GetTop(S_op, &Operation);
while (Operation != '(') {
Pop(&S_op, &Operation);
if (Operation != '(') {
Pop(&S_nums, &topElem);
Pop(&S_nums, &SecondElem);
result = Operate_char2int(SecondElem, Operation, topElem);
printf("现在执行的是 %c %c %c\n", SecondElem, Operation, topElem);
Push(&S_nums, result);
}
}
} else if (exp[i] == '(') {
Push(&S_op, exp[i]);
} else if (exp[i] == '+' || exp[i] == '-') {
GetTop(S_op, &Operation);
if (Operation == '*' || Operation == '/') {
while (!StackEmpty(S_op) &&
Operation != '(') { //栈顶元素优先级更高故出栈
Pop(&S_op, &Operation);
Pop(&S_nums, &topElem);
Pop(&S_nums, &SecondElem);
result = Operate_char2int(SecondElem, Operation, topElem);
printf("现在执行的是 %c %c %c\n", SecondElem, Operation, topElem);
Push(&S_nums, result);
}
}
Push(&S_op, exp[i]); //并将该元素压入栈
} else if (exp[i] == '*' || exp[i] == '/') {
Push(&S_op, exp[i]); //由于乘除优先级最高,直接进栈
} else { //操作数压入操作数栈
Push(&S_nums, exp[i]);
}
}
while (!StackEmpty(S_op)) {
Pop(&S_op, &Operation);
Pop(&S_nums, &topElem);
Pop(&S_nums, &SecondElem);
result = Operate_char2int(SecondElem, Operation, topElem);
printf("现在执行的是 %c %c %c\n", SecondElem, Operation, topElem);
Push(&S_nums, result);
// Push(&S_nums,result);//?result是2,这里压入栈的是\x02
// int压入char栈会强转为十六进制
}
Pop(&S_nums, &topElem);
if (StackEmpty(S_nums))
return result;
else
return -9999;
}
调用
int main(){
// 2.表达式求值
char suffix[Maxsize] = {0};
//中缀表达式:a * b + (c - d / e) * f
//后缀式: a b * c d e / - f * +
// char exp[Maxsize] = {'a', '*', 'b', '+', '(', 'c', '-','d','/', 'e',
// ')',
// '*', 'f'};
char exp[Maxsize] = {'2', '+', '(', '5', '-', '3', ')', '/', '2'};
transform(suffix, exp);
puts(suffix);
printf("%s\n", suffix);
printf("%s计算结果是%d \n", exp, calculte_suffix(suffix));
printf("%s计算结果是%d \n", exp, calculte_exp(exp));
return 0 ;
}
3.3.2_经验总结
1.char之间不能直接运算,故需写一个switch语句即func Operation_char2int
2.当int 压入 char栈时,强转十六进制,但类型不是char 而是int
3.当栈中的char 或int
%d和%x均存在时,统一存储格式较麻烦
可在operation处运用isdigit(char c)函数 判断传过来的实参是
(1)char里包含的int,还是
(2)本身是个int,
----isxdigit判断字符串中是否是十六进制字符,还包括A~F,易与英文字符弄混,故这里选用isdigit判断更为合适和便捷
4.数组直接传入的是首地址,无需使用引用等,os操作数组是按首地址+i更改地址上的data的
可参见本链接。
char ↔ \leftrightarrow ↔ int 在c99中的函数分析 :
1)hoit( )函数将十六进制字符串转整型,但该函数并非c标准库
2)atoi( )函数 是c标准库,作用为将字符串强转整型
3)itoa( )函数 头文件stdlib.h可能不包含该函数(将整型转字符串)
4)用sprintf(str[ ],"%d",int)输出的str[ ]或str无法压入栈,因为这里是数组(或数组指针),和char不符
5)sscanf( )函数用于从字符串中读取指定格式的数据,
sscanf可代替atoi,且比atoi适用范围更广 参考链接 。
本文遇到的问题是char 和 int 混在一起,sscanf接受输入的参数必须是字符串
sscanf(str,"%d %[a-z]", &num,lowercase);
这里lowercase[]是数组
3.3.3栈在递归中的应用
汉诺塔(未验证)
void hanoi(int n, char A, char B, char C) {
// 将塔座A上按直径由小到大且至上而下编号为1至n的n个圆盘按规则搬到塔座C上,B用作辅助塔座。
if (n == 1)
printf(" move %c to %c ", A, C);
else {
hanoi(n - 1, A, C, B); // 将A上编号为1至n-1的圆盘移到B, C作辅助塔
printf(" move %c to %c ", A, C);
hanoi(n - 1, B, A, C); // 将B上编号为1至n-1的圆盘移到C, A作辅助塔
}
}
反向打印数组A[n]的值
void recfunc(int A[], int n) {
//传入的n是A[]的长度,在调用时需n-1
if (n == 0)
return;
else {
printf("%d ", A[n - 1]);
// cout << A[n] << " ";//c++
n--;
recfunc(A, n);
}
}
cout是c++独有,与printf区别在于cout无需控制变量类型格式化
。
可参见本链接
搜索链表最后一个结点并打印其数值(见单链表)
代码略
递归算法求阶乘
//计算正整数n!
int factorial(int n) {
if (n == 0 || n == 1)
return 1;
else
return n * factorial(n - 1);
}
递归算法求斐波那契数列
int Fib(int n) {
int result = 0;
if (n == 0)
result = 0;
else if (n == 1)
result = 1;
else
result = Fib(n - 1) + Fib(n - 2);
printf("现在是n=%d函数在运行,取得的结果是%d\n", n, result);
return result;
}
更简洁的
int Fbi(int i) {
if (i < 2)
return i == 0 ? 0 : 1;
return Fbi(i - 1) + Fbi(i - 2);
}/* 这里Fbi就是函数自己,等于在调用自己 */
调用
int main(){
int x = factorial(10);
printf("%d\n", x);
int y = Fib(11); //斐波那契数列第一项n=0,故此处输出的是第n+1项
printf("%d\n", y);
printf("%d\n", Fbi(10));
int A[] = {3, 6, 2, 8, 8, 0, 3};
recfunc(A, sizeof(A) / sizeof(int));
return 0;
}
改为非递归过程的目的是提高效率。
单向递归和尾递归可直接用迭代实现其非递归过程,其他情形必须借助栈实现非递归过程。
单向递归 是指 递归过程执行时 虽然可能有 多个分支 ,但可以 保存前面计算的结果 以供后面的语句使用。
尾递归 指程序中只有一个递归语句 ,且在程序最后,这时递归栈保存的内容无需再利用。
附录:栈的基本操作
/* 顺序栈 */
/* 构造一个空栈S */
bool InitStack(SqStack* S) {
S->top = -1;
return true;
}
bool StackEmpty(SqStack S) {
if (S.top == -1)
return true;
return false;
}
/* 返回S的元素个数,即栈的长度 */
int StackLength(SqStack S) {
return S.top + 1;
}
/* 若栈不空,则用e返回S的栈顶元素,并返回OK;否则返回ERROR */
bool GetTop(SqStack S, Elemtype* e) {
if (S.top == -1)
return false;
else
*e = S.data[S.top];
return true;
}
/* 插入元素e为新的栈顶元素 */
bool Push(SqStack* S, Elemtype e) {
if (S->top == Maxsize - 1) /* 栈满 */
return false;
S->top++; /* 栈顶指针增加一 */
S->data[S->top] = e; /* 将新插入元素赋值给栈顶空间 */
return true;
}
/* 若栈不空,则删除S的栈顶元素,用e返回其值,并返回OK;否则返回ERROR */
bool Pop(SqStack* S, Elemtype* e) {
if (S->top == -1)
return false;
*e = S->data[S->top]; /* 将要删除的栈顶元素赋值给e */
S->top--; /* 栈顶指针减一 */
return true;
}
bool visit(Elemtype c) {
printf("%d ", c);
return true;
}
/* 从栈底到栈顶依次对栈中每个元素显示 */
bool StackTraverse(SqStack S) {
int i;
i = 0;
while (i <= S.top) {
visit(S.data[i++]);
}
printf("\n");
return true;
}
/*链栈*/
/* 构造一个空栈S */
bool InitLinkStack(LinkStack* S) {
S->top = (LinkStackPtr)malloc(sizeof(StackNode));
if (!S->top)
return false;
S->top = NULL;
S->count = 0;
return true;
}
/* 把S置为空栈 */
bool ClearLinkStack(LinkStack* S) {
LinkStackPtr p, q;
p = S->top;
while (p) {
q = p;
p = p->next;
free(q); //为什么不可以直接free p呢?
}
S->count = 0;
return true;
}
/* 若栈S为空栈,则返回TRUE,否则返回FALSE */
bool LinkStackEmpty(LinkStack S) {
if (S.count == 0)
return true;
else
return false;
}
/* 返回S的元素个数,即栈的长度 */
int LinkStackLength(LinkStack S) {
return S.count;
}
/* 若栈不空,则用e返回S的栈顶元素,并返回OK;否则返回ERROR */
bool GetTop_LinkStack(LinkStack S, Elemtype* e) {
if (S.top == NULL)
return false;
else
*e = S.top->data;
return true;
}
/* 插入元素e为新的栈顶元素 */
bool Push_LinkStack(LinkStack* S, Elemtype e) {
LinkStackPtr s = (LinkStackPtr)malloc(sizeof(StackNode));
s->data = e;
s->next = S->top; /* 把当前的栈顶元素赋值给新结点的直接后继,见图中① */
S->top = s; /* 将新的结点s赋值给栈顶指针,见图中② */
S->count++;
return true;
}
/* 若栈不空,则删除S的栈顶元素,用e返回其值,并返回OK;否则返回ERROR */
bool Pop_LinkStack(LinkStack* S, Elemtype* e) {
LinkStackPtr p;
if (LinkStackEmpty(*S))
return false;
*e = S->top->data;
p = S->top; /* 将栈顶结点赋值给p,见图中③ */
S->top = S->top->next; /* 使得栈顶指针下移一位,指向后一结点,见图中④ */
free(p); /* 释放结点p */
S->count--;
return true;
}
bool LinkStackTraverse(LinkStack S) {
LinkStackPtr p;
p = S.top;
while (p) {
visit(p->data);
p = p->next;
}
printf("\n");
return true;
}