队列的存储结构和常见操作(c 语言实现)

一、队列(queue)

队列和栈一样,在实际程序的算法设计和计算机一些其他分支里,都有很多重要的应用,比如计算机操作系统对进程 or 作业的优先级调度算法,对离散事件的模拟算法,还有计算机主机和外部设备运行速度不匹配的问题解决等,很多很多。其实队列的本质还是线性表!只不过是一种特殊的或者说是受限的线性表,是这样的:

1)、限定在表的一端插入、另一端删除。 插入的那头就是队尾,删除的那头就是队头。也就是说只能在线性表的表头删除元素,在表尾插入元素。形象的说就是水龙头和水管,流水的水嘴是队头,进水的泵是队尾,管子中间不漏水不进水。这样呲呲的流动起来,想想就是这么个过程。

2)、先进先出 (FIFO结构)。显然我们不能在表(队列)的中间操作元素,只能是在尾部进,在头部出去,还可以类似火车进隧道的过程。(first in first out = FIFO 结构)

注意,当队列没有元素的时候,我们就说队列是空队列。

 

1、双端队列

double-ended queue:限定插入和删除在表的两端进行,也是先进先出 (FIFO)结构,类似铁路的转轨网络。实际程序中应用不多。

这种结构又细分为三类:

1)、输入受限的双端队列:一个端点可插入和删除,另一个端点仅可删除。

2)、输出受限的双端队列:一个端点可插入和删除,另一个端点仅可插入。   

3)、等价于两个栈底相连接的栈:限定双端队列从某个端点插入的元素,只能在此端点删除。

 

2、链队(有链的地方,就有指针)

用链表表示的队列,限制仅在表头删除和表尾插入的单链表。一个链队列由一个头指针和一个尾指针唯一确定。(因为仅有头指针不便于在表尾做插入操作)。为了操作的方便,也给链队列添加一个头结点,因此,空队列的判定条件是:头指针和尾指针都指向头结点。

之前的链式结构,总是使用一个结点的结构来表示链表,其实不太方便,这里使用新的存储结构。定义一个结点结构,和一个队列结构。两个结构嵌套。

 1 #ifndef queue_Header_h
 2 #define queue_Header_h
 3 #include <stdio.h>
 4 #include <stdlib.h>
 5 #include <stdbool.h>
 6
 7 //队列的结点结构
 8 typedef struct Node{
 9     int data;
10     struct Node *next;
11 } Node, *Queue;
12
13 //队列的结构,嵌套
14 typedef struct{
15     Queue front;
16     Queue rear;
17 } LinkQueue;
18
19 //初始化
20 //开始必然是空队列,队尾指针和队头指针都指向头结点
21 void initQueue(LinkQueue *queue)
22 {
23     //初始化头结点
24     queue->front = queue->rear = (Queue)malloc(sizeof(Node));
25
26     if (NULL == queue->front) {
27         exit(0);
28     }
29
30     queue->front->next = NULL;
31 }
32
33 //判空
34 bool isEmpty(LinkQueue queue)
35 {
36     return queue.rear == queue.front ? true : false;
37 }
38
39 //入队,只在一端入队,另一端出队,同样入队不需要判满
40 void insertQueue(LinkQueue *queue, int temp)
41 {
42     Queue q = (Queue)malloc(sizeof(Node));
43
44     if (NULL == q) {
45         exit(0);
46     }
47     //插入数据
48     q->data = temp;
49     q->next = NULL;
50     //rear 总是指向队尾元素
51     queue->rear->next = q;
52     queue->rear = q;
53 }
54
55 //出队,需要判空
56 void deleteQueue(LinkQueue *queue)
57 {
58     Queue q = NULL;
59
60     if (!isEmpty(*queue)) {
61         q = queue->front->next;
62         queue->front->next = q->next;
63         //这句很关键,不能丢
64         if (queue->rear == q) {
65             queue->rear = queue->front;
66         }
67
68         free(q);
69     }
70 }
71
72 //遍历
73 void traversal(LinkQueue queue)
74 {
75     int i = 1;
76     Queue q = queue.front->next;
77
78     while (q != NULL) {
79         printf("队列第%d个元素是:%d\n", i, q->data);
80         q = q->next;
81         i++;
82     }
83 }
84
85 //销毁
86 void destoryQueue(LinkQueue *queue)
87 {
88     while (queue->front != NULL) {
89         queue->rear = queue->front->next;
90         free(queue->front);
91         queue->front = queue->rear;
92     }
93
94     puts("销毁成功!");
95 }
96
97 #endif

 测试

 1 #include "queue.h"
 2
 3 int main(int argc, const char * argv[])
 4 {
 5     LinkQueue queue;
 6     puts("初始化队列 queue");
 7     initQueue(&queue);
 8     traversal(queue);
 9
10     puts("队尾依次插入0 1 2 3");
11     insertQueue(&queue, 0);
12     insertQueue(&queue, 1);
13     insertQueue(&queue, 2);
14     insertQueue(&queue, 3);
15     traversal(queue);
16
17     puts("先进先出,删除队列从头开始, 0 ");
18     deleteQueue(&queue);
19     traversal(queue);
20
21     puts("先进先出,删除队列从头开始, 1 ");
22     deleteQueue(&queue);
23     traversal(queue);
24
25     puts("先进先出,删除队列从头开始, 2 ");
26     deleteQueue(&queue);
27     traversal(queue);
28
29     puts("先进先出,删除队列从头开始, 3");
30     deleteQueue(&queue);
31     traversal(queue);
32
33     destoryQueue(&queue);
34     return 0;
35 }

结果:

初始化队列 queue

队尾依次插入0 1 2 3

队列第1个元素是:0

队列第2个元素是:1

队列第3个元素是:2

队列第4个元素是:3

先进先出,删除队列从头开始, 0 

队列第1个元素是:1

队列第2个元素是:2

队列第3个元素是:3

先进先出,删除队列从头开始, 1 

队列第1个元素是:2

队列第2个元素是:3

先进先出,删除队列从头开始, 2 

队列第1个元素是:3

先进先出,删除队列从头开始, 3

销毁成功!

Program ended with exit code: 0

 

3、顺序队列

限制仅在表头删除和表尾插入的顺序表,利用一组地址连续的存储单元依次存放队列中的数据元素。因为队头和队尾的位置是变化的,所以也要设头、尾指针。  

初始化时的头尾指针,初始值均应置为 0。 入队尾指针增 1 ,出队头指针增 1 。头尾指针相等时队列为空,在非空队列里,头指针始终指向队头元素,尾指针始终指向队尾元素的下一位置。

初始为空队列,那么头尾指针相等。

入队,那么尾指针加1,头指针不变。先进先出,J1先进队,则 rear+1,尾指针始终指向队尾元素的下一位!如,J2进队,rear 继续+1,J3进队,尾指针继续加1,如图

 

出队,则尾指针不变,头指针加1,注意这里都是加1,先进先出原则,J1先删除,front+1,指向了 J2,J2删除,front+1指向了 J3,如图

 

最后,J3删除,则头指针再次和尾指针相等,说明队列空了。如图

在顺序队列中,当尾指针已经指向了队列的最后一个位置的下一位置时,若再有元素入队,就会发生“溢出”。如图位置,再次入队,就会溢出。

 

4、循环队列的诞生

顺序队列的 “假溢出” 问题:队列的存储空间未满,却发生了溢出。很好理解,比如 rear 现在虽然指向了最后一个位置的下一位置,但是之前队头也删除了一些元素,那么队头指针经历若干次的 +1 之后,遗留下了很多空位置,但是顺序队列还在傻乎乎的以为再有元素入队,就溢出呢!肯定不合理。故循环队列诞生!

解决“假溢出”的问题有两种可行的方法:

(1)、平移元素:把元素平移到队列的首部。效率低。否决了。

(2)、将新元素插入到第一个位置上,构成循环队列,入队和出队仍按“先进先出”的原则进行。操作效率高、空间利用率高。

      

虽然使用循环队列,解决了假溢出问题,但是又有新问题发生——判空的问题,因为仅凭 front = rear 不能判定循环队列是空还是满。比如如图:

这是空循环队列的样子

这是满循环队列的样子

解决办法:

(1)、另设一个布尔变量以区别队列的空和满;

(2)、少用一个元素的空间,约定入队前测试尾指针在循环下加 1 后是否等于头指针,若相等则认为队满;(最常用)

(3)、使用一个计数器记录队列中元素的总数。

对于第2个方案,必须牺牲一个元素的空间,那么入队的时候需要测试,循环意义下的加 1 操作可以描述为:

1     if (rear + 1 = MAXQSIZE)
2
3            rear = 0;
4
5      else
6
7           rear ++; 

利用模运算可简化为:

1 rear = (rear + 1)% MAXQSIZE  

基本操作

 1 #ifndef ___queue_Header_h
 2 #define ___queue_Header_h
 3 #include <stdio.h>
 4 #include <stdlib.h>
 5 #define MAX_SIZE 5
 6
 7 typedef struct{
 8     int *base;
 9     int rear;//如果队列不空,指向队尾元素的下一个位置
10     int front;//初始的时候指向表头
11 } CirularQueue;
12
13 //初始化
14 void initQueue(CirularQueue *queue)
15 {
16     queue->base = (int *)malloc(MAX_SIZE*sizeof(int));
17
18     if (NULL == queue->base) {
19         exit(0);
20     }
21
22     queue->front = queue->rear = 0;
23 }

