队列(顺序队列,循环队列,链式队列)超详细讲解

队列的基本知识

队列(ADT):限定在表的一端插入,在表的另一端删除的线性表。其中,队尾(rear)为新元素依次进队的位置,队头(front)则为队列中元素依次出队的位置。 用白话来说就是一群人排队买饭,前头的人买完饭就走,这是出队,后面来的的人加入排队买饭,这是入队。最前头的是队头,最后面的是队尾。

队列是先进先出(First In First Out,FIFO)的线性数据结构。 这和买饭是一个情况的,先到的人先买到饭。与队列先进先出相反的是,栈是后进先出。

一.顺序队列

队列的顺序表示我这里使用一维数组来实现,其数据结构定义如下:

typedef struct//定义一个队列
{
    
    
	int data[MaxSize];//定义一个数组
	int front, rear;//定义队头指针和队尾指针
}SqQueue;

需要注意的是,front指向的是队头元素前一个单元的下标位置,rear指向的是队尾元素的下标位置。MaxSize表示队列中最多允许存储的元素数量。

一开始的时候 f 和 r 会设置指向-1的位置。 初始时,空队列的顺序表示如下图的(a)所示(为了演示方便,图中 f 表示front,r 表示rear)。元素入队时先将队尾下标加1,然后元素入队;元素出队时,先将队头下标加1,然后元素出队。元素a,b,c依次入队后的情况如下图(b)所示,每有一个元素入队,r 就+1一次,因为下图(b)入队了三个元素,于是 r+3=4。执行两次元素出队操作后的情况如下图(c)所示,每出队一次,f 就+1一次,因为下图(c)出队了两个元素,于是 f+2=1;下图(d)所示是元素d入队后的情况,入队一个元素,r+1=3。

从下图(d)可以看到,当再有元素需要入队时,将产生“溢出”,但很显然队列中还有2个空的存储单元,这种现象被称为“假溢出”。这种“假溢出”的现象的发生,说明上述存储方式是有缺陷的。那么就对上述存储方式进行改进,我们常用的一种改进方法是采用循环队列结构,即把数组从逻辑上看成一个头尾相连的环。当有新元素入队时,将该新元素存入环形结构的下一个单元位置。这里我不做详细讲解,我会在这篇博客的循环队列进行详细讲解,可以翻目录快速到达哟。

在这里插入图片描述

基本操作

循环队列有八大操作:初始化,销毁,入队,出队,判空,判满,读取队头元素,输出队列所有元素。

1.初始化

顺序队列的初始化只需要让队头指针和队尾指针指向-1的位置就好了,指向-1是因为我们用的是一维数组来实现这个顺序队列。

void InitQueue(SqQueue &Q)//初始化队列
{
    
    
	Q.front = Q.rear = -1;
}

2.销毁

销毁操作和初始化操作是一样的,也是让队头指针和队尾指针指向-1就好了,可以在前面加上是否销毁的判断代码。

void DestroyQueue(SqQueue &Q)//销毁队列
{
    
    
	char a;
	printf("是否销毁队列(Y/N):");
	getchar();
	scanf("%c", &a);
	if (a == 'Y')
	{
    
    
		Q.front = Q.rear = -1;
		printf("队列销毁成功\n");
	}
	else
		printf("队列销毁失败\n");
}

3.入队

入队就是元素进入队列,每入队一次,队尾指针rear就+1一次。需要注意的是,队尾指针rear先+1,然后才进行输入,把元素放入队尾指针rear现在所指向的位置。这个与下面的出队操作刚好相反。

我们可以在入队前加一个入队确认的代码,出队的时候也可以加上这个,这里用getchar()处理掉之前遗留的字符。

bool EnQueue(SqQueue &Q)//入队
{
    
    
	char a;
	printf("是否进行入队(Y/N):");
	getchar();
	scanf("%c", &a);
	if (a == 'N')
		return false;
	if (QueueFull(Q))//判断队列是否满了,如果队列满了就无法进行入队操作,直接返回return false。
	{
    
    
		printf("队满,无法进行入队\n");
		return false;
	}
	printf("请输入一个要入队的数字:");
	int x;
	scanf("%d", &x);
	Q.rear = Q.rear + 1;//队尾指针往前移动一格
	Q.data[Q.rear] = x;//把元素x放入此时队尾指针指向的队列位置
	printf("入队成功,入队的元素是%d\n", x);
	return true;
}

4.出队

出队就是删除队列里面的元素,每出队一次,队头指针front就+1一次。需要注意的是,直接让队头指针front往前移动1个位置,这个元素就被删除了,与入队同样的是,在一开始可以加上出队确认的代码。

bool DeQueue(SqQueue &Q)//出队
{
    
    
	char a;
	printf("是否进行出队(Y/N):");
	getchar();
	scanf("%c", &a);
	if (a == 'N')
		return false;
	if (QueueEmpty(Q))//判断队列是否为空,如果队列为空就无法进行出队操作,直接返回return false。
		return false;
	printf("出队成功,出队的元素是:%d\n",Q.data [Q.front+1]);
	Q.front = Q.front + 1;//队头指针往前移动一格
	return true;
}

5.判空

