栈和队列
栈的相关知识
- 栈的思想
如何理解呢,在这里,我们其实可以把栈的结构想象成只有底边的一个容器,第一次进去的就会被放在最底下,然后依此类推。关键的思想就是在栈中,只能同一端进入或是出去。先进后出,这个和我们下一个要讲的队列刚好是相反的。 - 栈的顺序存储方式(源码剖析)
//这里和我们上次讲的顺序表是差不多的
#define MAXSIZE 600
typedef struct Zhan{
int data[MAXSIZE];//用来存取数据的数组
int top;//栈顶指针,此处指向栈顶的数组下标
}Zhan,*Zhan1;
- 栈的各种操作
栈的初始化
Zhan1 Init(Zhan s){
s.top=-1;//因为此时栈中没有任何的变量
return s;
}
判断栈空
bool EmptyStack(Zhan s){
if(s.top==-1)//判断栈是否为空主要就是看当前的栈顶指针是否为-1,是-1即为空栈
return true;
else
return false;
}
入栈
bool Push(Zhan s,int x){
s.data[++top]=x;//要先把top指针加一才能入栈,因为你如果不加一top是-1,这就错了
return true;
}
出栈
bool Pop(Zhan s,int x){//这个x得接受地址,而不能是单纯的值
x=s.data[top--];//把栈顶元素给x,因为x里面存着的一直都是栈顶的值。然后top在取过之后再减,因为和入栈的操作不同。出栈要取当前的栈顶元素
}
读栈顶元素
//其实在刚才的出栈的代码中就实现了读取栈顶元素
int StackTop(Zhan s,int x)//其实这边也可以不用整型,因为这个是地址传递,用布尔类型也行
{
x=s.data[top];
return x;
}
- 栈的链式存储方式
//栈的链式存储方式其实就和我们上次讲的单链表的结构是一样的
typedef struct Zhan{
int data;//数据域
struct Zhan *next;指针域
}*Zhan1;
这里插入删除就不写了,参考上篇文章,是一样的。唯一的区别就是栈的链表结构只能在表头进行插入和删除
8. 总结
如果懂了我上篇文章讲的顺序存储结构和链式存储结构的话这里应该特别的好懂。主要就是一些思想上的区别。记住栈的思想是只能在同一端进行插入和删除,并且先进后出。
队列的相关知识
思想:队列的思想其实就是和我们平时排队是一个意思。一般都是在队尾进入队伍,然后在开头离开队伍。总结来讲就是先进先出,然后在一头进行插入,在另一头进行输出。和栈一样,分为顺序存储结构和链式存储结构。
1,顺序存储结构
上代码
//队列的顺序结构
#define MAXSIZE 500
typedef struct QueueNode{
int data[MAXSIZE];//定义队列数组
int front,rear;//分别指向队头和队尾的指针;
//⚠️:队头指针指向的是第一个元素的下标位置,而队尾指针指向的是最后一个元素的下一个元素
}QueueNode;
在讲操作之前我们来先讲一下循环队列和普通队列的区别。由上面我对队列的介绍可以知道。当s.fronts.rear时可以知道这是个空队列。但是队之间的空间能被浪费,因为前面的只负责出,所以front只可能增加,而不会减少。所以这样就造成了队之间的空间遭到破坏。而当我们引入循环队列这个概念的时候,这一切就变得非常容易了。当我们的队尾指针指到最后大于空间是又会指向前面已经出队的空间,于是之前的空间又可以被我们重复利用了。
循环队列判空的条件是Q.frontQ.rear,而到了判断是否是满队时我们发现条件貌似也是这个等式,那这就不满足我们条理清晰的目的了。这里我只讲一种解决办法,也是最常用的解决办法,就是数组中空一格元素的空间出来,因为这样我们就可用(Q.rear+1)%MAXSIZE==Q.front来表示。
队列的初始化
//队列的初始化
bool InitQueue(QueueNode Q){
Q.front=Q.rear=0;//把队头和队尾指针都指向第一个元素
return true;
}
队列的插入
bool InserQueue(QueueNode Q,int x){
if((Q.rear+1)%MAXSIZE=Q.front)//利用我们上面讲的判断循环队列的是否为空的条件来判断
{printf("队列已满");
return false;
}
Q.data[Q.rear]=x
;//先把x插入到队尾指针的位置然后队尾元素加一
Q.rear=(Q.rear+1)%MAXSIZE;
return true;
}
队列的出队
//队列的出队
bool OutQueue(QueueNode Q,int x){
if(Q.front==Q.rear){//根据上面循环队列判断队空的条件来判断队空
printf("队列为空");
return false;
}
x=Q.data[Q.front];
Q.front=(Q.front+1)%MAXSIZE;//队首指针加一
return true;
}
小总结:其实换汤不换药,我们这里讲队列其实只是换了一种思想操作什么的和一般顺序结构其实无差别
队列的链式存储结构
讲到这可能会有同学问,我觉得顺序存储已经很好了,为什么还要链式存储呢,其实通过链式存储和顺序存储的差别我们就可以看出来,链式存储比顺序存储的优势就是,第一,没有长度的限制,不需要一开始就要固定长度。二,不存在溢出的情况。三,适合数据量大的工程。
链式存储的结构
//链式存储的结构
typedef struct LinkNode{//节点
int data;//数据域
LinkNode *next;//指向下一个节点的指针域
}LinkNode;
typedef struct QueueNode{//队列
LinkNode *front,*rear;//队列的头节点和尾节点
}QueueNode;
队的初始化
//队的初始化
bool InitQueue(QueueNode Q){
Q.front=Q.rear=(LinkNode*)malloc(sizeof(LinkNode));//创建头节点
//笔者这里认为没必要给Q.rear去分配空间,最简单的做法是使尾指针初始的时候指向头节点就行;
Q.front->next=NULL;
}
判断队空
//判断队空
bool EmptyQueue(QueueNode Q){
if(Q.front==Q.rear==NULL)//链式存储结构队空的条件
return true;
else
return false;
}
入队
bool InsertQueue(QueueNode Q,int x){
LinkNode *s;
s=(LinkNode *)malloc(sizeof(LinkNode));//给新生成的节点分配空间
s->data=x;//给新节点的数据域赋值
s->next=NULL;
Q.rear->next=s;
Q.rear=s;//尾指针指向新的尾节点
return true;
}
出队
//出队
bool OutQueue(QueueNode Q,int x){
x=Q.front->next;//讲队首的值赋给x
LinkNode *p=Q.front->next;//用一个指针指向即将要删除的节点,方便后面删除。
Q.front->next=p->next;
free(p);
}
总结:其实如果我们认真看就会发现,无论是栈还是队列,更像是对我们才学的顺序结构和链式结构的具体应用,只要真正的懂顺序存储结构和链式存储结构本章就不会有太大的问题。