【问题标题】:Problems with circular singly linked list iterator循环单链表迭代器的问题
【发布时间】:2015-10-06 02:15:34
【问题描述】:

我正在尝试为我的循环单链表创建一个迭代器,但我不知道如何实现 next() 和 hasNext() 方法。我怀疑我需要 1) 在链表类或迭代器类中有附加字段,或者 2) 引用其他东西而不是“头”和“尾”? 我的代码如下:

public class CircularSinglyLinkedList2<E> implements Iterable<E> {

private Node head;
private Node tail;

public CircularSinglyLinkedList2() {
    head = null;
    tail = null;
}

class Node {
    E data;
    Node next;

    private Node(E data) {
        this.data = data;
    }
}

private boolean isEmpty() {
    return head == null;
}

private int size() {
    int count = 0;
    if(isEmpty()) {
        return 0;
    }
    Node p = head;
    count++;
    p = p.next;
    while(p != head) {
        count++;
        p = p.next;
    }
    return count;
}

public void insert(E data) {
    Node node = new Node(data);
    if(isEmpty()) {
        node.next = node;
        head = node;
        tail = node;
    } else {
        node.next = head;
        head = node;
        tail.next = head;
    }
}

public void delete(E data) {
    if(isEmpty()) {
        return;
    }
    if(head == tail) {
        if(head.data == data) {
            head = null;
            tail = null;
        }
        return;
    }
    Node p = head.next, q = head;
    while(p != head) {
        if(p.data == data) {
            q.next = p.next;
            return;
        }
        q = p;
        p = p.next;
    }
}

public Node search(E data) {
    if(isEmpty()) {
        return null;
    }
    Node p = head;
    if(p.data == data) {
        return p;
    }
    p = p.next;
    while(p != head) {
        if(p.data == data) {
            return p;
        }
        p = p.next;
    }
    return null;
}

public boolean contains(E data) {
    return search(data) != null;
}

public Iterator<E> iterator() {
    return new SLLIterator();
}


private class SLLIterator implements Iterator<E> {

    private Node p;
    private Node q;

    public SLLIterator() {
        if(!isEmpty()) {
            p = head.next;
            q = head;
        }
    }

    @Override
    public boolean hasNext() { doesnt't work
        if(p == q || p == head) {
            return false;
        }
        return true;
    }

    @Override
    public E next() { //doesn't work
        E data = q.data;
        q = p;
        p = p.next;
        return data;
    }

} 

【问题讨论】:

  • 你似乎有合理的hasNext()next() 方法。 不起作用是什么意思?
  • 我插入了一些随机整数,并使用了一个 for-each 循环来打印出列表,但它没有得到所有的值。特别是,我认为它总是会错过列表中的最后一个值,即它的编写方式。

标签: java data-structures linked-list singly-linked-list circular-list


【解决方案1】:

是的,您的迭代器有问题。它忽略了最后一项(头部)。例如,假设您的列表中只有一个元素。所以headtail 都指向它,还有head.next

现在,如果我们在这样一个列表上的一个新迭代器上询问 hasNext(),我们应该得到一次 true,然后,在我们调用 next() 之后,得到 false。

但你的条件是:

   if(p == q || p == head) {
        return false;
    }

这意味着对于单元素列表,无论如何您都将返回false

此外,即使我们假设我们有多个元素,比如:

2->1

然后用p = head.nextq = head 初始化迭代器。你的hasNext() 会返回true,但是你的next() 是做什么的呢?

public E next() { //doesn't work
    E data = q.data;
    q = p;
    p = p.next;
    return data;
}

因此,它在开头返回2,并移动pq。没关系,2 打印出来了。但是现在q 指向1p 再次指向2。再一次,您的 hasNext() 看到 p == head 并说没有更多元素!

因此,q 当前指向的元素,即实际的下一个元素,将被忽略。

你应该做的是设置一个标志,告诉你什么时候击中头部第二次而不是第一次。而且真的不需要两个指针。这是我的迭代器版本:

private class SLLIterator implements Iterator<E> {

    private Node p;
    private boolean atStart;

    public SLLIterator() {
        if(!isEmpty()) {
            p = head;
            atStart = true;
        }
    }

    @Override
    public boolean hasNext() { 
        if(isEmpty() || p == head && ! atStart) {
            return false;
        }
        return true;
    }

    @Override
    public E next() {
        E data = p.data;
        atStart = false;
        p = p.next;
        return data;
    }

    @Override
    public void remove() {
        throw new UnsupportedOperationException();
    }

}

这个迭代器有一个atStart 标志,它以true 开头。所以当hasNext()在单项列表上被调用时,它看到p指向头部,但它也看到这是它第一次这样做,所以它返回true

next()方法,在这种情况下,会给你p指向的数据,也就是head中的数据。此时atStart改成false,所以虽然p前进了又转回来,又是head,但是下次我们调用hasNext(),就不会再返回true了。

如果你有一个更长的列表,比如2-&gt;1,你会第一次得到2,并且p 被提升到1 并且atStart 是假的。

现在第二次,hasNext() 看到p 指向1,而不是head,因此它返回true。在next() 中,我们返回1,并将p 推进到头部。

再次调用hasNext() 将停止,因为p 回到了头部,但atStart 为假。

【讨论】:

  • 感谢您的回复。除了使用布尔标志或跟踪节点数之外,您知道是否有其他方法?
  • 不,我不认为,因为没有办法,在这样设计的单项列表中,知道hasNext()方法是否已经被调用。因此,如果它返回一次 true,它将始终返回 true,除非您有 something 来跟踪它被调用的次数。您可以通过使用 q 来“作弊”,该 q 最初是 null,然后不是,但这与使用布尔标志相同。
【解决方案2】:

您的hasNext() 不完全正确。由于您的 p == head 条件,您总是会错过打印最后一个元素。所以检查p 无助于决定hasNext()

理想情况下,您要检查的是q == head,这是当您完成所有元素的迭代并返回到起始元素时,您的hasNext() 应该返回false,但如果您只是检查q == head ,它不会起作用,因为对于你的起始元素本身它会失败。因此,使用标志来确定您是回来还是第一次访问。

我建议你这样做:

使用visitingAgain 标志。

boolean visitingAgain = false;

在您的hasNext() 中使用它,如下所示。

@Override
public boolean hasNext() { 
   if (p == q || (q == head && visitingAgain)){
      return false;
   }
   visitingAgain = true; // once you start iterating change this flag.
   return true;
}

注意我没有完全检查您的insertdelete 逻辑,如果这不起作用,请确保您的其他功能正确。如果有任何问题,请告诉我。

【讨论】:

  • 感谢您的回答,但这正是我想知道是否可以避免的问题。你知道除了在 hasNext() 中使用 boolean 或 int 之外还有什么方法吗?
  • 除了使用布尔值或整数之外,还有其他方法。但这可能不会使代码比这个解决方案更好,或者它可能会使代码看起来更糟。 IMO,这是一个相当简单明了的。
  • @AliMustafa 无论你做什么,你都必须以某种方式确定结束,因为你必须编写一些逻辑。所以我认为最简单的事情是使用标志。即使您想尝试其他方法,正如 Codebencher 所说,我认为它不会让您的代码更简单/更好。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-01-14
  • 1970-01-01
  • 2015-02-16
  • 2020-05-26
相关资源
最近更新 更多