判断队空就是判断队尾指针和队头指针有没有指向同一个位置。

bool QueueEmpty(SqQueue &Q)//判断队空
{
    
    
	return (Q.rear == Q.front);
}

6.判满

判断队满直接判断队尾指针rear有没有指向队列最后一个位置就可以了。 这样子判断就足够了,不需要再加上队头指针rear指向-1这个条件,因为假溢出本来就是当作队满情况对待的。

bool QueueFull(SqQueue &Q)//判断队满
{
    
    
	return (Q.rear == MaxSize - 1);
}

7.读取队头元素

读取队头元素前要先对队列进行判空操作,队列里要有元素才能进行读取队头操作。由概念可知,队头元素就是队头指针前面的那个位置的元素。所以直接打印 Q.data[Q.front +1]就好了。

bool GetHead(SqQueue &Q)//读取队头元素
{
    
    
	if (QueueEmpty(Q))
		return false;
	printf("队头元素是:%d\n", Q.data [Q.front +1]);
	return true;
}

8.输出队列所有元素

输出队列所有的元素,只需要用一个for循环,从队头元素到队尾元素一个个输出就好了。

bool print(SqQueue &Q)//输出队列元素
{
    
    
	if (QueueEmpty(Q))
		return false;
	printf("队列的元素是:");
	for (int i = Q.front + 1; i <= Q.rear; i++)
	{
    
    
		printf("%d  ", Q.data[i]);
	}
	printf("\n");
	return true;
}

全部代码及实现结果

#define MaxSize 10

#include<stdio.h>

typedef struct
{
    
    
	int data[MaxSize];
	int front, rear;
}SqQueue;

void InitQueue(SqQueue &Q)//初始化队列
{
    
    
	Q.front = Q.rear = -1;
}

void DestroyQueue(SqQueue &Q)//销毁队列
{
    
    
	char a;
	printf("是否销毁队列(Y/N):");
	getchar();
	scanf("%c", &a);
	if (a == 'Y')
	{
    
    
		Q.front = Q.rear = -1;
		printf("队列销毁成功\n");
	}
	else
		printf("销毁失败\n");
}

bool QueueEmpty(SqQueue &Q)//判断队空
{
    
    
	return (Q.rear == Q.front);
}

bool QueueFull(SqQueue &Q)//判断队满
{
    
    
	return (Q.rear == MaxSize - 1);
}

bool TakeQueue(SqQueue &Q)//在队列里面放入元素
{
    
    
	int i;
	printf("请输入要创建队列的长度:");
	scanf("%d", &i);
	if (i > MaxSize || i < 0)
	{
    
    
		printf("创建失败\n");
		return false;
	}
	printf("请输入%d个数字:",i);
	for (int j = 0; j < i; j++)
	{
    
    
		Q.rear = Q.rear + 1;
		scanf("%d", &Q.data[Q.rear]);
	}
	return true;
}

bool EnQueue(SqQueue &Q)//入队
{
    
    
	char a;
	printf("是否进行入队(Y/N):");
	getchar();
	scanf("%c", &a);
	if (a == 'N')
		return false;
	if (QueueFull(Q))
	{
    
    
		printf("队满,无法进行入队\n");
		return false;
	}
	printf("请输入一个要入队的数字:");
	int x;
	scanf("%d", &x);
	Q.rear = Q.rear + 1;
	Q.data[Q.rear] = x;
	printf("入队成功,入队的元素是%d\n", x);
	return true;
}

bool GetHead(SqQueue &Q)//读取队头元素
{
    
    
	if (QueueEmpty(Q))
		return false;
	printf("队头元素是:%d\n", Q.data [Q.front +1]);
	return true;
}
 
bool DeQueue(SqQueue &Q)//出队
{
    
    
	char a;
	printf("是否进行出队(Y/N):");
	getchar();
	scanf("%c", &a);
	if (a == 'N')
		return false;
	if (QueueEmpty(Q))
		return false;
	printf("出队成功,出队的元素是:%d\n",Q.data [Q.front+1]);
	Q.front = Q.front + 1;
	return true;
}

bool Print(SqQueue &Q)//输出队列元素
{
    
    
	if (QueueEmpty(Q))
		return false;
	printf("队列的元素是:");
	for (int i = Q.front + 1; i <= Q.rear; i++)
	{
    
    
		printf("%d  ", Q.data[i]);
	}
	printf("\n");
	return true;
}

int main()
{
    
    
	SqQueue(Q);
	InitQueue(Q);//初始化队列
	TakeQueue(Q);//在队列里面放入元素
	EnQueue(Q);//入队
	Print(Q);//输出队列元素
	GetHead(Q);//读取队头元素
	DeQueue(Q);//出队
	Print(Q);//输出队列元素
	if (QueueEmpty(Q))//判断队空
		printf("队空\n");
	else
		printf("队列不是空的\n");
	if (QueueFull(Q))//判断队满
		printf("队满\n");
	else
		printf("队列不是满的\n");
	DestroyQueue(Q);//销毁队列
	Print(Q);//输出队列元素
	return 0;
}

在这里插入图片描述

二.循环队列

循环队列我这里使用一维数组来实现,其数据结构定义如下:

