Inversione di un elenco collegato in Java, in modo ricorsivo


101

Sto lavorando a un progetto Java per una classe da un po 'di tempo ormai. È un'implementazione di una lista collegata (qui chiamata AddressList, contenente semplici nodi chiamati ListNode). Il problema è che tutto dovrebbe essere fatto con algoritmi ricorsivi. Sono stato in grado di fare tutto bene senza un metodo:public AddressList reverse()

ListNode:

public class ListNode{
  public String data;
  public ListNode next;
}

In questo momento la mia reversefunzione chiama solo una funzione di supporto che accetta un argomento per consentire la ricorsione.

public AddressList reverse(){
  return new AddressList(this.reverse(this.head));
}

Con la mia funzione di aiuto con la firma di private ListNode reverse(ListNode current).

Al momento, funziona in modo iterativo usando uno stack, ma questo non è ciò che richiede la specifica. Avevo trovato un algoritmo in C che lo inverteva ricorsivamente e lo converte a mano in codice Java, e funzionava, ma non lo capivo.

Edit: Nevermind, l'ho capito nel frattempo.

private AddressList reverse(ListNode current, AddressList reversedList){
  if(current == null) 
      return reversedList;
  reversedList.addToFront(current.getData());
  return this.reverse(current.getNext(), reversedList);
}

Mentre sono qui, qualcuno vede problemi con questo percorso?


2
No, non ci sono problemi con la tua soluzione. Al contrario, è anche "migliore" della soluzione preferita "Little Lisper" in quanto lascia intatto l'elenco originale. Ciò sarebbe particolarmente utile in un ambiente multi-core, dove i valori immutabili sono fortemente preferiti.
Ingo

Risposte:


318

