数据结构之栈、堆、队列
文章目录
1.栈(stack)[重点]
1.1基本知识
特性:
- 只能一端即使用top指针插入和删除操作的线性表(可以使用顺序表或者链表实现)
- 栈顶指针绑定指向最先插入的元素
- 栈的方向一般是从上往下,即地址从高到低
- 具有先进后出的特点,能够保存特定生命周期的元素
1.2栈的操作
顺序栈:
const int MAX=100;
int data[MAX];
int top;
//栈空及栈满
//1.规定top==0时栈空,则top==MAX-1为栈满,缺点在于浪费了top=0时的一个元素空间
//推荐使用第1种,能够像链式栈有头结点,作为一个栈存在的标识
//2.规定top==-1为栈空,则top==MAX-1为栈满,即top=0也可以使用
//入栈和出栈
//入栈时首先应该判断是否栈满,防止上溢出
data[++top]=e;//top指针先下移一位,再入栈
//出栈时首先判断是否栈空,防止下溢出
e=data[top--];//先将元素赋给接受变量,top指针再上移一位
链式栈:
typedef struct LNode{
int data;
struct LNode *next;
}LNode;//与带头结点的单链表相差无异
LNode *C;
//栈空及栈满
//栈空:即单链表头结点next指针为NULL
C->next==NULL;
//栈满,不存在的,内存有多大,我就有多大!
//入栈及出栈:
//入栈:必须使用头插法,保证头指针指向最新入栈的元素
p->next=C->next;C->next=p;
//出栈:就是单链表的删除操作
LNode *s;
s=C->next;
C->next=s->next;
data=s->data;//接受数据
delete s;
1.3 栈的简单应用实例
1.3.1判断算术表达式中括号是否匹配的算法
//实现一个判断算术表达式中括号是否匹配的算法,返回一个bool值,表达式已存入字符数组exp[]中,字符个数n
//算法分析:1.使用一个顺序栈s,将exp中字符取出,判断是否为‘(’,则入栈,为‘)’,则消除栈中‘(’,成功则继续循环操作,否则匹配失败!!
//算法实现:
bool match(const char exp[],int n){
char s[n];
int top=0;
int i=0;
while(i<n){
if(exp[i]=='(')
s[++top]=exp[i];
if(exp[i]==')'){
if(top==0)
return false;
else
--top;
}
i++;
}
if(top==0)
return true;
else
return false;
}
1.3.2算术表达式的前缀、中缀、后缀计算及转换
中缀表达式:
指人类识别的算术表达式,如3*2+(1/2-2.3)/2
前缀表达式:
从右往左扫描表达式,遇到数字,压入栈中,遇到运算符,弹出两个数字计算其值,将结果压入栈中,最后得到结果
后缀表达式:
与前缀扫描方向相反
中缀转换为前缀,将前缀计算结果:
//算法分析:1.首先编写将中缀转换为前缀函数transfer(),返回一个前缀表达式字符数组
//2.编写将前缀表达式计算结果的函数calcu(),返回计算结果
//算法缺点:只能转化0~9的算术表达式,只支持加减乘除运算!!
//测试样例:1+((2+3)*4)-5
//算法实现:
//1.实现transfer()
int priOutStack(char c){
int pri;
switch(c){
case ')':pri=8;break;
case '+':pri=3;break;
case '-':pri=3;break;
case '*':pri=6;break;
case '/':pri=6;break;
}
return pri;
}
int priInStack(char c){
int pri;
switch(c){
case ')':pri=0;break;
case '+':pri=3;break;
case '-':pri=3;break;
case '*':pri=6;break;
case '/':pri=6;break;
}
return pri;
}
const char *transfer(const char infix[],int n){
if(!match(infix,n))
throw "error!";
int i=n-2;//除去字符数组的结束字符'\0'
static char prefix[50];
//这里我犯了一个低级错误:返回了一个局部变量的地址,编译器会直接报错,
//解决办法:改为static变量;;;或者直接传入一个全局变量的prefix指针;;
//;;;;或者可以改为使用new动态分配内存
//可以返回一个局部变量的值,但是不能返回一个局部变量的地址,由于局部变量存于栈区
//返回一个地址不会消失,但是其指向的栈区有可能就不是自己原来的内容了!!!
int j=0;
char opStack[30];
int top=0;
while(i>=0){
if(isdigit(infix[i])){
prefix[j]=infix[i];
j++;i--;
}else{
if(infix[i]=='('){
while(opStack[top]!=')'){
prefix[j++]=opStack[top--];
}
i--;top--;
}else{
while(priOutStack(infix[i])<priInStack(opStack[top])){
prefix[j++]=opStack[top--];
}
opStack[++top]=infix[i--];
}
}
}
while(top!=0){
prefix[j++]=opStack[top--];
}
return prefix;
}
//2.实现calcu()
int OP(int a,char op,int b){
switch(op){
case '+':return a+b;break;
case '-':return a-b;break;
case '*':return a*b;break;
case '/':
if(b==0) throw "error";
else return a/b;
break;
}
}
int calcu(const char prefix[],int n){
int calcu[n];
int top=0;
int i=0;
char op;
int a,b,c;
while(prefix[i]!='\0'){
if(prefix[i]>='0'&&prefix[i]<='9')
calcu[++top]=prefix[i++]-'0';
else{
op=prefix[i++];
a=calcu[top--];//注意顺序,这里是第1个数
b=calcu[top--];//这是第2个数
c=OP(a,op,b);
calcu[++top]=c;
}
}
return calcu[top];
}
int main(){
char exp[14]="1+((2+3)*4)-5";
if( match(exp,14))
cout<<"匹配成功!\n";
else
cout<<"匹配失败!\n";
cout<<exp<<"\n";
try{
const char *p=transfer(exp,14);//返回一个常量指针,必须创建一个常量指针进行接收
cout<<p<<"\n";
int res=calcu(p,14);
cout<<res<<"\n";
}catch(const char *e){
cout<<e;
}
}
/*
输出如下:
匹配成功!
1+((2+3)*4)-5
5432+*1+-
16
*/
2.队列[重点]
2.1基本知识
特性:
-
只能在队尾的rear指针一端插入,队顶的front另一端删除,特别适用于串行进行的任务设计
-
具有先进先出的特点
-
可以使用顺序表或者链表实现
-
队顶指针绑定在第一个元素上,队列从rear插入从上往下移动,在front删除也是从上往下
2.2队列的操作
顺序队列:
const int MAX=100;
int data[MAX];
int front;
int rear;
//初始化为指向0,作为顺序队列存在的标志,插入元素先下移一位
front=0;
rear=0;
//队空和队满状态
front==rear;//队空
front==MAX-1 || rear==MAX-1;//队满
//出队与进队
data[++rear]=e;//进队
e=data[++front];//出队,不是减减了,都是先移动指针,在插入或者取出元素
//缺点,由于两个指针都是从上往下移动,当同时指向MAX-1,会出现假溢出的状态!!
//改进:习惯使用一个循环队列
//初始化:
front=rear=0;
//队空及队满
front==rear//队空
(rear+1)%MAX==front||(front+1)%MAX==rear//队满,(rear+1)%MAX表示循环将rear指针下移一位
//进队和出队
data[(rear+1)%MAX]=e;//进队
e=data[(front+1)%MAX];//出队,都是先移动指针,在插入和取出元素
链式队列:
typedef struct LNode{
int data;
struct LNode *next;
}LNode;
typedef struct LQueue{
LNode *front;
LNode *rear;
}LQueue;
LQueue *C;//创建指向队列链表的头结点指针,但是并没有创建一个LQueue节点内存,必须new一个节点才行
LQueue *C=new LQueue;
//初始化
C->front=C->rear=NULL;//这个时候才能初始化成功!!
//队空及队满
C->front==NULL ||C->rear==NULL//队空,队满,不存在的,内存有多大,我就有多大!
//进队与出队
//进队
C->rear->next=p;//将rear指向的队尾节点元素的next指向新元素p
C->rear=p;//将rear指针指向新链接的队尾元素p
//出队
LNode *s;
s=C->front;//创建一个节点指针,指向front指向的节点
C->front=C->front->next;
e=s->data;//接收即将释放的节点数据
delete s;
//注意点:第一个节点入队时,还需要初始化front的指向,第一个节点进队只需要一句C->rear=p;
//最后一个节点出队时,还需要初始化rear的指向为NULL
//简单的实例操作
int main(){
LQueue *C=new LQueue;//创建指针并分配一个LQueue内存,才能初始化成功!!
//初始化
C->front=C->rear=NULL;
//进队,两个元素
LNode *p1,*p2;
p1->next=p2->next=NULL;
p1->data=100;
p2->data=300;
C->rear=p1;//第一个节点元素操作,将rear指针指向新链接的队尾元素p
C->front=p1;
C->rear->next=p2;//第二个节点元素操作将rear指向的队尾节点元素的next指向新元素p
C->rear=p2;//将rear指针指向新链接的队尾元素p
cout<<C->front->data<<"\t"<<C->front->next->data<<"\n";
//出队
LNode *s1;
s1=C->front;//创建一个节点指针,指向front指向的节点
C->front=C->front->next;
int e1=s1->data;//接收即将释放的节点数据
cout<<e1<<"\n";
delete s1;
LNode *s2;//最后一个元素出队
s2=C->front;
C->front=C->rear=NULL;
int e2=s2->data;
cout<<e2;
delete s2;
delete[] C;
}
2.3 队列的简单应用实例
++
3 堆heap[重点]
3.1基本知识
特性:
- 堆只是程序运行时动态申请的一块内存,并使用一个指针指向这一块内存
- 如何使用堆,通过栈存储的指向堆内存的指针进行快速访问,也就是说,堆只是一块内存,需要借助指针进行访问,不具有先进先出的特点!
4. 栈和队列的综合应用实例
4.1实现一个共享栈
++
4.2使用两个栈实现一个队列
++
4.3使用顺序表实现一个双端队列
++