typedef struct //定义一个队列
{
    
    
	int data[MaxSize];//定义一个数组
	int front, rear;//定义队头指针和队尾指针
}SqQueue;

一开始的时候 f 和 r 会指向0的位置,与顺序队列不同,顺序队列的 f 和 r 指向的是-1的位置。 下图中,(a)是初始化生成的空队列,(b)到(d)是循环队列的入队和出队示意图。在空队列中将元素a,b,c,d依次入队,得到(b)所示的队列状态;然后a,b,c依次出队,得到(c)所示的队列状态;最后e,f 入队,形成(d)所示的队列状态。可以看出,当 e入队时,rear已经为4,此时,再执行 rear=(rear+1)%MaxSize后 rear=0,因此e被插入下标位置为0的单元,解决了“假溢出”问题。

注意:
1.循环队列并不真的是一个环状的队列,只是为了方便演示而把队列做成环状的。循环队列是对顺序队列的一种改进,即把数组从逻辑上看成一个头尾相连的环。当有新元素入队时,将该新元素存入环形结构的下一个单元位置。
2.front前进一个单元位置:front=(front+1)%MaxSize;
3.rear前进一个单元位置:rear=(rear+1)%MaxSize;
4.循环队列判断队列已满/已空有三种方法,取决于代码的写法,这里我采用的是方法一,三种方法的详细介绍在下面,可以翻目录快速到达哟。

基本操作

顺序队列同样是八大操作:初始化,销毁,入队,出队,判空,判满,读取队头元素,输出队列所有元素。
循环队列的代码部分不作详解,与顺序队列相似。

1.初始化

顺序队列的初始化只需要让队头指针和队尾指针指向0的位置就好了

void InitQueue(SqQueue &Q)//初始化队列
{
    
    
	Q.front = Q.rear = 0;//初始化让队头指针队尾指针都指向0
}

2.销毁

销毁操作如同初始化一样,让队头指针和队尾指针重新指向0即可。

void DestroyQueue(SqQueue &Q)//销毁队列
{
    
    
	char a;
	getchar();
	printf("是否销毁队列(Y/N):");
	scanf("%c", &a);
	if (a == 'Y')
	{
    
    
		Q.rear = Q.front=0;
		printf("队列销毁成功\n");
	}
	else
		printf("队列销毁失败\n");
}

3.入队

bool EnQueue(SqQueue &Q)//入队
{
    
    
	printf("是否进行入队(Y/N):");
	getchar();
	char a;
	scanf("%c", &a);
	if (a == 'N')
		return false;
	if (QueueFull(Q))
	{
    
    
		printf("队满,无法进行入队\n");
		return false;
	}
	printf("请输入一个要入队的数字:");
	int x;
	scanf("%d", &x);//输入一个要入队的数
	Q.rear = (Q.rear + 1) % MaxSize;//队尾指针往前移动一格
	Q.data[Q.rear] = x;//把元素x放入此时队尾指针指向的队列位置
	printf("入队成功,入队的元素是%d\n", x);
	return true;
}

4.出队

bool DeQueue(SqQueue &Q)//出队操作(删除一个队头元素)
{
    
    
	char a;
	printf("是否进行出队(Y/N):");
	getchar();
	scanf("%c", &a);
	if (a == 'N')
		return false;
	if (QueueEmpty(Q))
		return false;
	int x = Q.data[(Q.front + 1) % MaxSize];
	Q.front =(Q.front + 1) % MaxSize;
	printf("出队的元素是:%d\n", x);
	return true;
}

5.判空

循环队列的判空这里我们采用的是判断队头指针和队尾指针指向的是否是同一个位置。

bool QueueEmpty(SqQueue &Q)//判断队列是否为空
{
    
    
	return (Q.front == Q.rear);
}

6.判满

上面的图(b)就是队满的情况,这里我使用的是方法1来实现循环队列,所以队列里会留有一个空位置用来进行队列的判空判满操作。方法1的判满技巧是判断队尾指针下一个位置是不是队头指针指向的位置。

bool QueueFull(SqQueue &Q)//判断队列是否已满
{
    
    
	return (Q.rear + 1) % MaxSize == Q.front;
}

7.读取队头元素

bool GetHead(SqQueue &Q)//获取队头元素
{
    
    
	if (QueueEmpty(Q))
		return false;
	printf("队头元素是:%d\n", Q.data[(Q.front + 1) % MaxSize]);
	return true;
}

8.输出队列所有元素

bool Print(SqQueue &Q)//输出队列元素
{
    
    
	if (QueueEmpty(Q))
		return false;
	printf("队列的元素是:");
	for (int i = (Q.front + 1) % MaxSize; i <= Q.rear; i++)
	{
    
    
		printf("%d  ", Q.data[i]);
	}
	printf("\n");
	return true;
}

全部代码及实现结果

#define MaxSize 10

#include<stdio.h>

typedef struct 
{
    
    
	int data[MaxSize];//用静态数组存放队列元素
	int front, rear;//队头指针和队尾指针
}SqQueue;

void InitQueue(SqQueue &Q)//初始化队列
{
    
    
	Q.front = Q.rear = 0;//初始化让队头指针队尾指针都指向0
}

