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


今天看图的广度优先遍历的时候,发现用到了队列,补一下链队列的知识,参考《大话数据结构》的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个字节)。所以空间上链队列更灵活。

总结:

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


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM