队列 和 栈 是 一种 受限制的 线性表。所以 他们的 实现方式 都 相差 无几。之前有过 链栈 和 链式线性表 的 实现经验,自然 写 链队 ,也毫无问题。
下面详细讲解每一段代码 的技术要点
下面是队列节点的数据结构
struct QueueNode { ElementType data; QueueNode * next; }; //生成一个节点 QueueNode * queueNodeMake(ElementType data){ QueueNode * pNode = (QueueNode *)malloc(sizeof(QueueNode)); if (pNode != NULL) { pNode->data = data; pNode->next = NULL; } return pNode; }
无论是 线性表,还是栈,队列的 节点 结构 都是 一个 数据元素 和 一个 后继指针的 经典结构。(可以加一个前驱,形成双向XXX)。
然后是 根据 数据元素 生成 一个 节点的 函数,这个 函数 需要 注意 :1.内存分配失败的问题 2.将节点 后继设置为NULL,减少出错的可能性。
下面是 队列 的 数据结构:
struct LinkQueue { QueueNode * front;//头指针 QueueNode * rear;//队尾 int len; };
front 是 头指针,其后继节点 是 队头节点,
rear 指向 队尾节点。
len :队列长度;有 len 这个部分,是一个非常 优秀的 实现方案,简化了许多操作 ,使逻辑清晰。例如:1.在判断队列是否为空时 2. 判断队列长度时 3.在出队时,判断队列是否为空时。
但是 要 时刻 谨记 len 长度的变化问题 :1.出队时 减1 2.入队时 + 1 3.清空队列时, 清0
下面是 初始化 队列函数
E_State queueInit(LinkQueue * queue){ QueueNode * node = (QueueNode *) malloc(sizeof(QueueNode)); if (node == NULL) { return E_State_Error; } else { //初始头节点为NULL 很重要 node->next = NULL; queue->front = queue->rear = node; queue->len = 0; return E_State_Ok; } }
这个函数 比较简单,只是 要注意 初始化 队列的 属性
queue->front = queue->rear = node; queue->len = 0;
以及 头节点的 后继 设置 为NULL.
//初始头节点为NULL 很重要 node->next = NULL;
下面是 清空 队列函数:
void queueClear(LinkQueue * queue){ QueueNode * next = queue->front->next; while (next != NULL) { //顺序很重要 QueueNode * freeNode = next; next = next->next; free(freeNode); } //重置成空队列的状态 queue->rear = queue->front; queue->len = 0; }
写这个函数的时候 注意 两部分:
1.释放节点的顺序问题,操作失误很可能导致 内存问题
//顺序很重要 QueueNode * freeNode = next; next = next->next; free(freeNode);
2.漏写 重新 空队列时的 状态
queue->rear = queue->front; queue->len = 0;
下面是销毁队列函数
void queueDestory(LinkQueue * queue){ queueClear(queue); free(queue->front); queue->front = queue->rear = NULL; }
1.清空队列 2. 释放头节点 3. 设置 指针为NULL
下面是 判空 和 求 长度函数
bool queueEmpty(LinkQueue queue){ return queue.len == 0 ? true : false; } int queueLen(LinkQueue queue){ return queue.len; }
证明 len 属性的 好处
入队 和 出 队 是 队列最难写的两个函数。首先 给出 入队的函数
//入队 E_State enqueue(LinkQueue * queue,ElementType data){ QueueNode * newNode = queueNodeMake(data);//设置新节点的值,并将节点后继设置成 NULL if (newNode != NULL) { //将队尾的后继设置成 新节点 queue->rear->next = newNode; //设置队尾节点 queue->rear = newNode; queue->len++; } return newNode != NULL ? E_State_Ok : E_State_Error; }
QueueNode * newNode = queueNodeMake(data);//设置新节点的值,并将节点后继设置成 NULL
这个函数 简化了 入队的 细节问题(将队尾 后继 设置 为NULL)
queue->rear = newNode; queue->len++;
在一个 使队尾指向 新节点,并将长度+1
出队的细节处理比 入队更加多。
//出队 E_State dequeue(LinkQueue * queue,ElementType * top){ if (queue->len != 0) { QueueNode * frontNode = queue->front->next;//指向队头节点 queue->front->next = frontNode->next; queue->len--; //容易出错 if (frontNode == queue->rear)//出队列的是 尾指针,重新设置尾指针 { queue->rear = queue->front; } //容易漏写 *top = frontNode->data; free(frontNode); return E_State_Ok; } else//空队列 { return E_State_Error; } }
再次证明 len 在判空时 的逻辑清晰 和 便利行。
QueueNode * frontNode = queue->front->next;//指向队头节点 queue->front->next = frontNode->next; queue->len--;
将头节点后继 设置 为 队头节点的 后继 并 将 队列长度 减1
//容易出错 if (frontNode == queue->rear)//出队列的是 尾指针,重新设置尾指针 { queue->rear = queue->front; }
出队列的是尾指针,需要重新设置 尾指针,要不尾指针指向一个被释放的空间。( 可以将条件 改成 queue->len == 0 ,牛逼的 len 啊)
//容易漏写 *top = frontNode->data; free(frontNode);
返回队头数据,以及 释放内存。 ×top = frontNode->data; 这段代码 很有可能漏写。
最后是遍历 队列函数
void queueTraverse(LinkQueue queue){ QueueNode * next = queue.front->next; printf("---------队列遍历开始---------\n"); while (next != NULL) { printf("---------%d---------\n",next->data); //容易漏写 next = next->next; } printf("---------队列遍历结束---------\n"); }
比较简单,但是 这一次 我竟然 漏写了 next = next->next; ,是我 在写这一段代码 唯一 犯下的错误。
以后写完代码,需要 检查代码。 而不是 等 发现问题了,再折返 来查问题。
下面是完整代码:(可直接拷贝运行)
欢迎指出代码不足
// LinkQueue.cpp : 定义控制台应用程序的入口点。 // #include "stdafx.h" #include <stdlib.h> typedef int ElementType; enum E_State { E_State_Error = 0, E_State_Ok, }; struct QueueNode { ElementType data; QueueNode * next; }; //生成一个节点 QueueNode * queueNodeMake(ElementType data){ QueueNode * pNode = (QueueNode *)malloc(sizeof(QueueNode)); if (pNode != NULL) { pNode->data = data; pNode->next = NULL; } return pNode; } struct LinkQueue { QueueNode * front;//头指针 QueueNode * rear;//队尾 int len; }; E_State queueInit(LinkQueue * queue){ QueueNode * node = (QueueNode *) malloc(sizeof(QueueNode)); if (node == NULL) { return E_State_Error; } else { //初始头节点为NULL 很重要 node->next = NULL; queue->front = queue->rear = node; queue->len = 0; return E_State_Ok; } } void queueClear(LinkQueue * queue){ QueueNode * next = queue->front->next; while (next != NULL) { //顺序很重要 QueueNode * freeNode = next; next = next->next; free(freeNode); } //重置成空队列的状态 queue->rear = queue->front; queue->len = 0; } void queueDestory(LinkQueue * queue){ queueClear(queue); free(queue->front); queue->front = queue->rear = NULL; } bool queueEmpty(LinkQueue queue){ return queue.len == 0 ? true : false; } int queueLen(LinkQueue queue){ return queue.len; } //入队 E_State enqueue(LinkQueue * queue,ElementType data){ QueueNode * newNode = queueNodeMake(data);//设置新节点的值,并将节点后继设置成 NULL if (newNode != NULL) { //将队尾的后继设置成 新节点 queue->rear->next = newNode; //设置队尾节点 queue->rear = newNode; queue->len++; } return newNode != NULL ? E_State_Ok : E_State_Error; } //出队 E_State dequeue(LinkQueue * queue,ElementType * top){ if (queue->len != 0) { QueueNode * frontNode = queue->front->next;//指向队头节点 queue->front->next = frontNode->next; queue->len--; //容易出错 if (frontNode == queue->rear)//出队列的是 尾指针,重新设置尾指针 { queue->rear = queue->front; } //容易漏写 *top = frontNode->data; free(frontNode); return E_State_Ok; } else//空队列 { return E_State_Error; } } void queueTraverse(LinkQueue queue){ QueueNode * next = queue.front->next; printf("---------队列遍历开始---------\n"); while (next != NULL) { printf("---------%d---------\n",next->data); //容易漏写 next = next->next; } printf("---------队列遍历结束---------\n"); } int _tmain(int argc, _TCHAR* argv[]) { LinkQueue queue; queueInit(&queue); int len = 10; ElementType data; enqueue(&queue,1);//入队 dequeue(&queue,&data);//出队 queueClear(&queue);//清空 for (int i = 1; i <= len; i++) { enqueue(&queue,i); } queueTraverse(queue); char * empty = queueEmpty(queue) ? "空" : "不为空"; printf("队列长度 %d, 队列是否为空 : %s ",queueLen(queue),empty); queueDestory(&queue); return 0; }
运行截图;