使用链接列表进行排队 - 这应该如何运作?

时间:2015-06-10 16:30:01

标签: java linked-list queue

我很难理解这个数据结构。在我的课程中,我们使用以下代码使用链接列表实现队列:

class Queue
{
    private Item sentinel , tail;

    public Queue () {
        sentinel = new Item(0,null); 
        tail = sentinel;
    }

    public void enqueue(int value) {
        tail.next = new Item(value , null);
        tail = tail.next; 
    }

    public int dequeue ()
    {
        int value = sentinel.next.value;​
        sentinel.next = sentinel.next.next;
        return value; 
    } 
}

我不知道这应该如何工作,当我们调用构造函数方法时,我们sentinel[0|null]然后我们让tail=sentineltail[0|null]。通过致电.enqueue(x),我们得到以下信息:

[0|pointer to tail],  tail[x|null]

如果我们现在致电.dequeue()sentinel.nextnull

我向我的讲师询问了这个问题,我收到了以下回复,这对我来说并不是更清楚:“当我们通过Queue q = new Queue();调用构造函数时,我们将创建虚拟项目sentinel,其值为零,其下一个指针为空。同时我们让尾部指向sentinel。所以显然向队列中添加元素不是问题。“

我看不到让尾巴指向哨兵的位置:/

2 个答案:

答案 0 :(得分:1)

事实上,你的直觉是正确的。这个课程不能正常工作。

当你排队的东西,它运作良好 - 在尾部添加东西,一切正常。

当您将所有物品出列时,麻烦就开始了。当您这样做时,sentinel.next将变为null - 您已从队列中删除了所有内容 - 但tail仍将指向您排队的最后一项。所以基本上你的尾巴与你的sentinel断开了。

您可以将这些内容排入队列,并将其添加到旧尾后,但不再可以从sentinel访问。如果您尝试将任何内容进一步出列,您将获得NullPointerException

为了证明这一点,我在您的Queue类中添加了以下方法(并添加了Item类,因为您没有将它放在帖子中):

@Override
public String toString() {
    StringBuilder sb = new StringBuilder("Queue: ");
    for ( Item item = sentinel.next; item != null; item = item.next ) {
        sb.append('[').append(item.value).append(']');
    }
    return sb.toString();
}

现在,有了这个主程序:

public static void main(String[] args) {

    Queue queue = new Queue();
    queue.enqueue(5);
    queue.enqueue(10);
    System.out.println(queue);
    queue.dequeue();
    System.out.println(queue);
    queue.dequeue();
    System.out.println(queue);
    queue.enqueue(15);
    queue.enqueue(20);
    System.out.println(queue);
    queue.dequeue();
    System.out.println(queue);
}

你得到:

Queue: [5][10]
Queue: [10]
Queue: 
Queue: 
Exception in thread "main" java.lang.NullPointerException
        at testing.Queue.dequeue(SimpleTest.java:48)
        at testing.SimpleTest.main(SimpleTest.java:27)

应该得到的是:

Queue: [5][10]
Queue: [10]
Queue: 
Queue: [15][20]
Queue: [20]

要实现这一点,您必须在出列时到达时纠正尾部。

public int dequeue ()
{
    int value = sentinel.next.value;
    if ( sentinel.next == tail ) {
        tail = sentinel;
    }
    sentinel.next = sentinel.next.next;
    return value; 
}

实际上,当队列为空时,还应该保护dequeue()方法不被调用。抛出一个NullPointerException并不好,一个更明智的例外会更好。事实上它有助于创造一个更优雅的dequeue(),而不是纠正尾巴,我们只需更改哨兵 - 抛弃旧哨兵并使用我们刚出列的项目作为我们的新哨兵:

public int dequeue ()
{
    int value = sentinel.next.value;
    if ( sentinel.next == null ) {
        throw new IllegalStateException("No items to dequeue");
    }
    sentinel = sentinel.next;
    return value; 
}

如果我们没有进行空检查,那么当我们尝试出列时,sentinel将变为null,然后我们再也无法出列。通过空检查,我们确保我们有一个要出列的项目,它就成了哨兵。如果它恰好是队列中的最后一项,那么我们tailsentinel指向相同的项目,就像他们在开始时所做的那样,因此我们知道我们可以继续添加项目可通过sentinel访问。

请注意,在尝试出队之前检查队列是否为空的方法也会派上用场。

答案 1 :(得分:0)

如果我们现在调用.dequeue(),则sentinel.next仍然指向null。这个不对。 senitel只在构造函数中初始化,不再进一步改变。

所以,在connstructor之后你得到 - (Senitel) - > null和here指向Senitel。你拨打enqueue就可以了。 (Senitel) - > (Element1) - >空值。 这里,尾部指向Element1和Senitel仍然指向(Senitel)。你叫dequeue,它变成(Senitel) - >空。