二叉树的基本算法

161 阅读4分钟

1.给定两个可能有环也可能无环的单链表,头节点head1和head2。请实现一个函数,如果两个链表相交,请返回相交的 第一个节点。如果不相交,返回null

【要求】如果两个链表长度之和为N,时间复杂度请达到O(N),额外空间复杂度 请达到O(1)。

(1)解析过程:

image.png

思路:设置快慢指针,快指针一次走2步,慢指针一次走1步,同时出发。当两者相遇时,将快指针回到原出发位置,并设置每次走一步,当快慢指针再次相遇时就是两者相交的第一个节点。

(2)代码解析

//定义next指针和值
public static class Node {
    public int value;
    public Node next;

    public Node(int data) {
        this.value = data;
    }
}

// 找到链表第一个入环节点,如果无环,返回null
public static Node getLoopNode(Node head) {
    if (head == null || head.next == null) {
        return null;
    }
    // n1 慢  n2快
    Node slow = head.next;
    Node fast = head.next.next;
    while (slow != fast) {
        if (fast.next == null) {
            return null;
        }
        fast = fast.next.next;
        slow = slow.next;
    }
    //slow fast 相遇
    fast = head;//n2 -> 从头走
    while (slow != fast) {
        slow = slow.next;
        fast = fast.next;
    }
    return slow;
}

//

2.能不能不给单链表的头节点,只给想要删除的节点,就能做到在链表上把这个点删掉

//	// 如果两个链表都无环,返回第一个相交节点,如果不想交,返回null
public static Node noLoop(Node head1, Node head2) {
    if (head1 == null || head2 == null) {
        return null;
    }
    Node cur1 = head1;
    Node cur2 = head2;
    int n = 0;
    while (cur1.next != null) {
        n++;
        cur1 = cur1.next;
    }
    while (cur2.next != null) {
        n--;
        cur2 = cur2.next;
    }
    if (cur1 != cur2) {
        return null;
    }
    //n :链表1长度减去链表2长度的值
    cur1 = n > 0 ?head1: head2;//谁长,谁的头变成cur1
    cur2 = cur1 == head1 ? head2:head1;//// 谁短,谁的头变成cur2
    n = Math.abs(n);
   	while (n != 0) {
       n--;
       cur1 = cur1.next;
   }
    while (cur1 != cur2) {
        cur1 = cur1.next;
        cur2 = cur2.next;
    }
    return cur1;
}

4.二叉树的遍历

(1)先序遍历,中序遍历,后序遍历

image.png

先序:任何子树的处理顺序都是,先头结点、再左子树、然后右子树

中序:任何子树的处理顺序都是,先左子树、再中子树、然后右子树

后序:任何子树的处理顺序都是,先左子树、再右子树、然后中子树

(2)递归方式实现二叉树的先序、中序、后序遍历

  • 理解递归序
  • 先序、中序、后序都可以在递归序的基础上加工出来
  • 第一次到达一个节点就打印就是先序、第二次打印即中序、第三次即后序
//
public static class Node {
    public int value;
    public Node left;
    public Node right;

    public Node(int v) {
        value = v;
    }
}

public static void f (Node head) {
    if (head == null) {
        return;
    }
    //1
    f(head.left);
    //2
    f(head.right);
    //3
}
//先序打印所有的节点
public static void pre(Node head) {
    if (head == null) {
        return;
    }
    in(head.left);
    in(head.rigth);
}
public static void pos(Node head) {
    if (head == null) {
        return;
    }
    pos(head.left);
    pos(head.right);
}

(3)非递归方式实现二叉树的先序、中序、后序遍历

  • 任何递归函数都可以改成非递归
  • 自己设计压栈的来实现

思路:使用栈的方式完成先序遍历

image.png

代码解析:

//使用栈的方式完成--先序遍历
public static void pre(NOde head) {
    if (head != null) {
        Stack<Node> stack = new Stack<>();
        stack.add(head);
        while (!stack.isEmpty()) {
            head = stack.pop();//将头结点先压入栈
            if (head.right != null) {//有右就先压入右节点
                stack.push(head.right);
            }
            if (head.left != null) {
                stack.push(head.left);//有左压入左,先右再左
            }
        }
     }
}
//使用栈的方式完成--后序遍历
public static void pos(Node head) {
    if (head != null) {
        Stack<Node> s1 = new Stack<Node>();
        Stack<Node> s2 = new Stack<Node>();
        s1.push(head);
        while (!s1.isEmpty()) {
            head = s1.pop();// 头 右 左
            s2.push(head);
            if (head.left != null) {
                s1.push(head.left);
            }
            if (head.right != null) {
                s1.push(head.right);
            }
        }
        //左右头
        while (!s2.Empty()) {
            System.out.print(s2.pop().value + " ");
        }
    }
    System.out.println();
}

中序遍历的代码解析

image.png

public static void in(Node cur) {
    System.out.print("in-order: ");//中序遍历
    if (cur != null) {
        Stack<Node> stack = new Stack<Node>();
        while (!stack.isEmpty() || cur != null) {
            if (cur != null) {
                cur = cur.left;
            } else {
                cur = stack.pop();
                cur = cur.right;
            }
        }
    }
    System.out.println();
}

5.实现二叉树的按层遍历

1)其实就是宽度优先遍历,用队列

image.png

//层序遍历
public static void level(Node head) {
    if (head == null) {
        return;
    }
    Queue<Node> queue = new LinkedList<>();
    queue.add(head);
    while (!queue.isEmpty()) {
        Node cur = queue.poll();
        if (cur.left != null) {
            queue.add(cur.left);
        }
        if (cur.right != null) {
            queue.add(cur.right);
        }
    }
}

