【问题标题】:delete node linked list recursively in Java在Java中递归删除节点链表
【发布时间】:2018-09-22 17:21:26
【问题描述】:

我正在学习数据结构并尝试理解 Java 中的链表。我的问题是我在递归删除给定索引处的节点时遇到了麻烦。我的目标是得到 O(log n) 而不是使用循环并最终得到 O(n)。

public class LinkedList {
    Node head;
    int index=0;
    Node temp;
    Node prev;
    public LinkedList(Node head){
        this.head=head;
        temp=head;
        prev=null;
    }
    public int length(){
        int counter=0;
        Node n= head.next;
        while(n!=null){
            counter=counter+1;
            n=n.next;
        }
        return counter;
    }
    public void push(Node newNode){
        newNode.next=head;
        head=newNode;
    }
    public void add(Node prevNode, int value){
        if(prevNode==null){
            System.out.println("The given previous node can not be null!");
            return;
        }
        Node newNode= new Node(value,null);
        newNode.next=prevNode.next;
        prevNode.next=newNode;
    }
    public void add(int index, int value){
        length();
        if((index<0)||(index>length())){
            System.out.println("Array out of bound!");
            return;
        }
        if(index==0){
            push(new Node(value,null));
            return;
        }
        Node newNode= new Node(value,null);
        Node prevNode=head;
            for(int i=1;i<index;i++){
            prevNode=prevNode.next;
        }
        newNode.next=prevNode.next;
        prevNode.next=newNode;
    }
    public void delete(){
        head=head.next;
    }
    public void delete(int index){
        if((index<0)||(index>length())){
            System.out.println("Array out of bound!");
            return;
        }
        if(index==0){
            delete();
        return;}
        if(head.next==null||head==null){
            head=null;
        return;}
        if(this.index!=index){
            this.index++;
            prev=temp;
            temp=temp.next;
            delete(index);
        }if(this.index==index){
            prev=temp.next;
        }
    }
    public void search(int value){
        if(head!=null){
        if(value!=head.value){
            head=head.next;
            index=index+1;
            search(value);
        }else if(value==head.value){
            System.out.println("The value \""+value+"\" was found in index: "+index);}}}
    public void display(){
        Node n= head;
        System.out.print("{");
        while(n!=null){
            System.out.print(" ("+n.value+") ");
            n=n.next;
        }System.out.print("}");
        System.out.println("\n------------------------------");
    }
    public static void main(String[]args){
        LinkedList ll= new LinkedList(new Node(2,null));
        ll.push(new Node(5,null));
        ll.push(new Node(6,null));
        ll.push(new Node(13,null));
        ll.push(new Node(1,null));
        ll.display();
        ll.add(ll.head.next,8);
        ll.display();
        ll.add(0, 0);
        ll.display();
        ll.add(6, 4);
        ll.display();
        System.out.println(ll.length());
        ll.search(13);
        ll.delete(2);
        ll.display();
    }
}

因此,当我尝试删除索引 2 处的条目时,它会删除该索引之前的所有数字,但不会删除该索引处的所有数字 - 所以它会删除 [0] 和 [1] 而不是 [2]。

例如在这段代码中,删除前的数组填充为:{0,1,13,8,6,5,4,2}。 调用delete(2)后,有以下条目:{13,8,6,5,4,2}

我想要的只是删除 13,使数组看起来像这样:{0,1,8,6,5,4,2}

我非常感谢任何改进我的代码的提示。

【问题讨论】:

  • 谁说在linkedlist中递归删除是o(logn)?为什么要使用递归?它可以实现简单的迭代,你只是让你的代码难以理解
  • (+, -, *, / , if) 是一步,循环是 n 步,调用函数将是 n 步,因为函数可能包含循环。如果我错了,请纠正我。谢谢
  • 我不知道,你在说什么
  • 我知道它可以通过迭代来实现,但正如我所说,我想使用递归来提高我的知识
  • 你有完整的错误代码。很难说是什么导致了这个特定问题,而不是您应该更喜欢自己调试代码。当然,我可以分享伪代码来实现这一点,既然你提到了,你正在努力提高你的知识。

标签: java recursion linked-list


【解决方案1】:

理解您的代码非常困难,但由于您要求逻辑来提高您的理解,所以分享伪代码,您可以参考相应地更正您的代码。

Node delete (index i, Node n) // pass index and head reference node and return head
   if (n==null) // if node is null
      return null;
   if (i==1)  // if reached to node, which needs to be deleted, return next node reference.
      return n.next;  
   n.next= delete(n.next,i-1);
   return n; // recursively return current node reference