C'è un codice in una risposta che lo spiega, ma potresti trovare più facile iniziare dal basso verso l'alto, chiedendo e rispondendo a piccole domande (questo è l'approccio in The Little Lisper):

  1. Qual è il contrario di null (l'elenco vuoto)? nullo.
  2. Qual è il contrario di un elenco di un elemento? l'elemento.
  3. Qual è il contrario di un elenco di n elementi? il contrario del resto dell'elenco seguito dal primo elemento.

public ListNode Reverse(ListNode list)
{
    if (list == null) return null; // first question

    if (list.next == null) return list; // second question

    // third question - in Lisp this is easy, but we don't have cons
    // so we grab the second element (which will be the last after we reverse it)

    ListNode secondElem = list.next;

    // bug fix - need to unlink list from the rest or you will get a cycle
    list.next = null;

    // then we reverse everything from the second element on
    ListNode reverseRest = Reverse(secondElem);

    // then we join the two lists
    secondElem.next = list;

    return reverseRest;
}

30
Wow, mi piace tutta quella cosa delle "Tre domande".
sdellysse

4
Grazie. La piccola domanda dovrebbe essere la base dell'apprendimento del Lisp. È anche un modo per nascondere l'induzione ai neofiti, che è essenzialmente ciò che è questo modello. Consiglio di leggere il Little Lisper se vuoi davvero risolvere questo tipo di problema.
zoccolo

44
eccezioni per circostanze eccezionali. Perché utilizzare una cattura per una condizione nota che è verificabile da un se?
Luke Schafer

4
Credo che non sia necessario creare la variabile: secondElem poiché list.next è ancora secondElem. Dopo "ListNode reverseRest = Reverse (secondElem);", puoi prima fare "list.next.next = list" e poi "list.next = null". E questo è tutto.
ChuanRocks

3
Puoi spiegare perché list.next = null? Stavo cercando di capire il ciclo ma non ci sono riuscito.
Rohit

29

Mi è stata posta questa domanda durante un colloquio ed ero infastidito dal fatto di averlo armeggiato poiché ero un po 'nervoso.

Questo dovrebbe invertire una lista concatenata singolarmente, chiamata con reverse (head, NULL); quindi se questa fosse la tua lista:

1-> 2-> 3-> 4-> 5-> Null
diventerebbe:
5-> 4-> 3-> 2-> 1-> Null

    //Takes as parameters a node in a linked list, and p, the previous node in that list
    //returns the head of the new list
    Node reverse(Node n,Node p){   
        if(n==null) return null;
        if(n.next==null){ //if this is the end of the list, then this is the new head
            n.next=p;
            return n;
        }
        Node r=reverse(n.next,n);  //call reverse for the next node, 
                                      //using yourself as the previous node
        n.next=p;                     //Set your next node to be the previous node 
        return r;                     //Return the head of the new list
    }
    

modifica: ho fatto 6 modifiche su questo, dimostrando che è ancora un po 'complicato per me lol


2
Sarei un po 'seccato dal requisito "deve essere ricorsivo" in un'intervista, ad essere onesti, se Java è specificato. Altrimenti andrei con p = null; while (n.next! = null) {n2 = n.next; n.next = p; p = n; n = n2;} n.next = p; ritorno n ;. La pila O (N) è per gli uccelli.
Steve Jessop

Oh sì, anche un controllo nullo sulla testa, essendo Java.
Steve Jessop

23

Sono arrivato a metà (fino a null, e un nodo come suggerito da plinth), ma ho perso traccia dopo aver effettuato una chiamata ricorsiva. Tuttavia, dopo aver letto il post per plinto, ecco cosa mi è venuto in mente:

Node reverse(Node head) {
  // if head is null or only one node, it's reverse of itself.
  if ( (head==null) || (head.next == null) ) return head;

  // reverse the sub-list leaving the head node.
  Node reverse = reverse(head.next);

  // head.next still points to the last element of reversed sub-list.
  // so move the head to end.
  head.next.next = head;

  // point last node to nil, (get rid of cycles)
  head.next = null;
  return reverse;
}

molto carino, proprio come fare contro :)
Karthikeyan D

9

Ecco un'altra soluzione ricorsiva. Ha meno codice all'interno della funzione ricorsiva rispetto ad alcune delle altre, quindi potrebbe essere un po 'più veloce. Questo è C # ma credo che Java sarebbe molto simile.

class Node<T>
{
    Node<T> next;
    public T data;
}

class LinkedList<T>
{
    Node<T> head = null;

    public void Reverse()
    {
        if (head != null)
            head = RecursiveReverse(null, head);
    }

    private Node<T> RecursiveReverse(Node<T> prev, Node<T> curr)
    {
        Node<T> next = curr.next;
        curr.next = prev;
        return (next == null) ? curr : RecursiveReverse(curr, next);
    }
}

8

L'algoritmo dovrà lavorare sul seguente modello,

  • tieni traccia della testa
  • Ricorso fino alla fine della lista di collegamento
  • Collegamento inverso

Struttura:

Head    
|    
1-->2-->3-->4-->N-->null

null-->1-->2-->3-->4-->N<--null

null-->1-->2-->3-->4<--N<--null

null-->1-->2-->3<--4<--N<--null

null-->1-->2<--3<--4<--N<--null

null-->1<--2<--3<--4<--N<--null

null<--1<--2<--3<--4<--N
                       |
                       Head

Codice:

public ListNode reverse(ListNode toBeNextNode, ListNode currentNode)
{               
        ListNode currentHead = currentNode; // keep track of the head

        if ((currentNode==null ||currentNode.next==null )&& toBeNextNode ==null)return currentHead; // ignore for size 0 & 1

        if (currentNode.next!=null)currentHead = reverse(currentNode, currentNode.next); // travarse till end recursively

        currentNode.next = toBeNextNode; // reverse link

        return currentHead;
}

Produzione:

head-->12345

head-->54321

7

Penso che questa sia una soluzione più pulita, che assomiglia a LISP

// Example:
// reverse0(1->2->3, null) => 
//      reverse0(2->3, 1) => 
//          reverse0(3, 2->1) => reverse0(null, 3->2->1)
// once the first argument is null, return the second arg
// which is nothing but the reveresed list.

Link reverse0(Link f, Link n) {
    if (f != null) {
        Link t = new Link(f.data1, f.data2); 
        t.nextLink = n;                      
        f = f.nextLink;             // assuming first had n elements before, 
                                    // now it has (n-1) elements
        reverse0(f, t);
    }
    return n;
}

7

So che questo è un vecchio post, ma la maggior parte delle risposte non sono ricorsive in coda, cioè eseguono alcune operazioni dopo essere tornate dalla chiamata ricorsiva, e quindi non sono le più efficienti.

Ecco una versione ricorsiva della coda:

public Node reverse(Node previous, Node current) {
    if(previous == null)
        return null;
    if(previous.equals(head))
        previous.setNext(null);
    if(current == null) {    // end of list
        head = previous;
        return head;
    } else {
                    Node temp = current.getNext();
        current.setNext(previous);
        reverse(current, temp);
    }
    return null;    //should never reach here.
} 

Chiama con:

Node newHead = reverse(head, head.getNext());

9
fai riferimento a una variabile chiamata "head" nel tuo metodo, ma questa non è dichiarata da nessuna parte.
maratona

è probabilmente un metodo per la classe List contenente l'attributo Node head
ChrisMcJava

4
void reverse (node1, node2) {
if (node1.next! = null)
      reverse (node1.next, node1);
   node1.next = node2;
}
chiama questo metodo come reverse (start, null);

4
public Node reverseListRecursive(Node curr)
{
    if(curr == null){//Base case
        return head;
    }
    else{
        (reverseListRecursive(curr.next)).next = (curr);
    }
    return curr;
}

3
public void reverse() {
    head = reverseNodes(null, head);
}

private Node reverseNodes(Node prevNode, Node currentNode) {
    if (currentNode == null)
        return prevNode;
    Node nextNode = currentNode.next;
    currentNode.next = prevNode;
    return reverseNodes(currentNode, nextNode);
}

Penso che questa sia la soluzione migliore ... semplice, ricorsione della coda ottimizzabile e solo un controllo nullo.
sdanzig

2
public static ListNode recRev(ListNode curr){

    if(curr.next == null){
        return curr;
    }
    ListNode head = recRev(curr.next);
    curr.next.next = curr;
    curr.next = null;

    // propogate the head value
    return head;

}

Questa è la soluzione migliore, ma non la risposta migliore poiché non viene fornita alcuna spiegazione :). All'inizio ho derivato una soluzione simile, ma ho perso il riferimento head. Questa soluzione lo risolve.
OpenUserX03

