什么是动态队列以及如何实现它

830 阅读7分钟

在这篇文章中,我们将看到什么是动态队列以及如何实现它。简而言之,动态队列是一个队列,其分配的内存可以根据队列中插入或删除的元素数量而增加或减少。

内容列表

  1. 问题简介
  2. 队列的属性
  3. 队列操作
  4. 用数组实现队列
  5. 使用数组实现队列的局限性
  6. 动态队列。用关联列表实现队列
  7. 在队列实现的范围内对关联列表和数组的比较
  8. 总结

问题简介

在这个问题中,我们将看到如何实现一个动态队列,即可调整大小的队列。

队列的属性

根据定义,队列是一种线性数据结构,其中元素的插入和删除是以这样的方式进行的,即先插入的元素将先从队列中删除。换句话说,队列数据结构的工作原理是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();
        
    }
}

使用数组实现队列的局限性

以下是使用数组来实现队列的两个主要限制。

  1. 队列大小
  2. 内存浪费

队列大小。

这是使用数组实现队列时可能面临的最常见的问题,因为数组大小需要提前声明,这是数组数据结构的限制。因此,使用数组实现队列时,队列的大小将受到限制。由于使用数组时不能实现动态内存分配,因此使用数组时不能实现动态队列。

内存浪费。

你可以从上面的实现中看到,当我们在队列中排队等待一个元素时,我们使用后置指针来执行这个操作,当我们从队列中排队等待一个元素时,我们使用前置指针。由前部指向的元素将被检索到一个变量中,这个索引被零值保持,表示位于这里的元素被删除,前部被增加,指向队列中的下一个元素。
queue-memory-wastage-example-min
你可以看到,如果一个元素从队列中被移除,那么数组的位置(元素被移除的地方)就被浪费了,我们不能再使用它在那个位置插入一个新的元素,因为如果我们这样做,就违反了队列的工作原则,即先进先出。 这样一来
,数组的很多空间被浪费了,将来也无法使用。所以,这就是使用数组实现队列导致内存浪费的原因。

动态队列。用关联列表实现队列

由于关联列表也是以线性方式工作的,所以我们可以使用关联列表来实现队列。

如果我们在列表的末尾插入一个新的元素,并从列表的顶部删除一个元素,那么链接列表将作为一个队列工作。同样地,我们将使用前后两个指针来实现它,就像我们在数组中所做的那样。

下面是使用关联列表的队列的完整实现。

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位。
queue-linkedlist-example-min
这两种操作都是在线性时间内进行的,也就是O(1)

链接列表和数组在队列实现范围内的比较

好吧,让我们来谈谈在这个前景中对关联列表和数组的比较。
正如在**"在队列实现中使用数组的限制 "**一节中所阐述的,为了实现一个内存友好的动态队列,我们不能使用数组,因为数组的大小是固定的,也就是说,需要提前声明,队列永远不会自动调整大小,这是动态队列的关键属性,而且,在使用数组的时候会浪费很多计算机内存。与此相反,如果使用链表来实现队列,它的内存效率要高得多,而且它的大小可以自动增加或减少,这一点在上一节中作了解释和说明。

结论

所以,所有上述讨论的结论是,实现一个内存友好的动态队列,关联列表应该是首选的数据结构。数组不能用来实现动态队列。

还有一点需要注意的是,在实现动态队列时,关联列表在所有方面都比数组要好。