目录
一、栈和队列的基本介绍
1.栈--先进后出
顺序栈:类似动态线性表
链式栈:以链表头作为栈尾
2.队列--先进先出
(本质就是特殊的单链表,有头尾两指针)
链式队列:结点+结点类型的头指针和尾指针
循环队列:一维数组
二、顺序栈队列(c语言)的完整实现
在栈和队列中进行插入删除时,注意:
没有结点,只有一个结点,有很多个结点,这三种情况
1.代码部分
头文件和具体实现部分:
#define _CRT_SECURE_NO_WARNINGS 1
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <stdbool.h>//包含bool类型
typedef int QDataType;
//节点
typedef struct QueueNode
{
struct QueueNode* next;
QDataType data;
}QueueNode;
//再在队列中建立2个指针
typedef struct Queue
{
//指针的类型是指针指向变量的类型,而不是指针变量自身的类型
QueueNode* head;//头指针
QueueNode* tail;//尾指针
}Queue;
void QueueInit(Queue* pq);
void QueueDestroy(Queue* pq);
void QueuePush(Queue* pq, QDataType x);
void QueuePop(Queue* pq);
QDataType QueueFront(Queue* pq);
QDataType QueueBack(Queue* pq);
int QueueSize(Queue* pq);
bool QueueEmpty(Queue* pq);
#define _CRT_SECURE_NO_WARNINGS 1
#include "queue.h"
void QueueInit(Queue* pq)
{
assert(pq);//结构不能为空,但此时指针可以为空
pq->head = NULL;
pq->tail = NULL;
}
void QueueDestroy(Queue* pq)
{
assert(pq);
QueueNode* cur = pq->head;
while (cur != NULL)
{
QueueNode* nnext = cur->next;
free(cur);
cur = nnext;
}
pq->head = pq->tail = NULL;
}
//入队列--形参为结构体的地址,pq代表结构体的指针
void QueuePush(Queue* pq, QDataType x)
{
assert(pq);
QueueNode* newnode = (QueueNode*)malloc(sizeof(QueueNode));//节点类型指针
newnode->data = x;
newnode->next = NULL;
if (pq->head == NULL)
{
pq->head = pq->tail = newnode;//因为head,tail这些都是指针newnode,所以可以直接赋值
}
else
{
pq->tail->next = newnode;
pq->tail = newnode;
}
}
//出队列
void QueuePop(Queue* pq)
{
//队头出
assert(pq);
assert(!QueueEmpty(pq));
QueueNode* nnext = pq->head->next;
free(pq->head);
//free函数释放由malloc函数申请到的整块内存空间,参数是指向要释放空间的首地址
pq->head = nnext;
if (QueueEmpty(pq))
{
pq->tail = NULL;
}
}
//取队头数据
QDataType QueueFront(Queue* pq)
{
assert(pq);
assert(!QueueEmpty(pq));
return pq->head->data;
}
//取队尾数据
QDataType QueueBack(Queue* pq)
{
assert(pq);
assert(!QueueEmpty(pq));
return pq->tail->data;
}
//队列元素个数
int QueueSize(Queue* pq)
{
assert(pq);
int n = 0;
QueueNode* cur = pq->head;
while (cur)
{
cur = cur->next;
n++;
}
return n;
}
//判断队列是否为空
bool QueueEmpty(Queue* pq)
{
assert(pq);
return pq->head==NULL;
}
测试用例:
void TestQueueq()
{
Queue q;
QueueInit(&q);
QueuePush(&q, 1);
QueuePush(&q, 2);
QueuePush(&q, 3);
QueuePush(&q, 4);
QueuePop(&q);
QueuePop(&q);
QueuePop(&q);
QueuePop(&q);
printf("%d\n", QueueBack(&q));//取队尾数据
QueuePush(&q, 10);
QueuePush(&q, 10);
QueueDestroy(&q);
}
int main()
{
TestQueueq();
return 0;
}
2.解说部分
队列的基本操作函数:(动态链表相似)(函数都是传的地址)
1.初始化操作:将头尾指针赋空值防产生野指针
2.销毁:另建一个指针,从头开始销毁,注意结点间联系不要断
3.入队列:建立一个新的结点存入的数据,用指针指向这个新节点,将结点插入队列后面
注意:
1.当队列是空时,将新节点指针都赋值给队列头尾指针
2.当队列有元素时,插入新节点后还要移动队列里的尾指针
注意:
4.出队列:从头出,传入队列的地址(指针),
(1)删除前先判断此时结点个数,可调用专门判断结点是否为空的函数查看。
(2)函数里另新建指针帮助头结点后移一位和用free释放原结点空间,
(3)注意结点为空时队尾的指针也要置空
5.取队头数据
判断形参(队列的地址)是否为空,再判断队列的结点个数是否为空,都不为空就用队列的头指针返回第一个结点的数据
6.取队尾数据
和5高度相似,用尾指针掉尾数据
7.返回队列元素个数
(1)判断形参(队列的地址)是否为空,
(2)新建一个队列结点类型指针,将头指针赋值给它,循环计数直到该新建指针指向空(也就是遍历完了队列)
8.判断队列是否为空
该函数返回值类型可以是bool类型,(要添加<stdbool.h>的头文件,且还要申明使用了bool类型的函数)
返回值是:队列的头指针==空值NULL
三、顺序栈(c语言)的代码实现
#define _CRT_SECURE_NO_WARNINGS 1
#pragma once//没有这句会有struct类型重定义的报错
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <stdbool.h>//包含bool类型
typedef int STDataType;
typedef struct Stack
{
STDataType* a;//数组首地址
int top;//数组下标(数组元素个数)
int capacity;//此时空间里,数组元素个数可达到的最大值
}ST;
bool StackEmpty(ST* ps);
void StackInit(ST* ps);
void StackDestory(ST* ps);
void StackPush(ST* ps, STDataType x);
void StackPop(ST* ps);
STDataType StackTop(ST* ps);
int StackSize(ST* ps);
bool isValid(char* s);
void p(char* s);
#define _CRT_SECURE_NO_WARNINGS 1
#include "stack.h"
bool StackEmpty(ST* ps);//使用bool类型时还要再前面申明
//初始化
void StackInit(ST* ps)
{
assert(ps);
ps->a = NULL;
ps->top = 0;
//top给0,意思是它指向栈顶数据的下一个,给-1,意思是它指向栈顶数据
ps->capacity = 0;
}
//销毁栈
void StackDestory(ST* ps)
{
free(ps->a);//void free(void *ptr)释放有动态存储分配函数申请到的整块内存空间,ptr是指向要释放空间的首地址
ps->a = NULL;
ps->capacity = ps->top = 0;
}
//压栈
void StackPush(ST* ps, STDataType x)
{
assert(ps);
//注意空间不够的情况
if (ps->top==ps->capacity)
{
int newCapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;
STDataType* tmp = realloc(ps->a, sizeof(STDataType) * newCapacity);//??
//sizeof() 功能是返回一个变量或者类型的大小(以字节为单位)
if (tmp == NULL)
{
printf("realloc fail\n");
exit(-1);
}
ps->a = tmp;
ps->capacity = newCapacity;
}
ps->a[ps->top] = x;
ps->top++;
}
//出栈--删除数据
void StackPop(ST* ps)
{
assert(ps);
assert(!StackEmpty(ps));
ps->top--;
}
//取栈顶的数据
STDataType StackTop(ST* ps)
{
assert(ps);
assert(!StackEmpty(ps));
return ps->a[ps->top - 1];
}
//栈里面总共有多少数据
int StackSize(ST* ps)
{
assert(ps);
return ps->top;//top是从0开始的吗?
}
//判断栈是否为空
bool StackEmpty(ST* ps)
{
assert(ps);
return ps->top == 0;
}
测试用例:
void TestStack1()
{
ST st;
StackInit(&st);
StackPush(&st, 1);
StackPush(&st, 2);
StackPush(&st, 3);
StackPush(&st, 4);
StackPop(&st);
StackPop(&st);
StackPop(&st);
StackPop(&st);
StackDestory(&st);//尽管前面已经将栈里的内容都删除了,但是为它分配的空间还在
}
void TestStack2()
{
ST st;
StackInit(&st);
StackPush(&st, 1);
StackPush(&st, 2);
StackPush(&st, 3);
StackPush(&st, 4);
printf("%d ", StackTop(&st));//先取栈顶的数据
StackPop(&st);
printf("%d ", StackTop(&st));//先取栈顶的数据
StackPop(&st);
StackPush(&st, 5);
StackPush(&st, 6);
while (!StackEmpty(&st))
{
printf("%d ", StackTop(&st));//先取栈顶的数据
StackPop(&st);
}
//定义一个栈后,就养成先初始化再销毁的习惯
StackDestory(&st);
}
void TestStack3()
{
char s[7] = "()[]{}";
isValid(&s);
//p(s);
}
int main()
{
MyStack* st = myStackCreate();
myStackPush(st, 1);
myStackPush(st, 2);
myStackPush(st, 3);
myStackPop(st);
myStackFree(st);
return 0;
}
四、简单应用
1.四则运算计算器
原理:
初始化一个栈,用于保存暂时还不能确定运算顺序的运算符,从左到右处理各个元素,指导末尾,可能遇到三种情况:
1.遇到操作数:直接加入后缀表达式
2.遇到界限符:遇到"(" 直接入栈,遇到")"则依次弹出站内运算符并加入后缀表达式,直到弹出"("为止。注意:"("弹出时不加入后缀表达式
3.遇到运算符:依次弹出栈中优先级高于或等于当前运算符的所有运算符,并加入后缀表达式,若遇到"("或栈空则停止。之后再把当前运算符入栈
其中,
中缀表达式:运算符在两个操作数中间
后缀表达式(也叫逆波兰表达式):运算符在两个操作数后面
前缀表达式(也叫波兰表达式):运算符在两个操作数前面
关于前缀、中缀和后缀三者的具体转换可以看b站王道考研的视频
完整代码实现(c++):
#pragma once
#ifndef _STACK_H_
#define _STACK_H_
#include <iostream>
#include <assert.h>
using namespace std;
const int MaxSize = 100;
template<class STDataType>
class Stack
{
public:
STDataType* data;//动态数组首元素地址
int top;//数组下标(数组元素个数)
//判空
bool StackEmpty()
{
return top == 0;
}
//构函--初始化
Stack()
{
data = new STDataType[MaxSize];
top = 0;
}
//销栈
virtual ~Stack()
{
delete[] data;
top = 0;
}
//入栈
void StackPush(STDataType x)
{
//要先判断空间够不够
data[top] = x;
top++;
}
//出栈
void StackPop()
{
assert(!StackEmpty());
top--;
}
//取栈顶元素
STDataType StackTop()
{
assert(!StackEmpty());
STDataType e = data[top - 1];
return e;//因为数组下标从0开始
}
//栈里数据个数
int StackSize()
{
return top;
}
//计算后缀
STDataType cmpvalue(char* postexp)
{
STDataType a, b, c, d;
while (*postexp != '\0')
{
switch (*postexp)
{
case '+':
{
//将操作数栈里的元素出栈
a = StackTop();
StackPop();
b = StackTop();
StackPop();
c = b + a;
//入栈
StackPush(c);
break;
}
case '-':
{
a = StackTop();
StackPop();
b = StackTop();
StackPop();
c = b - a;
//入栈
StackPush(c);
break;
}
case '*':
{
a = StackTop();
StackPop();
b = StackTop();
StackPop();
c = b * a;
//入栈
StackPush(c);
break;
}
case '/':
{
a = StackTop();
StackPop();
b = StackTop();
StackPop();
//要判断除数是否为0
if (a != 0)
{
c = b / a;
//入栈
StackPush(c);
break;
}
else
{
printf("\n\t除0,错误\n");
exit(0);
}
break;
}
default:
{
d = 0;
while (*postexp >= '0' && *postexp <= '9')
{
d = d * 10 + *postexp - '0';
postexp++;
}
StackPush(d);
break;
}
}
postexp++;//指针后移
}
a = StackTop();
return a;
}
//将中缀表达式转换成后缀表达式
void trans(char* exp, char postexp[])
{
char e;
int i = 0;//i作为postexp的下标
//exp表达式未扫描完时循环
while (*exp != '=' && *exp != '\0')
{
switch (*exp)
{
case '(':
{
//遇左括号直接进栈
StackPush('(');
exp++;
break;
}
case ')':
{
e = StackTop();
while (e != '(')
{
postexp[i++] = e;//将栈里元素赋值给后缀集
StackPop();//出栈StackPop(&OPND);
e = StackTop();
}
//退出循环时说明扫描到了'(',直接将'(’出栈
StackPop();
exp++;//继续扫描其他字符
break;
}
case '+':
/*遇到运算符:依次弹出栈中优先级高于或等于当前运算符的所有运算符,
并加入后缀表达式*/
case '-'://加和减优先级相同,一起操作(因为case '+'没有break,直接执行后一个case的代码,不同判断
{
while (!StackEmpty())
{
e = StackTop();
if (e != '(')
{
postexp[i++] = e;
StackPop();//出栈元素e--因为优先级低,直接出栈入后缀里
e = StackTop();
}
else
break;//一旦有一个'('或者栈空了就退出循环
}
//当前符号进栈
StackPush(*exp);
exp++;
break;
}
case '*':
case '/':
{
while (!StackEmpty())
{
if (((e = StackTop()) == '*' || e == '/')&& !StackEmpty())//优先级很高了,只有栈里同级的才能连续出栈
{
postexp[i++] = e;
StackPop();
//e = StackTop();
}
else
break;
}
//当前符号进栈
StackPush(*exp);
exp++;
break;
}
default://数字直接进数组
{
while (*exp >= '0' && *exp <= '9')
{
postexp[i++] = *exp;
exp++;
}
postexp[i++] =' ';//用用#标识一个数值串结束,以便能够处理数组大于9的数
break;
}
}
}
//此时exp扫描完毕,栈不空时循环
while (!StackEmpty())
{
e = StackTop();
postexp[i++] = e;
StackPop();
}
//给postexp表达式添加结束标识
postexp[i] = '\0';
}
};
#endif
#include "stack.h"
int main() {
cout << "简易计算器" << endl;
cout << "请输入一个无空格的算式:" << endl;
char exp[MaxSize];
cin >> exp;
//cout << "中缀表达式" << exp << endl;
char postexp[MaxSize];
Stack<int> OPND; //数字栈
Stack<char> Optr;//操作符栈
Optr.trans(exp, postexp);//将中缀改为前缀--操作符栈
//cout << "中缀表达式" << exp << endl;
cout << "后缀表达式" << postexp << endl;
cout << "表达式的值" << OPND.cmpvalue(postexp)<< endl;
return 0;
}
2.括号配对问题
可结合上面栈的实现来看(c语言)
bool isValid(char* s) {
ST st;
StackInit(&st);
///*s表示是存的值,s是值的地址
while (*s)//数组字符串末尾是\0,ASCII码值为0
{
if (*s == '(' || *s == '[' || *s == '{')
{
StackPush(&st, *s);
printf("%c", *s);
s++;
}
else
{
//判断字符串一开始就是右括号
if (StackEmpty(&st))
{
//StackDestory(&st);
return false;
}
STDataType t = StackTop(&st);
StackPop(&st);
if ((*s == ')' && t != '(')
|| (*s == ']' && t != '[')
|| (*s == '}' && t != '{')
)
{
StackDestory(&st);
return false;
}
else
{
s++;
}
}
}
bool ret = StackEmpty(&st);
StackDestory(&st);
return ret;
}