求长度

//求长度
int getLength(CirularQueue queue)
{
    //这样把所以的情况都考虑到了
    return (queue.rear - queue.front + MAX_SIZE) % MAX_SIZE;
}

第一种情况,长度的求法

第二种情况,长度的求法,利用模运算,两个情况合二为一!

//入队,先判满
void insertQueue(CirularQueue *queue, int e)
{
    if ((queue->rear + 1) % MAX_SIZE == queue->front) {
        puts("循环队列是满的!");
    }
    else
    {
        queue->base[queue->rear] = e;
        queue->rear = (queue->rear + 1) % MAX_SIZE;
    }
}

如下时为满,损失一个空间,不存储元素。方便判满

 1 //出队
 2 void deleteQueue(CirularQueue *queue)
 3 {
 4     if (queue->front == queue->rear) {
 5         puts("队列是空的!");
 6     }
 7     else
 8     {
 9         queue->front = (queue->front + 1) % MAX_SIZE;
10     }
11 }
12
13 //遍历
14 void traversal(CirularQueue queue)
15 {
16     int q = queue.front;
17
18     for (int i = 0; i < getLength(queue); i++) {
19         printf("循环队列的第%d个元素为%d\n", i + 1, queue.base[q]);
20         q++;
21     }
22 }
23
24 #endif

测试

 1 #include "Header.h"
 2
 3 int main(int argc, const char * argv[]) {
 4     CirularQueue queue;
 5     puts("循环队列初始化:");
 6     initQueue(&queue);
 7
 8     puts("循环队列初始化后长度:");
 9     printf("%d\n", getLength(queue));
10
11     puts("循环队列5个元素入队,总长度为5,但是损失一个位置空间,实际存储4个元素。先进先出原则:");
12     puts("循环队列元素0入队");
13     insertQueue(&queue, 0);
14     puts("循环队列元素1入队");
15     insertQueue(&queue, 1);
16     puts("循环队列元素2入队");
17     insertQueue(&queue, 2);
18     puts("循环队列元素3入队");
19     insertQueue(&queue, 3);
20
21     puts("循环队列元素遍历:");
22     traversal(queue);
23
24     puts("循环队列元素继续入队,无法完成:");
25     insertQueue(&queue, 4);
26
27     puts("循环队列元素0出队之后,先进先出原则:");
28     deleteQueue(&queue);
29     traversal(queue);
30
31     puts("循环队列元素1出队之后,先进先出原则:");
32     deleteQueue(&queue);
33     traversal(queue);
34
35     puts("循环队列元素2出队之后,先进先出原则:");
36     deleteQueue(&queue);
37     traversal(queue);
38
39     puts("循环队列元素3出队之后,先进先出原则:");
40     deleteQueue(&queue);
41     traversal(queue);
42
43     puts("4个元素全部删除,循环队列已经空了:");
44     deleteQueue(&queue);
45
46     traversal(queue);
47
48     return 0;
49 }

结果;

循环队列初始化:

循环队列初始化后长度:

0

循环队列5个元素入队,总长度为5,但是损失一个位置空间,实际存储4个元素。先进先出原则:

循环队列元素0入队

循环队列元素1入队

循环队列元素2入队

循环队列元素3入队

循环队列元素遍历:

循环队列的第1个元素为0

循环队列的第2个元素为1

循环队列的第3个元素为2

循环队列的第4个元素为3

循环队列元素继续入队,无法完成:

循环队列是满的!

循环队列元素0出队之后,先进先出原则:

循环队列的第1个元素为1

循环队列的第2个元素为2

循环队列的第3个元素为3

循环队列元素1出队之后,先进先出原则:

循环队列的第1个元素为2

循环队列的第2个元素为3

循环队列元素2出队之后,先进先出原则:

循环队列的第1个元素为3

循环队列元素3出队之后,先进先出原则:

4个元素全部删除,循环队列已经空了:

队列是空的!

Program ended with exit code: 0

 

小结:

若用户需要循环队列,那么要设置队列的最大长度,否则无法完成判断空,如果用户不知道最大长度是多少,那么应该使用链队。队列在程序设计中和栈一样,应用很多,未完待续。

 

 

辛苦的劳动,转载请注明出处,谢谢……

http://www.cnblogs.com/kubixuesheng/p/4104802.html

时间: 2024-10-29 04:32:31

队列的存储结构和常见操作(c 语言实现)的相关文章

线性链表其他种类(静态,双向,循环)的存储结构和常见操作

一.静态单链表 在不支持动态空间分配的环境中,要使用链表存储数据,那么可采用静态链表的方法:即在一块预分配的存贮空间中,用下标作为指针链来构成链式结构. //既然是静态链表,那么可以使用一维数组实现存储,java没有指针,那么就用这来使用链表结构 //在不支持动态空间分配的环境中,要使用链式结构技术,可采用静态链表的方法:即在一块预分配的存贮空间中,用下标作为指针. //存储结构:在数组中增加一个"指针"域,存放下一元素在数组中的下标.且0为代表空指针   //设S为SLinkList

栈的存储结构和常见操作(c 语言实现)

俗话说得好,线性表(尤其是链表)是一切数据结构和算法的基础,很多复杂甚至是高级的数据结构和算法,细节处,除去数学和计算机程序基础的知识,大量的都在应用线性表. 一.栈 其实本质还是线性表:限定仅在表尾进行插入或删除操作. 俗称:后进先出 (LIFO=last in first out结构),也可说是先进后出(FILO). 同样的,栈也分为顺序和链式两大类.其实和线性表大同小异,只不过限制在表尾进行操作的线性表的特殊表现形式. 1.顺序栈:利用一组地址连续的存储单元依次存放自栈底到栈顶的数据元素,

队列能存储结构体空结点吗

问题描述 队列能存储结构体空结点吗 用层次遍历判断完全二叉树 队列存储结点 包括空结点 如果遇到空节点 然后队列还有非空结点 就不是完全二叉树

动态单链表的传统存储方式和10种常见操作-C语言实现

顺序线性表的优点:方便存取(随机的),特点是物理位置和逻辑为主都是连续的(相邻).但是也有不足,比如:前面的插入和删除算法,需要移动大量元素,浪费时间,那么链式线性表 (简称链表) 就能解决这个问题.   一般链表的存储方法 一组物理位置任意的存储单元来存放线性表的数据元素,当然物理位置可以连续,也可以不连续,或者离散的分配到内存中的任意位置上都是可以的.故链表的逻辑顺序和物理顺序不一定一样.   因为,链表的逻辑关系和物理关系没有必然联系,那么表示数据元素之间的逻辑映象就要使用指针,每一个存储

C语言单链表常见操作汇总_C 语言

C语言的单链表是常用的数据结构之一,本文总结了单链表的常见操作,实例如下: #include<stdio.h> #include<stdlib.h> //定义单链表结构体 typedef int ElemType; typedef struct Node { ElemType data; struct Node *next; }LNode,*LinkList; //创建单链表 void Build(LinkList L) { int n; LinkList p,q; p=L; pr

C#创建安全的字典(Dictionary)存储结构_C#教程

在上面介绍过栈(Stack)的存储结构,接下来介绍另一种存储结构字典(Dictionary). 字典(Dictionary)里面的每一个元素都是一个键值对(由二个元素组成:键和值) 键必须是唯一的,而值不需要唯一的,键和值都可以是任何类型.字典(Dictionary)是常用于查找和排序的列表.   接下来看一下Dictionary的部分方法和类的底层实现代码:   1.Add:将指定的键和值添加到字典中. public void Add(TKey key, TValue value) { Ins

数据结构的C++实现之队列的链式存储结构

队列的链式存储结构,其实就是线性表的单链表,只不过它只能尾进头出而已,我们把它简称为链队列.为了操作上的 方便,我们将队头指针指向链队列的头节点,而队尾指针指向终端节点.空队列时,front和rear都指向头节点. 示例程序 :(改变自<大话数据结构>) #include<iostream> using namespace std; typedef int ElemType; typedef struct Node { ElemType data; struct Node *nex

字符串和字符串的常见存储结构

继续接去年的常见数据结构和算法总结 系列随笔记录 一.计算机里进行非数值处理的对象基本上是字符串数据,比处理浮点和整数都要复杂 string串定义:由 0 个或多个 字符 组成的 有限的 序列,通常记为:s ="a1 a2 a3 - ai -an"  ( n≥0 ,且n是有限的).其中的引号不属于串,只是一个标记作用! n就是串的长度,且字符串里的字符 ai 的值由 字母.数字或其他字符 组成的. 二.字符串为什么要用双引号标记 作用:避免字符串与变量名或数的常量混淆.   char

数据结构的C++实现之队列的顺序存储结构(循环队列)

队列(Queue)是只允许在一端进行插入操作,而在另一端进行删除操作的线性表.是一种先进先出的线性表(FIFO). 允许插入的一端称为队尾,允许删除的一端称为队头.我们在<栈的顺序存储结构>中发现,栈操作的top指针在Push时增 大而在Pop时减小,栈空间是可以重复利用的,而队列的front.rear指针都在一直增大,虽然前面的元素已经出队了,但它 所占的存储空间却不能重复利用.但大多数程序并不是这样使用队列的,一般情况下出队的元素就不再有保存价值了,这些 元素的存储空间应该回收利用,由此想