数据结构与算法学习之(五):栈与队列(下)

队列(queue)是只允许在一端进行插入操作,另一端进行删除的线性表。与栈相反,队列是一种先进先出(First In First Out)的线性表。在实际中也经常运用到队列的概念,例如键盘的缓冲区,输入的字母“god”在输入缓冲区中也存储为“god”,而不是“dog”

队列的实现上,我们更愿意用链式存储结构来实现。根据先进先出原则,对于顺序存储结构,添加元素在表尾进行操作,其时间复杂度为O(1),而对于删除操作,在表头进行,删除表头元素后,其余元素均要向前移动一个单位,时间复杂度为O(n)。如下图所示。



对于链式存储结构而言,定义了一个头指针和尾指针之后,就可以通过指针的变化完成入队列和出队列的操作。插入和删除的时间复杂度都是O(1).

1.队列的顺序存储结构
在顺序存储结构的队列中,一般使用的是 循环队列。即将队列存储空间的最后一个位置绕到第一个位置,形成环状空间,如下图所示。若最后一个存储位置已被使用, 只要存储空间的第一个位置空闲,即可将元素加入第一个位置



将循环队列的数据和基本操作封装成一个循环队列类,用以示范。其定义与构造函数如下。
    class  sq_Queue    
    { private:  //数据成员
           int mm;         //存储空间容量
        int front;     //排头指针
              int rear;      //队尾指针
              int s;         //标志,便于判断队列是否为空
        T *q;          //循环队列存储空间首地址
         public:   //成员函数
              sq_Queue(int);    //构造函数,建立空循环队列
              void prt_sq_Queue();  //输出排头与队尾指针以及队中元素
              void ins_sq_Queue(T);  //入队
              T del_sq_Queue();     //退队
    };
    //建立容量为mm的空循环队列
    template <class T>
       sq_Queue<T>::sq_Queue(int m) 
       { mm=m;          //存储空间容量
         q=new T[mm];   //动态申请存储空间
         front=mm;   rear=mm;  s=0;
         return;
       }
1.1入队操作
入队操作步骤如下:
  1. 判断队列是否已满,所队列非空且队尾指针等于队头指针时,队列为满,发生“上溢”,结束。
  2. 队尾指针+1
  3. 新元素插入队尾指针指向的位置
代码如下
    template <class T>
       void sq_Queue<T>::ins_sq_Queue(T x)
    { if ((s==1)&&(rear==front))  //存储空间已满,上溢错误
            { cout <<"Queue_overflow!" <<endl;   return; }
         rear=rear+1;  //队尾指针进一
         if (rear==mm+1) rear=1;
         q[rear-1]=x;   //新元素入队
         s=1;   //入队后队列非空
      return;
       }
1.2退队操作
退队操作的步骤如下:
  1. 判断队列是否为空,若s=0,为空,此时称作“下溢”,结束。
  2. 若不为空,队头指针+1
  3. 判断退队后是否为空,若为空,置标志位
代码如下
    template <class T>
       void sq_Queue<T>::ins_sq_Queue(T x)
    { if ((s==1)&&(rear==front))  //存储空间已满,上溢错误
            { cout <<"Queue_overflow!" <<endl;   return; }
         rear=rear+1;  //队尾指针进一
         if (rear==mm+1) rear=1;
         q[rear-1]=x;   //新元素入队
         s=1;   //入队后队列非空
      return;
       }
2.队列的链式存储结构
链式存储结构的队列如下图



同样的,其类的定义于构造函数如下
    struct node
       { T d;
         node *next;
       };
    //定义带链队列类
       template <class T> 
    class  linked_Queue
    { private:  //数据成员
        node<T> *front;    //带链队列排头指针
        node<T> *rear;    //带链队列队尾指针
         public:   //成员函数
             linked_Queue();    //构造函数,建立空队列,即队列初始化
          void prt_linked_Queue();  //顺序输出带链队列中的元素
          void ins_linked_Queue(T);  //入队
          T del_linked_Queue(); //退队
    };
    //带链队列初始化
    template <class T>
       linked_Queue<T>::linked_Queue() 
       { front=NULL;  rear=NULL;  //排头指针与队尾指针均为空
         return;
       }
2.1入队操作
链式存储结构的步骤如下:
  1. 申请一个新的结点,并置数据域为入队值
  2. 原来队尾结点的指针指向新结点
  3. 队尾指针指向新结点
代码如下
   template <class T>
       void linked_Queue<T>::ins_linked_Queue(T x)
    { node<T> *p;
         p=new node<T>;   //申请一个新结点
         p->d=x;              //置新结点数据域值
         p->next=NULL;        //置新结点指针域值为空
         if (rear==NULL)      //原队列为空
                 front=p;
      else
                 rear->next=p;    //原队尾结点的指针指向新结点
         rear=p;               //队尾指针指向新结点
      return;
       }
2.2退队操作
退队操作的步骤如下
排头指针指向下一个结点
释放结点空间
代码如下
    template <class T>
       T linked_Queue<T>::del_linked_Queue()
    { T y;
         node<T> *q;
         if (front==NULL) { cout <<"空队!" <<endl; return(0); }
         y=front->d;         //排头元素赋给变量
         q=front;
         front=q->next;   //排头指针指向下一个结点
         delete q;      //释放结点空间
         if (front==NULL) rear=NULL;  //队列已空
      return(y);  //返回退队的元素
       }
为了方便测试,定义一个输出函数prt和检测队列是否为空的函数flag,对应实现代码如下,
        //输出
       template <class T>
       void linked_Queue<T>::prt_linked_Queue()
    { node<T> *p;
         p=front;
         if (p==NULL) { cout <<"空队列!" <<endl; return; }
         do { cout <<p->d <<endl;
              p=p->next;
            } while (p!=NULL);
      return;
       }
            //检测
    template <class T>
       int linked_Queue<T>::flag_linked_Queue()
       { if (front==NULL) return(0);  //若带链的队列为空,则函数返回0
         return(1);  //正常函数返回1
       }

在main函数中测试,代码如下
       int main()
       { linked_Queue<int> q;  //建立一个空的带链队列,元素为整型
         q.ins_linked_Queue(50);  //插入50
         q.ins_linked_Queue(60);  //插入60
         q.ins_linked_Queue(70);  //插入70
         q.ins_linked_Queue(80);  //插入80
         q.ins_linked_Queue(90);  //插入90
         q.ins_linked_Queue(100); //插入100
         cout <<"输出带链队列中的元素:" <<endl;
      q.prt_linked_Queue();
      if (q.flag_linked_Queue())  //若带链队列非空,则退队
          cout <<"输出退队元素:" <<q.del_linked_Queue() <<endl;
         if (q.flag_linked_Queue())  //若带链队列非空,则退队
          cout <<"输出退队元素:" <<q.del_linked_Queue() <<endl;
         if (q.flag_linked_Queue())  //若带链队列非空,则退队
          cout <<"输出退队元素:" <<q.del_linked_Queue() <<endl;
         cout <<"再次输出带链队列中的元素:" <<endl;
      q.prt_linked_Queue();
         return 0;
       }

测试结果如下图,



总结:队列先进先出的原则与栈的后进先出原则相对应,引用博客大牛“真实的归宿 ”的例子来总结以下队列应用。
【举例1】模拟打印机缓冲区。
在主机将数据输出到打印机时,会出现主机速度与打印机的打印速度不匹配的问题。这时主机就要停下来等待打印机。显然,这样会降低主机的使用效率。为此人们设想了一种办法:为打印机设置一个打印数据缓冲区,当主机需要打印数据时,先将数据依次写入这个缓冲区,写满后主机转去做其他的事情,而打印机就从缓冲区中按照先进先出的原则依次读取数据并打印,这样做即保证了打印数据的正确性,又提高了主机的使用效率。由此可见,打印机缓冲区实际上就是一个队列结构。
【举例2】CPU分时系统
在一个带有多个终端的计算机系统中,同时有多个用户需要使用CPU运行各自的应用程序,它们分别通过各自的终端向操作系统提出使用CPU的请求,操作系统通常按照每个请求在时间上的先后顺序,将它们排成一个队列,每次把CPU分配给当前队首的请求用户,即将该用户的应用程序投入运行,当该程序运行完毕或用完规定的时间片后,操作系统再将CPU分配给新的队首请求用户,这样即可以满足每个用户的请求,又可以使CPU正常工作。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值