您的位置:首页 > 理论基础 > 数据结构算法

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

2015-11-02 15:33 603 查看
  数据结构学着就是有意思,真诚推荐郝斌老师的数据结构视频,真的讲解的非常详细,容易理解。

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

  队列的定义:队列是一种特殊的线性表,只允许在表的头部(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的大小关系。

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

  队列的定义:

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


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

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


  队列的入队函数:

/*
判断当前队列是否已满的函数
因为我们默认认为当一个n的队列已经存入了n-1个值后,就默认它已经满了,不允许它再存储了,所以,
判断队列是否已满就是判断rear和front是否已经是“紧挨着”,即rear在移动一位就会和front重合。
*/
int full_queue(QUEUE *pQ){
if((pQ->rear+1)%6==pQ->front){
return 0;
}
else{
return 1;
}
}

/*
入队函数
根据队列的特性,入队只能在队列的尾部,即rear处进行,所以入队函数的算法就是将被插入的数据放在rear的位置
插入完成后rear向后移动一位,使rear永远指向队列中最后一个元素的下一个元素。
在这里有一个需要注意的地方:在我们移动rear的位置时,如果当前rear的位置已经是数组的最后一个位置,应该怎么办?
就应该让rear在回到0的位置,因为我们的队列默认设置的就是循环队列,也就是说,我们的队列是可以拐弯的,当队列没有满,
但是rear却已经在队列的最后一个位置了,那我们就应该在将队列移动到0的位置。这种情形在数学上就是加1取余。
*/
int en_queue(QUEUE *pQ, int val){
if(full_queue(pQ)==0){
return 0;
}
else{
pQ->pBase[pQ->rear]=val;
pQ->rear=(pQ->rear+1)%6;
return 1;
}
}


  队列的遍历函数:

/*
遍历队列的函数
因为这只是遍历一个队列,所以我们不能破坏原有的队列结构,所以,我们必然需要去定义一个新的i来表示当前所在的下标。
遍历的语法非常简单,就是从头开始,直到队列的末尾,即i到了最后一个结点的下一个结点(rear处),遍历就算是结束了。
*/
void traverse_queue(QUEUE *pQ){
int i=pQ->front;  //令i从“头”开始遍历

while(i!=pQ->rear){
printf("%d ",pQ->pBase[i]);
i=(i+1)%6;  //将i向下移动一个位置
}
}


  出队函数:

/*
当前队列是否为空的函数
*/
int empty_queue(QUEUE *pQ){
if(pQ->front==pQ->rear){
return 0;
}
else{
return 1;
}
}

/*
出队函数
因为出队函数已经破换了队列原有的数据结构,所以我们就没有必要去重新定义一个新的下标
出队函数的算法非常简单,就是在队列的头部(front处)进行删除,删除之后将front向下移动一个位置。
*/
int out_quene(QUEUE *pQ,int *pVal){
if(empty_queue(pQ)==0){
return 1;  //1表示当前队列为空
}
else{
*pVal=pQ->pBase[pQ->front];
pQ->front=(pQ->front+1)%6;
return 0;  //0表示当前当前出队成功
}
}


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

本文系原创,若转载请标明出处。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: