数据结构之队列定义及基本操作实现

  数据结构学着就是有意思,真诚推荐郝斌老师的数据结构视频,真的讲解的非常详细,容易理解。

  一直在跟着郝斌老师的数据结构视频学习,看完了队列的视频,记录下来,总结一下。

  队列的定义:队列是一种特殊的线性表,只允许在表的头部(front处)进行删除操作,在表的尾部(rear处)进行插入操作的线性数据结构,这种结构就叫做队列。进行插入操作的一端称为队尾,进行删除操作的一端称为队尾。

  队列的类型:链式队列,即用链表实现的队列。静态队列:即用数组实现的队列。在这里,我们采用用数组实现的静态队列。因为用链表实现的队列,是一种动态队列,操作更加容易,所以我们这里采用的是静态队列。

  在静态队列中,郝斌老师讲的是一种更为复杂的静态循环队列。这里解释一下就是,假如我们采用的是静态队列,那就必然涉及到数组,如果我们在不断的从队列中删除数据,那么我们队列的头部(front处)就会不断的向上走(在这里说明一下,队列中front永远指向队列的头部元素,rear永远指向队列的最后一个元素的下一个元素),这样就会造成我们被删除的位置永远无法再被利用,就是造成空间的浪费,所以,如果这样,队列这种数据结构也就失去了原本存在的意义。所以,用静态数组实现的静态队列一般都是静态循环队列。那么,问题就来了,既然是静态循环队列,什么时候队列才是空呢?什么时候队列才是满呢?

  这里首先先说一下,什么时候队列才能为空,根据我们前面的讲解,队列在删除时,front会不断的向上移动,如下面的示意图:

如上面的示意图,在初始状态下front和rear都处于0的位置,在插入元素时,rear会向上走,则填入一个元素后0的位置就会被填入,当再填入一个元素后1的位置则又被填满。此时rear在2的位置,front在0的位置,此时队列中有两个元素,分别位于0和1处。那么,怎么才算是队列为空呢?很简单,就是不断的删除元素,当元素全都被删完了以后就表示当前队列为空了。删除0的元素,front将会移动一个位置到1,再删除一个元素front将会移动一个位置到2,此时整个队列中没有元素存在,整个队列就为空。所以,表示队列是否为空的条件就是front和rear处于一个位置上。

  那么,何时才表示当前队列已满呢?可能大家也猜出来了,大家可能会认为当front和rear在同一位置时,整个队列也可以同时表示当前队列已满。那就是不断的插入元素,rear不断地移动位置,直到rear再次移动到0的位置后,所有的位置都被元素插满,此时整个队列已满。没错,此时整个队列确实已满。但是这样就会和我们的队列为空的条件重复,所以这里我们有两种办法来解决这种情况,我们可以定义一个表示count个数的值,表示当前队列中元素的个数。如果front和rear处于同一个位置,并且count为0则队列为空。如果front和rear处于同一个位置,并且count不为9,则表示当前队列已满。另外一个方法就是我们默认不全用整个属于,我们用n-1个元素,这样,当rear和front处于“紧挨着”的位置时,就表示当前队列已满,并且在下标来看,并没有严格的front和rear的大小关系。

  好了,如果前面大家已经理解,那么,接下来就可以来实现队列的基本操作了(代码都有注释,我就不再详细解释了)。

  队列的定义:

 1 /*
 2 定义队列
 3 队列是一种只能在一段插入一段删除的数据结构。
 4 这里采用静态循环队列,所以队列采用数组实现 
 5 */
 6 typedef struct Queue{
 7     int * pBase;  //用来存放数组 
 8     int front;  //用来表示当前为front,指向队列头 
 9     int rear;   //用来表示当前为rear,指向队列尾部的下一个结点 
10 }QUEUE;

  队列的初始化操作:大家在这里也可以将队列的长度作为参数传入,我这里就直接写了

1 /*
2 队列初始化函数
3 初始化一个空队列,就是给出队列一个长度,并且里面没有任何数值 
4 */
5 void init(QUEUE *pQ){
6     pQ->pBase=(int *)malloc(sizeof(int)*6);  //给出一个6个整形长度的队列数组
7     pQ->front=0;
8     pQ->rear=0;  //将队列初始化为空    
9 }

  队列的入队函数:

 1 /*
 2 判断当前队列是否已满的函数
 3 因为我们默认认为当一个n的队列已经存入了n-1个值后,就默认它已经满了,不允许它再存储了,所以,
 4 判断队列是否已满就是判断rear和front是否已经是“紧挨着”,即rear在移动一位就会和front重合。 
 5 */
 6 int full_queue(QUEUE *pQ){
 7     if((pQ->rear+1)%6==pQ->front){
 8         return 0;
 9     }
10     else{
11         return 1;
12     }
13 }
14 
15 /*
16 入队函数
17 根据队列的特性,入队只能在队列的尾部,即rear处进行,所以入队函数的算法就是将被插入的数据放在rear的位置
18 插入完成后rear向后移动一位,使rear永远指向队列中最后一个元素的下一个元素。 
19 在这里有一个需要注意的地方:在我们移动rear的位置时,如果当前rear的位置已经是数组的最后一个位置,应该怎么办?
20 就应该让rear在回到0的位置,因为我们的队列默认设置的就是循环队列,也就是说,我们的队列是可以拐弯的,当队列没有满,
21 但是rear却已经在队列的最后一个位置了,那我们就应该在将队列移动到0的位置。这种情形在数学上就是加1取余。 
22 */
23 int en_queue(QUEUE *pQ, int val){
24     if(full_queue(pQ)==0){
25         return 0;
26     }    
27     else{
28         pQ->pBase[pQ->rear]=val;
29         pQ->rear=(pQ->rear+1)%6;
30         return 1; 
31     }
32 }

  队列的遍历函数:

 1 /*
 2 遍历队列的函数 
 3 因为这只是遍历一个队列,所以我们不能破坏原有的队列结构,所以,我们必然需要去定义一个新的i来表示当前所在的下标。
 4 遍历的语法非常简单,就是从头开始,直到队列的末尾,即i到了最后一个结点的下一个结点(rear处),遍历就算是结束了。 
 5 */
 6 void traverse_queue(QUEUE *pQ){
 7     int i=pQ->front;  //令i从“头”开始遍历
 8     
 9     while(i!=pQ->rear){
10         printf("%d ",pQ->pBase[i]);
11         i=(i+1)%6;  //将i向下移动一个位置 
12     }    
13 }

  出队函数:

 1 /*
 2 当前队列是否为空的函数 
 3 */
 4 int empty_queue(QUEUE *pQ){
 5     if(pQ->front==pQ->rear){
 6         return 0;
 7     }
 8     else{
 9         return 1;
10     }
11 } 
12 
13 /*
14 出队函数 
15 因为出队函数已经破换了队列原有的数据结构,所以我们就没有必要去重新定义一个新的下标
16 出队函数的算法非常简单,就是在队列的头部(front处)进行删除,删除之后将front向下移动一个位置。 
17 */
18 int out_quene(QUEUE *pQ,int *pVal){
19     if(empty_queue(pQ)==0){
20         return 1;  //1表示当前队列为空 
21     }
22     else{
23         *pVal=pQ->pBase[pQ->front];
24         pQ->front=(pQ->front+1)%6;
25         return 0;  //0表示当前当前出队成功 
26     }    
27 } 

  上述就是队列的定义及基本操作的代码实现。

本文系原创,若转载请标明出处。

原文地址:https://www.cnblogs.com/WuNaiHuaLuo/p/4930296.html