当前位置: 首页 > 知识库问答 >
问题:

数据结构-随机队列

程胡非
2023-03-14

我目前正在研究普林斯顿算法第一部分的队列分配。其中一个任务是实现随机队列。这是一个关于使用不同数据结构的实现和权衡的问题。

问题:

随机化队列类似于堆栈或队列,只是从数据结构中的项中均匀随机地选择删除的项。创建实现以下API的通用数据类型:

public class RandomizedQueue<Item> implements Iterable<Item> {
    public RandomizedQueue() // construct an empty randomized queue
    public boolean isEmpty() // is the queue empty?
    public int size() // return the number of items on the queue
    public void enqueue(Item item) // add the item
    public Item dequeue() // remove and return a random item
    public Item sample() // return (but do not remove) a random item
    public Iterator<Item> iterator() // return an independent iterator over items in random order
    public static void main(String[] args)   // unit testing
}

这里的问题是实现de队列操作和迭代器,因为de队列删除并返回随机元素,迭代器以随机顺序迭代队列。

1.数组实现:

我考虑的主要实现是数组实现。除了随机性之外,这将与数组队列的实现相同。

查询1.1:对于出列操作,我只需从数组的大小中随机选择一个数字并返回该项,然后将数组中的最后一项移动到返回项的位置。

但是,这种方法会更改队列的顺序。在这种情况下,这并不重要,因为我是按随机顺序排队的。但是,我想知道是否有一种时间/内存效率高的方法,可以在保留队列顺序的同时从数组中取出随机元素,而不必创建新数组并将所有数据传输给它。

// Current code for dequeue - changes the order of the array after dequeue
private int[] queue; // array queue
private int N; // number of items in the queue

public Item dequeue() {
    if (isEmpty()) throw NoSuchElementException("Queue is empty");
    int randomIndex = StdRandom.uniform(N);
    Item temp = queue[randomIndex]        

    if (randomIndex == N - 1) {
        queue[randomIndex] = null; // to avoid loitering
    } else {
        queue[randomIndex] = queue[N - 1];
        queue[randomIndex] = null; 
    }

    // code to resize array

    N--;
    return temp;
}

查询1.2:为了让迭代器满足随机返回元素的要求,我创建了一个包含队列所有索引的新数组,然后使用Knuth shuffle操作对数组进行洗牌,并返回队列中特定索引处的元素。但是,这需要创建一个等于队列长度的新数组。同样,我确信我错过了一个更有效的方法。

2.内部类实现

第二个实现涉及内部节点类。

public class RandomizedQueue<Item> {
    private static class Node<Item> {
        Item item;
        Node<Item> next;
        Node<Item> previous;
    }
}

查询2.1.在这种情况下,我明白了如何有效地执行队列操作:返回随机节点并更改相邻节点的引用。

然而,我对如何返回一个迭代器感到困惑,该迭代器以随机顺序返回节点,而不必创建一个以随机顺序连接节点的全新队列。

此外,除了可读性和易于实现之外,在数组上使用这种数据结构还有什么好处?

这篇文章有点长。我感谢你们花时间阅读我的问题并帮助我解决问题。谢谢

共有3个答案

狄鹏
2023-03-14

使用数组实现(必须是动态的/可调整大小的),以实现除构建迭代器之外的所有操作的恒定(摊销)最坏情况运行时(由于无序排列,这需要线性时间)。

以下是我的实现:

import java.util.Arrays;
import java.util.Iterator;
import java.util.NoSuchElementException;
import java.util.Random;

/* http://coursera.cs.princeton.edu/algs4/assignments/queues.html
 * 
 * A randomized queue is similar to a stack or queue, except that the item
 * removed is chosen uniformly at random from items in the data structure. 
 */
public class RandomizedQueue<T> implements Iterable<T> {

    private int queueEnd = 0;   /* index of the end in the queue,
                                   also the number of elements in the queue. */

    @SuppressWarnings("unchecked")
    private T[] queue = (T[]) new Object[1];    // array representing the queue

    private Random rGen = new Random();     // used for generating uniformly random numbers

    /**
     * Changes the queue size to the specified size.
     * @param newSize the new queue size.
     */
    private void resize(int newSize) {
        System.out.println("Resizing from " + queue.length + " to " + newSize);
        T[] newArray = Arrays.copyOfRange(queue, 0, newSize);
        queue = newArray;
    }


    public boolean isEmpty() {
        return queueEnd == 0;
    }

    public int size() {
        return queueEnd;
    }

    /**
     * Adds an element to the queue.
     * @param elem the new queue entry.
     */
    public void enqueue(T elem) {
        if (elem == null)
            throw new NullPointerException();

        if (queueEnd == queue.length) 
            resize(queue.length*2);

        queue[queueEnd++] = elem;
    }

    /**
     * Works in constant (amortized) time.
     * @return uniformly random entry from the queue.
     */
    public T dequeue() {    
        if (queueEnd == 0)  // can't remove element from empty queue
            throw new UnsupportedOperationException();

        if (queueEnd <= queue.length/4)     // adjusts the array size if less than a quarter of it is used
            resize(queue.length/2);

        int index = rGen.nextInt(queueEnd);     // selects a random index

        T returnValue = queue[index];   /* saves the element behind the randomly selected index 
                                            which will be returned later */

        queue[index] = queue[--queueEnd];   /* fills the hole (randomly selected index is being deleted) 
                                               with the last element in the queue */
        queue[queueEnd] = null;         // avoids loitering

        return returnValue;
    }

    /**
     * Returns the value of a random element in the queue, doesn't modify the queue.
     * @return random entry of the queue.
     */
    public T sample() {
        int index = rGen.nextInt(queueEnd);     // selects a random index
        return queue[index];
    }