2)可以通过设置flag变量的方式,来发现某一层的结束

序列化与反序列化:数与字符串是一一对应的(每个数字都与值一一对应的)

image.png

image.png

在进行二叉树的反序列化的时候可以将节点一个一个的作为数组的索引

首先从头开始,找到为1的就继续下面的左右节点,如果没有就空,,,,以此类推

6.实现二叉树的序列化和反序列化

1)先序方式序列化和反序列化

public static Queue<String> preSerial(Node head) {
    Queue<String> ans = new LinkedList<>();
    pres(head, ans);//返回的头结点和添加的数值
    return ans;
}

public static void pres(Node head, Queue<String> ans) {
    if (head == null) {//将节点转化为字符串的
        ans.add(null);
    } else {
        ans.add(String.valueOf(head.value));
        pres(head.left, ans);
        pres(head.right, ans);
    }
}

//先序遍历的反序列化
public static Node buildByPreQueue(Queue<String> prelist) {//先序方式的反序列化
    if (prelist == null || prelist.size() == 0) {
        return null;
    }
    return preb(prelist);
}

public static Node preb(Queue<String> prelist) {
    //将字符串转换成整型再将这个节点建立出来作为此时的头节点
    String value = prelist.poll();
    if (value == null) {
        return null;
    }
    Node node = new Node(Integer.valueOf(value));
    head.left = preb(prelist);
    head.right = preb(prelist);
	return head;
}


2)按层方式序列化和反序列化

//按成方式的序列化
public static Queue<String> levelSerial(Node head) {
    Queue<String> ans = new LinkedList<>();
    if (head == null) {
        ans.add(null);
    } else {
        ans.add(String.valueOf(head.value));
        Queue<Node> queue = new LinkedList<Node>();
        queue.add(head);
        while (!queue.isEmpty()) {
            head = queue.poll(); // head 父   子
            if (head.left != null) {
                ans.add(String.valueOf(head.left.value));
                queue.add(head.left);
            } else {
                ans.add(null);
            }
            if (head.right != null) {
                ans.add(String.valueOf(head.right.value));
                queue.add(head.right);
            } else {
                ans.add(null);
            }
        }
    }
    return ans;
}

//按成方式的反序列化
public static Node buildByLevelQueue(Queue<String> levelList) {
    if (levelList == null || levelList.size() == 0) {
        return null;
    }
    Node head = generateNode(levelList.poll());
    Queue<Node> queue = new LinkedList<Node>();
    if (head != null) {
        queue.add(head);
    }
    Node node = null;
    while (!queue.isEmpty()) {
        node = queue.poll();
        node.left = generateNode(levelList.poll());
        node.right = generateNode(levelList.poll());
        if (node.left != null) {
            queue.add(node.left);
        }
        if (node.right != null) {
            queue.add(node.right);
        }
    }
    return head;
}

public static Node generateNode(String val) {
    if (val == null) {
        return null;
    }
    return new Node(Integer.valueOf(val));
}

7.如何设计一个打印整棵树的打印函数

public static Node getSuccessorNode(Node node) {
    if (node == null) {return node;}
    if (node.right != null) {
        return getLeftMost(node.right);
    } else {
        Node parent = node.parent;
        while (parent != null && parent.right == node) {
            node = parent;
            parent = node.parent;
        }
        return parent;
    }
}

public static Node getLeftMost(Node node) {
    if (node == null) {return node;}
    while (node.left != null) {
        node = node.left;
    }
    return node;
}

8.求二叉树最宽的层有多少个节点

思路:遍历当前层的最后节点curEnd=null,遍历下一层的最后节点为nextEnd=null。最后比较得到最大层的宽度大小

image.png

每次将下一层的节点给顶到当前节点curEnd,在从头开始遍历下一层的节点

代码:

public static int maxWidthNoMap(Node head) {
    if (head == null) {return 0;}
    Queue<Node> queue = new LinkedList<>();
    queue.add(head);
    Node curEnd = head; // 当前层,最右节点是谁
    Node nextEnd = null; // 下一层,最右节点是谁
    int max = 0;
    int curLevelNodes = 0; //当前层的节点数
    while (!queue.isEmpty()) {
        Node cur = queue.poll();
        if (cur.left != null) {
            queue.add(cur.left);
            nextEnd = cur.left;
        }
        if (cur.right != null) {
            queue.add(cur.right);
            nextEnd = cur.right;
        }
        curLevelNodes++;
        if (cur == curEnd) {
            max = Math.max(max, curLevelNodes);
            curLevelNodes = 0;
            curEnd = nextEnd;
        }
    }
    return max;
}

9.请把一段纸条竖着放在桌子上,然后从纸条的下边向上方对折1次,压出折痕后展开。此时折痕是凹下去的,即折痕突起的方向指向纸条的背面。 如果从纸条的下边向上方连续对折2次,压出折痕后展开,此时有三条折痕,从上到下依次是下折痕、下折痕和上折痕。

给定一个输入参数N,代表纸条都从下边向上方连续对折N次。 请从上到下打印所有折痕的方向。 例如:N=1时,打印: down N=2时,打印: down down up

public static void printAllFolds(int N) {
    process(1, N, true);
    System.out.println();
}

// 当前你来了一个节点,脑海中想象的!
// 这个节点在第i层,一共有N层,N固定不变的
// 这个节点如果是凹的话,down = T
// 这个节点如果是凸的话,down = F
// 函数的功能:中序打印以你想象的节点为头的整棵树!
public static void process(int i, int N, boolean down) {
    if (i > N) {
        return;
    }
    process(i + 1, N, true);
    System.out.print(down ? "凹 " : "凸 ");
    process(i + 1, N, false);
}