2

Inverti con algoritmo ricorsivo.

public ListNode reverse(ListNode head) {
    if (head == null || head.next == null) return head;    
    ListNode rHead = reverse(head.next);
    rHead.next = head;
    head = null;
    return rHead;
}

Da iterativo

public ListNode reverse(ListNode head) {
    if (head == null || head.next == null) return head;    
    ListNode prev = null;
    ListNode cur = head
    ListNode next = head.next;
    while (next != null) {
        cur.next = prev;
        prev = cur;
        cur = next;
        next = next.next;
    }
    return cur;
}

Sfortunatamente il tuo inverso ricorsivo è sbagliato !!
Sree Aurovindh

@SreeAurovindh - Perché?
rayryeng

2

Questa soluzione dimostra che non sono richiesti argomenti.

/**
 * Reverse the list
 * @return reference to the new list head
 */
public LinkNode reverse() {
    if (next == null) {
        return this; // Return the old tail of the list as the new head
    }
    LinkNode oldTail = next.reverse(); // Recurse to find the old tail
    next.next = this; // The old next node now points back to this node
    next = null; // Make sure old head has no next
    return oldTail; // Return the old tail all the way back to the top
}

Ecco il codice di supporto, per dimostrare che funziona:

public class LinkNode {
    private char name;
    private LinkNode next;

    /**
     * Return a linked list of nodes, whose names are characters from the given string
     * @param str node names
     */
    public LinkNode(String str) {
        if ((str == null) || (str.length() == 0)) {
            throw new IllegalArgumentException("LinkNode constructor arg: " + str);
        }
        name = str.charAt(0);
        if (str.length() > 1) {
            next = new LinkNode(str.substring(1));
        }
    }

    public String toString() {
        return name + ((next == null) ? "" : next.toString());
    }

    public static void main(String[] args) {
        LinkNode head = new LinkNode("abc");
        System.out.println(head);
        System.out.println(head.reverse());
    }
}

