Java 在链表末尾插入节点

Java 在链表末尾插入节点,java,recursion,data-structures,recursive-datastructures,Java,Recursion,Data Structures,Recursive Datastructures,这类问题有一个简单的迭代解法 Node Insert(Node head,int data) { Node newNode = new Node(); newNode.data = data; if (head == null) { return newNode; } Node current = head; while (current.next != null) { current = current.next

这类问题有一个简单的迭代解法

Node Insert(Node head,int data) {
    Node newNode = new Node();
    newNode.data = data;
    if (head == null) {
        return newNode;
    }
    Node current = head; 
    while (current.next != null) {
        current = current.next;
    }
    current.next = newNode;
    return head;
}
它工作得非常好。但我想学习递归,并从这个角度看问题。因此,我提出了下面的解决方案,它看起来很优雅,但我必须承认它只是直觉,给定的代码工作正常。我想开发一个使用递归的心智模型,或者至少用某种方法来验证我的代码是否可以正常工作。如何从理论上验证以下解决方案是否有效

递归版本

Node Insert(Node head,int data) {
    // Base case.
    if (head == null) {
        Node newNode = new Node();
        newNode.data = data;
        return newNode;
    }
    // Smaller problem instance.
    head.next = Insert(head.next, data);
    return head;
}

我会把代码再进一步,删除多个退出点。这允许您对列表的影响以及返回哪个节点mut进行推理

Node appendRecursive(Node head, int data) {
    // By default return the same list we were given.
    Node list = head;
    if (list == null) {
        // At end of list or list is empty.
        list = new Node();
        list.data = data;
    } else {
        // Recurse.
        head.next = appendRecursive(head.next, data);
    }
    return list;
}
就推理而言,你通常需要使用归纳法

  • 如果列表为空(
    list==null
    ),则创建一个新节点,该节点将成为您的列表
  • 如果列表不是空的,则新列表必须是附加了新数据的列表
鉴于上述情况,可以推断,在所有情况下,由于列表为空或不为空,该功能将正常工作

在列表上使用递归通常被认为是低效和笨拙的,因为迭代算法更适合线性结构。更好的练习是编写自己的
结构,因为树非常适合递归算法。您将发现,在树上递归地执行所需的函数通常更容易、更优雅

static class Tree {

    Node head = null;

    class Node {

        Node left;
        Node right;
        int data;

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

    void insert(int data) {
        head = insert(head, data);
    }

    private Node insert(Node node, int data) {
        if (node == null) {
            // Empty tree becomes just the node.
            return new Node(data);
        } else {
            // Pick the correct branch to add this data to.
            if (data < node.data) {
                node.left = insert(node.left, data);
            } else {
                node.right = insert(node.right, data);
            }
        }
        return node;
    }

    private CharSequence toString(Node n) {
        StringBuilder s = new StringBuilder();
        if (n != null) {
            // First print the tree on the left.
            if (n.left != null) {
                s.append(toString(n.left)).append(",");
            }
            // Then the data in this node.
            s.append(n.data);
            // Then the tree on the right.
            if (n.right != null) {
                s.append(",").append(toString(n.right));
            }
        }
        return s;
    }

    @Override
    public String toString() {
        // Even toString is recursive.
        StringBuilder s = new StringBuilder("{");
        s.append(toString(head));
        return s.append("}").toString();
    }
}

public void test() {
    Tree tree = new Tree();
    for (int i : new int[]{6, 5, 4, 3, 2, 1}) {
        tree.insert(i);
    }
    System.out.println(tree);
}
静态类树{
节点头=空;
类节点{
左淋巴结;
节点权;
int数据;
专用节点(int数据){
这个数据=数据;
}
}
无效插入(整型数据){
头部=插入(头部,数据);
}
专用节点插入(节点,int数据){
if(node==null){
//空树只会变成节点。
返回新节点(数据);
}否则{
//选择要将此数据添加到的正确分支。
if(数据<节点数据){
node.left=插入(node.left,数据);
}否则{
node.right=插入(node.right,数据);
}
}
返回节点;
}
私有字符序列到字符串(节点n){
StringBuilder s=新的StringBuilder();
如果(n!=null){
//首先打印左侧的树。
如果(n.left!=null){
s、 追加(toString(n.left))。追加(“,”);
}
//然后是该节点中的数据。
s、 附加(n.数据);
//然后是右边的那棵树。
如果(n.right!=null){
s、 追加(“,”)。追加(toString(n.right));
}
}
返回s;
}
@凌驾
公共字符串toString(){
//甚至toString也是递归的。
StringBuilder s=新的StringBuilder(“{”);
s、 追加(toString(head));
返回s.append(“}”).toString();
}
}
公开无效测试(){
树=新树();
for(inti:newint[]{6,5,4,3,2,1}){
树.插入(i);
}
System.out.println(树);
}
请注意,在
toString
方法中判断在何处添加“,”是多么简单,这在打印列表时是一个出了名的笨拙问题

Node Insert(Node head,int data) {
    if (head == null) {
        head  = new Node();
        head.data = data;
    }
    else{
    head.next = Insert(head.next, data);
    }
    return head;
}
假设您有5,3,2,1,并且您想要添加4,那么:-insert(节点(5),int 4)

if(node(5)==null)
no然后
head.next=node(5)。next
node(3)
并调用
Insert(node(3),4)

if(node(3)==null)
no然后
head.next=node(3)。next
node(2)
并调用
Insert(node(2),4)

if(node(2)==null)
no然后
head.next=node(2)。next
node(1)
并调用
Insert(node(1),4)

if(node(1)==null)
no然后
head.next=node(1)。next
null
,因为节点(1)”之后没有元素,所以调用
插入(node(1)。next,4)


(节点(1).下一步)==null
yes,然后设置
head=new节点()
并设置数据
head=new节点()在返回端头

递归解决方案通常必须遵守以下规则:

  • 它必须区分一般情况和基本情况。然后,它必须包含某种类型的代码分支(通常是一个
    if
    )到两个代码块:基本块和通用块
  • 基块必须返回立即响应(不是递归响应)
  • 通用块必须(递归地)重新调用相同的函数,但不能使用相同的参数值(这将产生无限递归),而是使用向前推进到基本情况的值
  • 对于corse,这是一个简单的递归模型,实际上可能更复杂(不止一个基本情况,两个函数之间的递归,等等)

    如果我们根据这些规则从理论上分析你的提案,我们可以看到它符合所有这些规则