链队列的存储和入队出队操作

今天看图的广度优先遍历的时候,发现用到了队列,补一下链队列的知识,参考《大话数据结构》的P118~120,自己写了一个简单的测试例子便于理解。

理解起来并不难,用的是单链表结构。front指向链表的头结点(是虚结点,它的next指向第一个节点),rear指向链表的尾节点。

下面举个简单的例子,实现链队列的创建,入队和出队操作。

 

第一个程序调试了很久,编译没有问题,运行总是崩溃。是对内存分配没有考虑全面,先把错误的程序放上来思考一下。

 1 #include <iostream>
 2 #include <string>
 3 #include <stdlib.h>
 4 using namespace std;
 5 #define OVERFLOW 0
 6 
 7 //链队列的结点结构
 8 typedef struct QNode
 9 {
10     char data;
11     struct QNode *next;//定义指向下一个结点的指针next
12 }QNode;
13 
14 //队列的链式存储结构
15 typedef struct
16 {
17     QNode *front,*rear;//定义指向队头结点和队尾结点的指针
18 }LinkQueue;
19 
20 //入队操作,在链表尾部插入结点
21 LinkQueue *EnQueue(LinkQueue *Q,char e)
22 {
23     Q=new LinkQueue;//或者Q=(LinkQueue*)malloc(sizeof(LinkQueue));
24     Q->front=Q->rear=NULL;
25     QNode *s;//定义一个指向结点的指针s
26     s=new QNode;//或者s=(QNode *)malloc(sizeof(QNode));
27     if(!s) exit(OVERFLOW);//如果分配结点内存失败,直接结束程序
28     //exit函数的头文件是stdlib.h。
29     //exit的声明为void exit(int value);
30     //exit的作用是,退出程序,并将参数value的值返回给主调进程。
31     //exit(a); 等效于在main函数中return a;
32 
33     //给s指向的新结点填内容
34     s->data=e;
35     s->next=NULL;
36     Q->rear->next=s;//使rear指向结点的next指向新节点s
37     Q->rear=s;//使rear指向新节点s
38     cout<<"ok"<<" ";
39     return Q;//返回指向该链队列的地址
40 }
41 
42 //出队操作,把头结点的next指向的元素出队,返回出队的元素
43 char DeQueue(LinkQueue *Q)
44 {
45     char e;
46     QNode *s;//定义一个指向结点的指针s
47     if(Q->front==Q->rear) return 0;//如果链队列为空,返回0,也可以定义其他char类型的标志
48     s=Q->front->next;//把要删除的结点的地址暂存给p
49     e=s->data;//把要删除的结点的data给e,需要返回该元素
50     Q->front->next=s->next;//把要删除的结点的下一个结点的地址给头结点的next
51     if(Q->rear==s)
52         Q->rear=Q->front;//如果要删除的结点是队尾结点,说明删除该结点后链队列为空,rear和front同时指向头结点
53     delete s;//或者frees);
54     return e;//返回出队元素
55 }
56 
57 int main()
58 {
59     LinkQueue *p=NULL;
60     p=EnQueue(p,'A');//插入A
61     cout<<p->rear->data<<endl;
62 }

运行到第36行时候崩溃

通过思考,是因为没有让Q指向的链结构LinkQueue中的front和rear指针指向new出来的QNode。

 

修改后的程序可以正常运行,代码和解释如下(VS2012测试通过):

 1 #include <iostream>
 2 #include <string>
 3 #include <stdlib.h>
 4 using namespace std;
 5 
 6 #define OVERFLOW 0
 7 
 8 //链队列的结点结构
 9 typedef struct QNode