2

Ecco un semplice approccio iterativo:

public static Node reverse(Node root) {
    if (root == null || root.next == null) {
        return root;
    }

    Node curr, prev, next;
    curr = root; prev = next = null;
    while (curr != null) {
        next = curr.next;
        curr.next = prev;

        prev = curr;
        curr = next;
    }
    return prev;
}

Ed ecco un approccio ricorsivo:

public static Node reverseR(Node node) {
    if (node == null || node.next == null) {
        return node;
    }

    Node next = node.next;
    node.next = null;

    Node remaining = reverseR(next);
    next.next = node;
    return remaining;
}

1

Poiché Java è sempre un valore di passaggio, per invertire in modo ricorsivo un elenco collegato in Java, assicurarsi di restituire la "nuova intestazione" (il nodo principale dopo il ripristino) alla fine della ricorsione.

static ListNode reverseR(ListNode head) {
    if (head == null || head.next == null) {
        return head;
    }

    ListNode first = head;
    ListNode rest = head.next;

    // reverse the rest of the list recursively
    head = reverseR(rest);

    // fix the first node after recursion
    first.next.next = first;
    first.next = null;

    return head;
}

1

PointZeroTwo ha una risposta elegante e lo stesso in Java ...

public void reverseList(){
    if(head!=null){
        head = reverseListNodes(null , head);
    }
}

private Node reverseListNodes(Node parent , Node child ){
    Node next = child.next;
    child.next = parent;
    return (next==null)?child:reverseListNodes(child, next);
}

Questo è perfetto perché non vuoi sempre che il metodo della lista prenda la lista come argomenti ma si inverta con i suoi figli, grazie
Manu

0
public class Singlelinkedlist {
  public static void main(String[] args) {
    Elem list  = new Elem();
    Reverse(list); //list is populate some  where or some how
  }

  //this  is the part you should be concerned with the function/Method has only 3 lines

  public static void Reverse(Elem e){
    if (e!=null)
      if(e.next !=null )
        Reverse(e.next);
    //System.out.println(e.data);
  }
}

class Elem {
  public Elem next;    // Link to next element in the list.
  public String data;  // Reference to the data.
}

0
public Node reverseRec(Node prev, Node curr) {
    if (curr == null) return null;  

    if (curr.next == null) {
        curr.next = prev;
        return curr;

    } else {
        Node temp = curr.next; 
        curr.next = prev;
        return reverseRec(curr, temp);
    }               
}

chiamare utilizzando: head = reverseRec (null, head);


0

Quello che hanno fatto gli altri ragazzi, in altri post è un gioco di contenuti, quello che ho fatto è un gioco di linkedlist, inverte il membro della LinkedList e non il valore di membri.

Public LinkedList reverse(LinkedList List)
{
       if(List == null)
               return null;
       if(List.next() == null)
              return List;
       LinkedList temp = this.reverse( List.next() );
       return temp.setNext( List );
}

sry ho dimenticato che hai anche bisogno di un metodo di supporto per impostare la coda successiva, con valore nullo
Nima Ghaedsharafi

0
package com.mypackage;
class list{

    node first;    
    node last;

    list(){
    first=null;
    last=null;
}

/*returns true if first is null*/
public boolean isEmpty(){
    return first==null;
}
/*Method for insertion*/

public void insert(int value){

    if(isEmpty()){
        first=last=new node(value);
        last.next=null;
    }
    else{
        node temp=new node(value);
        last.next=temp;
        last=temp;
        last.next=null;
    }

}
/*simple traversal from beginning*/
public void traverse(){
    node t=first;
    while(!isEmpty() && t!=null){
        t.printval();
        t= t.next;
    }
}
/*static method for creating a reversed linked list*/
public static void reverse(node n,list l1){

    if(n.next!=null)
        reverse(n.next,l1);/*will traverse to the very end*/
    l1.insert(n.value);/*every stack frame will do insertion now*/

}
/*private inner class node*/
private class node{
    int value;
    node next;
    node(int value){
        this.value=value;
    }
    void printval(){
        System.out.print(value+" ");
    }
}

 }

