在这篇文章中,我们将看到什么是动态队列以及如何实现它。简而言之,动态队列是一个队列,其分配的内存可以根据队列中插入或删除的元素数量而增加或减少。
内容列表
- 问题简介
- 队列的属性
- 队列操作
- 用数组实现队列
- 使用数组实现队列的局限性
- 动态队列。用关联列表实现队列
- 在队列实现的范围内对关联列表和数组的比较
- 总结
问题简介
在这个问题中,我们将看到如何实现一个动态队列,即可调整大小的队列。
队列的属性
根据定义,队列是一种线性数据结构,其中元素的插入和删除是以这样的方式进行的,即先插入的元素将先从队列中删除。换句话说,队列数据结构的工作原理是FIFO(先入先出)。它可以通过使用任何以线性方式表现的数据结构来实现,如数组、链接列表等。
队列操作
一个典型的队列有以下操作:
1 Enqueue
2 Dequeue
Enqueue在队列中插入一个元素。
Dequeue从队列中移除/删除一个元素。
这些元素只能按照在队列中排队的相同顺序从队列中删除。
Enqueue和Dequeue操作通常以线性时间执行,即O(1)。
现在,如果我们谈论队列的应用,它是一种数据结构,可以用来实现CPU中的任务调度等。
用数组实现队列
由于队列是一个线性数据结构,我们可以用一个数组来实现它,因为元素以线性方式存储在数组中。
我们将用Java语言来实现它。
以下是队列类的定义:
class Queue{
private int front;
private int rear;
private int[] queue;
public Queue(int size){
this.queue = new int[size];
this.front = 0;
this.rear = 0;
}
public void enqueue(int value){
// to be implemented
}
public int dequeue(){
// to be implemented
return null;
}
public void printQueue(){
for(int i=0; i < rear; i++){
System.out.println(this.queue[i]);
}
}
}
如果我们看到上面有两个指针被用于队列类的前部和后部,这些指针将用于在线性时间内执行插入和删除操作,即O(1)。最初,前部和后部都指向队列的起始元素,我们可以看到0值被分配给两个指针,即它指的是起始索引。当队列执行enqueue/dequeue操作时,前面和后面的指针会相应地被更新。
让我们看看下面的enqueue函数的实现,并理解如何在线性时间内使用后部来执行它。
public void enqueue(int value){
if (rear <= queue.length-1){
queue[rear++] = value;
} else {
System.out.println("Queue is full ..");
}
}
看看enqueue函数的实现,最初,我们的后指针是0,当我们enqueue一个元素时,它将在第0个索引上插入,并将自己增加1,这样,下一次当元素被插入时,它将插入下一个索引上。这就是enqueue函数将元素放入队列的方式,它将在线性时间内执行这一操作。
现在让我们来看看dequeue函数的实现,以及如何在线性时间内使用front来执行它。
public int dequeue(){
int element = 0;
if (front == rear){
System.out.println("Queue is empty ..");
} else {
element = queue[front];
queue[front] = 0;
front++;
}
return element;
}
Front指向队列的第一个元素,因为它的值仍然是0,在enqueue函数中没有被操作。当调用dequeue函数时,front指向的元素将被设置为0,这意味着该元素被从队列中移除,现在队列中的空间被消耗了,然后front被增加1,所以现在它指向队列中的下一个元素,现在是队列的前面。
下面是使用java中的数组实现的队列与驱动类的完整代码:
class Queue{
private int front;
private int rear;
private int[] queue;
public Queue(int size){
this.queue = new int[size];
this.front = 0;
this.rear = 0;
}
public void enqueue(int value){
if (rear <= queue.length-1){
queue[rear++] = value;
} else {
System.out.println("Queue is full ..");
}
}
public int dequeue(){
int element = 0;
if (front == rear){
System.out.println("Queue is empty ..");
} else {
element = queue[front];
queue[front] = 0;
front++;
}
return element;
}
public void printQueue(){
for(int i=0; i < rear; i++){
System.out.println(this.queue[i]);
}
}
}
class Main{
public static void main(String args[]){
Queue queue = new Queue(5);
queue.enqueue(4);
queue.enqueue(5);
queue.enqueue(6);
queue.enqueue(7);
System.out.println("Following are the elements in the queue");
queue.printQueue();
System.out.println("The element dequeued from the queue is " + queue.dequeue());
System.out.println("Following are the elements in the queue");
queue.printQueue();
}
}
使用数组实现队列的局限性
以下是使用数组来实现队列的两个主要限制。
- 队列大小
- 内存浪费
队列大小。
这是使用数组实现队列时可能面临的最常见的问题,因为数组大小需要提前声明,这是数组数据结构的限制。因此,使用数组实现队列时,队列的大小将受到限制。由于使用数组时不能实现动态内存分配,因此使用数组时不能实现动态队列。
内存浪费。
你可以从上面的实现中看到,当我们在队列中排队等待一个元素时,我们使用后置指针来执行这个操作,当我们从队列中排队等待一个元素时,我们使用前置指针。由前部指向的元素将被检索到一个变量中,这个索引被零值保持,表示位于这里的元素被删除,前部被增加,指向队列中的下一个元素。

