栈
栈是一种先进后出的的特殊线性表,只允许在固定的一端进行插入和删除元素操作,进行输入插入和删除操作的一端称为栈顶,另一端称为栈底
下面采用静态顺序表实现的方式简单封装一个栈
存储方式
#define MAX_SIZE 100
typedef int DataType;
typedef struct
{
DataType arr[MAX_SIZE];
int top;
}Stack;
相应操作
void StackInit(Stack *ps)
{
ps->top = 0;
}
void StackDestroy(Stack *ps)
{
ps->top = 0;
}
void StackPush(Stack *ps, DataType data)
{
assert(ps->top < MAX_SIZE);
ps->arr[ps->top++] = data;
}
void StackPop(Stack *ps)
{
assert(ps->top > 0);
ps->top--;
}
DataType StackTop(Stack *ps)
{
return ps->arr[ps->top - 1];
}
int StackSize(Stack *ps)
{
return ps->top;
}
int StackEmpty(Stack *ps)
{
return ps->top == 0 ? 1 : 0;
}
栈的应用
括号匹配问题
将需要检验的文档以字符串的方式表示,遍历整个字符串,遍历的过程中会出现三种情况
(1)左括号:遇到左括号进行入栈操作
(2)右括号:遇到右括号需要判断栈当前的状态。如果栈为空,则直接可以断定右括号多(在遍历的过程中只有左括号入栈);如果栈不为空,则判断栈顶元素和当前遍历到的括号是否匹配,如果匹配则出栈,不匹配表明不匹配
(3)其他字符:不做处理,继续向后遍历
遍历完整个字符串的时候判断最后栈是否为空,为空则表示括号匹配,如果不为空,则表示左括号多
void MatchBrackets(char *str)
{
Stack stack;
StackInit(&stack);
while (*str != '\0')
{
if (*str == '(' || *str == '[' || *str == '{')
{
StackPush(&stack, *str);
}
else if (*str == ')' || *str == ']' || *str == '}')
{
if (StackEmpty(&stack))
{
printf("右括号多\n");
return;
}
else
{
if ((StackTop(&stack) == '(' && *str == ')')
|| (StackTop(&stack) == '[' && *str == ']')
|| (StackTop(&stack) == '{' && *str == '}'))
{
StackPop(&stack);
}
else
{
printf("括号不匹配\n");
return;
}
}
}
str++;
}
if (StackEmpty(&stack))
{
printf("匹配成功\n");
}
else
{
printf("左括号多\n");
}
}
RPN求值
为了方便用C语言对后缀表达式进行标识,用结构体和枚举进行封装,用Item类型的数组来存放整个后缀表达式
typedef enum
{
NUMBER,
OPRETOR
}Type;
typedef enum
{
ADD,
SUB,
MUL,
DIV
}OP;
typedef struct
{
Type type;
int number;
OP opreator;
}Item;
遍历整个数组,遇到操作数压栈
遇到操作符连续出栈两次,根据此操作数对出栈的两个操作数做相应处理,然后将结果压入栈中(要注意DIV 和 SUB操作时,左右两个操作数的顺序)
遍历完整个数组,栈中只剩下的最后一个元素,则是整个表达式的结果
int RPN(Item *arr, int sz)
{
Stack stack;
int operand1 = 0;
int operand2 = 0;
OP tmp;
StackInit(&stack);
while (sz--)
{
if (arr->type == NUMBER)
{
StackPush(&stack, arr->number);
}
else
{
tmp = arr->opreator;
operand1 = StackTop(&stack);
StackPop(&stack);
operand2 = StackTop(&stack);
StackPop(&stack);
switch(tmp)
{
case ADD:
{
StackPush(&stack, operand1+operand2);
}
break;
case SUB:
{
StackPush(&stack, operand2-operand1);
}
break;
case MUL:
{
StackPush(&stack, operand2*operand1);
}
break;
case DIV:
{
StackPush(&stack, operand2/operand1);
}
break;
default:
break;
}
}
arr++;
}
return StackTop(&stack);
}
int main()
{
int result = 0;
Item arr[] = {
{NUMBER, 12, DEFAULT},
{NUMBER, 3, DEFAULT},
{NUMBER, 4, DEFAULT},
{OPRETOR, -1, ADD},
{OPRETOR, -1, MUL},
{NUMBER, 6, DEFAULT},
{OPRETOR, -1, SUB},
{NUMBER, 8, DEFAULT},
{NUMBER, 2, DEFAULT},
{OPRETOR, -1, DIV},
{OPRETOR, -1, ADD},
};
int size = sizeof(arr)/sizeof(arr[0]);
result = RPN(arr, size);
printf("%d\n", result);
return 0;
}
迷宫
采用回溯法来找迷宫出口
回溯法:对一个包括有很多个结点,每个结点有若干个搜索分支的问题,把原问题分解为若干个子问题求解的算法。当搜索到某个结点发现无法再继续搜索下去时,就让搜索过程回溯到该结点的前一个结点,继续搜索该结点外的其他尚未搜索的分支;如果发现该结点无法再继续搜索下去,就让搜索过程回溯到这个结点的前一个结点继续这样的搜索过程;一直进行下去,直到搜索到问题的解或者搜索完了全部可搜索的分支没有解存在为止
借助栈来完成回溯,用一个二维数组来构造迷宫,可走路线标记为1,墙标记为0,用一个结构体封装二维数组元素的坐标,所以与普通栈有差异的是用于存放迷宫位置的数据类型也是一个结构体,封装了这个二维数组的横纵坐标
typedef struct Coordinates
{
int x;
int y;
}Coordinates;
typedef Coordinates DataType;
typedef struct
{
DataType arr[MAX_SIZE];
int top;
}Stack;
简单迷宫
二维数组构造简单迷宫:
int Maze[6][6] = {
{0,0,0,0,0,0},
{0,0,1,0,0,0},
{0,0,1,0,0,0},
{0,0,1,1,1,0},
{0,0,1,0,1,1},
{0,0,1,0,0,0}
};
按左、上、右、下的顺序进行尝试,将走过的位置压入栈中,每走一步都对走过的位置进行标记,防止重复循环,无路可走时进行回溯,回溯的时候记得出栈,走过的路通过再次标记的方式避免重复走,所以如果能走,则选择的是与前面不同的路线,在走的过程中判断当前位置是否为出口,如果是出口函数renturn即可
用栈实现回溯时无法判断回溯后的位置已尝试的方向,所以必须要对走之后和回溯过的位置进行标记
void PrintMaze(int Maze[][6])
{
int i = 0;
int j = 0;
for (; i < 6; i++)
{
for (j = 0; j < 6; j++)
{
if (Maze[i][j] == 0)
{
printf("█");
}
else if (Maze[i][j] == 1)
{
printf(" ");
}
else if (Maze[i][j] == 3)
{
printf(" ");
}
else
{
printf("★");
}
}
printf("\n");
}
}
void FindExport(int Maze[][6], Coordinates* p)
{
Stack stack;
Coordinates pos;
StackInit(&stack);
pos.x = p->x;
pos.y = p->y;
StackPush(&stack, pos);//先把入口压入栈中
while (1)
{
Maze[pos.x][pos.y] = 2;
//左
if (Maze[pos.x][pos.y - 1] == 1)
{
pos.y -= 1;
StackPush(&stack, pos);
Maze[pos.x][pos.y] = 2;
if ((pos.x == 0 || pos.x == 5) || (pos.y == 0 || pos.y == 5))
{
printf("找到出口\n");
PrintMaze(Maze);
return;
}
}
//上
else if (Maze[pos.x - 1][pos.y] == 1)
{
pos.x -= 1;
StackPush(&stack, pos);
Maze[pos.x][pos.y] = 2;
if ((pos.x == 0 || pos.x == 5) || (pos.y == 0 || pos.y == 5))
{
printf("找到出口\n");
PrintMaze(Maze);
return;
}
}
//右
else if (Maze[pos.x][pos.y + 1] == 1)
{
pos.y += 1;
StackPush(&stack, pos);
Maze[pos.x][pos.y] = 2;
if ((pos.x == 0 || pos.x == 5) || (pos.y == 0 || pos.y == 5))
{
printf("找到出口\n");
PrintMaze(Maze);
return;
}
}
//下
else if (Maze[pos.x + 1][pos.y] == 1)
{
pos.x += 1;
StackPush(&stack, pos);
Maze[pos.x][pos.y] = 2;
if ((pos.x == 0 || pos.x == 5) || (pos.y == 0 || pos.y == 5))
{
printf("找到出口\n");
PrintMaze(Maze);
return;
}
}
else
{
Maze[StackTop(&stack).x][StackTop(&stack).y] = 3;
StackPop(&stack);
pos = StackTop(&stack);
}
system("cls");
PrintMaze(Maze);
Sleep(300);
}
}
通路间不带环
二维数组构造 通路间不带环迷宫:
int Maze[7][6] = {
{0,0,0,0,0,0},
{0,1,1,1,1,1},
{0,1,0,0,0,0},
{0,1,0,1,1,1},
{0,1,0,1,0,0},
{0,1,1,1,1,1},
{0,1,0,0,0,0},
};
找到出口将出口堵住,让其进行回溯再找下一条出口,直到回溯到入口即栈为空时表示找完了所有出口路线
void PrintMaze(int Maze[][6])
{
int i = 0;
int j = 0;
for (; i < 7; i++)
{
for (j = 0; j < 6; j++)
{
if (Maze[i][j] == 0)
{
printf("█");
}
else if (Maze[i][j] == 1)
{
printf(" ");
}
else if (Maze[i][j] == 3)
{
printf(" ");
}
else
{
printf("★");
}
}
printf("\n");
}
}
void FindExport(int Maze[][6], Coordinates* p)
{
Stack stack;
Coordinates pos;
StackInit(&stack);
pos.x = p->x;
pos.y = p->y;
StackPush(&stack, pos);//先把入口压入栈中
while (1)
{
Maze[pos.x][pos.y] = 2;
//左
if (Maze[pos.x][pos.y - 1] == 1)
{
pos.y -= 1;
StackPush(&stack, pos);
Maze[pos.x][pos.y] = 2;
if ((pos.x == 0 || pos.x == 6) || (pos.y == 0 || pos.y == 5))
{
printf("找到出口\n");
PrintMaze(Maze);
system("pause");
continue;
}
}
//上
else if (Maze[pos.x - 1][pos.y] == 1)
{
pos.x -= 1;
StackPush(&stack, pos);
Maze[pos.x][pos.y] = 2;
if ((pos.x == 0 || pos.x == 6) || (pos.y == 0 || pos.y == 5))
{
printf("找到出口\n");
PrintMaze(Maze);
system("pause");
continue;
}
}
//右
else if (Maze[pos.x][pos.y + 1] == 1)
{
pos.y += 1;
StackPush(&stack, pos);
Maze[pos.x][pos.y] = 2;
if ((pos.x == 0 || pos.x == 6) || (pos.y == 0 || pos.y == 5))
{
printf("找到出口\n");
PrintMaze(Maze);
system("pause");
continue;
}
}
//下
else if (Maze[pos.x + 1][pos.y] == 1)
{
pos.x += 1;
StackPush(&stack, pos);
Maze[pos.x][pos.y] = 2;
if ((pos.x == 0 || pos.x == 6) || (pos.y == 0 || pos.y == 5))
{
printf("找到出口\n");
PrintMaze(Maze);
system("pause");
continue;
}
}
else
{
if (!StackEmpty(&stack))
{
Maze[StackTop(&stack).x][StackTop(&stack).y] = 3;
StackPop(&stack);
}
pos = StackTop(&stack);
}
if (StackEmpty(&stack))
{
printf("再无出口\n");
return;
}
system("cls");
PrintMaze(Maze);
Sleep(100);
}
}
通路间带环
二维数组构造 通路间带环迷宫:
int Maze[6][6] = {
{0,0,0,0,0,0},
{0,1,1,1,0,0},
{0,1,0,1,0,0},
{0,1,0,1,0,0},
{0,1,1,1,1,1},
{0,1,0,0,0,0},
};
只有一个出口,不能对出口再进行封堵,采用递归的方式实现,在回溯前将不可再走的位置置为通路
void PrintMaze(int Maze[][6])
{
int i = 0;
int j = 0;
for (; i < 6; i++)
{
for (j = 0; j < 6; j++)
{
if (Maze[i][j] == 0)
{
printf("█");
}
else if (Maze[i][j] == 1)
{
printf(" ");
}
else
{
printf("★");
}
}
printf("\n");
}
}
void FindExport(int Maze[][6], Coordinates* p)
{
Coordinates nextPos;
Maze[(*p).x][(*p).y] = 2;
system("cls");
PrintMaze(Maze);
Sleep(100);
if ((*p).y == 5)
{
system("cls");
printf("找到出口\n");
PrintMaze(Maze);
Maze[(*p).x][(*p).y] = 1;
system("pause");
return;
}
//左
if (Maze[(*p).x][(*p).y - 1] == 1)
{
nextPos = *p;
nextPos.y -= 1;
FindExport(Maze, &nextPos);
}
//上
if (Maze[(*p).x - 1][(*p).y] == 1)
{
nextPos = *p;
nextPos.x -= 1;
FindExport(Maze, &nextPos);
}
//右
if (Maze[(*p).x][(*p).y + 1] == 1)
{
nextPos = *p;
nextPos.y += 1;
FindExport(Maze, &nextPos);
}
//下
if (Maze[(*p).x + 1][(*p).y] == 1)
{
nextPos = *p;
nextPos.x += 1;
FindExport(Maze, &nextPos);
}
Maze[(*p).x][(*p).y] = 1;//回溯前将不可再走的位置置为通路
}