什么是顺序队列
顺序队列是先进先出的数据结构,它是一种特殊的线性表,只能在队头front进行删除操作,只能在队尾rear进行插入操作,所以把进行插入操作的一端称为队尾,进行删除操作的一端称为队头。
顺序队列的队头指针front指向队头元素,队尾指针rear指向下一个入队元素的存储位置
由于入队和出队操作时头尾指针只会增加不会减小,当rear指向到分配的连续空间之外时,队列无法再插入新元素,被删除元素的空间永远无法被重新利用,这种情况叫队列的“假上溢”现象,即头尾指针前的空间没有使用,但是却显示队列已满。
为了避免“假上溢”的现象,实际使用队列时,一般使用循环队列。
循环队列
在实际使用队列时,为了使队列空间可以重复使用,避免“假上溢”现象,人们使用%(取余)运算控制front和rear指针的移动:如果front指针+1或者rear指针+1时超过了一开始分配给队列的使用空间,就使用%运算使他们指向这片使用空间的起始位置。
在分配内存空间时,循环队列实际分配的空间=要分配的空间+1,此区域不进行任何操作,目的是区分队列全空和队列全满时的情况。
循环队列front指针始终指向队列头元素,rear指针指向队尾元素的下一个位置
循环队列判空条件为front == rear,判满条件为front == (rear + 1) % MaxSize
双端队列
双端队列,顾名思义就是一个两端都是结尾的队列,相对于普通队列,双端队列的头部head和尾部tail都可以进行出队和入队操作,因此双端队列可以同时实现栈和队列的操作。为了节省空间,一般双端队列都是在循环队列的基础上实现。
双端队列head指针始终指向队列头元素,tail指针指向队尾元素的下一个位置
双端队列判空条件为head == tail,判满条件为head == (tail + 1) % MaxSize
双端队列若使用头部入队和头部出队(或尾部入队和尾部出队)可以实现栈的功能
双端队列的基本操作
双端队列一般有如下基本操作:
- isFull():判断队列是否已满
- isEmpty():判断队列是否为空
- headInsert():从队头插入
- tailInsert():从队尾插入
- headPop():从队头出队
- tailPop():从队尾出队
- getHead():获取队头元素
- getTail():获取队头元素
- display():遍历双端队列
## 双端队列的定义 定义一个双端队列类DQueue,里面安放指针和各种操作的方法,使用构造器初始化maxSize的值,初始化后的queue数组的实际长度等于队列长度+1,目的是建立循环队列区分队满和队空。
- 定义变量maxSize表示双端队列的最大容量。
- 定义数组queue[]用于存放数据。
- 定义队头指针head指向队列的队头元素。
- 定义队尾指针tail指向队尾元素的下一个区域。
//双端队列类
class DQueue{
public int maxSize; //双端队列的最大容量
public int queue[]; //数据域
public int head = 0; //队头指针,指向第一个元素
public int tail = 0; //队尾指针,指向最后一个元素的后一个位置
public DQueue(int n) {
//队尾指针需要指向最后一个元素的后一个位置,所以需要空出一个位置,即数组的实际大小+1
this.maxSize = n + 1;
queue = new int[this.maxSize];
}
}
判断队满 isFull()
因为双端队列是在循环队列的基础上实现的,所以判断队满条件和循环队列判断队满的条件一致。
//判断队列是否已满
public boolean isFull() {
return (tail + 1) % maxSize == head;
}
判断队空 isEmpty()
因为双端队列是在循环队列的基础上实现的,所以判断队空条件和循环队列判断队空的条件一致。
//判断队列是否为空
public boolean isEmpty() {
return tail == head;
}
从头部入队 headInsert()
因为是从头部入队且head指针指向的是对头元素,所以从头部入队前要找到head指针所指位置的前一个位置,此位置就是待插入位置。修改head指针,使head指针-1指向待插入位置,然后使用%运算防止越界。
- 判断队列是否已满。
- 修改head指针,找到head指针的前一个位置。
- 插入数据。
//从头部入队
public void headInsert(int data) {
//先判断队列是否已满
if(isFull())
return ;
//头插,head自减1找到待插入位置,使用%防止越界
head = (head - 1 + maxSize) % maxSize;
queue[head] = data;
}
从尾部入队 tailInsert()
因为tail指向的队尾元素的后一个位置,此位置正好是待插入位置。所以从尾部入队时,先在tail所指位置插入数据,然后再修改tail指针,使tail指针+1指向插入元素位置的后一个位置,并使用%运算防止越界。
- 判断队列是否已满。
- 插入数据。
- 找到tail指针的后一个位置,修改tail指针。
//从尾部入队
public void tailInsert(int data) {
//先判断队列是否已满
if(isFull())
return ;
queue[tail] = data;
//尾插,tail自增1找到待插入的位置,使用%防止越界
tail = (tail + 1) % maxSize;
}
从头部出队 headPop()
head始终指向头部第一个元素,从头部出队前先使用变量接取待出队的值,然后修改head指针使其+1,即向后移动指向已出队元素的后一元素。修改指针时同样使用%运算防止溢出。
- 判断队列是否为空。
- 使用变量接取队头元素。
- 修改head指针,找到head指针的后一个位置。
- 返回变量值。
//从头部出队
public int headPop() {
//先判断队列是否为空
if(isEmpty())
return -1;
int value = queue[head];
//出队后向后移动head指针
head = (head + 1) % maxSize;
return value;
}
从尾部出队 tailPop()
因为tail始终指向队尾元素的下一个位置,所以出队前先使tail-1,即向前移动tail指针,使用%运算防止溢出,向前移动后的tail指针指向的元素即是队尾元素。
- 判断队列是否为空。
- 修改tail指针,找到tail指针的前一个位置。
- 使用变量接取队尾元素。
- 返回变量值。
//从尾部出队
public int tailPop() {
//先判断队列是否为空
if(isEmpty())
return -1;
//先向前移动tail指针指向待出队数据位置
tail = (tail - 1 + maxSize) % maxSize;
int value = queue[tail];
return value;
}
获取队头元素 getHead()
只需返回head指针所指元素即可。
//获取队头元素
public int getHead() {
return queue[head];
}
获取队尾元素 getTail()
队尾指针指向队尾元素的后一个位置,队尾元素位置就是tail-1。如果队尾指针目前指向的queue数组索引是0,此时-1就会产生数组越界,所以取队尾元素时需要%运算来防止越界。
//获取队尾元素
public int getTail() {
return queue[(tail - 1 + maxSize) % maxSize];
}
遍历队列 display()
双端队列的遍历方式与循环队列的遍历相同,不停往后移动head指针来遍历队列中的元素,遍历时同样需要%运算防止数组越界。
//遍历队列
public void display() {
if(isEmpty()) {
System.out.println("队列空");
return ;
}
while(head != tail) {
System.out.println(queue[head]);
//head向后移动
head = (head + 1) % maxSize;
}
}
完整代码
//循环双端队列类
class DQueue{
public int maxSize; //双端队列的最大容量
public int queue[]; //数据域
public int head = 0; //队头指针,指向第一个元素
public int tail = 0; //队尾指针,指向最后一个元素的后一个位置
public DQueue(int n) {
//队尾指针需要指向最后一个元素的后一个位置,所以需要空出一个位置,即数组的实际大小+1
this.maxSize = n + 1;
queue = new int[this.maxSize];
}
//判断队列是否已满
public boolean isFull() {
return (tail + 1) % maxSize == head;
}
//判断队列是否为空
public boolean isEmpty() {
return tail == head;
}
//从头部入队
public void headInsert(int data) {
//先判断队列是否已满
if(isFull())
return ;
//头插,head自减1找到待插入位置,使用%防止越界
head = (head - 1 + maxSize) % maxSize;
queue[head] = data;
}
//从尾部入队
public void tailInsert(int data) {
//先判断队列是否已满
if(isFull())
return ;
queue[tail] = data;
//尾插,tail自增1找到待插入的位置,使用%防止越界
tail = (tail + 1) % maxSize;
}
//从头部出队
public int headPop() {
//先判断队列是否为空
if(isEmpty())
return -1;
int value = queue[head];
//出队后向后移动head指针
head = (head + 1) % maxSize;
return value;
}
//从尾部出队
public int tailPop() {
//先判断队列是否为空
if(isEmpty())
return -1;
//先向前移动tail指针指向待出队数据位置
tail = (tail - 1 + maxSize) % maxSize;
int value = queue[tail];
return value;
}
//获取队头元素
public int getHead() {
return queue[head];
}
//获取队尾元素
public int getTail() {
return queue[(tail - 1 + maxSize) % maxSize];
}
//遍历队列
public void display() {
if(isEmpty()) {
System.out.println("队列空");
return ;
}
while(head != tail) {
System.out.println(queue[head]);
//head向后移动
head = (head + 1) % maxSize;
}
}
}