【问题标题】:Is there a way to achieve retrieval (get()) with O(1) time complexity for ArrayDeque? [closed]有没有办法为 ArrayDeque 实现 O(1) 时间复杂度的检索 (get())? [关闭]
【发布时间】:2020-11-01 16:17:35
【问题描述】:

我正在尝试将 ArrayDeque 用于应该具有 O(1) 时间复杂度的 addfront、addback 和检索的类。我只能想到使用 toArray() 进行检索,不幸的是 O(n)。有没有办法实现 ArrayDeque 的检索方法是 O(1)?

【问题讨论】:

  • AFAIK,不,没有。
  • 从数组中间移除怎么样?有限制吗?
  • 嗯...有一个remove(Object) 方法。它是 O(N)。
  • 1. addFront = addFirst()。 2.addback = addLast()。 3.retrieve = getFirst()getLast()。所有这些操作都在“摊销恒定时间”内运行。你的问题?
  • @MarquisofLorne 有没有办法实现 get(index) w 恒定时间?

标签: java time deque arraydeque


【解决方案1】:

ArrayDeque API 没有提供任何方法来执行此操作。

但是,您可以编写实现getArrayDeque 的自定义子类。像这样的:

public E get(int i) {
    if (i < 0 || i > size()) {
        throw new ....("out of bounds");
    }
    long pos = this.head + i; 
    if (pos >= this.elements.length) {
        pos -= this.elements.length;
    }
    return this.elements[(int) pos];
}

注意:此代码尚未编译/调试。使用风险自负!

这是O(1),对ArrayDeque API 中现有操作的性能没有影响。


更新

以上内容不能作为标准ArrayDeque 类的子类工作,因为它正在访问的字段是包私有的。但是,您可以复制原始类并将上述方法添加到副本中。

(只要确保您从 OpenJDK 代码库复制代码,并满足 GPLv2 要求。)

【讨论】:

  • 我认为您实际上不能从子类访问this.elements
  • 啊,是的。这些字段是包私有的,而不是protected。仅当您将自定义类添加到 java.util.collections 时,该代码才有效……这是一个禁忌。所以你需要复制 ArrayDeque 并修改它......而不是扩展正式版本。
【解决方案2】:

没有。

我查看了ArrayDeque的源代码,没有任何地方可以通过索引访问任意数组元素的方法。为了使操作在 O(1) 中执行,这是必需的。

不过,实现您自己的满足您要求的类应该不会太难。搜索“循环缓冲区”。如果您的数组溢出,请将全部内容复制到一个双倍大小的新数组中。当然,这不能在恒定时间内完成,但添加仍将在摊销的恒定时间内完成,与 ArrayDeque 相同。

我假设get() 的意思是按元素在队列中的位置/索引检查(不删除)元素,从前面或从后面开始计数。

编辑

我想另一种方法是使用数组并找到一种方法 addfront 固定时间,但我不确定如何

这是一个简单的实现。请根据您的需要进一步开发。这些想法是使用循环缓冲区并在旧数组太小时复制到新数组。我相信ArrayDeque 使用了相同的想法。

public class MyArrayDeque<E> {

    private Object[] elements;
    
    // Index to first element.
    private int front = 0;
    // Index to first free space after last element.
    // back == front means queue is empty (not full).
    private int back = 0;
    
    public MyArrayDeque(int initialCapacity) {
        if (initialCapacity < 0) {
            throw new IllegalArgumentException("Initial capacity must not be negative");
        }
        // There’s always at least 1 free space, so add 1 to have room for initialCapacity elements
        elements = new Object[initialCapacity + 1];
    }

    public void addFront(E elem) {
        checkCapacity();
    
        if (front == 0) {
            front = elements.length - 1;
        } else {
            front--;
        }
        
        elements[front] = elem;
    }

    public void addBack(E elem) {
        checkCapacity();
        
        elements[back] = elem;

        if (back == elements.length - 1) {
            back = 0;
        } else {
            back++;
        }
    }

    // Makes sure the queue has room for one more element.
    private void checkCapacity() {
        boolean needToExpand;
        if (front == 0) {
            needToExpand = back == elements.length - 1;
        } else {
            needToExpand = back == front - 1;
        }
        if (needToExpand) {
            Object[] newElements = new Object[elements.length * 2];
            if (front <= back) {
                int size = back - front;
                System.arraycopy(elements, front, newElements, 0, size);
                front = 0;
                back = size;
            } else {
                int numberOfElementsToCopyFirst = elements.length - front;
                System.arraycopy(elements, front, newElements, 0, numberOfElementsToCopyFirst);
                System.arraycopy(elements, 0, newElements, numberOfElementsToCopyFirst, back);
                front = 0;
                back = numberOfElementsToCopyFirst + back;
            }
            elements = newElements;
        }
    }

    /** Gets the ith element counted from the front without removing it. */
    public E get(int i) {
        int index = front + i;
        if (index >= elements.length) {
            index -= elements.length;
        }
        boolean outOfRange;
        if (front <= back) {
            outOfRange = index < front || index >= back;
        } else {
            outOfRange = index >= back && index < front;
        }
        if (outOfRange) {
            throw new ArrayIndexOutOfBoundsException(i);
        }
        return getInternal(index);
    }

    @SuppressWarnings("unchecked")
    private E getInternal(int index) {
        return (E) elements[index];
    }

}

简单演示:

    MyArrayDeque<String> queue = new MyArrayDeque<>(1);
    
    queue.addFront("First element added");
    queue.addBack("Added at back");
    queue.addFront("Added at front");
    
    System.out.println(queue.get(1));

输出是:

添加的第一个元素

【讨论】:

    【解决方案3】:

    您可以使用额外的结构来实现它,因此空间复杂度将变为 O(2n),这可能不是很重要。 我可以建议的方法是使用 HashMap 并在那里存储您放入队列的 Object 的索引和链接。此外,您需要跟踪可用的第一个和最后一个索引。每次您必须按索引访问元素时 - 您必须根据起始索引计算移位。当然,每次从队列中添加或删除元素时,您都必须注意更新开始和结束索引。唯一的缺点 - 从中​​间移除可能需要 O(n),这对于队列情况可能并不重要。 以下是使用附加结构时对象状态的示例:

    indexMap: {}
    startIndex:0
    endIndex:0
    
    --> add an element to the head 
    newCalculatedIndex = startIndex == endIndex ? startIndex : startIndex -1; 
    //newCalculatedIndex = 0 
    indexMap: {(0,'A')}
    startIndex:0
    endIndex:0
    
    --> add an element to the head 
    //newCalculatedIndex = 0-1 = -1
    indexMap: {(-1,'B'), (0,'A')}
    startIndex:-1
    endIndex:0
    
    --> add an element to the tail 
    newCalculatedIndex = startIndex == endIndex ? endIndex : endIndex + 1; 
    //newCalculatedIndex = 0 + 1 = 1
    indexMap: {(-1,'B'), (0,'A'), (1,'C')}
    startIndex:-1
    endIndex:1
    
    --> Access element with index 2:
    calculatedIndex = -1 + 2 = 1 -> indexMap.get(1) returns 'C'
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2020-12-13
      • 2021-06-23
      • 2020-11-26
      • 1970-01-01
      • 1970-01-01
      • 2015-05-25
      • 2020-08-18
      • 1970-01-01
      相关资源
      最近更新 更多