0

La soluzione è:

package basic;

import custom.ds.nodes.Node;

public class RevLinkedList {

private static Node<Integer> first = null;

public static void main(String[] args) {
    Node<Integer> f = new Node<Integer>();
    Node<Integer> s = new Node<Integer>();
    Node<Integer> t = new Node<Integer>();
    Node<Integer> fo = new Node<Integer>();
    f.setNext(s);
    s.setNext(t);
    t.setNext(fo);
    fo.setNext(null);

    f.setItem(1);
    s.setItem(2);
    t.setItem(3);
    fo.setItem(4);
    Node<Integer> curr = f;
    display(curr);
    revLL(null, f);
    display(first);
}

public static void display(Node<Integer> curr) {
    while (curr.getNext() != null) {
        System.out.println(curr.getItem());
        System.out.println(curr.getNext());
        curr = curr.getNext();
    }
}

public static void revLL(Node<Integer> pn, Node<Integer> cn) {
    while (cn.getNext() != null) {
        revLL(cn, cn.getNext());
        break;
    }
    if (cn.getNext() == null) {
        first = cn;
    }
    cn.setNext(pn);
}

}


0
static void reverseList(){

if(head!=null||head.next!=null){
ListNode tail=head;//head points to tail


ListNode Second=head.next;
ListNode Third=Second.next;
tail.next=null;//tail previous head is poiniting null
Second.next=tail;
ListNode current=Third;
ListNode prev=Second;
if(Third.next!=null){



    while(current!=null){
    ListNode    next=current.next;
        current.next=prev;
        prev=current;
        current=next;
    }
    }
head=prev;//new head
}
}
class ListNode{
    public int data;
    public ListNode next;
    public int getData() {
        return data;
    }

    public ListNode(int data) {
        super();
        this.data = data;
        this.next=null;
    }

    public ListNode(int data, ListNode next) {
        super();
        this.data = data;
        this.next = next;
    }

    public void setData(int data) {
        this.data = data;
    }
    public ListNode getNext() {
        return next;
    }
    public void setNext(ListNode next) {
        this.next = next;
    }





}

0
private Node ReverseList(Node current, Node previous)
    {
        if (current == null) return null;
        Node originalNext = current.next;
        current.next = previous;
        if (originalNext == null) return current;
        return ReverseList(originalNext, current);
    }

inizia con ReverseList (head, null)
pat

0
//this function reverses the linked list
public Node reverseList(Node p) {
    if(head == null){
        return null;
    }
    //make the last node as head
    if(p.next == null){
        head.next = null;
        head = p;
        return p;
    }
    //traverse to the last node, then reverse the pointers by assigning the 2nd last node to last node and so on..
    return reverseList(p.next).next = p;
}

0
//Recursive solution
class SLL
{
   int data;
   SLL next;
}

SLL reverse(SLL head)
{
  //base case - 0 or 1 elements
  if(head == null || head.next == null) return head;

  SLL temp = reverse(head.next);
  head.next.next = head;
  head.next = null;
  return temp;
}

0

Ispirato da un articolo discute delle implementazioni immutabili di strutture di dati ricorsive, ho messo insieme una soluzione alternativa utilizzando Swift.

La principale soluzione per documenti di risposta che evidenzia i seguenti argomenti:

  1. Qual è il contrario di zero (la lista vuota)?
    • Non importa qui, perché in Swift abbiamo una protezione nulla.
  2. Qual è il contrario di un elenco di un elemento?
    • L'elemento stesso
  3. Qual è il contrario di un elenco di n elementi?
    • Il contrario del secondo elemento seguito dal primo elemento.

Li ho chiamati dove applicabile nella soluzione di seguito.

/**
 Node is a class that stores an arbitrary value of generic type T 
 and a pointer to another Node of the same time.  This is a recursive 
 data structure representative of a member of a unidirectional linked
 list.
 */
public class Node<T> {
    public let value: T
    public let next: Node<T>?

    public init(value: T, next: Node<T>?) {
        self.value = value
        self.next = next
    }