【讨论】:

    【解决方案2】:

    经过努力,我设法解决了问题,这是答案,但我仍然不确定复杂度是 O(n) 还是 O(log n)。

     public void delete(int index){
            //check if the index is valid
            if((index<0)||(index>length())){
                System.out.println("Array out of bound!");
                return;
            }
            //pass the value head to temp only in the first run
            if(this.index==0)
                temp=head;
            //if the given index is zero then move the head to next element and return
            if(index==0){
                head=head.next;
            return;}
            //if the array is empty or has only one element then move the head to null
            if(head.next==null||head==null){
                head=null;
            return;}
            if(temp!=null){
                prev=temp;
                temp=temp.next;
                this.index=this.index+1;
                //if the given index is reached
               //then link the node prev to the node that comes after temp
              //and unlink temp
                if(this.index==index){
                    prev.next=temp.next;
                    temp=null;
                    return;
                }
             //if not then call the function again
               delete(index);
            }
        }
    

    【讨论】:

      【解决方案3】:

      一个链表中的Java递归删除方法

      好的,让我们举个例子。这很简单,但是一旦掌握了窍门并理解了delete 递归算法,您就可以轻松制作示例类generic,处理封装,优化代码,然后继续生产。

      本例中的类

      为了举例,假设基本的 Singly LinkedListNode 类非常简单。内部静态Node 类仅存储原始int 类型,并且它仅包含对列表中以下Node 元素的next 引用。 LinkedList 只包含一个head 节点,它是链表的开头。这不是一个双向链表,它没有对前一个节点的引用。遍历是从给定的Node(通常是头节点)到next 引用按顺序完成的,一个节点一个接一个。我为两者都添加了toString() 实现,稍后会派上用场:

      public class LinkedList {
      
      protected Node head;
      
      public LinkedList(Node head) {
          super();
          this.head = head;
      }
      
      static class Node {
      
          protected int data;
          protected Node next;
      
          Node(int data, Node next) {
              this.data = data;
              this.next = next;
          }
      
          @Override
          public String toString() {
              StringBuilder builder = new StringBuilder();
              builder.append("Node ");
              builder.append(data);
              if (null != next)
                  builder.append(" -> ");
              return builder.toString();
          }
      }
      
      @Override
      public String toString() {
          StringBuilder builder = new StringBuilder();
          builder.append("LinkedList [");
          Node node = head;
          while (node != null) {
              builder.append(node);
              node = node.next;
          }
          builder.append("]");
          return builder.toString();
      }
      

      }

      实现递归删除方法

      现在,让我们添加一个递归的delete() 方法。删除单链表中的节点只能通过从前一个节点的next 引用中取消链接来完成。此规则的唯一例外是 head 节点,我们将其删除为空。因此,很明显,我们需要(除了起点 current node 引用之外)对前一个节点的引用。

      因此,我们的递归delete() 方法签名可以是:

      private LinkedList delete(Node node, Node prev, int key)
      

      虽然此方法的返回类型可以完全省略(void),但它对支持链式能力非常有用,因此 API 调用可以成为单行、点分隔的语法,例如:

      System.out.println(list.push(0).append(2).deleteAll(1));
      

      因此,为了链式能力,我们也将从此方法返回对整个 LinkedList 实例的引用。根据参数列表:

      第一个参数是当前节点,检查它是否匹配给定的key。下一个参数是前一个节点,以防我们需要取消链接当前节点。最后一个参数是我们在所有要删除(取消链接)的节点中寻找的键。

      方法修饰符是private,因为它不打算公开使用。我们将把它包装在一个用户友好的facade 方法中,这将以head 作为当前节点,null 作为前一个节点开始递归:

      public LinkedList deleteAll(int key) {
          return delete(head, null, key);
      }
      

      现在,让我们看看如何实现递归delete(...) 方法,我们从终止递归的两个基本条件开始;空当前节点或列表中的单个节点,这也是head 节点:

      private LinkedList delete(Node node, Node prev, int key) {
          if (node == null)
              return this;
          if (node == head && head.data == key && null == head.next) { // head node w/o next pointer
              head = null;
              return this;
          }
          //...more code here
      }
      

      到达第一个基本条件意味着我们已经到达链表的末尾(找到key 或没有),或者链表是空的。我们完成了,我们返回了对链表的引用。

      第二个基本条件检查我们的当前节点是否是头节点,以及它是否匹配key。在这种情况下,我们还检查它是否恰好是链表中的单个节点。在这种情况下,头节点需要“特殊”处理,并且必须分配null 才能被删除。自然,删除head节点后,链表为空,我们就完成了,所以我们返回一个对链表的引用。

      下一个条件检查当前节点是否与key 匹配,如果它是头节点,但不是单独在列表中。

      private LinkedList delete(Node node, Node prev, int key) {
          //...previous code here
          if (node == head && head.data == key) { // head with next pointer
              head = head.next;
              return delete(head, null, key);
          }
          //...more code here
      }
      

      我们稍后会优化此代码,但现在,在这种情况下,我们只需将对 head 的引用向前移动一步,因此 head 被有效删除(旧引用将被垃圾回收)并且我们重复以新的head 作为当前节点,null 仍然是前一个节点。

      下一个案例涵盖了与key 匹配的常规(中间或尾部)节点:

      private LinkedList delete(Node node, Node prev, int key) {
          //...previous code here
          if (node.data == key) {
              prev.next = node.next;
              return delete(prev, null, key);
          }
          //...more code here
      }
      

      在这种情况下,我们通过从当前节点取消链接前一个节点的next 指针并将其分配给当前节点地址的下一个来删除当前节点。我们基本上“跳过”了当前节点,这成为了抓取。然后我们递归,前一个节点是当前节点,null 作为前一个节点。

      在所有这些处理的案例中,我们都匹配到了key。最后,我们处理不匹配的情况:

      private LinkedList delete(Node node, Node prev, int key) {
          //...previous code here
          return delete(node.next, node, key);
      }
      

      显然,我们将下一个节点作为当前节点进行递归,将旧的当前节点作为前一个节点。 key 在所有递归调用中保持不变。

      整个(未优化的)方法现在看起来像这样:

      private LinkedList delete(Node node, Node prev, int key) {
          if (node == null)
              return this;
          if (node.data == key && node == head && null == node.next) { // head node w/o next pointer
              head = null;
              return this;
          }
          if (node.data == key && node == head) { // head with next pointer
              head = head.next;
              return delete(head, null, key);
          }
          if (node.data == key) { // middle / tail
              prev.next = node.next;
              return delete(prev, null, key);
          }
          return delete(node.next, node, key);
      }
      

      尾递归优化

      如果使用尾递归,许多编译器(包括 javac)可以优化递归方法。当递归调用是该方法执行的最后一件事时,递归方法是尾递归的。然后,编译器可以用简单的goto/label 机制替换递归,并为每个递归帧节省运行时所需的额外内存空间。

      我们可以轻松优化我们的递归delete(...) 方法以符合要求。我们可以保留对当前node 和前一个节点prev 的引用,而不是从每个处理的条件(案例)中递归返回,并在每个案例处理中为它们分配适当的值。这样,唯一的递归调用将发生在方法的末尾:

      private LinkedList delete(Node node, Node prev, int key) {
          if (node == null)
              return this;
          if (node.data == key && head == node && null == node.next) { // head node w/o next pointer
              head = null;
              return this;
          }
          Node n = node.next, p = node;
          if (node.data == key && head == node) { // head with next pointer
              head = head.next;
              n = head;
              p = null;
          } else if (node.data == key) { // middle / tail
              prev.next = node.next;
              n = prev;
              p = null;
          }
          return delete(n, p, key);
      }
      

      测试这个递归方法:

      我添加了一个简单的main 测试驱动方法来测试delete(...) 方法的实现,通过外观方法deleteAll(...)

      public static void main(String[] args) {
          LinkedList list = new LinkedList(new Node(0, new Node(1, new Node(1, new Node(2, new Node(2, new Node(3, null)))))));
          System.out.println(list);
          System.out.println(list.deleteAll(6));
          System.out.println(list.deleteAll(1));
          System.out.println(list.deleteAll(3));
          System.out.println(list.deleteAll(2));
          System.out.println(list.deleteAll(0));
      }
      

      输出(使用我提供的toString() 方法)是:

      LinkedList [Node 0 -> Node 1 -> Node 1 -> Node 2 -> Node 2 -> Node 3]
      LinkedList [Node 0 -> Node 1 -> Node 1 -> Node 2 -> Node 2 -> Node 3]
      LinkedList [Node 0 -> Node 2 -> Node 2 -> Node 3]
      LinkedList [Node 0 -> Node 2 -> Node 2]
      LinkedList [Node 0]
      LinkedList []
       
      

      虽然距离最初的帖子已经 3 年了,但我相信其他一些初学 Java 程序员,如果不是 OP,觉得这个解释很有用。

      【讨论】:

        猜你喜欢
        • 2012-11-06
        • 1970-01-01
        • 1970-01-01
        • 2016-07-19
        • 2023-03-26
        • 1970-01-01
        • 2022-07-08
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多