链队列的存储和入队出队操作
今天看图的广度优先遍历的时候,发现用到了队列,补一下链队列的知识,参考《大话数据结构》的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个字节)。所以空间上链队列更灵活。
总结:
在可以确定队列长度最大值的情况下,建议用循环队列。如果无法预估队列长度,用链队列。
-------------------------------------------------
原创博客 转载请注明出处http://www.cnblogs.com/hslzju
-------------------------------------------------