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

时间:2022-05-09 10:20:47
队列(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正常工作。