>  Q&A  >  본문

java - 关于单链表实现的一个stack的问题

背景:菜鸟一枚,想补补基础。最近在看算法,1.3.26有一道题是:编写一个方法remove()接受一条链表和一个字符串key作为参数,删除链表中所有的item域为key的节点。

上代码:

public class Stack<Item> implements Iterable<Item> {
    private Node<Item> first; // top of stack
    private int n; // size of the stack

    // helper linked list class
    private static class Node<Item> {
        private Item item;
        private Node<Item> next;
    }

    /**
     * Initializes an empty stack.
     */
    public Stack() {
        first = null;
        n = 0;
    }

    /**
     * Returns true if this stack is empty.
     *
     * @return true if this stack is empty; false otherwise
     */
    public boolean isEmpty() {
        return first == null;
    }

    /**
     * Returns the number of items in this stack.
     *
     * @return the number of items in this stack
     */
    public int size() {
        return n;
    }

    /**
     * Adds the item to this stack.
     *
     * @param item
     *            the item to add
     */
    public void push(Item item) {
        Node<Item> oldfirst = first;
        first = new Node<Item>();
        first.item = item;
        first.next = oldfirst;
        n++;
    }

    /**
     * Removes and returns the item most recently added to this stack.
     *
     * @return the item most recently added
     * @throws NoSuchElementException
     *             if this stack is empty
     */
    public Item pop() {
        if (isEmpty())
            throw new NoSuchElementException("Stack underflow");
        Item item = first.item; // save item to return
        first = first.next; // delete first node
        n--;
        return item; // return the saved item
    }

    /**
     * Returns (but does not remove) the item most recently added to this stack.
     *
     * @return the item most recently added to this stack
     * @throws NoSuchElementException
     *             if this stack is empty
     */
    public Item peek() {
        if (isEmpty())
            throw new NoSuchElementException("Stack underflow");
        return first.item;
    }

    /**
     * Returns a string representation of this stack.
     *
     * @return the sequence of items in this stack in LIFO order, separated by
     *         spaces
     */
    public String toString() {
        StringBuilder s = new StringBuilder();
        for (Item item : this)
            s.append(item + " ");
        return s.toString();
    }

    // 1.3.20
    public void delete(int k) {

        // 不允许大于n的值
        if (k <= n) {
            Node<Item> nowNextNode = first;
            // 要第几个就next到第几个的上一个
            for (int i = 1; (k - 1) != i; i++) {
                nowNextNode = nowNextNode.next;
            }
            // 删除节点
            Node<Item> needLinePoint = nowNextNode.next.next;
            nowNextNode.next = needLinePoint;
        } else {
            // 这里简单的打印一下
            StdOut.print("!------------------删除元素越界!------------------!");
        }
    }

    // 1.3.21
    public boolean find(String key) {
        // 其实也可以用foreach
        Node<Item> nowNode = first;
        while (true) {
            if (nowNode.item.equals(key)) {
                StdOut.print("output : true");
                return true;
                // 链表没有下一个了
            } else if (first.next.equals(null)) {
                StdOut.print("output : false");
                return false;
            }
            nowNode = nowNode.next;
            StdOut.println("then next~");
        }

    }

    // 1.3.24 
    public void removeAfter(Node<Item> node) {
        if (node == null || node.next == null) {
            // 什么也别做
        } else {
            if (isEmpty())
                throw new NoSuchElementException("Stack underflow");
            Node<Item> tempNode = node.next.next;
            node.next = null;
            node.next = node.next.next;
            n--;
        }
    }

