Я уже некоторое время работаю над Java-проектом для класса. Это реализация связного списка (здесь называется AddressList, содержащего простые узлы с именем ListNode). Загвоздка в том, что все придется делать с помощью рекурсивных алгоритмов. Я все делал нормально без одного метода: public AddressList reverse()
ListNode:
public class ListNode{
public String data;
public ListNode next;
}
Сейчас моя функция reverse просто вызывает вспомогательную функцию, которая принимает аргумент, разрешающий рекурсию.
public AddressList reverse(){
return new AddressList(this.reverse(this.head));
}
С моей вспомогательной функцией, имеющей подпись private ListNode reverse(ListNode current).
На данный момент он работает итеративно с использованием стека, но это не то, что требует спецификация. Я нашел алгоритм на C, который рекурсивно реверсировал и преобразовывал его в код Java вручную, и он работал, но я этого не понимал.
Обновлено: Неважно, я тем временем понял это.
private AddressList reverse(ListNode current, AddressList reversedList){
if (current == null)
return reversedList;
reversedList.addToFront(current.getData());
return this.reverse(current.getNext(), reversedList);
}
Пока я здесь, кто-нибудь не видит проблем с этим маршрутом?




Мне задали этот вопрос на собеседовании, и меня раздражало, что я возился с ним, так как немного нервничал.
Это должно перевернуть односвязный список, вызываемый с помощью reverse (head, NULL); так что, если бы это был ваш список:
1->2->3->4->5->null it would become: 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
}
edit: я сделал около 6 правок на этом, показывая, что это все еще немного сложно для меня lol
Честно говоря, если бы была указана Java, я бы немного рассердился на требование "должен быть рекурсивным" на собеседовании. В противном случае я бы пошел с p = null; в то время как (n.next! = null) {n2 = n.next; n.next = p; р = п; n = n2;} n.next = p; return n ;. Стопка O (N) предназначена для птиц.
Ах да, нулевая проверка на голову тоже, это Java.
В одном ответе есть код, который объясняет это, но вам может быть легче начать снизу вверх, задавая и отвечая на крошечные вопросы (это подход в The Little Lisper):
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;
}
Вау, мне нравятся все эти «Три вопроса».
Спасибо. Этот маленький вопрос должен быть основой изучения Лиспа. Это также способ скрыть индукцию от новичков, что по сути и является этим шаблоном. Я рекомендую прочитать Little Lisper, если вы действительно хотите решить эту проблему.
Я считаю, что вы могли бы устранить первый / второй вопрос с помощью try / catch NullPointerException и вернуть list. Это устранит два условия и поместит возврат из метода в конец.
исключения в исключительных обстоятельствах. Зачем использовать уловку для известного условия, которое можно проверить с помощью if?
Я считаю, что вам не нужно создавать переменную: secondElem, поскольку list.next по-прежнему является secondElem. После «ListNode reverseRest = Reverse (secondElem);» вы можете сначала выполнить «list.next.next = list», а затем «list.next = null». Вот и все.
Вы можете объяснить, почему list.next = null? Я пытался понять цикл, но не понял.
@Rohit list.next = null разрывает связь между элементом и его последующим элементом. Без него у вас позже будет цикл между этим элементом и последующим элементом.
Алго должен будет работать на следующей модели,
Состав:
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
Код:
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;
}
Выход:
head-->12345
head-->54321
Я прошел половину пути (до нуля и одного узла, как подсказывает плинтус), но потерял след после выполнения рекурсивного вызова. Однако, прочитав пост у плинтуса, я пришел к следующему:
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;
}
очень красиво. просто как делать минусы :)
void reverse(node1,node2){
if (node1.next!=null)
reverse(node1.next,node1);
node1.next=node2;
}
call this method as reverse(start,null);
Я думаю, что это более чистое решение, напоминающее 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;
}
Я знаю, что это старый пост, но большинство ответов не являются хвостовыми рекурсивными, т.е. они выполняют некоторые операции после возврата из рекурсивного вызова и, следовательно, не самые эффективные.
Вот хвостовая рекурсивная версия:
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.
}
Звоните с:
Node newHead = reverse(head, head.getNext());
вы ссылаетесь на переменную с именем "head" в своем методе, но она нигде не объявлена.
это, вероятно, метод класса List, содержащий атрибут заголовка узла
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.
}
public Node reverseListRecursive(Node curr)
{
if (curr == null){//Base case
return head;
}
else{
(reverseListRecursive(curr.next)).next = (curr);
}
return curr;
}
Вот еще одно рекурсивное решение. В ней меньше кода в рекурсивной функции, чем в некоторых других, поэтому она может быть немного быстрее. Это C#, но я считаю, что Java будет очень похожей.
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);
}
}
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;
}
Это лучшее решение, но не самое лучшее отвечать, так как никаких объяснений не дается :). Сначала я получил аналогичное решение, но потерял ссылку на голову. Это решение решает эту проблему.
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);
}
}
вызов с использованием: head = reverseRec (null, head);
PointZeroTwo получил элегантный ответ и то же самое в 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);
}
Это идеально, потому что вы не всегда хотите, чтобы этот метод списка принимал список в качестве аргументов, а чтобы он полностью изменял себя со своими собственными дочерними элементами, спасибо
То, что сделали другие ребята, в другом посте - это игра с контентом, то, что я сделал, - это игра со связанными списками, она меняет значение члена LinkedList, а не значение участников.
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 );
}
я забыл, что вам также нужен вспомогательный метод для установки следующего хвоста с нулевым значением
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+" ");
}
}
}
Обратный рекурсивным алгоритмом.
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;
}
По итеративному
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;
}
К сожалению, ваш рекурсивный реверс неверен!
@SreeAurovindh - Почему?
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;
}
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);
}
Я думаю, что это лучшее решение ... простое, с оптимизацией хвостовой рекурсии и только одна нулевая проверка.
Вот ссылка, если кто-то ищет реализацию 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)
Я был бы более чем счастлив улучшить свой ответ, если бы человек, получивший отрицательное голосование, объяснил бы мне свой поступок. В любом случае, у меня он все еще работает в Scala :)
Просто чтобы отрицательный голос знал, что это рекурсивное (фактически хвостовое рекурсивное) решение.
Scala - это не Java, даже если они оба работают на JVM.
@sharth Вау, приятно это знать. Вы потрудились прочитать первую строчку моего ответа?
@VenkatSudheerReddyAedama Вы получили отрицательное голосование, потому что исходный вопрос касался реализации на Java. Несмотря на то, что Scala работает в JVM, это не помогает ответить на вопрос ... хотя и довольно элегантно. FWIW, я не голосовал против вас.
Это решение демонстрирует, что никаких аргументов не требуется.
/**
* 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
}
Вот вспомогательный код, демонстрирующий, что это работает:
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());
}
}
Вот простой итеративный подход:
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;
}
А вот рекурсивный подход:
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;
}
Решение:
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);
}
}
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;
}
}
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);
}
начать с ReverseList (head, null)
Поскольку Java всегда передается по значению, чтобы рекурсивно перевернуть связанный список в Java, обязательно возвращайте «новый заголовок» (головной узел после реверсии) в конце рекурсии.
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;
}
//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;
}
Вот как мы бы сделали это в Opal - чистом функциональном языке программирования. И ИМХО - делать это рекурсивно имеет смысл только в этом контексте.
List Reverse(List l)
{
if (IsEmpty(l) || Size(l) == 1) return l;
return reverse(rest(l))::first(l);
}
rest (l) возвращает список, который является исходным списком без первого узла. first (l) возвращает первый элемент. :: - оператор конкатенации.
//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;
}
Вдохновленный статья, обсуждающим неизменяемые реализации рекурсивных структур данных, я создал альтернативное решение, используя Swift.
Лучшее решение для ответных документов, в котором выделяются следующие темы:
Я назвал их там, где это возможно, в приведенном ниже решении.
/**
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)
}
Переворачивание связанного списка с помощью рекурсии. Идея заключается в корректировке ссылок путем их переворота.
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
}
Не могли бы вы подробнее рассказать о своем ответе, добавив еще немного описания предлагаемого вами решения?
Я добавил комментарии. Большое спасибо
Вот C# версия Reverse для связанного списка.
public void Reverse()
{
Node currentNode, nextNode=null, prevNode=null;
currentNode = head;
while(currentNode!=null)
{
nextNode = currentNode.next;
currentNode.next = prevNode;
prevNode = currentNode;
currentNode = nextNode;
}
head = prevNode;
}
public void reverseLinkedList(Node node){
if (node==null){
return;
}
reverseLinkedList(node.next);
Node temp = node.next;
node.next=node.prev;
node.prev=temp;
return;
}
Нет, с твоим решением проблем нет. Напротив, оно даже «лучше», чем излюбленное решение «Little Lisper», в том, что оно позволяет сохранить исходный список без изменений. Это было бы особенно полезно в многоядерной среде, где настоятельно рекомендуется неизменяемые значения.