文章目录
1.0写在前面(关于队列)
本来还想写一个队列的但是,队列其实和栈差不多,队列就是先入先出,栈就是先入后出,感觉没什么好写,就水一下算惹~。唯一需要注意的是,栈如何判断满员等问题。一般可以用循环队列解决。本文以栈为主。
队列和栈一样支持pop和push两个操作,但是与栈不同,pop完成的不是取出最顶端的元素,而是取出最底端的元素。也就是说最初放入的元素能够最先被取出(这点刚好和栈相反)
// 队列的顺序存储结构(循环队列)
#define MAX_QSIZE 5 // 最大队列长度+1
typedef struct {
int *base; // 初始化的动态分配存储空间
int front; // 头指针,若队列不空,指向队列头元素
int rear; // 尾指针,若队列不空,指向队列尾元素的下一个位置
} SqQueue;
// 构造一个空队列Q
SqQueue* Q_Init() {
SqQueue *Q = (SqQueue*)malloc(sizeof(SqQueue));
// 存储分配失败
if (!Q){
exit(OVERFLOW);
}
Q->base = (int *)malloc(MAX_QSIZE * sizeof(int));
// 存储分配失败
if (!Q->base){
exit(OVERFLOW);
}
Q->front = Q->rear = 0;
return Q;
}
// 销毁队列Q,Q不再存在
void Q_Destroy(SqQueue *Q) {
if (Q->base)
free(Q->base);
Q->base = NULL;
Q->front = Q->rear = 0;
free(Q);
}
// 将Q清为空队列
void Q_Clear(SqQueue *Q) {
Q->front = Q->rear = 0;
}
// 若队列Q为空队列,则返回1;否则返回-1
int Q_Empty(SqQueue Q) {
if (Q.front == Q.rear) // 队列空的标志
return 1;
else
return -1;
}
// 返回Q的元素个数,即队列的长度
int Q_Length(SqQueue Q) {
return (Q.rear - Q.front + MAX_QSIZE) % MAX_QSIZE;
}
// 若队列不空,则用e返回Q的队头元素,并返回OK;否则返回ERROR
int Q_GetHead(SqQueue Q, int &e) {
if (Q.front == Q.rear) // 队列空
return -1;
e = Q.base[Q.front];
return 1;
}
// 打印队列中的内容
void Q_Print(SqQueue Q) {
int p = Q.front;
while (Q.rear != p) {
cout << Q.base[p] << endl;
p = (p + 1) % MAX_QSIZE;
}
}
// 插入元素e为Q的新的队尾元素
int Q_Put(SqQueue *Q, int e) {
if ((Q->rear + 1) % MAX_QSIZE == Q->front) // 队列满
return -1;
Q->base[Q->rear] = e;
Q->rear = (Q->rear + 1) % MAX_QSIZE;
return 1;
}
// 若队列不空,则删除Q的队头元素,用e返回其值,并返回1;否则返回-1
int Q_Poll(SqQueue *Q, int &e) {
if (Q->front == Q->rear) // 队列空
return -1;
e = Q->base[Q->front];
Q->front = (Q->front + 1) % MAX_QSIZE;
return 1;
}
队列的应用
队列的应用其实有很多,最常见的就是排队的问题,排队实际上就是一个队列,先进先出。比如说银行排队等等,由于更加贴近我们平时的生活,理解起来应该也不难。
1.1栈的定义
栈(stack) 是限定仅在表尾进行插入或删除操作的线性表。因此,对栈来说,表断有其特殊的含义,称为栈顶,相应的表底称为栈底,不含元素的称为空栈。
栈又称为后进先出 的线性表(LIFO结构);结构如下:
1.2栈的基本操作
Status InitStack(SqStack &S);//构造一个空栈
Status DestroyStack(SqStack &S);//摧毁一个栈
Status ClearStack(SqStack &S);//把栈置空
Status StackEmpty(SqStack S);//判断是否为空栈
Status StackLength(SqStack S);//返回栈的长度
Status GetTop(SqStack S, SElemType &e);//返回栈顶的元素
Status Push(SqStack &S, SElemType e);//插入新元素到栈顶
Status Pop(SqStack &S, SElemType &e);//从栈顶弹出一个元素
Status StackTraverse(SqStack S, Status(*visit));//从栈底依次对每个元素调用visit,一旦visit失败,则操作失败
1.3栈的常用基本操作的实现
1.3.1数据类型的定义
这里为了方便操作将元素类型定义为int类型,具体使用过程中,可以是其他数据类型。
#define OVERFLOW 0
#define OK 1
#define TRUE 1
#define FALSE 0
#define ERROR 0
typedef int ElemType;
typedef int Status;
typedef struct {
ElemType *base; //栈底
ElemType *top; //栈顶指针
int stacksize; //当前已分配的存储空间
} SqStrack;
1.3.2构造一个空栈(InitStack)
初始化让栈的顶部等于底部,即
top == base
就可以判定为此栈为空,也就是初始化空栈,当其他需要判定是否为空栈的时候也可以使用这种方法来判断。
Status InitStack(SqStack *S) {
S->base = (ElemType *)malloc(sizeof(ElemType));
if (!S->base) {
exit(OVERFLOW);
} //申请失败
S->top = S->base;
S->stacksize = 1;
return OK;
}
1.3.3插入一个元素为栈顶元素(Push)
当插入一个新元素的时候需要注意如果内存空间不够需要重新申请空间,否则会插入失败,同时也要注意栈顶的指针向后移动一个位置, 即
*S.top = e; S.top++;
或者写作*S->top++ = e;
Status Push(SqStack *S, ElemType e) {
//将新的元素e插入到栈顶
if (S->top - S->base >= S->stacksize) {
S->base =
(ElemType *)realloc(S->base, (S->stacksize + 1) * sizeof(ElemType));
if (!S->base) {
exit(OVERFLOW);
}
S->top = S->base + S->stacksize;
S->stacksize = S->stacksize + 1;
} //如果空间不够就重新申请空间
*S->top++ = e;
return OK;
}
1.3.4弹出栈顶元素并返回(Pop)
这里需要注意判断栈是否为空,如果为空就不能成功返回值。判定方法和上面1.3.2提到的一样。
Status Pop(SqStack *S, ElemType *e) {
//若栈不为空,则弹出栈顶的元素,否则返回空栈
if (S->top == S->base) {
printf("此时栈中为空\n");
return ERROR;
}
*e = *(--(S->top));
return OK;
}
1.3.5返回栈顶的元素,不删除(GetTop)
同理,这里也需要注意一个判断栈是否为空,空就无法返回栈顶的元素。
同时也要注意,这里不能把*e = *(S.top - 1)
写成*e = *(S.top - -);
,后者就变成弹出元素了,S.top--
实现自减以后再赋值,会改变栈顶指针的位置
Status GetTop(SqStack S, ElemType *e) {
if (S.top == S.base) {
printf("此时栈中为空\n");
return ERROR;
} //此时栈中为空
*e = *(S.top - 1);
return OK;
}
1.3.6判断栈是否为空(StackEmpty)
这里方法和前面提到的一样,不再重复赘述。
Status StackEmpty(SqStack S) {
if (S.top == S.base) {
printf("此时栈中为空\n");
return FALSE;
}
return TRUE;
}
1.3.7返回当前栈的大小(StackLength)
我们可以注意到,定义结构体时,我们有一个变量
stacksize
其中存储的就是我们的当前栈的大小,所以这个函数只需要返回这个参数就好惹~代码参上;
int StackLength(SqStack S) { return S.stacksize; }
1.3.8栈的操作代码的简单整合与验证
/*
* @Author: Henry
* @Date: 2020-07-04 14:48:11
* @LastEditors: Henry
* @LastEditTime: 2021-04-05 14:54:47
*/
#include <stdio.h>
#include <stdlib.h>
#define OVERFLOW 0
#define OK 1
#define TRUE 1
#define FALSE 0
#define ERROR 0
typedef int ElemType;
typedef int Status;
typedef struct {
ElemType *base; //栈底
ElemType *top; //栈顶指针
int stacksize; //当前已分配的存储空间
} SqStack;
Status InitStack(SqStack *S) {
S->base = (ElemType *)malloc(sizeof(ElemType));
if (!S->base) {
exit(OVERFLOW);
} //申请失败
S->top = S->base;
S->stacksize = 1;
return OK;
}
Status GetTop(SqStack S, ElemType *e) {
if (S.top == S.base) {
printf("此时栈中为空\n");
return ERROR;
} //此时栈中为空
*e = *(S.top - 1);
return OK;
}
Status Push(SqStack *S, ElemType e) {
//将新的元素e插入到栈顶
if (S->top - S->base >= S->stacksize) {
S->base =
(ElemType *)realloc(S->base, (S->stacksize + 1) * sizeof(ElemType));
if (!S->base) {
exit(OVERFLOW);
}
S->top = S->base + S->stacksize;
S->stacksize = S->stacksize + 1;
} //如果空间不够就重新申请空间
*S->top++ = e;
return OK;
}
Status Pop(SqStack *S, ElemType *e) {
//若栈不为空,则弹出栈顶的元素,否则返回空栈
if (S->top == S->base) {
printf("此时栈中为空\n");
return ERROR;
}
*e = *(--(S->top));
return OK;
}
int StackLength(SqStack S) { return S.stacksize; }
Status StackEmpty(SqStack S) {
if (S.top == S.base) {
printf("此时栈中为空\n");
return FALSE;
}
return TRUE;
}
int main() {
SqStack S;
ElemType e;
InitStack(&S);
GetTop(S, &e);
printf("请输入需要插入的元素:\n");
int n;
scanf("%d", &n);
Push(&S, n);
GetTop(S, &e);
printf("此时栈顶的元素为:%d\n", e);
printf("此时栈的大小为:%d\n", StackLength(S));
Pop(&S, &e);
printf("此时弹出的元素为:%d\n", e);
StackEmpty(S);
system("pause");
return 0;
}
1.4栈的应用举例
1.4.1写在前面
为方便操作,我将栈的操作写成了头文件,这样就可以简化代码了,头文件名为
my_stack
/*
* @Author: Henry
* @Date: 2020-07-04 14:48:11
* @LastEditors: Henry
* @LastEditTime: 2021-04-05 15:30:01
*/
#include <stdio.h>
#include <stdlib.h>
#define OVERFLOW 0
#define OK 1
#define TRUE 1
#define FALSE 0
#define ERROR 0
typedef struct {
ElemType *base; //栈底
ElemType *top; //栈顶指针
int stacksize; //当前已分配的存储空间
} SqStack;
Status InitStack(SqStack *S) {
S->base = (ElemType *)malloc(sizeof(ElemType));
if (!S->base) {
exit(OVERFLOW);
} //申请失败
S->top = S->base;
S->stacksize = 1;
return OK;
}
Status GetTop(SqStack S, ElemType *e) {
if (S.top == S.base) {
// printf("此时栈中为空\n");
return ERROR;
} //此时栈中为空
*e = *(S.top - 1);
return OK;
}
Status Push(SqStack *S, ElemType e) {
//将新的元素e插入到栈顶
if (S->top - S->base >= S->stacksize) {
S->base =
(ElemType *)realloc(S->base, (S->stacksize + 1) * sizeof(ElemType));
if (!S->base) {
exit(OVERFLOW);
}
S->top = S->base + S->stacksize;
S->stacksize = S->stacksize + 1;
} //如果空间不够就重新申请空间
*S->top++ = e;
return OK;
}
Status Pop(SqStack *S, ElemType *e) {
//若栈不为空,则弹出栈顶的元素,否则返回空栈
if (S->top == S->base) {
// printf("此时栈中为空\n");
return ERROR;
}
*e = *(--(S->top));
return OK;
}
int StackLength(SqStack S) { return S.stacksize; }
Status StackEmpty(SqStack S) {
if (S.top == S.base) {
// printf("此时栈中为空\n");
return TRUE;
}
return FALSE;
}
1.4.2 数制转换
要解决这个问题,首先我们要知道进制转换是怎么实现的。如下:
我们不难发现,在十进制数转换为X进制数的过程中,除以进制数,倒写余数,先得到的数后输出,这就类似于我们的栈。所以我们可以用栈实现,下面以10->2
为例。
/*
* @Author: Henry
* @Date: 2020-07-04 14:48:11
* @LastEditors: Henry
* @LastEditTime: 2021-04-05 15:29:27
*/
typedef int ElemType;
typedef int Status;
#include "my_stack.h"
int main() {
int num;
printf("请输入一个需要转换为 二进制 的 十进制 数:\n");
scanf("%d", &num);
int temp = num; //保存num
//输入一个需要转换为2进制的数字
SqStack S;
InitStack(&S);
//先初始化一个空栈
while (num > 0) {
Push(&S, num % 2); //把取模得到的数字入栈
num = num / 2; //更新num的值
}
while (!StackEmpty(S)) {
//当栈非空,输出栈顶的元素,即逆序输出,倒写余数
int e;
Pop(&S, &e);
printf("%d", e);
}
printf("\n");
system("pause");
return 0;
}
结果验证正确
1.4.3括号匹配的检验
假设表达式中允许包含两种括号,圆括号和方括号,其嵌套的顺序随意,即
([]())
或者[([][])]
等均为正确的格式。
我们可以用发现,这个可以先把左括号入栈,如果出现了匹配的右括号,就把左括号出栈,就可以实现括号的匹配了,如果最后没有元素入栈了,但是栈非空,说明没有匹配成功,再者就是栈空了,但是有右括号要入栈,则匹配失败,那么按照这种思路来就很简单了。
/*
* @Author: Henry
* @Date: 2020-07-04 14:48:11
* @LastEditors: Henry
* @LastEditTime: 2021-04-05 16:03:37
*/
typedef char ElemType;
typedef int Status;
#include "my_stack.h"
int main() {
while (1) {
char s[+10086];
scanf("%s", s);
SqStack S;
InitStack(&S); //初始化一个栈
int flag = 1; //用于标记是否匹配成功
for (int i = 0; s[i] != '\0'; i++) {
if (StackEmpty(S) && ((s[i] == ')') || (s[i] == ']'))) {
flag = 0;
break; //匹配失败了就没必要继续匹配下去了
} //如果当前栈为空,下一个需要匹配的为右括号,则匹配失败
else if (((s[i] == '(') || (s[i] == '['))) {
//如果是左括号,则入栈,需要匹配
Push(&S, s[i]);
} else if ((s[i] == ')' || s[i] == ']')) {
char e;
GetTop(S, &e);
if (e == '(' && s[i] == ')') {
Pop(&S, &e);
} else if (e == '[' && s[i] == ']') {
Pop(&S, &e);
} else {
flag = 0;
}
}
}
if (!StackEmpty(S) && flag == 1) {
flag = 0;
} //如果栈没有空,而且之前的匹配成功了,则匹配失败,因为还有部分无法匹配。
if (flag) {
printf("YES\n");
} else {
printf("NO\n");
}
printf("\n");
}
system("pause");
return 0;
}
结果验证正确:
1.4.4表达式转换(转换为后缀表达式)
表达式求值中最困难的还是将表达式转换为逆波兰式,即后缀表达式。
算术表达式有前缀表示法、中缀表示法和后缀表示法等形式。日常使用的算术表达式是采用中缀表示法,即二元运算符位于两个运算数中间。
比如2+3*(7-4)+8/4
转换为后缀表达式为2 3 7 4 - * + 8 4 / +
具体的转换过程如下:
①:先算7-4写作后缀表达式为7 4 -
为了更好理解将‘7 4 -’
记作T
②:再算3*T 和 8/4 记作 ‘3 T *’ 和 ‘8 4 /’
分别记作A
和B
③:再计算2+A
,为:‘2 A +’
记作C
④:再计算C + B
,为:C B +
⑤:最后将所有的带会原式为:2 3 7 4 - * + 8 4 / +
(即把C用2 A +带入, B用8 4 /带入,然后A再用3 T * 带入,直到不能再带入)
我们不难发现:
如果要输出后缀表达式,我们只需要按照优先级来计算即可,先计算优先级高的,因此我们可以用栈来模拟(如果要运算则需要一个队列和一个栈 一个为数字队列,一个为操作符栈,如果单纯输出后缀表达式,只需要一个操作符栈即可):
下面以输出后缀表达式为例:
/*
* @Author: Henry
* @Date: 2021-04-09 19:26:32
* @LastEditors: Henry
* @LastEditTime: 2021-04-10 14:47:53
*/
//函数操作在此不表
int numOP(char Op) {
if (Op == '-' || Op == '+') {
return 1;
} else if (Op == '*' || Op == '/') {
return 2;
} else if (Op == '(' || Op == ')') {
return 3;
}
} //设置运算优先级
int main() {
char s[21];
scanf("%s", s);
SqStack OP;
InitStack(&OP);
int first = 1;
for (int i = 0; i < strlen(s); i++) {
if (((i == 0 || s[i - 1] == '(') && (s[i] == '+' || s[i] == '-')) ||
(s[i] >= '0' && s[i] <= '9') || (s[i] == '.')) {
if (!first) {
printf(" ");
}
if (s[i] != '+') {
printf("%c", s[i]);
}
while (s[i + 1] == '.' || (s[i + 1] >= '0' && s[i + 1] <= '9')) {
i++;
printf("%c", s[i]);
}
first = 0;
} else {
if (s[i] == ')') {
while (!StackEmpty(OP) && GetTop(OP) != '(') {
printf(" %c", GetTop(OP));
Pop(&OP);
}
Pop(&OP);
} else if (StackEmpty(OP) || numOP(s[i]) > numOP(GetTop(OP))) {
Push(&OP, s[i]);
} else {
while (!StackEmpty(OP) && GetTop(OP) != '(') {
printf(" %c", GetTop(OP));
Pop(&OP);
}
Push(&OP, s[i]);
}
}
}
while (!StackEmpty(OP)) {
printf(" %c", GetTop(OP));
Pop(&OP);
}
system("pause");
return 0;
}
1.4.5迷宫问题
求迷宫中一条径的算法的基本思想是:
递归回溯
若当前位置“可通“,则纳入”当前路径”,并继续朝“下一个位置探索”,即切换“下一位置”为“当前位置”,如此反复直到出口;若当前位置“不可通”则应顺着“来向”退回到“前一通道块”,然后朝着除“来向”之外的其他方向继续探索;若该通道块的四周4个方块均“不可通”,则应从“当前路径”上删除该通道块。假设以栈S记录“当前路径”,则栈顶中存放的是“当前路径上最后一个通道块”。由此,“纳入路径”的操作即为“当前位置压入”,“从当前路径上删除前一块通道块”的操作即为“弹出”。
该问题更方便的可以直接用递归来求解,思想基本一致,故细节在此不表。以后学习图的遍历
的时候还会再遇见的~PS :实质上递归也是使用了栈,调用的是系统栈,返回即出栈。
1.4.6汉诺塔问题
大名鼎鼎的汉诺塔问题,相信学理工科的同学都不陌生吧
将塔座A上的n个圆盘移至塔座C上,并仍按同样顺序叠排,圆盘移动时必须遵循下列规则:
(1)每次只能移动一个圆盘;
(2)圆盘可以插在A、B和C中的任一塔座上;
(3)任何时刻都不能将一个较大的圆盘压在较 小的圆盘之上。
同样的这个问题也有两种解决方案:递归和栈但实质上递归也是使用了栈,调用的是系统栈,返回即出栈。
基本思路:
首先先将n-1个点盘子移动到y轴
然后将第n个盘子移动到z轴
递归核心代码:
void move(int n, char x, char y, char z){ //将n个盘子从x借助y移动到z上
//xyz代表三根柱子
if( n == 1){ //只有一个盘的情况下
cout << x <<" --->" << z << endl;
}
else {
move (n - 1, x, z, y); //将第n-1个盘子从x借助z移动到y上
cout << x <<"--->"<< z << endl; //输出将第n个盘子从x移动到z上
move (n - 1, y, x, z); //将第n-1个盘子从y借助x移动到z上
}
}
栈的核心代码:
int hanoi(int n,StackType* x,StackType* y,StackType* z)
{
if(1==n)
{
move(x,z);
printf("from %c to %c\n",x->name,z->name);
return 1;
}
hanoi(n-1,x,z,y);//先解决n-1个盘子移到辅助柱子Y的汉洛塔问题
move(x,z);//最后一个从x移到Z
printf("from %c to %c\n",x->name,z->name);
hanoi(n-1,y,x,z);//解决n-1个盘子移到柱子z的汉洛塔问题
return 1;
}
int move(StackType* s1,StackType* s2)
{
ElemType temp;
temp=pop(s1);
push(s2,temp);
return 1;
}
1.5C++中的栈(STL容器)
栈的基本用法
push() 向栈内压入一个新成员
pop() 从栈顶弹出一个新成员
empty()如果栈为空则返回true,否则返回false
top()返回栈顶,但是不删除成员
size()返回栈内的元素的大小
头文件“stack”
示例代码
#include<iostream>
#include<stack>
using namespace std;
int main(){
stack < int > stk;
//入栈
for( int i = 0; i < 50; i++ ){
stk.push( i );
}
cout << "栈的大小:" << stk.size() <<endl;
while (!stk.empty()){
cout<<stk.top()<<endl;
stk.pop();
}
cout << "栈的大小:" << stk.size()<<endl;
return 0;
}
栈源码
#include <stdlib.h>
#include <iostream>
using namespace std;
#define MAXSIZE 0xffff
template <class type>
class my_stack {
int top;
type* my_s;
int maxsize;
public:
my_stack() : top(-1), maxsize(MAXSIZE) {
my_s = new type[maxsize];
if (my_s == NULL) {
cerr << "动态存储分配失败!" << endl;
exit(1);
}
}
my_stack(int size) : top(-1), maxsize(size) {
my_s = new type[maxsize];
if (my_s == NULL) {
cerr << "动态存储分配失败!" << endl;
exit(1);
}
}
~my_stack() { delete[] my_s; }
//是否为空
bool Empty();
//压栈
void Push(type tp);
//返回栈顶元素
type Top();
//出栈
void Pop();
//栈大小
int Size();
};
template <class type>
bool my_stack<type>::Empty() {
if (top == -1) {
return true;
} else
return false;
}
template <class type>
type my_stack<type>::Top() {
if (top != -1) {
return my_s[top];
} else {
cout << "栈空\n";
exit(1);
}
}
template <class type>
void my_stack<type>::Push(type tp) {
if (top + 1 < maxsize) {
my_s[++top] = tp;
} else {
cout << "栈满\n";
exit(1);
}
}
template <class type>
void my_stack<type>::Pop() {
if (top >= 0) {
top--;
} else {
cout << "栈空\n";
exit(1);
}
}
template <class type>
int my_stack<type>::Size() {
return top + 1;
}
1.6C++中的队列(STL容器)
头文件
“queue”
//front 是用来访问队列最底部的元素的
#include<bits/stdc++.h>
#include<queue>
using namespace std;
int main(){
//关闭cin同步,此时速度会比scanf()还快。而且输入方便
std::ios::sync_with_stdio(false);
queue<int>que ;
que.push(1);
que.push(2);
que.push(3);
cout << que.front() << endl;
//输出1
que.pop();
cout << que.front() << endl;
//输出2;
que.pop();
cout << que.front() << endl;
que.pop();
return 0;
}
队列源码
#ifndef QUEUE_H_
#define QUEUE_H_
#include <iostream>
template <class T>
struct Node {
T data;
Node<T>* next;
};
template <class T>
class Queue {
private:
Node<T>* front;
Node<T>* rear;
public:
Queue();
~Queue();
void EnQueue(T x);
void Delete();
bool Empty();
T GetFront();
void Print();
};
template <class T>
Queue<T>::Queue() {
front = rear = new Node<T>;
front->next = nullptr;
}
template <class T>
Queue<T>::~Queue() {
while (front) {
rear = front->next;
delete front;
front = rear;
}
delete rear;
}
template <class T>
void Queue<T>::EnQueue(T x) {
Node<T>* p = new Node<T>;
p->data = x;
rear->next = p;
rear = p;
rear->next = nullptr;
}
template <class T>
void Queue<T>::Delete() {
Node<T>* p = front->next;
front->next = p->next;
delete p;
}
template <class T>
bool Queue<T>::Empty() {
return front == rear ? true : false;
}
template <class T>
T Queue<T>::GetFront() {
return front->next->data;
}
template <class T>
void Queue<T>::Print() {
Node<T>* p = front->next;
while (p) {
std::cout << p->data << " ";
p = p->next;
}
std::cout << std::endl;
}
#endif // !QUEUE_H_