void DestroyQueue(SqQueue &Q)//销毁队列
{
    
    
	char a;
	getchar();
	printf("是否销毁队列(Y/N):");
	scanf("%c", &a);
	if (a == 'Y')
	{
    
    
		Q.rear = Q.front=0;
		printf("队列销毁成功\n");
	}
	else
		printf("队列销毁失败\n");
}

bool QueueEmpty(SqQueue &Q)//判断队列是否为空
{
    
    
	return (Q.front == Q.rear);
}

bool QueueFull(SqQueue &Q)//判断队列是否已满
{
    
    
	return (Q.rear + 1) % MaxSize == Q.front;
}

bool TakeQueue(SqQueue &Q)//把元素放入队列
{
    
    
	int i;
	printf("请输入你要创建的队列长度:");
	scanf("%d", &i);
	if (i > MaxSize - 1 || i < 0)
	{
    
    
		printf("创建失败\n");
		return false;
	}
	printf("请输入%d个数字:", i);
	for (int j = 0; j < i; j++)
	{
    
    
		Q.rear = (Q.rear + 1) % MaxSize;
		scanf("%d", &Q.data[Q.rear]);
	}
	return true;
}

bool EnQueue(SqQueue &Q)//入队
{
    
    
	printf("是否进行入队(Y/N):");
	getchar();
	char a;
	scanf("%c", &a);
	if (a == 'N')
		return false;
	if (QueueFull(Q))
	{
    
    
		printf("队满,无法进行入队\n");
		return false;
	}
	printf("请输入一个要入队的数字:");
	int x;
	scanf("%d", &x);
	Q.rear = (Q.rear + 1) % MaxSize;//队尾指针往前移动一格
	Q.data[Q.rear] = x;//把元素x放入此时队尾指针指向的队列位置
	printf("入队成功,入队的元素是%d\n", x);
	return true;
}

bool GetHead(SqQueue &Q)//获取队头元素
{
    
    
	if (QueueEmpty(Q))
		return false;
	printf("队头元素是:%d\n", Q.data[(Q.front + 1) % MaxSize]);
	return true;
}

bool DeQueue(SqQueue &Q)//出队操作(删除一个队头元素)
{
    
    
	char a;
	printf("是否进行出队(Y/N):");
	getchar();
	scanf("%c", &a);
	if (a == 'N')
		return false;
	if (QueueEmpty(Q))
		return false;
	int x = Q.data[(Q.front + 1) % MaxSize];
	Q.front =(Q.front + 1) % MaxSize;
	printf("出队的元素是:%d\n", x);
	return true;
}

bool Print(SqQueue &Q)//输出队列元素
{
    
    
	if (QueueEmpty(Q))
		return false;
	printf("队列的元素是:");
	for (int i = (Q.front + 1) % MaxSize; i <= Q.rear; i++)
	{
    
    
		printf("%d  ", Q.data[i]);
	}
	printf("\n");
	return true;
}


int main()
{
    
    
	SqQueue Q;
	InitQueue(Q);//初始化
	TakeQueue(Q);//在队列里面放入元素
	EnQueue(Q);//入队
	Print(Q);//输出队列元素
	GetHead(Q);//获取队头元素
	DeQueue(Q);//出队
	Print(Q);//输出队列元素
	if (QueueEmpty(Q))//判断队列是否为空
		printf("队列为空\n");
	else
		printf("队列不为空\n");
	if (QueueFull(Q))//判断队列是否已满
		printf("队列满了\n");
	else
		printf("队列未满\n");
	DestroyQueue(Q);//销毁队列
	Print(Q);
	return 0;
}

在这里插入图片描述

循环队列判断队列已满/已空的三种方法

如果队列满的时候还留有一个空间,那么就可以使用方法一。

如果队列所有空间都填上元素,那么就可以使用方法二,方法三。

a.方法一(留一个位置)

给循环队列留下一个空位置,让队头指针指向空队列。

队满条件:队尾指针的下一个位置是队头,即(Q.rear+1)%MaxSize == Q.front。
队空条件:Q.rear ==Q.front

队列元素个数:(rear+MaxSize-front)%MaxSize

b.方法二(用一个计数器)

如果要把队列的所有空间都填上元素,那么队满和队空的时候用方法一的判断方法判断就会失效,因为此时队尾指针和队头指针指向的都是同一个位置,无法进行判断。所以我们设置一个int型变量size,size的初始值为0,当队列进行一次入队操作,size+1,当队列进行一次出队操作,size-1。

队满条件:size==MaxSize

队空条件:size==0

c.方法三(设置一个判断数)

除了方法二,我们还可以设置一个判断数 s 来判断队满和队空。s 的初始值为0。当队列进行一次入队操作,s=1;当队列进行一次出队操作,s=0。当rear == front的时候,我们只需看一下s的值就可以进行判断。如果s == 0,说明最近一次队列进行了出队操作或者队列本身就是空的,那么就可以得出队空的结论。如果s == 1,说明最近一次队列进行了入队操作,那么就可以得出队列是满的结论。

队满条件:front == rear && s == 1

队空条件:front == rear && s == 0

三.链式队列(带头节点)

