#栈
一种操作受限的特殊的线性表,可以用顺序表和链表来表示栈,普通的顺序表可以在任意位置插入或删除元素,但是栈这一数据结构只能在端点进行插入和删除,其运算规则是后进先出,生活中有很多栈的模型例如手枪弹夹,他就是一颗一颗子弹压入弹夹中,最后压入的子弹最先打出去,这也是遵循了后进先出的原则,因此对于栈的插入我们称为压栈或者入栈,对于栈中元素删除的操作我们称为出栈或者弹栈。通常对栈进行操作都是依靠栈顶指针进行插入和删除。另外在递归操作中,也是用到了栈来存储数据,因为栈的操作特点非常符合递归的原理,递归是先调用函数,再进行值的返回。
在顺序栈中,每次元素入栈后栈顶指针都会向上移动一次,方便下次插入操作。同理,出栈的时候通过栈顶指针删除栈中元素 &x = S.data[S.Top - 1]
总结
栈的操作代码实现
初始化栈 判断栈是否为空 压栈 获取栈顶元素 弹栈
//
// Created by Administrator on 2023/1/26.
//
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
//初始化栈-出栈
/*
* 步骤
* 初始化栈 判断栈是否为空 压栈 获取栈顶元素 弹栈
*/
#define MaxSize 50
typedef int ElemType;
typedef struct{
ElemType data[MaxSize];
int top;
}Sqstack;
//初始化栈
void InitStack(Sqstack &S)
{
S.top = -1;
}
//判断栈是否为空
bool StackEmpty(Sqstack *S)
{
if(S->top == -1)
{
return true;
}
return false;
}
//压栈
bool Push(Sqstack &S, ElemType elem)
{
if(S.top > MaxSize - 1)
{
return false;
}
S.data[++S.top] = elem;
return true;
}
//获取栈顶元素
ElemType getNum(Sqstack *S)
{
bool flag = StackEmpty(S);
if(flag)
{
return -1;
}
return S->data[S->top - 1];
}
//弹栈
bool Pop(Sqstack &S,ElemType &n)
{
if( n > S.top)
{
return false;
}
n = S.data[S.top -1];
return true;
}
int main1() {
Sqstack S;
ElemType m;
//初始化栈
InitStack(S);
bool flag;
flag = StackEmpty(&S);
if(flag)
{
printf("栈是空的\n");
}
Push(S,3);
Push(S,4);
Push(S,5);
int n =getNum(&S);//获取栈顶元素
printf("获取栈顶元素为%d\n",n);
flag=Pop(S,m);//弹出栈顶元素
if(flag)
{
printf("弹出元素为%d\n",m);
}
return 0;
}
链栈
链栈是栈的链表表示形式,只能在链表的头部操作,在我们创建链栈的时候通常这种方向操作更加方便,也更加符合栈的特点。我们通常用头插法和头删法来进行链栈的入栈和出栈操作,链栈的头指针就是栈顶,因此在插入和删除的时候就是用头指针进行操作,并且链栈是不需要头节点的,没有头节点其实操作起来更加的方便,并且也更加适合栈这一数据结构,链表中的空栈相当于头指针指向NULL。
链栈的操作代码实现
入栈,出栈
//
// Created by Administrator on 2023/1/27.
//
#include <cstdlib>
#include "stdio.h"
typedef int ElemType;
typedef struct Stacknode{
ElemType data;
struct Stacknode *next;
}StackNode, *LinkStack;
// 压栈 弹栈
//链栈的初始化
void InitStack1(LinkStack &S)
{
S = (LinkStack)malloc(sizeof (StackNode));
S->next = NULL;
}
//链栈的入栈
void Push1(LinkStack S,ElemType n)
{
StackNode *p = (LinkStack) malloc(sizeof (StackNode));
//栈只能在栈顶进行操作 所以要用到头插法
p->data = n;
p->next = S->next;
S->next = p;
}
//链栈的出栈
bool Pop1(LinkStack &S,ElemType &n)
{
//栈顶操作 头删法
StackNode *p;
p = S->next;
if(p==NULL)
{
return false;
}
n = p->data;
S->next = p->next;
free(p);
return true;
}
//打印链表
void PrintList(LinkStack L)
{
L = L->next;
while (L!=NULL)
{
printf("%3d",L->data);
L = L->next;
}
}
int main()
{
int x;
LinkStack S;
InitStack1(S);
Push1(S,1);
Push1(S,2);
Push1(S,3);
PrintList(S);
Pop1(S,x);
PrintList(S);
return 0;
}
#队列
队列其实是一种操作受限的特殊线性表,也是一对一的,其特殊性就在于他只能在两端进行操作,在表的一端插入,在表的另一端删除。向队列中插入元素称为入队或进队,删除元素称为出队或者离队,队列遵循的是先进先出原则,与我们日常生活中的队列模型是一样的。
同样的队列的实现也可以用顺序表实现和链表实现。
用顺序表实现的队列
//
// Created by Administrator on 2023/1/27.
//
//
// Created by Administrator on 2023/1/26.
//
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
//初始化队列
/*
* 步骤
* 初始化队列 判断队列是否为空 入队 获取队头元素 出队
*/
#define MaxSize 3
typedef int ElemType;
typedef struct{
ElemType data[MaxSize];
ElemType front;
ElemType rear;
}SqQueque;
//初始化队列
void InitQueque(SqQueque &Q)
{
Q.front = 0;
Q.rear = 0;
}
//判断队列是否为空
bool QuequeEmpty(SqQueque *Q)
{
if(Q->front == Q->rear)
{
return true;
}
return false;
}
//入队
bool EnQueque(SqQueque &Q,ElemType n)
{
if(Q.rear == MaxSize) //入队先判断队列是否满,用尾指针来判断队列是否已经满了
{
return false;
}
Q.data[Q.rear] = n;
Q.rear++;
return true;
}
ElemType getFrontElem(SqQueque *Q)
{
bool flag = QuequeEmpty(Q);
if(flag)
{
return -1;
} else{
return Q->data[Q->front];
}
}
//出队
bool OutQueque(SqQueque &Q,ElemType &n)
{
if(Q.front == Q.rear) // 判断队列是否为空,当首指针与尾指针相等的时候 队列为空。
{
return false;
}
n = Q.data[Q.front];
Q.front++;
return true;
}
int main2() {
SqQueque Q;
int s;
int n = 0;
InitQueque(Q); //初始化队列
bool flag = QuequeEmpty(&Q);
if(flag)
{
printf("队列为空\n");
}
EnQueque(Q,3);//入队
EnQueque(Q,4);
EnQueque(Q,5);
s = getFrontElem(&Q);
printf("队头元素为%d\n",s);
OutQueque(Q,n);
printf("元素出队,数值为%d\n",n);
s = getFrontElem(&Q);
printf("队头元素为%d\n",s);
bool flag1 = EnQueque(Q,6);
if(!flag1)
{
printf("队列发生上溢出");
}
return 0;
}
然而这种形式的队列有一个缺点那就是会发生溢出,就是当尾指针大于设置的MaxSize时,如果这个时候你用头指针进行对前面元素的删除的话,这个时候队列是没有满的,但是由于是靠尾指针来进行插入操作但是此时尾指针已经判定溢出,因此虽然这个时候队列没有满但是不可以再往队列里面存放数据了。
这个时候 我们就需要引入循环队列,循环队列的特点就是首尾相连,循环队列的特性很好的解决这一问题,将空间重复的反复的应用。因此我们在移动首尾指针的时候就可以利用取模运算来实现这一特性,当你的首尾指针移动的时候也就是 rear + 1 % MaxSize, front +1 % MaxSize
这
但是如果使用上述条件虽然解决了空间浪费的问题,但是当尾指针填满最后一个空间之后再次移动尾指针,他会与首指针指向同一个空间,此时队列空与队列满的判断条件就相等了,因此为了区分判断队空与队满,我们有以下三个解决方法 ,此时如果少用一个元素空间来区分的话,那么当长度达到MaxSize-1的时候,就可以作为队满的判断条件,也就是
(rear +1)%MaxSize == front因此这样就很好区别队满与队空的判断条件了
第二种方式 不浪费存储空间还解决上溢的方法
//
// Created by JustinLiang on 2023/4/1.
//
#include <stdio.h>
#include <cstdlib>
typedef int Elemtype;
#define Maxsize 6
//顺序存储队列
typedef struct {
Elemtype data[Maxsize];
Elemtype rear;
Elemtype front;
int size;
}SqQueQue;
//初始化队列
void InitSqQueque(SqQueQue &Q)
{
Q.front = 0;
Q.rear = 0;
Q.size = 0;
}
//循环队列的判空
bool QueIsEmpty(SqQueQue Q)
{
if(Q.size == 0)
{
return true;
}
return false;
}
//入队
bool EnQueQue(SqQueQue &Q)
{
//判满
if(Q.size == Maxsize)
{
return false;
}
int x;
printf("请输入入队的值\n");
scanf("%d",&x);
Q.data[Q.rear] = x;
Q.rear = (Q.rear +1) %Maxsize;
Q.size++;
return true;
}
//出队
bool DeQueQue(SqQueQue &Q,Elemtype &n)
{
//判空
bool flag = QueIsEmpty(Q);
if(!flag)
{
n = Q.data[Q.front];
Q.front = (Q.front+1) % Maxsize;
Q.size--;
return true;
}
return false;
}
//获取队头元素
bool getTopElem(SqQueQue Q, Elemtype &n)
{
if(Q.size == 0)
{
return false;
}
n = Q.data[Q.front];
return true;
}
int main()
{
SqQueQue Q;
InitSqQueque(Q);
int n=0;
int m=0;
bool flag = QueIsEmpty(Q);
if(flag)
{
printf("队列为空\n");
}
for (int i = 0; i < 7 ; ++i) {
bool flag1 = EnQueQue(Q);
if(!flag1)
{
printf("队列已满不能继续输入\n");
}
}
getTopElem(Q,m);
printf("此时队头为%d\n",m);
DeQueQue(Q,n);
printf("出队元素为%d\n",n);
getTopElem(Q,m);
printf("此时队头为%d",m);
}
第三种方式 设置信号量 删除后设置信号量置为0 插入后信号量置为1 并且队列空或满时头指针与尾指针都相等。
//
// Created by JustinLiang on 2023/4/1.
//
#include <stdio.h>
#include <cstdlib>
typedef int Elemtype;
#define Maxsize 6
//顺序存储队列
typedef struct {
Elemtype data[Maxsize];
Elemtype rear;
Elemtype front;
int Tag;
}SqQueQue;
//初始化队列
void InitSqQueque(SqQueQue &Q)
{
Q.front = 0;
Q.rear = 0;
Q.Tag = 0;
}
//循环队列的判空
bool QueIsEmpty(SqQueQue Q)
{
if(Q.front == Q.rear && Q.Tag == 0)
{
return true;
}
return false;
}
//入队
bool EnQueQue(SqQueQue &Q)
{
//判满 信号量为1 说明刚刚使用的是插入操作此时如果尾指针与头指针在一起那就说明为满
if(Q.front == Q.rear && Q.Tag == 1)
{
return false;
}
int x;
printf("请输入入队的值\n");
scanf("%d",&x);
Q.data[Q.rear] = x;
Q.rear = (Q.rear +1) %Maxsize;
Q.Tag = 1;
return true;
}
//出队
bool DeQueQue(SqQueQue &Q,Elemtype &n)
{
//判空
bool flag = QueIsEmpty(Q);
if(!flag)
{
n = Q.data[Q.front];
Q.front = (Q.front+1) % Maxsize;
Q.Tag = 0;
return true;
}
return false;
}
//获取队头元素
bool getTopElem(SqQueQue Q, Elemtype &n)
{
if(Q.front == Q.rear && Q.Tag == 0)
{
return false;
}
n = Q.data[Q.front];
return true;
}
int main()
{
SqQueQue Q;
InitSqQueque(Q);
int n=0;
int m=0;
bool flag = QueIsEmpty(Q);
if(flag)
{
printf("队列为空\n");
}
for (int i = 0; i < 7 ; ++i) {
bool flag1 = EnQueQue(Q);
if(!flag1)
{
printf("队列已满不能继续输入\n");
}
}
getTopElem(Q,m);
printf("此时队头为%d\n",m);
DeQueQue(Q,n);
printf("出队元素为%d\n",n);
getTopElem(Q,m);
printf("此时队头为%d",m);
}
以上都是队尾指针在队尾元素后一个空间,如果队尾指针指向队尾元素判断条件又不同 具体实现如下
//
// Created by Administrator on 2023/1/27.
//
//
// Created by Administrator on 2023/1/26.
//
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
//初始化队列
/*
* 步骤
* 初始化队列 判断队列是否为空 入队 获取队头元素 出队
*/
#define MaxSize 5
typedef int ElemType;
typedef struct{
ElemType data[MaxSize];
ElemType front;
ElemType rear;
}SqQueque;
//初始化队列
void InitQueque(SqQueque &Q)
{
Q.front = 0;
Q.rear = MaxSize-1;
}
//判断队列是否为空
bool QuequeEmpty(SqQueque Q)
{
if((Q.rear+1)%MaxSize == Q.front )
{
return true;
}
return false;
}
//入队
bool EnQueque(SqQueque &Q)
{
if((Q.rear+2) % MaxSize == Q.front) //入队先判断队列是否满,用尾指针来判断队列是否已经满了
{
return false;
}
int x;
printf("请输入值\n");
scanf("%d",&x);
Q.rear = (Q.rear+1)%MaxSize;
Q.data[Q.rear] = x;
return true;
}
bool getTopElem(SqQueque Q, ElemType &n) {
if ((Q.rear + 1) % MaxSize == Q.front) {
return false;
}
n = Q.data[Q.front];
}
//出队
bool OutQueque(SqQueque &Q,ElemType &n)
{
if((Q.rear+1) == Q.front) // 判断队列是否为空,当首指针与尾指针相等的时候 队列为空。
{
return false;
}
n = Q.data[Q.front];
Q.front = (Q.front+1)%MaxSize;
return true;
}
int main() {
SqQueque Q;
InitQueque(Q);
int n=0;
int m=0;
bool flag = QuequeEmpty(Q);
if(flag)
{
printf("队列为空\n");
}
for (int i = 0; i < 5 ; ++i) {
bool flag1 = EnQueque(Q);
if(!flag1)
{
printf("队列已满不能继续输入\n");
}
}
getTopElem(Q,n);
printf("队头元素为%d\n",n);
OutQueque(Q,m);
printf("出队元素为%d\n",m);
getTopElem(Q,n);
printf("队头元素为%d\n",n);
return 0;
}
队尾指针指向队尾元素的第二种方式 与上面方法的区别只是在入队时需要注意顺序
//
// Created by JustinLiang on 2023/4/1.
//
#include <stdio.h>
#include <cstdlib>
typedef int Elemtype;
#define Maxsize 6
//顺序存储队列
typedef struct {
Elemtype data[Maxsize];
Elemtype rear;
Elemtype front;
int size;
}SqQueQue;
//初始化队列
void InitSqQueque(SqQueQue &Q)
{
Q.front = 0;
Q.rear = Maxsize-1;
Q.size = 0;
}
//循环队列的判空
bool QueIsEmpty(SqQueQue Q)
{
if(Q.size == 0)
{
return true;
}
return false;
}
//入队
bool EnQueQue(SqQueQue &Q)
{
//判满
if(Q.size == Maxsize)
{
return false;
}
int x;
printf("请输入入队的值\n");
scanf("%d",&x);
Q.rear = (Q.rear +1) %Maxsize;
Q.data[Q.rear] = x;
Q.size++;
return true;
}
//出队
bool DeQueQue(SqQueQue &Q,Elemtype &n)
{
//判空
bool flag = QueIsEmpty(Q);
if(!flag)
{
n = Q.data[Q.front];
Q.front = (Q.front+1) % Maxsize;
Q.size--;
return true;
}
return false;
}
//获取队头元素
bool getTopElem(SqQueQue Q, Elemtype &n)
{
if(Q.size == 0)
{
return false;
}
n = Q.data[Q.front];
return true;
}
int main()
{
SqQueQue Q;
InitSqQueque(Q);
int n=0;
int m=0;
bool flag = QueIsEmpty(Q);
if(flag)
{
printf("队列为空\n");
}
for (int i = 0; i < 7 ; ++i) {
bool flag1 = EnQueQue(Q);
if(!flag1)
{
printf("队列已满不能继续输入\n");
}
}
getTopElem(Q,m);
printf("此时队头为%d\n",m);
DeQueQue(Q,n);
printf("出队元素为%d\n",n);
getTopElem(Q,m);
printf("此时队头为%d",m);
}
循环队列 顺序存储表示
//
// Created by Administrator on 2023/1/27.
//
#include <stdio.h>
//循环队列 抽象数据结构为 数组和头指针尾指针
#define MaxSize 5
typedef int ElemType;
typedef struct {
ElemType data[MaxSize];
ElemType front;//头指针
ElemType rear;//尾指针
}SqQueque1;
// 用循环队列解决上溢问题
//初始化循环队列 判断队列是否为空 入队 获取队首元素 出队
void InitQueque1(SqQueque1 &Q)
{
Q.rear = 0;
Q.front = 0;
}
bool QuequeEmpty1(SqQueque1 *Q)
{
if(Q->front ==Q->rear)
{
return true;
}
return false;
}
bool EnQueque1(SqQueque1 &Q, ElemType n)
{
//先判断队列满了没有
if( (Q.rear+1)%MaxSize == Q.front ) //因为要区别于空队列的判断条件,也就是说当尾指针指向最后一个空的空间时不可以放元素,这个时候尾指针已经处于循环队列的队尾了,队列已经是满队列了
{
return false;
}
Q.data[Q.rear] = n;
Q.rear = (Q.rear+1)%MaxSize; // 移动队尾指针
return true;
}
//计算循环队列长du
ElemType QuequeLength(SqQueque1 Q)
{
return ((Q.rear-Q.front)+MaxSize)%MaxSize;
}
ElemType getFrontElem1(SqQueque1 Q)
{
return Q.data[Q.front];
}
bool OutQueque1(SqQueque1 &Q,ElemType &n)
{
//先判断队列是否为空,为空的话不能出队
if(Q.front == Q.rear)
{
return false;
}
n = Q.data[Q.front]; //先进先出
Q.front = (Q.front+1)%MaxSize;//移动队首指针
return true;
}
int main3()
{
SqQueque1 Q;
int m;
int a =0 ;
InitQueque1(Q);
bool flag = QuequeEmpty1(&Q);
if(flag)
{
printf("循环队列为空\n");
}
EnQueque1(Q,1);
EnQueque1(Q,2);
EnQueque1(Q,3);
EnQueque1(Q,4);
a = QuequeLength(Q);
printf("长度为%d\n",a);
flag = EnQueque1(Q,5);//循环队列已满 不能插入返回False
if(!flag)
{
printf("队列已满,无法插入元素\n");
}
a = getFrontElem1(Q);
printf("此时循环队列首元素为%d\n",a);
OutQueque1(Q,m);
printf("循环队列出队元素为%d\n",m);
a = getFrontElem1(Q);
printf("此时循环队列首元素为%d\n",a);
flag = EnQueque1(Q,5);
if(flag)
{
printf("插入元素成功\n");
}
return 0;
}
如果是在不清楚队列多长的情况下需要用到链式存储,也就是链队 。
//
// Created by Administrator on 2023/1/27.
//
#include <cstdlib>
#include "stdio.h"
//链队的结构体定义
typedef int ElemType;
typedef struct Linknode{
ElemType data;
struct Linknode *next;
}LinkNode;
typedef struct {
LinkNode *front;
LinkNode *rear;
}LinkQueque;
//初始化链队 入队 出队
//初始化链队
void InitQueque2(LinkQueque &Q)
{
Q.rear = Q.front =(LinkNode*)malloc(sizeof(LinkNode));
}
//判断是否为空
bool IsEmpty(LinkQueque Q)
{
if(Q.front == Q.rear)
{
return true;
}
return false;
}
//入队 使用尾插法
void EnQueque2(LinkQueque &Q,ElemType n)
{
LinkNode *s = (LinkNode*) malloc(sizeof (LinkNode));
s->data = n;
s->next = NULL;
Q.rear->next = s;//利用尾部指针实现尾插法
Q.rear = s;
}
//出队 头删法
bool DeQueque(LinkQueque &Q,ElemType &n)
{
if(Q.front == NULL)//先要判断队列是否为空
{
return false;
}
LinkNode *p = Q.front->next;
n = p->data;
Q.front->next = p->next;
if(p == Q.rear)//特殊情况 如果是最后一个结点的话需要回收尾指针。
{
Q.rear = Q.front;
}
free(p);
return true;
}
int main()
{
LinkQueque Q;
int n ;
InitQueque2(Q);
bool flag = IsEmpty(Q);
if(flag)
{
printf("链队为空\n");
}
EnQueque2(Q,1);
EnQueque2(Q,2);
EnQueque2(Q,3);
DeQueque(Q,n);
printf("被删除元素值为%d\n",n);
return 0;
}
不带头节点的操作
//
// Created by JustinLiang on 2023/4/8.
//
#include <stdio.h>
#include <cstdlib>
typedef int Elemtype;
typedef int ElemType;
typedef struct Linknode{
ElemType data;
struct Linknode *next;
}LinkNode;
typedef struct {
LinkNode *front;
LinkNode *rear;
}LinkQueque;
//初始化链队 不带头节点
void InitLinkQueQue(LinkQueque &Q)
{
Q.rear = NULL;
Q.front = NULL;
}
//判空
bool IsQueQueEmpty(LinkQueque Q)
{
return (Q.front==NULL) ;
}
//入队
void InsertNode(LinkQueque &Q)
{
LinkNode *p = (LinkNode*) malloc(sizeof (LinkNode));
int x;
printf("输入入队的值\n");
scanf("%d",&x);
p->data = x;
p->next = NULL;
if(Q.front == NULL)
{
Q.front = p;
Q.rear = p;
} else{
Q.rear->next = p;
Q.rear = p;
}
}
//出队
bool DeQueQue(LinkQueque &Q)
{
bool flag =IsQueQueEmpty(Q);
if(flag)
{
return false;
}
LinkNode *p =Q.front;
if(Q.front == Q.rear)
{
Q.front = NULL;
Q.rear = NULL;
free(p);
} else{
Q.front = p->next;
free(p);
}
return true;
}
//遍历链表
void PrintLink(LinkQueque Q)
{
LinkNode *p = Q.front;
while (p != NULL)
{
printf("%3d",p->data);
p = p->next;
}
}
int main()
{
LinkQueque Q;
InitLinkQueQue(Q);
bool flag = IsQueQueEmpty(Q);
if(flag)
{
printf("链表为空");
}
flag = DeQueQue(Q);
if(!flag)
{
printf("链表为空无法删除");
}
for (int i = 0; i < 5; ++i) {
InsertNode(Q);
}
PrintLink(Q);
printf("\n");
for (int i = 0; i < 3; ++i) {
DeQueQue(Q);
}
PrintLink(Q);
}