10 {
11     char data;
12     struct QNode *next;//定义指向下一个结点的指针next
13 }QNode;
14 
15 //队列的链式存储结构,可以看到只占8个字节(sizeof(LinkQueue)==8)
16 typedef struct
17 {
18     QNode *front,*rear;//定义指向队头结点和队尾结点的指针
19 }LinkQueue;
20 
21 //链队列的初始化,申请内存
22 LinkQueue *InitQueue(LinkQueue *Q)
23 {
24     Q=new LinkQueue;//或者Q=(LinkQueue*)malloc(sizeof(LinkQueue));
25     Q->front=Q->rear=new QNode;
26     //初始化为空队列(front==rear表示空队列),并且同时指向头结点,如果只是置为相等,不指向new出来的QNode,程序会崩溃
27     return Q;
28 }
29 //为什么new一个头结点,头结点的next指向链队列的第一个节点,可以思考一下如果没有头结点会怎样?
30 
31 //入队操作,在链表尾部插入结点
32 void EnQueue(LinkQueue *Q,char e)
33 {
34     QNode *s;//定义一个指向结点的指针s
35     s=new QNode;//或者s=(QNode *)malloc(sizeof(QNode));
36     if(!s) exit(OVERFLOW);//如果分配结点内存失败,直接结束程序
37     //exit函数的头文件是stdlib.h。
38     //exit的声明为void exit(int value);
39     //exit的作用是,退出程序,并将参数value的值返回给主调进程。
40     //exit(a); 等效于在main函数中return a;
41 
42     //给s指向的新结点填内容
43     s->data=e;
44     s->next=NULL;
45     Q->rear->next=s;//使rear指向结点的next指向新节点s
46     Q->rear=s;//使rear指向新节点s
47     cout<<"ok"<<" ";
48 }
49 
50 //出队操作,把头结点的next指向的元素出队,返回出队的元素
51 char DeQueue(LinkQueue *Q)
52 {
53     char e;
54     QNode *s;//定义一个指向结点的指针s
55     if(Q->front==Q->rear) 
56     {
57         cout<<"error"<<" ";
58         return 0;//如果链队列为空,返回0,也可以定义其他char类型的标志
59     }
60     s=Q->front->next;//把要删除的结点的地址暂存给p
61     e=s->data;//把要删除的结点的data给e,需要返回该元素
62     Q->front->next=s->next;//把要删除的结点的下一个结点的地址给头结点的next
63     if(Q->rear==s)
64     {
65         Q->rear=Q->front;//如果要删除的结点是队尾结点,说明删除该结点后链队列为空,rear和front同时指向头结点
66     }
67     delete s;//或者free(s);
68     cout<<"ok"<<" "<<e<<endl;//输出出队元素
69     return e;//返回出队元素
70 }
71 
72 int main()
73 {
74     LinkQueue *p=NULL;
75     p=InitQueue(p);//如果缺少这一步程序就崩溃,一开始没有注意到这个问题,花了一个多小时
76 
77     //入队
78     cout<<"in:"<<endl;
79     EnQueue(p,'A');//插入A
80     cout<<p->rear->data<<endl;//输出A
81     EnQueue(p,'B');//插入B
82     cout<<p->rear->data<<endl;//输出B
83     EnQueue(p,'C');//插入C
84     cout<<p->rear->data<<endl;//输出C
85     EnQueue(p,'D');//插入D
86     cout<<p->rear->data<<endl;//输出D
87 
88     //出队
89     cout<<"out:"<<endl;
90     DeQueue(p);//输出A
91     DeQueue(p);//输出B
92     DeQueue(p);//输出C
93     DeQueue(p);//输出D
94     DeQueue(p);//没有元素了,出队失败
95 }

运行结果:

 

补充:比较循环队列和链队列

时间上:

基本操作入队和出队等都是常数时间,O(1)。

同样是O(1),也有细微差异。循环队列事先申请好空间,使用期间不释放。链队列每次申请和释放节点会存在时间开销。

空间上:

循环队列必须有一个固定的长度,如果存储的元素个数比长度小很多,造成空间浪费的问题。

链队列只需要一个指针域,代码中有讲到占8个字节(rear和front两个指针变量分别占4个字节)。所以空间上链队列更灵活。

总结:

在可以确定队列长度最大值的情况下,建议用循环队列。如果无法预估队列长度,用链队列。

posted @ 2016-04-18 15:22  Pearl_zju  阅读(5780)  评论(0编辑  收藏  举报