你可以看到,如果一个元素从队列中被移除,那么数组的位置(元素被移除的地方)就被浪费了,我们不能再使用它在那个位置插入一个新的元素,因为如果我们这样做,就违反了队列的工作原则,即先进先出。 这样一来
,数组的很多空间被浪费了,将来也无法使用。所以,这就是使用数组实现队列导致内存浪费的原因。
动态队列。用关联列表实现队列
由于关联列表也是以线性方式工作的,所以我们可以使用关联列表来实现队列。
如果我们在列表的末尾插入一个新的元素,并从列表的顶部删除一个元素,那么链接列表将作为一个队列工作。同样地,我们将使用前后两个指针来实现它,就像我们在数组中所做的那样。
下面是使用关联列表的队列的完整实现。
class Node{
int value;
Node next;
public Node(int value){
this.value = value;
this.next = null;
}
}
class Queue{
private Node front, rear;
public Queue(){
this.front = this.rear = null;
}
public void enqueue(int value){
Node new_node = new Node(value);
if (this.rear == null){
this.front = this.rear = new_node;
} else {
this.rear.next = new_node;
this.rear = new_node;
}
}
public Node dequeue(){
Node element;
if(this.front == null){
element = null;
} else {
element = this.front;
this.front = this.front.next;
element.next = null;
}
if(this.front == null){
this.rear = null;
}
return element;
}
public void printQueue(){
Node node = this.front;
if(node != null){
while(node != null){
System.out.println(node.value);
node = node.next;
}
} else {
System.out.println("Queue is empty ...");
}
}
}
class Main {
public static void main(String[] args) {
Queue queue = new Queue();
queue.enqueue(1);
queue.enqueue(2);
queue.enqueue(3);
queue.enqueue(4);
queue.enqueue(5);
System.out.println("The intial queue is following");
queue.printQueue();
System.out.println("The element which is dequeued: " + queue.dequeue().value);
System.out.println("Now the queue is following");
queue.printQueue();
}
}
从上面的实现可以看出,当enqueue被调用时,一个元素被使用rear指针插入到链接列表的末尾,新的元素被放在rear的下一个位置,成为新的rear,而当dequeue被调用时,一个元素被使用front指针从链接列表的顶部移除,被front指向的元素被dequeue函数返回,front在链接列表中向前移动1位。

这两种操作都是在线性时间内进行的,也就是O(1)。
链接列表和数组在队列实现范围内的比较
好吧,让我们来谈谈在这个前景中对关联列表和数组的比较。
正如在**"在队列实现中使用数组的限制 "**一节中所阐述的,为了实现一个内存友好的动态队列,我们不能使用数组,因为数组的大小是固定的,也就是说,需要提前声明,队列永远不会自动调整大小,这是动态队列的关键属性,而且,在使用数组的时候会浪费很多计算机内存。与此相反,如果使用链表来实现队列,它的内存效率要高得多,而且它的大小可以自动增加或减少,这一点在上一节中作了解释和说明。
结论
所以,所有上述讨论的结论是,实现一个内存友好的动态队列,关联列表应该是首选的数据结构。数组不能用来实现动态队列。
还有一点需要注意的是,在实现动态队列时,关联列表在所有方面都比数组要好。