    public func reversedList() -> Node<T> {
        if let next = self.next {
            // 3. The reverse of the second element on followed by the first element.
            return next.reversedList() + value
        } else {
            // 2. Reverse of a one element list is itself
            return self
        }
    }
}

/**
 @return Returns a newly created Node consisting of the lhs list appended with rhs value.
 */
public func +<T>(lhs: Node<T>, rhs: T) -> Node<T> {
    let tail: Node<T>?
    if let next = lhs.next {
        // The new tail is created recursively, as long as there is a next node.
        tail = next + rhs
    } else {
        // If there is not a next node, create a new tail node to append
        tail = Node<T>(value: rhs, next: nil)
    }
    // Return a newly created Node consisting of the lhs list appended with rhs value.
    return Node<T>(value: lhs.value, next: tail)
}

0

Inversione dell'elenco collegato utilizzando la ricorsione. L'idea è regolare i collegamenti invertendo i collegamenti.

  public ListNode reverseR(ListNode p) {

       //Base condition, Once you reach the last node,return p                                           
        if (p == null || p.next == null) { 
            return p;
        }
       //Go on making the recursive call till reach the last node,now head points to the last node

        ListNode head  = reverseR(p.next);  //Head points to the last node

       //Here, p points to the last but one node(previous node),  q points to the last   node. Then next next step is to adjust the links
        ListNode q = p.next; 

       //Last node link points to the P (last but one node)
        q.next = p; 
       //Set the last but node (previous node) next to null
        p.next = null; 
        return head; //Head points to the last node
    }

1
Potresti elaborare maggiormente la tua risposta aggiungendo un po 'più di descrizione sulla soluzione che fornisci?
abarisone

1
Ho aggiunto commenti. Grazie mille
gurubelli

0
public void reverseLinkedList(Node node){
    if(node==null){
        return;
    }

    reverseLinkedList(node.next);
    Node temp = node.next;
    node.next=node.prev;
    node.prev=temp;
    return;
}

-1
public void reverse(){
    if(isEmpty()){
    return;
     }
     Node<T> revHead = new Node<T>();
     this.reverse(head.next, revHead);
     this.head = revHead;
}

private Node<T> reverse(Node<T> node, Node<T> revHead){
    if(node.next == null){
       revHead.next = node;
       return node;
     }
     Node<T> reverse = this.reverse(node.next, revHead);
     reverse.next = node;
     node.next = null;
     return node;
}

-1

Ecco un riferimento se qualcuno sta cercando l'implementazione di Scala:

scala> import scala.collection.mutable.LinkedList
import scala.collection.mutable.LinkedList

scala> def reverseLinkedList[A](ll: LinkedList[A]): LinkedList[A] =
         ll.foldLeft(LinkedList.empty[A])((accumulator, nextElement) => nextElement +: accumulator)
reverseLinkedList: [A](ll: scala.collection.mutable.LinkedList[A])scala.collection.mutable.LinkedList[A]

scala> reverseLinkedList(LinkedList("a", "b", "c"))
res0: scala.collection.mutable.LinkedList[java.lang.String] = LinkedList(c, b, a)

scala> reverseLinkedList(LinkedList("1", "2", "3"))
res1: scala.collection.mutable.LinkedList[java.lang.String] = LinkedList(3, 2, 1)

Sarei più che felice di migliorare la mia risposta se la persona sottovalutata mi fornisse una spiegazione del suo atto. Comunque, per me funziona ancora in Scala :)
Venkat Sudheer Reddy Aedama

Solo così il downvoter sa, questa è una soluzione ricorsiva (in effetti, una ricorsiva di coda).
Venkat Sudheer Reddy Aedama

Scala non è Java, anche se entrambi girano sulla JVM.
Bill Lynch

@sharth Wow, bene saperlo. Ti sei preso la briga di leggere la prima riga della mia risposta?
Venkat Sudheer Reddy Aedama

@VenkatSudheerReddyAedama Hai ricevuto un voto negativo perché la domanda originale chiedeva un'implementazione in Java. Anche se Scala gira nella JVM, questo non aiuta a rispondere alla domanda ... anche se è piuttosto elegante. FWIW, non ti ho svalutato.
rayryeng
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.