(此文为王道数据结构学习笔记,只供个人后续复习使用,若有错误,还望指出我改正,谢谢)
队列的链式实现:
引入队头队尾指针
定义与初始化一个链式列表
typedef struct{
ElemType data;
struct LinkNode *next;
}LinkNode;//链式队列的单个结点的结构体
typedef struct{
LinkNode *front, *rear;//头指针,尾指针
}LinkQueue;//链式队列
void InitQueue(LinkQueue &Q) {
Q.front = Q.rear = (LinkNode *)malloc(sizeof(LinkNode));//初始时头指针尾指针都指向头节点
Q.front->next = NULL;
}//链式队列初始化
判断队列是否为空,当头尾指针都指向同一结点(即头结点)时,队列即为空。
bool IsEmpty(LinkQueue Q) {
if (Q.front = Q.rear) //如果头尾指针指向同一个结点,则队列为空
return true;
else
return false;
}
新结点入队
只能从队尾进入
void EnQueue(LinkQueue &Q, ElemType x) {
LinkNode *s = (LinkNode *)malloc(sizeof(LinkNode));//创建新结点
s->data = x;//给新结点赋值
s->next = NULL;//新结点的下一个结点为空
Q.rear->next = s;//尾指针所指结点(即现在的尾结点)的next指向新结点
Q.rear = s;//尾指针指向新结点,新结点成为新尾结点
}
结点出队:
只能从队头出队
void DeQueue(LinkNode &Q, ElemType &s) {
if (Q.front = Q.rear)//空队列
return false;
LinkNode *p = Q.front->next;//定义一个临时指针p指向头结点的下一个(即1号结点,队头结点)
x = p->data;//x返回该结点的值
Q.front->next = p->next;//令头结点的next指向该结点的next(即2号结点),完成出队
if (Q.rear == p)//如果队尾指针指向的队头结点
Q.rear = Q.front;//出队后队列即空
free(p);//释放临时指针
return true;
}
双端队列(真题出现过)
两端都可以插入删除
输入受限制的双端的队列:只能从一端入队,而两端都可以出队;
输出受限制的双端的队列:只能从一端出队,而两端都可以入队;
比栈的输出序列更多
栈的应用
1.括号匹配问题(即代码中左右括号需匹配)
每检索到一个右括号,即向左找最近一个没有被匹配过的左括号。
即左括号入栈,每遇到一个右括号,一个左括号出栈。
算法思路程序流程图:
算法实现:
#define MaxSize 100
typedef struct {
char data[MaxSize];
int top;
}SqStack;
void InitStack(SqStack &S) {
S.top = -1;
}//初始化括号栈
bool Push(SqStack &S, char e) {
if (S.top == MaxSize)
return false;
else {
S.data[++S.top] = e;
return true;
}
}//左括号入栈
bool Pop(SqStack &S, char &e) {
if (S.top == -1)
return false;
else {
e = S.data[S.top--];
return true;
}
}
bool StackEmpty(SqStack S) {
if (S.top == -1)
return true;
else
return false;
}
bool bracketCheck(char str[], int length) {
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; //扫描到右边
}
char 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() {
char str[100];
bool s;
int length;
printf("请输入一段100以内的字符串,以“@”作为结束。\n");
for (int i = 0; i < 100; i++) {
scanf_s("%c", &str[i]);
if (str[i] == '@')
break;
}
length = sizeof(str);
s = bracketCheck(str, length);
if(s==true)
printf("匹配成功!");
else
printf("匹配失败!");
}
2.表达式求值
三种算数表达式:
中缀表达式:即数学上的算数表达式,运算符在两个操作数中间
a + b
a + b - c * d
后缀表达式:(逆波兰表达式),运算符在两个操作数后面
a b +
a b + c d * -
前缀表达式:(波兰表达式),运算符在两个操作数前面
+ a b
- + a b * c d
计算后缀表达式的思路:后缀表达式是运算符在两个操作数后面,所以让操作数一一进栈,然后如果扫描到运算符,即让栈顶的两个操作数出栈,进行运算后(注意左右顺序,再将运算结果入栈作为新的操作数,重复上述操作,直至扫描完毕。
算法流程图:
算法实现:
char Reverse_Polish_notation(char str[], int length) {
SqStack S;
char a, b, s;
InitStack(S);
for (int i = 0; i < length; i++) { //扫描完所有字符
if (str[i] == '+'&&str[i] == '-'&&str[i] == '*'&&str[i] == '/')
Push(S, str[i]);
else {
Pop(S, a);
Pop(S, b);
if (str[i] == '+')
s = b + a;
if (str[i] == '-')
s = b - a;
if (str[i] == '*')
s = b * a;
if (str[i] == '/')
s = b / a;
Push(S, s);
}
}
return Pop(S, s);
}
中缀表达式转化为后缀表达式:
操作数的相对顺序不会改变,但运算符的相对顺序会改变。
思路:
初始化一个栈,用于保存暂时还不能确定运算顺序的运算符。
从左到右扫描处理各个字符,直到末尾,中途可能会遇到以下三种情况:
1.遇到操作数。直接加入后缀表达式。
2.遇到界限符号。遇到左括号直接入栈;遇到右括号则依次弹出栈内运算符并加入后缀表达式,直到弹出左括号为止(左括号不加入后缀表达式)
3.遇到运算符。依次弹出栈中高于或等于当前运算符的所有运算符,并且加入后缀表达式,直到碰到左括号或者栈空。之后再把当前运算符入栈。
按照上述方法处理完所有字符后,将栈中剩余运算符依次弹出,并加入后缀表达式。
算法流程图:
栈在递归中的应用
斐波那契数列
斐波那契数列定义为
Fib(n)=Fib(n-1) + Fib(n-2),Fib(1) = 1,Fib(0) = 0;其中n>1
可以通过调用自身形成函数栈完成Fib(n)计算的操作
int Fib(int n) {
if (n == 0)
return 0;
else if (n == 1)
return 1;
else
return Fib(n - 1) + Fib(n - 2);
}
函数栈内部会一一进栈,完成运算后一一出栈反馈结果给上一个函数继续完成出栈,直至出结果
队列在遍历中的应用:
用队列可以实现树的的层次遍历:
1.根结点入队;
2.若队列为空(所有结点都处理完),则结束遍历;否则重复3操作;
3.将队列中第一个结点出队,访问之。如果它有左孩子,将之入队;如果它有右孩子,将之入队。返回2。
如此可以用队列实现树的层序遍历
队列在操作系统中的应用:
多个进程争抢有限系统资源时FCFS(First Come First Service);
队列可以实现缓冲区功能。