看数据结构写代码(15)链式队列的实现

队列 和 栈 是 一种 受限制的 线性表。所以 他们的 实现方式 都 相差 无几。之前有过  链栈 和 链式线性表 的 实现经验,自然 写 链队 ,也毫无问题。

下面详细讲解每一段代码 的技术要点

下面是队列节点的数据结构

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;
}

运行截图;

时间: 2024-10-13 10:40:04

看数据结构写代码(15)链式队列的实现的相关文章

看数据结构写代码(19) 数组的实现

数组是 一种 使用广泛 的数据结构,任何编程语言都有数组.其本质上 是 线性表,一维数组 是 一个线性表,多维数组是多组 线性表. 其 便利性 就在于 查找 和 赋值 方便.所以 就没必要 用 链式存储方式. 下面 给出 数组的实现代码: // Array.cpp : 定义控制台应用程序的入口点. // #include "stdafx.h" #include <stdlib.h> #include <stdarg.h> #define ARRAY_MAX_DI

看数据结构写代码(51) 广义表

广义表是一种非线性的数据结构.但如果广义表的每个元素都是原子,它就变成了线性表.广义表广泛地用于人工智能等领域的LISP语言. 广义表一般记作 LS = (a1, a2, ···, an), n是它的长度,ai可以是单个元素(原子),也可以是广义表(子表),当广义表非空时,称第一个元素a1为LS的表头,称其余元素组成的表为LS的表尾.注意:表头是元素(可以是原子,也可以是广表),表尾一定是广义表.E=(a, E)是一个递归的表.D=(( ),(e),(a,(b,c,d)))是多层次的广义表,长度

看数据结构写代码(32) 赫夫曼树编码以及译码

杂谈:最近有点慵懒,不好不好.好几天都没写代码,原本准备上星期完结 树 这一章节的.现在 又耽误了.哎.要抓紧时间啊. 下面直接上代码: 可以到我的网盘下载源代码,或者 直接拷贝下面的源代码 运行 网盘地址:点击打开链接 // HuffmanTree.cpp : 定义控制台应用程序的入口点. //哈弗曼编码,译码 #include "stdafx.h" #include <stdlib.h> #include <cstring> enum E_State { E

看数据结构写代码(9)链栈的实现

在写链栈的时候 和 顺序栈一样 犯了两个错误: 一个是 在 入栈 和 进栈顶时候 忘记 操作 linkstack.len 了,另一个是 在写 stackClear 的时候 犯了一个 低级的内存错误. 这两个问题 都是 粗心造成的. 希望 引以为戒 在做下一个例子:数值转换时,又发现了一个问题:在 stackPop 没有返回 pop元素的值.唉  欢迎指出代码不足 下面上代码: // LinkStack.cpp : 定义控制台应用程序的入口点. // #include "stdafx.h"

看数据结构写代码(16)顺序队列的实现(循环队列)

循环队列的基本结构如下: front 属性 表示 队头,rear 属性表示 队尾. 在队空时 :q.rear 和 q.front 都为0 ,其余时刻q.rear 指向 队尾的后继节点,q.front指向 队头. 当在队尾插入元素时,q.rear + 1 ,在删除 队头元素时 ,q.front + 1,这样的操作 会造成 "假溢出"问题. 图(d) 就是一种 假溢出 问题,q.rear 指向 空间的边界,再插入 会造成 溢出,但是 实际上 空间 并未满. 为了解决假溢出,将 队列 看成

看数据结构写代码(67) 置换 _ 选择排序(完结篇)

杂谈: 严蔚敏版<数据结构(C语言版)> 一书 终于看完了.这是 一个完结,也是 一个新的开端.<算法导论> 已到手. 置换选择排序的思想 是 将 归并段 尽量 变的 更大,而不是根据 内存 大小 限制在 固定的 大小. 这样 可以 利用赫夫曼树 来 进行 最优归并树,从而 使 外存 读写次数 最少. 下面给出 具体 代码:欢迎指出代码不足. // Replace_Selcetion.cpp : 定义控制台应用程序的入口点. // #include "stdafx.h&q

看数据结构写代码(44) 判断无向图是否有环路

在 看 严蔚敏的 数据结构 一书 7.5小节时,书上 说" 判断有向图是否存在环要不无向图复杂.对于无向图来说,深度优先遍历过程中遇到回边(即指向已访问过的顶点的边),则必定存在环路". 看的不明白,所以 网上 百度了一下. 有了思路:故写下算法 和思路,以便以后 温故. 思路: 1.一个n个顶点,e条边的 无向图,若 e>= n,必有环路. 2.若 e < n ,需要 深度 遍历,并把 父节点传入 参数中,如果 遇到 一个 节点 被访问过 并且 不是 父节点,那么 就有环

看数据结构写代码(8)顺序栈的实现

欢迎指出 代码 不足之处 在写顺序栈的时候 犯了两个错误,:一个是 对栈的 认识不够清楚,栈顶指针的下一个位置为栈顶元素: 另一个是粗心,在用 realloc 分配内存的时候,忽略了元素本身的大小,只写了 元素的个数. 希望引以为戒. 上代码: // SqStack.cpp : 定义控制台应用程序的入口点. // #include "stdafx.h" #include <stdlib.h> #include <cstdio> //故意将值设置的比较小,以测试

看数据结构写代码(21) 稀疏矩阵(十字链表方式)

写完 这个样例,花费了 我不少时间.大部分时间 花费在 调试 内存问题上. 比如在销毁十字链表时.多次释放节点空间,造成 _CrtIsValidHeapPointer(pUserData) 异常. 当使用malloc 分配 一个 空间时,会将这个空间的起始地址和长度 加到一个链表中去.free(p)的时候 ,会从 链表里 查找 是否 有 这个地址空间,找到了就将这个节点从链表中删除._CrtIsValidHeapPointer(pUserData)  这个函数 正是 检查 这个空间是否 在链表里