您的位置:首页 > 理论基础 > 数据结构算法

数据结构 - 队列简介 及 1个简单的c语言链式队列代码实现

2013-04-16 21:06 1086 查看

1. 队列的定义

所谓队列(queue)就是一种能实现"先进先出"的一种线性存储结构.

跟栈有点类似, 例如栈只有1个出入口, 任何元素进入或者离开栈都必须经过同1个出入口(栈顶), 所以栈能实现"先入后出"的效果.

队列同样有出入口, 只不过出入口分别位于队列的两端, 元素只能从入口进入队列, 而且只能从另一端的出口离开队列, 在队列中间里的元素是不允许插入或删除操作的, 所以先进入队列的元素肯定会比后进入的元素先离开队列, 就是所谓的"先进先出"了.

举个例子, 例如现实中排队买票就是1个队列, 1个人只能从队伍的尾部进入队列, 从队伍的前部(买票窗口)买票离开队列, 中间插队是不允许的.

2. 队列的分类

就如栈可以分成动态栈和静态栈一样.

队列也可以分成静态队列和链式队列.

所谓静态队列就是用数组(连续内存) 作为内核的队列.

而链式队列就是以链表为内核的队列了.

下面会简介链式队列, 而且是单链表的链式队列.

3. 链式队列(单链表)的大概结构

画个图便于理解:



如上图, 我们会将1个单链表的首节点作为队列的前端(front), 就是队列的出口啦. 而会将单链表的尾节点作为队列的尾端(Rear), 就是队列的入口.

而我们会定义两个指针, Front指针来存放队列出口元素的地址, Rear指针来存放队列入口元素的地址.

之所以这样设定是为了方便出列操作, 如上图, 当节点0 要出列时, 0 作为1个首节点, 出列后Front指针就必须指向出列元素的下1个节点地址(节点1), 而这个地址恰好保存于节点0的尾部指针中啊. 假如在单链表的尾部出列, 如上图, 加入节点4出列后, Front就必须指向节点3, 但是节点3的尾部指针指向节点4, 而根据节点4是找不到节点4上1个元素的地址的,
只能遍历链表啊.


而在尾节点入列同样方便, 如上图, 当节点4入列时, 只需要将队列原来的入口元素(Rear指针)的尾部地址指向入列节点, 然后 Rear指针指向这个新的入口元素!

上面就是1个最基本的链式队列结构, 但是有1个弊端, 就是当这个队列为空(所有元素出列后), 这个链表就消失了, 再入列时就无从下手!

所以实际操作我们会给在队列的前部(出口)加上1个不存放实际数据的头节点, 做为列表的出口, 这个头节点指向队列的真正的出口元素(pHead->pnext). 这样当队列所有元素出列后, 队列的Rear指针就手动指向不存放实际数据的头节点, 这样的话我们会认为这个队列是空队列.(pHead == pRear)

如下图:



4. 一个简单的链式队列的C语言代码实现

4.1 首先就是编写1个头文件

在这个头文件里我们会定义两个结构体,
1个是对应于队列的元素的, 所以这个结构体具有1个pNext 指针成员。
另1个结构体是对应于链式队列的, 具有4个成员, 分别是:
pHead 头结点地址
pRear 存放入口元素的地址
len 存放链表的长度信息
is_inited 用于判断链表结构体是否被初始化(里面的指针成员是否野指针)

此外, 这个头文件也会生命一些算法函数, 别的文件引用这个头文件, 就可以使用这些函数了。

代码如下:
/*
* linkqueue1.h
*
*  Created on: 2013-4-15
*      Author: gateman
*/
#include <bool_me.h>
#ifndef __LINKQUEUE1_H_
#define __LINKQUEUE1_H_

struct person_linkqueue1{
int id;
char name[16];
struct person_linkqueue1 * pNext;
};

typedef struct person_linkqueue1 PERSON_LQ;

struct linkqueue1_person{
PERSON_LQ * pHead;
PERSON_LQ * pRear;
int len;
BOOL is_inited;
};

typedef struct linkqueue1_person LQPERSON;

//create a new node with dynamic memory assigned
PERSON_LQ * person_lq_new(int id, char * pname);

//print the information of a node
void person_lq_print(PERSON_LQ * pnode);

//create a stuck with dynamic linklist
LQPERSON * lqperson_new(void);

//judge whether the link_queue is empty
BOOL lq_is_empty(LQPERSON * pLq);