    //1.3.26 接受一个链表和一个字符串key作为参数,删除链表中所有item域为key的节点
    // 那么为了降低难度,直接写成对this产生作用的方法
    public void remove(String key) {
        //首先肯定是一个能够不停next直到没有的循环
        //第二个就是要时时刻刻记住上一个节点,这样便于移除现在的节点
        //然后就是对等于key和不等于key的逻辑判断
          //先判断nowNode.next == null,如果是,说明已经到了尾部,结束循环
          //相等:移除节点,上一个节点还是原先的上一个节点,但会连接至“被移除节点”的next,继续循环
          //不相等:frontNode变更为nowNode,继续循环
        Node<Item> nowNode = first;
        Node<Item> frontNode = null;
        while(true){
            if (nowNode.next.equals(null)){
                StdOut.print("没有拥有该字符串的item");
                break;
            }else if (nowNode.item.equals(key)){
                    frontNode.next = nowNode.next;            
                    nowNode.next=null;
                    nowNode = frontNode.next;
                    StdOut.print("已搜索到该字符串!");
            }else {
              frontNode = nowNode;
              nowNode = nowNode.next;
            }
        }
    }

    /**
     * Returns an iterator to this stack that iterates through the items in LIFO
     * order.
     *
     * @return an iterator to this stack that iterates through the items in LIFO
     *         order
     */
    public Iterator<Item> iterator() {
        return new ListIterator<Item>(first);
    }

    // an iterator, doesn't implement remove() since it's optional
    private class ListIterator<Item> implements Iterator<Item> {
        private Node<Item> current;

        public ListIterator(Node<Item> first) {
            current = first;
        }

        public boolean hasNext() {
            return current != null;
        }

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

        public Item next() {
            if (!hasNext())
                throw new NoSuchElementException();
            Item item = current.item;
            current = current.next;
            return item;
        }
    }

}

但是一经测试就报java.lang.NullPointerException,为什么?
详细错误:

java.lang.NullPointerException
    at Unit1.Stack.remove(Stack.java:168)
    at Unit1.StackTest.Test(StackTest.java:19)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
    at java.lang.reflect.Method.invoke(Unknown Source)
    at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
    at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
    at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
    at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:86)
    at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:459)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:678)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:382)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:192)

测试里相关的代码:

public class StackTest {

    @Test
    public void Test() {

        Stack<String> stack = new Stack<String>();
        while (!StdIn.isEmpty()) {
            String item = StdIn.readString();
            if (item.equals("find")) {
                stack.find("3");
            }
            else if (item.equals("remove")) {
                stack.remove("1");
            }
            else if (item.equals("del")) {
                stack.delete(3);
            }
            else if (!item.equals("-")) {
                stack.push(item);
            } 
            else if (!stack.isEmpty()) {
                StdOut.print(stack.pop() + " ");
            }

        }
        StdOut.println("(" + stack.size() + " left on stack)");
    }
}

