数据结构与算法学习笔记(C语言)
1.定义:队列是一种先进先出(FIFO)的线性表,它只允许从一端插入,而在另一端删除删除元素。
这和我们日常生活中的排队是一样的,排在第一个的人先办事,后来的人排在队尾。
在队列中,允许插入的一段叫队尾(rear),允许删除的一端叫队头(front)。
插入元素到队尾叫入队,删除队头的元素叫出队。
队列的抽象数据类型定义:
ADT Queue {
数据对象:D = {a1, a2, a3...}
数据关系:R = {<a1, a2>,<a2, a3>...}
基本操作:
InitQueue(*Q) /*初始化队列*/
DestroyQueue(*Q) /*销毁队列*/
ClearQueue(*Q) /*清空队列*/
QueueEmpty(Q) /*若队列为空,返回true,否则返回false*/
QueueLength(Q) /*返回队列元素的个数*/
GetHead(Q, *e) /*获取队头元素*/
EnQueue(*Q, e) /*入队,元素插入队尾*/
DeQueue(*Q, *e) /*出队,删除队头元素*/
QueueTraverse(Q) /*遍历队列*/
}ADT Queue
2.队列的链式存储
一个链队列需要两个分别指示队头和队尾的指针唯一确定,为了操作方便,我们也给链队列添加一个头结点
如果没有头节点,那么初始化之后,入队第一个元素的时候,要把头指针指向新添加的节点,出队最后一个元素的时候,又和出队其他节点的操作不一样
链队列的C语言描述
typedef struct QNode {
QElemType data;
struct QNode *next;
}QNode, *QueuePtr;
typedef struct {
QueuePtr front; /*队头指针*/
QueuePtr rear; /*队尾指针*/
}LinkQueue;
基本操作的函数原型说明
Status InitQueue(LinkQueue *Q) /*构建一个空队列*/
Status DestroyQueue(LinkQueue *Q) /*销毁队列*/
Status ClearQueue(LinkQueue *Q) /*清空队列*/
bool QueueEmpty(LinkQueue Q) /*返回链队列是否为空*/
int QueueLength(LinkQueue Q) /*返回元素的个数*/
Status GetHead(LinkQueue Q, QElemType *e) /*获取队头元素*/
Status EnQueue(LinkQueue *Q, QElemType e) /*插入元素e为队尾元素*/
Status DeQueue(LinkQueue *Q, QElemType *e) /*队头元素出队*/
void QueueTraverse(LinkQueue Q) /*遍历队列元素并打印*/
1.队列初始化
Status InitQueue(LinkQueue *Q)
{
Q->front = (QueuePtr)malloc(sizeof(QNode));
if (!Q->front) exit(OVERFLOW);
Q->rear = Q->front;
Q->front->next = NULL;
return OK;
}
2.销毁队列
Status DestroyQueue(LinkQueue *Q)
{
while(Q->front) {
/*俩指针一前一后,逐节点释放空间*/
Q->rear = Q->front->next;
free(Q->front);
Q->front = Q->rear;
}
return OK;
}
3.清空队列
Status ClearQueue(LinkQueue *Q)
{
QueuePtr p;
if (Q->front == Q->rear) return OK;
/*已经是空队列*/
p = Q->front;
/*保存头节点的位置*/
Q->rear = p->next;
/*从头节点开始释放队列节点的元素,最后只剩头节点*/
while (Q->rear) {
Q->front = Q->rear->next;
free(Q->rear)
Q->rear = Q->front;
}
Q->front = p;
/*头指针指回头节点*/
Q->front->next = NULL;
}
4.判断队列是否为空
bool QueueEmpty(LinkQueue Q)
{
if (Q.front == Q.rear) return true;
else return false;
}
5.求队列长度
int QueueLength(LinkQueue Q)
{
int i = 0;
/*不用担心改变队尾指针的指向,值传递是实参的拷贝,不会改变传入的队列*/
Q.rear = Q.front->next;
while(Q.rear) {
++i;
Q.rear = Q.rear->next;
}
return 0;
}
6.取队头元素
Status GetHead(LinkQueue Q, QElemType *e)
{
if (Q.front == Q.rear) return ERROR;
*e = Q.front->next->data;
return OK;
}
7.入队
Status EnQueue(LinkQueue *Q, QElemType e)
{
QueuePtr p;
p = (QueuePtr)malloc(sizeof(QNode));
if (!p) exit(OVERFLOW);
p->data = e;
p->next = NULL;
Q->rear->next = p;
Q->rear = p;
return OK;
}
8.出队
Status DeQueue(LinkQueue *Q, QElemType *e)
{
QueuePtr p;
if (Q->rear == Q->front) return ERROR;
p = Q->front->next;
*e = p->data;
Q->front->next = p->next;
/*当队列中最后一个元素被删除时,让队尾指针指向头节点*/
if (p == Q->rear) Q->rear = Q->front;
free(p);
return OK;
}
9.打印队列中的元素
void QueueTraverse(LinkQueue Q)
{
Q.rear = Q.front->next;
while (Q.rear) {
printf("%c ", Q.rear->data);
}
printf("\n");
}
队列的顺序存储
1.定义:用一组地址连续的存储单元依次存放从队列到队尾的元素,附设两个整形变量作为队头和队尾的标记,记录队头、队尾的下标
队列为空时,Q.rear = Q.front = 0;队列满时,Q.rear = MAXSIZE;
但是,这里明显有一个大问题,因为元素出队,Q.front++,最后Q.front前面的存储单元都用不了了,造成队列“假空”,为了解决这个问题,我们定义循环队列
将申请的连续空间假想成环状的,0存储单元和最后一个存储单元首尾相接
注意:指针malloc申请的空间一定是地址连续的存储单元,想象成环状是我们在逻辑上这么想,与之相反的就像地球是圆的,我们可以认为汽车是在平地上行驶(不太恰当,但就这样吧)
从图中,可以看出,当循环队列为空时,Q.front = Q.rear = 0;
当循环队列满的时候,在环状空间逻辑上,Q.front 在Q.rear的下一个存储单元,
Q.front = (Q.rear + 1) % MAXSIZE;
循环队列的C语言描述
#define MAXSIZE 100
/*循环队列的最大存储空间,由循环队列的特征,可以知道分配的存储空间不可扩大*/
typedef struct {
QElemType *base;
int front;
int rear;
}SqQueue;
循环队列各种操作的实现
1.初始化队列
Status InitQueue(SqQueue *Q)
{
Q->base = (QElemType *)malloc(MAXSIZE * sizeof(QElemType));
if (!Q->base) exit(OVERFLOE);
Q->front = Q->rear = 0;
return OK;
}
2.销毁队列
Status DestroyQueue(SqQueue *Q)
{
Q->front = Q->rear = 0;
free(Q->base);
Q->base = NULL;
return OK;
}
3.清空队列
Status ClearQueue(SqQueue *Q)
{
Q->front = Q->rear = 0;
return OK;
}
4.判断队列是否为空
bool QueueEmpty(SqQueue Q)
{
if (Q.front == Q.rear) return true;
else return false;
}
5.返回队列元素的个数
int QueueLength(SqQueue Q)
{
/*这个式子不难理解,就是就算元素从屁股后面转到前面了,求余就能消除循环的影响而得到真实长度,
想像钟表指针之间的时差,相减对12取余就是了*/
return (Q.rear - Q.front + MAXSIZE) % MAXSIZE;
}
6.取队头元素
Status GetHead(SqQueue Q, QElemType *e)
{
if (Q.front == Q.rear) return ERROR;
*e = Q.base[Q.front];
return OK;
}
7.元素入队
Status EnQueue(SqQueue *Q, QElemType e)
{
if (Q->front == (Q->rear + 1) % MAXSIZE) return ERROR;
Q->base[Q->rear] = e;
Q->rear = (Q->rear + 1) % MAXSIZE;
/*元素没转过来的时候忽略取余,元素转到前面来后,取余得到就是从0开始的下标*/
return OK;
}
8.元素出队
Status DeQueue(SqQueue *Q, QElemType *e)
{
if (Q->front == Q->rear) return ERROR;
*e = Q->base[Q->front];
Q->front = (Q->front + 1) % MAXSIZE;
/*元素没转过来的时候忽略取余,元素转到前面来后,取余得到就是从0开始的下标*/
return OK;
}
9.遍历队列元素
void QueueTraverse(SqQueue Q)
{
if (Q.rear == Q.front) return;
/*循环队列为空,直接返回*/
else if (Q.rear > Q.front) {
for (i = Q.front; i < Q.rear; i++)
printf("%c ", Q.base[i]);
}
else {
/*如果队尾的下标小于队头的,表示元素从尾部转过来了,有两部分元素需要打印*/
for (i = Q.front; i < MAXSIZE; i++)
printf("%c ", Q.base[i]);
for (i = 0; i < Q.rear; i++)
printf("%c ", Q.base[i]);
}
}
队列这部分比较难以理解的可能就是循环队列这一部分了,结合钟表的表盘,理解一下取余对于求真实差值的意义,那么循环队列也不是很难理解,加油!