//add an element into the queue
void lq_Enqueue(LQPERSON * pLq, PERSON_LQ * pnode);

//remove an element from the queue, and get the element
BOOL lq_Dequeue(LQPERSON * pLq, PERSON_LQ ** pOutput);

//traverse the queue to print all the elements
void lq_print(LQPERSON * pLq);

//put out and free all the elements from the queue
void lq_clear(LQPERSON * pLq);

//free all the elements, and free the queue
void lq_free(LQPERSON * pLq);

#endif /* LINKQUEUE1_H_ */


下面会用列出上面声明的函数定义代码.

4.2 建立1个新节点(元素)函数 PERSON_LQ * person_lq_new(int id, char * pname)

这个函数作用就是动态分配1个新的内存给1个节点结构体, 既然是动态分配的, 那么这个节点很容易被其他函数访问。
而且必要时也可以手动释放。

逻辑步骤如下:

1. 新建1个节点类型指针. 并动态分配一段内存.

2. 如果分配不成功, 退出程序

3. 设置参数传入的成员值(id, name)

4. 尾部指针设成NULL

5. 返回这个指针

代码如下:

PERSON_LQ * person_lq_new(int id, char * pname){
PERSON_LQ * pnode = (PERSON_LQ *)malloc(sizeof(PERSON_LQ));
if (NULL == pnode){
base_error("fail to assign memory to a new node!");
}

pnode->id = id;
strncpy(pnode->name, pname+0, 15);
pnode->pNext=NULL;
return pnode;
}


4.3 打印1个节点的信息 void person_lq_print(PERSON_LQ * pnode)

这个不讲解了, 很简单

代码如下:

//print the information of a node
void person_lq_print(PERSON_LQ * pnode){
printf("id is %d, name is %s\n",pnode->id, pnode->name);
}


4.4 新建并初始化1个链式队列 LQPERSON * lqperson_new(void)

这个函数就是动态分配1段内存给1个链式队列, 并执行初始化, 最后返回这个结构体指针

步骤:

1. 新建1个链式队列结构体指针, 并动态分配1个内存

2. 若分配内存失败, 退出程序

3. 初始化链式队列的 pHead 头节点成员( 利用上面的 person_lq_new函数)

4. pRear 成员指向 pHead (代表空队列)

5. len长度设为0

6. is_inited 成员设为TRUE

7. 返回链式队列结构体指针

代码如下:

//create a stuck with dynamic linklist
LQPERSON * lqperson_new(void){
LQPERSON * pLq = (LQPERSON *)malloc(sizeof(LQPERSON));
if (NULL == pLq){
base_error("fail to assign memory to a linkqueue!");
}

pLq->pHead = person_lq_new(-1,"Head");
pLq->pRear = pLq->pHead; //empty queue;
pLq->len=0;
pLq->is_inited = TRUE;
return pLq;
}


4.5 判断链式队列是否为空 BOOL lq_is_empty(LQPERSON * pLq)

这个很简单, 判断 pRear 是否指向 pHead就ok了, 当然判断len ==0 也可以..

代码如下:

//judge whether the link_queue is empty
BOOL lq_is_empty(LQPERSON * pLq){
if (TRUE != pLq->is_inited){
base_error("the linkqueue is not initailed yet!");
}

if (pLq->pRear == pLq->pHead){
return TRUE;
}

return FALSE;
}


4.6 入列函数 void lq_Enqueue(LQPERSON * pLq, PERSON_LQ * pnode)

作用就是把1个节点元素放入队列, 逻辑并不复杂. 上面图解过了嘛

步骤:

1.入口元素地址 pRear的尾部指针指向这个入列元素

2,pRear 指向 这个入列元素, 这个入列元素就成为新的入口元素了

3. 这个入列元素的尾部指针指向NULL

4. 链表长度len+1

代码如下:

//add an element into the queue
void lq_Enqueue(LQPERSON * pLq, PERSON_LQ * pnode){
if (TRUE != pLq->is_inited){
base_error("the linkqueue is not initailed yet!");
}

pLq->pRear->pNext = pnode;
pLq->pRear = pnode;
pnode->pNext=NULL;

pLq->len++;
}


4.7 出列函数 BOOL lq_Dequeue(LQPERSON * pLQ, PERSON_LQ ** pOutput)

注意这个函数, 返回值是BOOL 也就是出列函数有可能失败, 因为如果1个空队列, 出列就肯定失败嘛

而且接受1个以地址传递的指针, 也就是指针的指针了, 用于接受出列元素的地址.

