C语言中的队列(顺序队列与链式队列)——概念及其基本操作

前言

太久没更新了,早就说要写结果拖到现在。闲话说完,正式开始内容。
队列——顾名思义,其实在线性表中也是一种极其重要的结构。
与栈不同的是,栈对元素的插入和删除均在一端进行,而队列,从队头删除,队尾插入,正如日常生活中的排队一样,还是比较形象的。
本文将对队列的概念及操作函数进行简要介绍,并说明顺序队列(利用数组)和链式队列(利用链表)的一些基本算法。

一、队列的相关概念

1.队列及其操作 (先进先出)

1.1 相关概念

(1)队列:只允许在表的一端删除元素,在另一端插入元素的线性表
(2)空队列:不含元素的队列
(3)队首:队列中只允许删除元素的一端,又称head,front
(4)队尾:队列中只允许插入元素的一端,又称rear,tail
(5)进队(入队):将新元素插入到队尾
(6)出队:从队列删除一个元素,即删除队首元素
注:进出队可类比为排队

1.2 队列的基本操作
(1)InitQueue(q):初始化,将q置为空队列
(2)QueueEmpty(q):判断q是否为空队列
(3)EnQueue(q,e):将e插入队列q的尾端
(4)DeQueue(q,e):取走队列q的首元素,赋值给e
(5)GetHead(q,e);读取队列首元素,赋值给e
(6)QueueClear(q):置q为空队列

二、链式队列的基本操作

1.链式队列:运用链式结构进行存储的队列,一般用带表头结点的单链表表示

为提高插入删除效率,在头指针中设置两个指针
一般形式:
(1)Q.front:队首指针,指向表头结点
(2)Q.rear:队尾指针,指向队尾结点
注:当队列为空时,(1)(2)均执行表头结点
(3)Q.front->data:不放元素
(4)Q.fornt->next:指向队首结点a1

2.定义结点类型

(1)存放元素的结点类型:数据域和指针域

//定义结点类型
typedef struct Qnode
{
     
	ElemType data;//其中数据域data为抽象元素类型
	struct Qnode *next;//其中next为指针类型 
}Qnode,*QueuePtr;//其中,Qnode为结点类型, QueuePtr:指向Qnode的指针类型

(2)由头、尾指针组成的结点类型

//由头、尾指针组成的结点类型
typedef struct
{
     
	Qnode *front;//头指针
	Qnode *rear;//尾指针 
}LinkQueue;//链式队列类型 

(3)生成空队列算法:初始化队列

//生成空队列算法:初始化队列	
#define LENG sizeof(Qnode)//求结点所占的单元数
LinkQueue InitQueue()//生成仅带表头结点的空队列Q
{
     
	LinkQueue Q;//说明变量Q
	Q.front = Q.rear = (QueuePtr)malloc(LENG);//生成表头结点
	Q.front->next = NULL;//表头结点的next为空指针
	return Q;//返回Q的值 
} 

(4)(非)空队列时插入新元素x

//(非)空队列时插入新元素x	
LinkQueue Enqueue(LinkQueue Q,ElemType e)
{
     
	Qnode *p;//说明变量p
	p = (Qnode*)malloc(LENG);//生成新元素结点
	p->data = e;//装入元素e
	p->next = NULL;//为队尾结点
	Q.rear->next = p;//插入新结点
	Q.rear = p;//修改尾指针
	return Q;//返回Q的新值 
} 

3.元素的删除(变化表头结点即可)

注:若原队列只有一个结点,则还需考虑尾指针
链式队列的出队算法

//链式队列的出队算法	
LinkQueue DelQueue(LinkQueue Q,ElemType *e)
{
     
	Qnode *p;//声明变量p
	if (Q.front==Q.rear)//若原队列为空
	{
     
		printf ("Empty queue");//空队列
		return Q; 
	}
	p = Q.front->next;//p指向要删除的队头结点
	(*e) = p->data;//取出元素
	Q.front->next = p->next;//删除队头结点
	if (Q.rear==p)//若原队列只有1个结点
	{
     
		Q.rear = Q.front;//修改尾指针 
	}
	free(p);//释放被删除的空间
	return Q;//返回出队后的Q 
}

三、顺序队列的基本操作

1.顺序队列与“假溢出”

假设用一维数组Q[0…5]表示顺序队列
设f指向队头元素,r指向队尾元素后一空单元
(1)初始化后:空队列r=f
(2)元素ABC进队后:f指向A,r指向C后一单元(即Q[3])
(3)ABC出队后:f后移至r,此时空队列f=r
(4)DEF进队后,r后移至F后(Q[5])后
(5)若此时G要进入,会判定为假溢出
但此时Q[0],Q[1]等还是空的

2.解决假溢出的方法一:移动元素

(6)DEF移到前端后,占据Q[0],Q[1],Q[2],此时fr也移动
(7)G进队之后,r指向Q[3]
此方法缺点:比较费时,移动元素开销大

3.方法二:将Q当循环表使用(循环队列)

即在(4)时,r会重新指向Q[0]
在(5)中,G要进队时便会放在Q[0],r再后移指向Q[1]
(6)此时HI进队之后,则队列已满,f=r
便产生了问题,f=r时,是空队列还是满队列(二义性)

(1)二义性解决方案

方案一 :增加一个标识变量
方案二:预留最后一个单元不使用,即:进队前测试
若r+1==f,表明还剩最后一个单元,认为此时是满队列
即:若队列为Q[0…maxleng-1],共有maxleng-1个元素

(2)循环队列的满队列条件
当r+1f或(f0)&&(rmaxleng-1),即:(r+1)%maxlengf为满队列
空队列条件仍为r==f

4.顺序队列算法举例

(1)定义队列的C类型

//顺序队列算法举例:定义队列的C类型
#define MAXLENG 100
typedef struct
{
     
	ElemType elem[MAXLENG];
	int front,rear;//分别指向队首结点和队尾结点后的下一个单元 
}SeQueue;
SeQueue Q;//定义结构变量Q表示队列 

(2)进队算法En_Queue

//假设用Q表示顺序队列,e为进队元素
int En_Queue (SeQueue &Q,ElenType e)
{
	if ((Q.rear+1)%MAXLENG==Q.front)//若Q已满,退出
		return ERROR;
	Q.elem[Q.rear] = e;//装入新元素e
	Q.rear++;//尾指针后移一个位置
	Q.rear = Q.rear%MAXLENG;//为循环队列
	return OK; 
} 
在这里插入代码片

(3)出队算法De_Queue

//出队算法De_Queue
int De_Queue
{
     
	if (Q.front==Q.rear)//Q为空队列,退出
		return ERROR;
	e = Q.elem[Q.front];//取走队头元素,赋值给e
	Q.front = (Q.front+1)%MAXLENG;//循环后移到一个位置
	return OK; 
 } 
 

总结

总的来说,队列的判空和判满比栈要复杂的多,可能出现假溢出现象。又因其在队头删除,要通过移动元素解决问题,比较花时间。因此,链式队列就具有其优越性。对计算机的要求也比数组低,不要求有连续空间。当存放数据较多时,可以选用链式队列。
ps:代码非原创。
如有错误,欢迎指正。
下一篇将介绍一些特殊矩阵的操作。

你可能感兴趣的:(数据结构,队列,算法,数据结构,c语言,指针)