链式队列是链表的一种使用方式。链式队列是说队列元素以链表的形式进行存储,并且规定只能从链表的尾部添加元素,从链表的头部移除元素。

下面对链式队列的节点和链式队列进行定义,LinkNode强调的是一个节点,LinkQueue强调的是一个队列。

typedef struct LinkNode//链式队列节点
{
    
    
	int data;//数据域
	struct LinkNode *next;//指针域
}LinkNode;

typedef struct//链式队列
{
    
    
	LinkNode *front, *rear;//队列的队头指针和队尾指针
}LinkQueue;

基本操作

链式队列的基本操作有:初始化,销毁,入队,出队,判空,读取队头元素,输出队列所有元素。

1.初始化

开辟一个新的节点空间,让队头指针和队尾指针指向这个节点空间,接着让这个节点指向空指针,这个节点就是头节点。

void InitQueue(LinkQueue &Q)//初始化
{
    
    
	Q.front = Q.rear = (LinkNode*)malloc(sizeof(LinkNode));
	Q.front->next = NULL;//让节点指向空指针
}

2.销毁

因为是用malloc函数申请的空间,所以要用free函数和循环函数一个个把节点空间释放。

bool DeleQueue(LinkQueue &Q)//销毁队列
{
    
    
	char a;
	printf("是否销毁队列(Y/N):");
	getchar();
	scanf("%c", &a);
	if (a == 'N')
	{
    
    
		printf("队列销毁失败\n");
		return false;
	}
	LinkNode *p, *q;//新建两个指针
	p = Q.front;//指针p指向头节点
	while (p != NULL)//当头结点的指针域不为0,即不是链尾时
	{
    
    
		q = p->next;//让q指向头结点的后续结点
		free(p);//把p指针指向的节点空间释放了,但是p指针还存在。
		p = q;//让p和q都指向后续结点,此时p和q指针指向的都是下一个节点,接下来重复这一段的操作就好了。
	}
	Q.rear = Q.front;//让队头指针和队尾指针都指向头节点
	printf("队列销毁成功\n");
	return true;
}

3.入队

在队列的尾部进行入队。

bool EnQueue(LinkQueue &Q)//入队
{
    
    
	char a;
	printf("是否进行入队操作(Y/N):");
	getchar();
	scanf("%c", &a);
	if (a == 'N')
	{
    
    
		printf("入队失败\n");
		return false;
	}
	int b;
	printf("请输入要入队的元素个数:");
	scanf("%d", &b);
	for (int i = 0; i < b;i++)//用循环进行多次入队操作
	{
    
    
		int x;
		printf("请输入要入队的元素(一次输入一个数字):");
		scanf("%d", &x);
		LinkNode *s = (LinkNode*)malloc(sizeof(LinkNode));//用malloc函数申请空间给新的节点s
		if (s == NULL)
		{
    
    
			printf("入队失败\n");
			return false;
		}
		s->data = x;//把输入的数放进新节点
		s->next = NULL;//让新节点指向空指针
		Q.rear->next = s;//新节点插入到rear后
		Q.rear = s;//修改表尾指针
		printf("入队成功,入队的元素是:%d\n", x);
	}
	return true;
}

4.出队

在队列的队头进行出队

bool DeQueue(LinkQueue &Q)//出队
{
    
    
	char a;
	printf("是否进行出队操作(Y/N):");
	getchar();
	scanf("%c", &a);
	if (a == 'N')
	{
    
    
		printf("出队失败\n");
		return false;
	}
	if (Q.rear == Q.front)//判断是否空队
		return false;
	LinkNode *p = Q.front->next;//新建一个指针指向出队元素所在的那个节点
	int x = p->data;//把队首元素的值放入x中
	printf("出队的元素是:%d\n", x);
	Q.front->next = p->next;//修改头节点的next指针
	if (Q.rear == p)//此次是最后一个节点出队
		Q.rear = Q.front;//修改rear指针
	free(p);//释放指针p指向的元素所在的节点空间
	return true;
}

5.判空

判断队头指针和队尾指针是不是指向同一个位置

bool EmptQueue(LinkQueue &Q)//判空
{
    
    
	if (Q.rear == Q.front)
	{
    
    
		printf("空队列\n");
		return true;
	}
	else
		printf("不是空队列\n");
	return false;
}

6.读取队头元素

bool FrontQueue(LinkQueue &Q)//取队头元素
{
    
    
	if (Q.rear == Q.front)
	{
    
    
		printf("空队,无队头元素\n");
		return false;
	}
	printf("队头元素是:%d\n", Q.front->next->data);//打印队头指针指向的下一个节点的元素值
	return true;
}

7.输出队列所有元素

bool Print(LinkQueue &Q)//输出队列元素
{
    
    
	if (Q.front == Q.rear)//判空
	{
    
    
		printf("队列为空\n");
		return false;
	}
	LinkNode *p = Q.front->next;//新建一个指针指向队首元素所在的节点
	printf("队列的元素是:");
	while (1)
	{
    
    
		printf("%d ", p->data);
		if (p == Q.rear)//判断p是否指向队尾节点
		{
    
    
			printf("\n");
			return true;
		}
		p = p->next;//节点p指向下一个节点
	}
}

全部代码及实现结果

#include<stdio.h>
#include<stdlib.h>

typedef struct LinkNode//链式队列节点
{
    
    
	int 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 EnQueue(LinkQueue &Q)//入队
{
    
    
	char a;
	printf("是否进行入队操作(Y/N):");
	getchar();
	scanf("%c", &a);
	if (a == 'N')
	{
    
    
		printf("入队失败\n");
		return false;
	}
	int b;
	printf("请输入要入队的元素个数:");
	scanf("%d", &b);
	for (int i = 0; i < b;i++)//用循环进行多次入队操作
	{
    
    
		int x;
		printf("请输入要入队的元素(一次输入一个数字):");
		scanf("%d", &x);
		LinkNode *s = (LinkNode*)malloc(sizeof(LinkNode));//用malloc函数申请空间给新的节点s
		if (s == NULL)
		{
    
    
			printf("入队失败\n");
			return false;
		}
		s->data = x;//把输入的数放进新节点
		s->next = NULL;//让新节点指向空指针
		Q.rear->next = s;//新节点插入到rear后
		Q.rear = s;//修改表尾指针
		printf("入队成功,入队的元素是:%d\n", x);
	}
	return true;
}

bool DeQueue(LinkQueue &Q)//出队
{
    
    
	char a;
	printf("是否进行出队操作(Y/N):");
	getchar();
	scanf("%c", &a);
	if (a == 'N')
	{
    
    
		printf("出队失败\n");
		return false;
	}
	if (Q.rear == Q.front)//判断是否空队
		return false;
	LinkNode *p = Q.front->next;//新建一个指针指向出队元素所在的那个节点
	int x = p->data;//把队首元素的值放入x中
	printf("出队的元素是:%d\n", x);
	Q.front->next = p->next;//修改头节点的next指针
	if (Q.rear == p)//此次是最后一个节点出队
		Q.rear = Q.front;//修改rear指针
	free(p);//释放指针p指向的元素所在的节点空间
	return true;
}

bool FrontQueue(LinkQueue &Q)//取队首元素
{
    
    
	if (Q.rear == Q.front)
	{
    
    
		printf("空队,无队头元素\n");
		return false;
	}
	printf("队头元素是:%d\n", Q.front->next->data);//打印队头指针指向的下一个节点的元素值
	return true;
}

bool EmptQueue(LinkQueue &Q)//判空
{
    
    
	if (Q.rear == Q.front)
	{
    
    
		printf("空队列\n");
		return true;
	}
	else
		printf("不是空队列\n");
	return false;
}

bool Print(LinkQueue &Q)//输出队列元素
{
    
    
	if (Q.front == Q.rear)//判空
	{
    
    
		printf("队列为空\n");
		return false;
	}
	LinkNode *p = Q.front->next;//新建一个指针指向队首元素所在的节点
	printf("队列的元素是:");
	while (1)
	{
    
    
		printf("%d ", p->data);
		if (p == Q.rear)//判断p是否指向队尾节点
		{
    
    
			printf("\n");
			return true;
		}
		p = p->next;//节点p指向下一个节点
	}
}

bool DeleQueue(LinkQueue &Q)//销毁队列
{
    
    
	char a;
	printf("是否销毁队列(Y/N):");
	getchar();
	scanf("%c", &a);
	if (a == 'N')
	{
    
    
		printf("队列销毁失败\n");
		return false;
	}
	LinkNode *p, *q;//新建两个指针
	p = Q.front;//指针p指向头节点
	while (p != NULL)//当头结点的指针域不为0,即不是链尾时
	{
    
    
		q = p->next;//让q指向头结点的后续结点
		free(p);//把p指针指向的节点空间释放了,但是p指针还存在。
		p = q;//让p和q都指向后续结点,此时p和q指针指向的都是下一个节点,接下来重复这一段的操作就好了。
	}
	Q.rear = Q.front;//让队头指针和队尾指针都指向头节点
	printf("队列销毁成功\n");
	return true;
}

int main()
{
    
    
	LinkQueue Q;
	InitQueue(Q);//初始化
	EnQueue(Q);//入队
	FrontQueue(Q);//取队头元素
	Print(Q);//输出队列所有元素
	DeQueue(Q);//出队
	Print(Q);//输出队列所有元素
	EmptQueue(Q);//判空
	DeleQueue(Q);//销毁队列
	Print(Q);//输出队列所有元素
	return 0;
}

在这里插入图片描述
在这里插入图片描述

四.链式队列(不带头节点)

带头节点的链式队列和不带头节点的链式队列差别不大,在链式队列的节点和链式队列进行定义这部分是一样的。

typedef struct LinkNode//链式队列节点
{
    
    
	int data;//数据域
	struct LinkNode *next;//指针域
}LinkNode;

typedef struct//链式队列
{
    
    
	LinkNode *front, *rear;//队列的队头指针和队尾指针
}LinkQueue;

1初始化

因为没有头节点,所以队头指针和队尾指针一开始都是指向空指针的。

void InitQueue(LinkQueue &Q)//初始化
{
    
    
	Q.front = Q.rear = NULL;
}

2.销毁

bool DeleQueue(LinkQueue &Q)//销毁队列
{
    
    
	char a;
	printf("是否销毁队列(Y/N):");
	getchar();
	scanf("%c", &a);
	if (a == 'N')
	{
    
    
		printf("销毁失败\n");
		return false;
	}
	if (Q.front == NULL)
	{
    
    
		printf("空队列,销毁失败\n");
		return false;
	}
	if (Q.front == Q.rear)//判断队列里是否只有一个节点
	{
    
    
		LinkNode *p = Q.front;//创建新指针指向那唯一的节点
		Q.front = NULL;
		Q.rear = NULL;
		free(p);
		printf("队列销毁成功\n");
		return true;
	}
	LinkNode *p, *q;
	p = Q.front;//让p指针指向第一个节点
	q = Q.front->next;//让q指针指向第二个节点
	while (q != NULL)//判断q指针指向的是不是最后一个节点
	{
    
    
		free(p);
		p = q;//p指针指向下一个节点
		q = q->next;//q指针也指向下一个节点
	}
	Q.front = Q.rear = NULL;//让队头指针和队尾指针指向空指针
	printf("队列销毁成功\n");
	return true;
}

3.入队

由于没有头节点,所以在进行第一个元素入队的时候要额外进行处理。让队头指针和队尾指针都指向第一个节点,这个节点就类型头节点。只不过是带数据元素的头节点。

bool EnQueue(LinkQueue &Q)//入队
{
    
    
	char a;
	printf("是否进行入队操作(Y/N):");
	getchar();
	scanf("%c", &a);
	if (a == 'N')
	{
    
    
		printf("入队失败\n");
		return false;
	}
	int j;
	printf("请输入要入队的元素个数:");
	scanf("%d", &j);
	for (int i = 0; i < j;i++)
	{
    
    
		int x;
		LinkNode *s = (LinkNode*)malloc(sizeof(LinkNode));//用malloc函数申请空间给新的节点s
		printf("请输入要入队的元素(一次输入一个):");
		scanf("%d", &x);
		s->data = x;//把元素放入节点s
		s->next = NULL;//让节点s指向空指针
		if (Q.front == NULL)//判空,如果为空队列的话,那么第一个元素入队要额外处理
		{
    
    
			Q.front = s;
			Q.rear = s;
		}
		else//不为空队列的话只需要对队尾指针进行操作
		{
    
    
			Q.rear->next = s;//把当前队尾指针指向的节点的指针域改为指向下一个节点
			Q.rear = s;//队尾指针指向s节点
		}
		printf("入队成功,入队的元素是:%d\n", x);
	}
	return true;
}

4.出队

bool DeQueue(LinkQueue &Q)//出队
{
    
    
	char a;
	printf("是否进行出队操作(Y/N):");
	getchar();
	scanf("%c", &a);
	if (a == 'N')
	{
    
    
		printf("出队失败\n");
		return false;
	}
	if (Q.front == NULL)
	{
    
    
		printf("出队失败\n");
		return false;
	}
	LinkNode *p = Q.front;
	int x = p->data;
	Q.front = p->next;
	if (Q.rear == p)
	{
    
    
		Q.front = NULL;
		Q.rear = NULL;
	}
	free(p);
	printf("出队成功,出队元素是:%d\n",x);
	return true;
}

5.判空

由于队列为空的时候,队尾指针和队头指针都是指向空指针的,所以判断队列是否为空就有两种方法,可以判断队头指针或者队尾指针是否指向空指针。两种方法的实现代码如下:

void IsEmpty1(LinkQueue &Q)//判空(队头指针)
{
    
    
	if (Q.front == NULL)
		printf("空队列\n");
	else
		printf("不是空队列\n");
}
void IsEmpty2(LinkQueue &Q)//判空(队尾指针)
{
    
    
	if (Q.rear == NULL)
		printf("空队列\n");
	else
		printf("不是空队列\n");
}

6.读取队头元素

因为队头指针指向的就是队头元素,所以直接输出 Q.front->data 就可以了。

bool FrontQueue(LinkQueue &Q)//读取队头元素
{
    
    
	if (Q.front == NULL)
	{
    
    
		printf("空队,无队头元素\n");
		return false;
	}
	printf("队头元素是:%d\n", Q.front->data);
	return true;
}

7.输出队列所有元素

进行遍历输出即可

bool Print(LinkQueue &Q)//输出队列元素
{
    
    
	if (Q.front == NULL)
	{
    
    
		printf("空队列\n");
		return false;
	}
	LinkNode *s = Q.front;//链式队列的队头指针指向的是队头元素,所以这里让指针s指向队头指针指向的节点
	printf("队列的元素是:");
	while (1)
	{
    
    
		printf("%d ", s->data);
		if (s == Q.rear)
		{
    
    
			printf("\n");
			return true;
		}
		s = s->next;
	}
}

全部代码及实现结果

#include<stdio.h>
#include<stdlib.h>

typedef struct LinkNode//链式队列节点
{
    
    
	int data;//数据域
	struct LinkNode *next;//指针域
}LinkNode;

typedef struct//链式队列
{
    
    
	LinkNode *front, *rear;//队列的队头指针和队尾指针
}LinkQueue;

void InitQueue(LinkQueue &Q)//初始化
{
    
    
	Q.front = Q.rear = NULL;
}

void IsEmpty1(LinkQueue &Q)//判空(队头指针)
{
    
    
	if (Q.front == NULL)
		printf("空队列\n");
	else
		printf("不是空队列\n");
}

void IsEmpty2(LinkQueue &Q)//判空(队尾指针)
{
    
    
	if (Q.rear == NULL)
		printf("空队列\n");
	else
		printf("不是空队列\n");
}

bool EnQueue(LinkQueue &Q)//入队
{
    
    
	char a;
	printf("是否进行入队操作(Y/N):");
	getchar();
	scanf("%c", &a);
	if (a == 'N')
	{
    
    
		printf("入队失败\n");
		return false;
	}
	int j;
	printf("请输入要入队的元素个数:");
	scanf("%d", &j);
	for (int i = 0; i < j;i++)
	{
    
    
		int x;
		LinkNode *s = (LinkNode*)malloc(sizeof(LinkNode));//用malloc函数申请空间给新的节点s
		printf("请输入要入队的元素(一次输入一个):");
		scanf("%d", &x);
		s->data = x;//把元素放入节点s
		s->next = NULL;//让节点s指向空指针
		if (Q.front == NULL)//判空,如果为空队列的话,那么第一个元素入队要额外处理
		{
    
    
			Q.front = s;
			Q.rear = s;
		}
		else//不为空队列的话只需要对队尾指针进行操作
		{
    
    
			Q.rear->next = s;//把当前队尾指针指向的节点的指针域改为指向下一个节点
			Q.rear = s;//队尾指针指向s节点
		}
		printf("入队成功,入队的元素是:%d\n", x);
	}
	return true;
}

bool Print(LinkQueue &Q)//输出队列元素
{
    
    
	if (Q.front == NULL)
	{
    
    
		printf("空队列\n");
		return false;
	}
	LinkNode *s = Q.front;//链式队列的队头指针指向的是队头元素,所以这里让指针s指向队头指针指向的节点
	printf("队列的元素是:");
	while (1)
	{
    
    
		printf("%d ", s->data);
		if (s == Q.rear)
		{
    
    
			printf("\n");
			return true;
		}
		s = s->next;
	}
}

bool DeQueue(LinkQueue &Q)//出队
{
    
    
	char a;
	printf("是否进行出队操作(Y/N):");
	getchar();
	scanf("%c", &a);
	if (a == 'N')
	{
    
    
		printf("出队失败\n");
		return false;
	}
	if (Q.front == NULL)
	{
    
    
		printf("出队失败\n");
		return false;
	}
	LinkNode *p = Q.front;
	int x = p->data;
	Q.front = p->next;
	if (Q.rear == p)
	{
    
    
		Q.front = NULL;
		Q.rear = NULL;
	}
	free(p);
	printf("出队成功,出队元素是:%d\n",x);
	return true;
}

bool FrontQueue(LinkQueue &Q)//读取队头元素
{
    
    
	if (Q.front == NULL)
	{
    
    
		printf("空队,无队头元素\n");
		return false;
	}
	printf("队头元素是:%d\n", Q.front->data);
	return true;
}

bool DeleQueue(LinkQueue &Q)//销毁队列
{
    
    
	char a;
	printf("是否销毁队列(Y/N):");
	getchar();
	scanf("%c", &a);
	if (a == 'N')
	{
    
    
		printf("销毁失败\n");
		return false;
	}
	if (Q.front == NULL)
	{
    
    
		printf("空队列,销毁失败\n");
		return false;
	}
	if (Q.front == Q.rear)//判断队列里是否只有一个节点
	{
    
    
		LinkNode *p = Q.front;//创建新指针指向那唯一的节点
		Q.front = NULL;
		Q.rear = NULL;
		free(p);
		printf("队列销毁成功\n");
		return true;
	}
	LinkNode *p, *q;
	p = Q.front;//让p指针指向第一个节点
	q = Q.front->next;//让q指针指向第二个节点
	while (q != NULL)//判断q指针指向的是不是最后一个节点
	{
    
    
		free(p);
		p = q;//p指针指向下一个节点
		q = q->next;//q指针也指向下一个节点
	}
	Q.front = Q.rear = NULL;//让队头指针和队尾指针指向空指针
	printf("队列销毁成功\n");
	return true;
}

int main()
{
    
    
	LinkQueue Q;
	InitQueue(Q);//初始化
	EnQueue(Q);//入队
	Print(Q);//输出队列元素
	FrontQueue(Q);//输出队头元素
	DeQueue(Q);//出队
	Print(Q);//输出队列元素
	IsEmpty1(Q);//判空(队头指针)
	IsEmpty2(Q);//判空(队尾指针)
	DeleQueue(Q);//销毁队列
	Print(Q);//输出队列元素
}

在这里插入图片描述
在这里插入图片描述

如果有什么不懂的,文中有错误的可以私信联系我,非常感谢。

觉得这篇博客对你有帮助的话可以点个赞呀。

猜你喜欢

转载自blog.csdn.net/m0_56992713/article/details/121018750