逻辑如下:

1. 判断是否为空队列, 否则返回FALSE

2. pOutput 指向出列元素(pHead 下1个元素)

3. pHead 头节点的尾部指针指向出列元素的下1个元素, 那个就是新的出口元素, 也就是说下次出列就轮到它啊

4. 如果出列元素是最后1个元素, 出列后 pRear 指向pHead, 代表成为1个空队列了

5. 队列长度len -1

6. 返回TRUE

代码如下:

//remove an element from the queue, and get the element
BOOL lq_Dequeue(LQPERSON * pLq, PERSON_LQ ** pOutput){
if (TRUE != pLq->is_inited){
base_error("the linkqueue is not initailed yet!");
}

if (TRUE == lq_is_empty(pLq)){
printf("the linkqueue is empty!\n");
return FALSE;
}

*pOutput = pLq->pHead->pNext;
pLq->pHead->pNext = (*pOutput)->pNext;

//if it's the last one
if(pLq->pRear == *pOutput){
pLq->pRear = pLq->pHead; //empty
}

pLq->len--;
return TRUE;
}


4.8 打印1个队列函数 lq_print(LQPERSON * pLq)

这个函数作用就是从出口开始打印队列的存放有效数据的函数, 就是从Front 到 Rear啦.

相当于1个遍历,

代码如下:

//traverse the queue to print all the elements
void lq_print(LQPERSON * pLq){
if (TRUE != pLq->is_inited){
base_error("the linkqueue is not initailed yet!");
}

if (TRUE == lq_is_empty(pLq)){
printf("the linkqueue is empty!\n");
}

PERSON_LQ * pnode = pLq->pHead;
while(NULL != pnode->pNext){
pnode=pnode->pNext;
person_lq_print(pnode);
}
}


4.9 删除所有队列元素函数 lq_clear

当然, pRear 指向 pHead, 这样这个队列马上就是1个空队列, 但是这样里面的节点就容易丢失, 也就是内存泄露啊

所以我们要逐个地释放里面节点的内存, 然后才把 pRear 指向 pHead

//put out and free all the elements from the queue
void lq_clear(LQPERSON * pLq){
if (TRUE != pLq->is_inited){
base_error("the linkqueue is not initailed yet!");
}

PERSON_LQ * pnode = pLq->pHead;
PERSON_LQ * pnext = pnode->pNext;
while(NULL != pnext){
pnode = pnext;
pnext = pnode->pNext;
free(pnode);
}

pLq->pHead->pNext=NULL;
pLq->pRear = pLq->pHead;
pLq->len=0;
}


4.9 销毁队列函数 lq_free

这个更简单, 首先执行清空.

然后把头节点的内存释放

最后把队列结构体释放

代码如下:

//free all the elements, and free the queue
void lq_free(LQPERSON * pLq){
lq_clear(pLq);
free(pLq->pHead);
free(pLq);
}


4.10 写个程序测试上面的函数

纯粹测试下啦:

代码如下:

int linkqueue1(){
PERSON_LQ * pnode = person_lq_new(1,"JasonPoon111212121212");
person_lq_print(pnode);
free(pnode);

LQPERSON * plq1 = lqperson_new();
lq_Enqueue(plq1, person_lq_new(1,"Jason"));
lq_Enqueue(plq1, person_lq_new(2,"Cindy"));
lq_Enqueue(plq1, person_lq_new(3,"Fiana"));
lq_Enqueue(plq1, person_lq_new(4,"Gateman"));

lq_print(plq1);

int i=0;
int j=plq1->len-1;
for (i=0; i < j; i++){
lq_Dequeue(plq1,&pnode);
printf("the out node is\n");
person_lq_print(pnode);
printf("\n");
free(pnode);
}

lq_print(plq1);
lq_Dequeue(plq1,&pnode);
printf("the out node is\n");
person_lq_print(pnode);
printf("\n");
free(pnode);

lq_print(plq1);
lq_Enqueue(plq1, person_lq_new(1,"Jason"));
lq_Enqueue(plq1, person_lq_new(2,"Cindy"));
lq_Enqueue(plq1, person_lq_new(3,"Fiana"));
lq_Enqueue(plq1, person_lq_new(4,"Gateman"));

lq_print(plq1);

printf("now clear the linkqueuei!\n");
lq_clear(plq1);
lq_print(plq1);

lq_free(plq1);

printf("linkq1 done\n");
return 0;
}


输出:

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: