队列学习笔记

  队列queue,是只允许在一端进行插入操作,而在另一端进行删除操作的线性表。遵循FIFO,允许插入的一端是队尾,允许删除的一端是队头。

  为了避免当队中只有一个元素时,队头和队尾重合使处理变得麻烦,引入了两个指针,front指向队头,rear指向队尾元素的下一位置。当front等于rear时,队列为空。假设一个队列有n个元素,则顺序存储的队列需要建立一个大于n的数组,并把队列中的所有元素存储在数组的前n个单元,数组下标为0的一端为队头。那么入队时,其实就是在队尾追加一个元素,不需要移动任何元素,时间复杂度为o(1).而出队时,则将队头元素移除,即将数组下标为0位置处的元素移除,那么这样就意味着队列中的所有元素都得向前移动,以保证队列的队头,这样一来,该操作的时间复杂度为O(n)。

  循环队列:将队列的头尾相接的顺序存储结构。 

  补充下基础知识:取模 %
   a % b 表示a除以b的余数
   (a+b) % p,其结果是a+b算术和除以p的余数,即(a+b) = kp + r,则(a+b)%p = r;
   例如:rear=1,front=2,SIZE=5 则(rear+1)%SIZE =>  (1+1)%5 = k*5 + r = 0*5 + 2;所以值为2,等于front,所以队列满。 

  队列顺序存储结构的定义。front指向队头,若队列不为空,rear指向队尾元素的下一位。

1 #define MAXSIZE   100
2 typedef int datatype;
3 typedef struct 
4 {
5     datatype data[MAXSIZE];
6     int front;
7     int rear;
8 }squeue;

  获取队列长度:

1 int  LengthQueue(squeue *sq)
2 {
3     return ((sq->rear - sq->front + MAXSIZE) % MAXSIZE);
4 }

  入队操作:循环队列入队前先判断队列是否满了,判断条件是(sq->rear+1)% MAXSIZE 是否和sq->front相等,如果相等则表示队列满。入队后,同样要改变rear的位置,不能简单的rear+1,而是通过(sq->rear + 1)% MAXSIZE的值赋给新队列的sq->rear。

 1 int EnterQueue(squeue *sq, datatype x)
 2 {
 3     if ((sq->rear + 1) % MAXSIZE == sq->front)
 4     {
 5         printf("queue full\n");
 6         return -1;
 7     }
 8     sq->data[sq->rear] = x;
 9     sq->rear = (sq->rear + 1) % MAXSIZE;
10     return 0;
11 }

  出队操作:循环队列的出队操作先判断队列是否为空。不为空则,将队头元素出队,front向后移动一位到最后则转到数组的头部,因此可以通过sq->front = (sq->front + 1)% MAXSIZE操作,处理该情况。

 1 datatype DeleteQueue(squeue *sq)
 2 {
 3     if (sq->rear == sq->front)
 4     {
 5         printf("queue empty\n");
 6         return -1;
 7     }  
 8     sq->front = (sq->front + 1) % MAXSIZE;
 9     return sq->data[sq->front];
10 }

  因为队列的顺序存储存在溢出的可能,所以我们又考虑循环队列的链式存储。

  循环队列的链式存储:为了操作方便,我们将队头指针指向头结点,而队尾指针指向终端结点。即front-->头结点-->对头-->。。。-->队尾 <--rear。空队列时,front和rear都指向头结点。

  结点及链队列定义:结点中包含一个数据域data以及一个指向下一个结点的指针域。而在链队列结构体定义中包含两个指向结点的front和rear指针。

 1 typedef int datatype;
 2 typedef struct qnode
 3 {
 4     datatype data;
 5     struct qnode *next;
 6 }qnode, *qptr;
 7 
 8 typedef struct 
 9 {
10     qptr front; 
11     qptr rear;
12 }linkqueue;

  链队列的入队操作:

  方法一中,先要入队的结点申请一个空间,并将新结点的值赋给p->data = x;将新结点p的后续置空:q->next = NULL;接着将新结点p赋值给原队尾结点的后继:lq->rear->front = p;最后,把当前结点p设置为新队尾结点:lq->rear = p;

  方法二中:直接给原队尾的后续开辟一个结点空间:lq->rear->next = (qptr)malloc(sizeof(qnode));然后将新开辟的结点设置为队尾结点:lq->rear = lq->rear->front;接着赋值:lq->rear->data = x;最后将新队尾结点的后续置空:lq->rear->next = NULL;

 1 int  EnterLinkQueue(linkqueue *lq, datatype x)
 2 {
 3     qptr *p;
 4     p = (qptr *)malloc(sizeof(qnode));
 5     if (p == NULL)
 6         return -1;
 7     p->data = x;
 8     p->next = NULL;
 9     
10     lq->rear->front = p;
11     lq->rear = p;
12     return 0;
13 }
14 
15 int EnterLinkQueueTwo(linkqueue *lq, datatype x)
16 {
17     lq->rear->next = (qptr *)malloc(sizeof(qnode));
18     lq->rear = lq->rear->next;
19    
20     lq->rear->data = x;
21     lq->rear->next = NULL;
22     return 0;
23 }

  链队列的出队操作:出队操作在队头,即将头结点的后继出队,让头结点的后继的后继结点作为新的头结点后继结点。额。。。。。。若队列除头结点外只剩下一个元素时,则需要将rear指针指向头结点。

  方法一:首先判断链队列是否为空,然后用一个结点指针p指向待删的队头结点:p = lq->front->next;并保存其值。接着,将待删结点的后继赋给头结点的后继:lq->front->next = p->next;判断,如果队头是队尾,则删除后将rear指向头结点。如果链队列中只有一个元素,则出队后,lq->rear应该指向头结点,即lq->rear=lq->front

  方法二:直接将头结点下移至原链队列的队头元素的位置。然后删除原头结点,设置新的头结点。

 1 datatype DeleteQueue(linkqueue *lq)
 2 {
 3     datatype data;
 4     qptr p;
 5    
 6     if (lq->front == lq->rear)
 7     {
 8         printf("linkqueue empty\n");
 9         return -1;
10     }
11     p = lq->front->next;
12     data = p->data;
13     lq->front->next = p->next;
14     if (lq->rear == p)
15     {
16         lq->rear = lq->front;
17     }
18     free(p);
19     return data;
20 }
21 
22 datatype DeleteQueueTwo(linkqueue *lq)
23 {
24     qptr q;
25     p = lq->front;
26     lq->front = q->next;
27     free(p);
28     return lq->front->data;
29 }

  创建一个空链队列:一个是链队列结构,一个是结点结构。

1 linkqueue *CreateLinkQueue()
2 {
3     linkqueue *lq;
4     lq = (linkqueue *)malloc(sizeof(linkqueue));
5    
6     lq->front = lq->rear = (qptr)malloc(sizeof(qnode));
7     lq->front->next = NULL;
8     return lq;
9 }

  创建一个空顺序循环队列:

1 int  InitQueue(squeue * sq)
2 {
3     sq->front = 0;
4     sq->rear = 0;
5     return 0;
6 }

  

  循环队列和链队列比较:1、从时间复杂度上考虑,他们的操作都是常数时间O(1),不过循环队列的顺序结构要事先申请好空间,使用期间不释放,而对于链式队列,每次申请和释放也会存在一些时间开销,如果入队出队频繁,则两者还是存在细微的差异。2、从空间复杂度上考虑,循环队列的顺序结构必须有一个固定的长度,所以就有了存储元素个数和空间浪费的问题。而链队列不存在该问题,尽管需要一个指针域的空间开销,但还是可以接受的,因此在空间上,链队列更加灵活。

  如果在确定队列长度最大值的情况下,建议使用循环队列的顺序结构,如果无法预估队列的长度时,用链队列。

2013-02-02 14:18

  

原文地址:https://www.cnblogs.com/zhou2011/p/2889995.html