报错行是stack里的

            if (nowNode.next.equals(null)){

以及测试里的

        stack.remove("1");

但是我认为一楼的答案并不是很好...找到空指针的引用后,就向前找这个引用的初始化的逻辑部分。
初始化部分:

        Node<Item> nowNode = first;
        Node<Item> frontNode = null;

我很同意授人鱼不如授人以渔。然而...
StdIn是一个能够不停输入的函数。
测试数据输入的是
1
2
3
4
5
remove
感谢各位!问题似乎已经解决了!附上代码:

    public void remove(String key) {
        // 首先肯定是一个能够不停next直到没有的循环
        // 然后就是对等于key和不等于key的逻辑判断
        // 先判断nowNode== null,如果是,说明已经到了尾部,结束循环。同样nowNode.next == null也是
        Node<Item> nowNode = first;
        while (true) {
            if (nowNode == null ) {
                StdOut.println("搜索结束");
                break;
            } 
            if (nowNode.item.equals(key)) {
                StdOut.println("已搜索到该字符串!");
                if (nowNode.next == null){
                    StdOut.println("已到达底端,退出!");
                    break;
                }
                //删除节点操作
                //临时存储的对象:直接让nextNode和nowNode交换
                Node<Item> nextNode ;
                nextNode = nowNode.next;
                nowNode.item = nextNode.item;
                nowNode.next = nextNode.next;
                nextNode = null;
            }
            else {
                nowNode = nowNode.next;
            }
        }
    }
PHP中文网PHP中文网2741일 전351

모든 응답(3)나는 대답할 것이다

  • ringa_lee

    ringa_lee2017-04-18 09:48:09

    예전에 밖에 있었을 땐 이런 질문 자체가 재미없었는데 1층이 너무 재미있었는데 좋아해주시는 분이 2명 계셨어요.
    授人鱼不如授人以渔。라는 한 문장이 공감을 불러일으키지 않았을까 걱정되는데, 특히 lz와 같은 문제에 공감하기 쉽습니다.
    找到空指针的引用后,就向前找这个引用的初始化的逻辑部分。이 문장은 약간 오해의 소지가 있습니다.
    저번에 올렸던 코드가 너무 헷갈리네요. first.next.equals(null)그런 코드에 대해 불평하고 싶지는 않습니다.
    실용적인 일을 할 수 있나요? 2층 @hsfzxjy 처럼

    먼저 오류에 대해 이야기해 보겠습니다. 아무도 이 오류를 발견하지 못하셨나요?

    으아아아

    xxx.equals(null) 및 xxx == null
    차이점은 프로그램이 정상적으로 실행될 수 있으면
    xxx.equals(null)은 false만 반환해야 합니다
    xxx == null이 반환될 수 있습니다. true, false를 반환할 수도 있음

    xxx가 null인 경우
    xxx.equals(null)는 NullPointerException을 발생시킵니다.
    xxx == null은 true를 반환합니다

    그럼 논리 문제가 있습니다

    마지막 키에서 다음 키는 null입니다
    그래서

    으아아아

    frontNode.next는 null을 가리키고 nowNode는 null을 가리킵니다.

    코드 개선:

    으아아아

    여러분의 아이디어를 바탕으로 구현해보았는데, 질문 하나 남깁니다.
    그리고 결제방식도 별로 안 중요하게 생각해요. 답변이 만족스럽지 못하더라도 양해해 주시기 바랍니다.

    회신하다
    0
  • 迷茫

    迷茫2017-04-18 09:48:09

    first이 대상인 경우 161행 frontNode.next에서 오류

    를 보고합니다.

    링크된 리스트가 비어 있으면 157행과 167행에 오류가 보고됩니다. nowNode.next

    시원한 한 해에는 할 수 없으니 차근차근 따라해 보는 건 어떨까요

    회신하다
    0
  • 怪我咯

    怪我咯2017-04-18 09:48:09

    ——————————보충 내용————————————————
    추가 참고 사항: == 기호는 등호의 양쪽을 비교합니다. 변수의 값은 이므로 두 객체 참조를 비교하는 데 사용되면 두 참조의 메모리 주소가 비교됩니다. equals 메소드는 실제로 참조의 특정 내용을 비교하는 데 사용될 수 있습니다.
    예를 들어

    으아아아

    그래서 ==는 null인지 판단할 때만 사용됩니다. 그렇지 않으면 Java의 대부분의 변수는 참조 유형이므로 가능하면 equals 메소드를 사용하는 것이 가장 좋습니다.
    ———————————————————————————————————————

    제목이 수정되었습니다.
    xxx가 비어 있다고 판단되는 경우에는 xxx.equls(null) 형식을 사용하지 마세요. 앞으로는 == 기호를 직접 사용하여 부울 isEmpty(객체 o) 도구 메서드를 결정하거나 사용자 정의할 수 있습니다

    으아아아

    ——————————————————————————————오래된 업데이트———————————— ——— ———————————————————————
    그래도 잠깐 살펴봤습니다.

    으아아아

    오류를 보고한 후 java.lang.NullPointerException은 계속해서 살펴보고 클래스의 패키지 이름이 포함된 첫 번째 줄을 찾습니다. 해당 줄 다음의 숫자는 오류가 발생한 줄의 수입니다.
    널 포인터의 참조를 찾은 후 이 참조의 초기화 논리 부분을 기대하세요.

    회신하다
    0
  • 취소회신하다