zoukankan      html  css  js  c++  java
  • 队列学习笔记

      队列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

      

  • 相关阅读:
    数据存储过程学习
    Python实现目录文件扫描功能
    python关于邮件发送包smtplib的实践分享
    Java自学笔记之学生管理系统
    Java自学笔记
    Java自学笔记1206
    java自学笔记
    Django常用命令
    django+SQLite搭建轻量级个人博客(六)数据校验
    django+SQLite搭建轻量级个人博客(五)分页
  • 原文地址:https://www.cnblogs.com/zhou2011/p/2889995.html
Copyright © 2011-2022 走看看