    /*
     * Every iteration will (should) return entries in a different order.
     */
    private class RanQueueIterator implements Iterator<T> {

        private T[] shuffledArray;

        private int current = 0;

        public RanQueueIterator() {
            shuffledArray = queue.clone();
            shuffle(shuffledArray);
        }

        @Override
        public boolean hasNext() {
            return current < queue.length;
        }

        @Override
        public T next() {
            if (!hasNext())
                throw new NoSuchElementException();

            return shuffledArray[current++];
        }


        /**
         * Rearranges an array of objects in uniformly random order
         * (under the assumption that {@code Math.random()} generates independent
         * and uniformly distributed numbers between 0 and 1).
         * @param array the array to be shuffled
         */
        public void shuffle(T[] array) {
            int n = array.length;
            for (int i = 0; i < n; i++) {
                // choose index uniformly in [i, n-1]
                int r = i + (int) (Math.random() * (n - i));
                T swap = array[r];
                array[r] = array[i];
                array[i] = swap;
            }
        }

    }

    @Override
    public Iterator<T> iterator() {
        return new RanQueueIterator();
    }

    public static void main(String[] args) {
        RandomizedQueue<Integer> test = new RandomizedQueue<>();

        // adding 10 elements
        for (int i = 0; i < 10; i++) {
            test.enqueue(i);
            System.out.println("Added element: " + i);
            System.out.println("Current number of elements in queue: " + test.size() + "\n");

        }


        System.out.print("\nIterator test:\n[");
        for (Integer elem: test)
            System.out.print(elem + " ");
        System.out.println("]\n");

        // removing 10 elements
        for (int i = 0; i < 10; i++) {
            System.out.println("Removed element: " + test.dequeue());
            System.out.println("Current number of elements in queue: " + test.size() + "\n");
        }       

    }   
}

注:我的实施基于以下任务:http://coursera.cs.princeton.edu/algs4/assignments/queues.html

额外的挑战:尝试实现一个toString()方法。

费辰阳
2023-03-14

对于您的查询1.1:这里您确实可以在固定时间内删除一个随机元素。其思路如下:

  • 选择要返回的随机元素
  • 将其与队列中的最后一个元素交换
  • 删除队列中的最后一个元素

这样,您就可以保持连续阵列,而不存在“孔”

公孙茂学
2023-03-14

在数组实现中,查询1.1似乎是最好的方法。移除随机元素的唯一其他方法是将所有元素向上移动以填充其位置。因此,如果您有[1,2,3,4,5]并且删除了2,那么您的代码将向上移动项目3、4和5,并减少计数。这将采取,平均n/2的项目移动,每次删除。所以移除是O(n)。令人不快的

如果在迭代时不添加和删除项目,那么只需在现有数组上使用Fisher-Yates shuffle,并开始从前到后返回项目。没有理由复制。这取决于你的使用模式。如果您设想在迭代时从队列中添加和删除项目,那么如果您不创建副本,事情就会变得不稳定。

使用链表方法,很难有效地实现随机出列操作,因为为了获得随机项,必须从前面遍历列表。因此,如果您在队列中有100个项目,并且您想要删除第85个项目,那么您必须从前面开始,在到达要删除的项目之前,遵循85个链接。由于您使用的是一个双链接列表,当要删除的项目超过中间点时,您可以通过从末尾倒数来将时间减少一半,但当队列中的项目数量很大时,这仍然是非常低效的。想象一下,从一百万个项目的队列中删除第500000个项目。

对于随机迭代器,可以在开始迭代之前将链表洗牌。这需要O(n logn)时间,但只需要O(1)额外的空间。同样,在添加或删除的同时,也存在迭代的问题。如果你想要那种能力,那么你需要复制一份。

 类似资料:
  • 洗牌算法 洗牌算法,顾名思义,就是只利用一次循环等概率的取到不同的元素(牌)。 如果元素存在于数组中,即可将每次 random 到的元素 与 最后一个元素进行交换,然后 count—,即可。 这相当于把这个元素删除,代码如下: #include <iostream> #include <ctime> using namespace std; const int maxn = 10; int a[m

  • 数组实现简单队列 class Node(object): def __init__(self, data): self.data = data self.next = None def __str__(self) -> str: return '(data=%d)' % self.data class SimpleQueue(o

  • 队列是一种先进先出(FIFO,first-in-first-out)的数据结构 javascript代码实现队列: <!doctype html> <html> <head> <meta charset=utf-8 /> <title>Queue Sample</title> </head> <body> <script type="text/javascript">

  • 我需要一个数据结构,将能够为给定的int提供前面和后面的邻居,这是结构的一部分。 null LinkedHashSet:查找项目非常快,顺序为O(1),检索下一个邻居非常快。没有明显的方法可以在不对集合进行反向排序的情况下获得上一个邻居。仅限装箱整数对象。 int[]:非常容易存储,因为不需要装箱,获取上一个和下一个邻居非常快,但由于索引未知,需要数组遍历,所以对项的检索是O(n),这是不可接受的

  • 第一次按个位上的数字进行排序,第二次按十位上的数字进行排序 排序:91, 46, 85, 15, 92, 35, 31, 22 经过基数排序第一次扫描之后,数字被分配到如下盒子中: Bin 0: Bin 1: 91, 31 Bin 2: 92, 22 Bin 3: Bin 4: Bin 5: 85, 15, 35 Bin 6: 46 Bin 7: Bin 8: Bin 9: 根据盒子的顺序,对数字

  • 本文向大家介绍php 数据结构之链表队列,包括了php 数据结构之链表队列的使用技巧和注意事项,需要的朋友参考一下 php 链表队列 实例代码: 如有疑问请留言或者到本站社区交流讨论,感谢阅读,希望能帮助到大家,谢谢大家对本站的支持!