1 实现和特性
链表的查询时间复杂度慢,所以有跳表优化
加速方式:升维
链表中next指向(存放)多个结点
出现树的原因
1.1 树
结点的定义
二叉树的遍历(Pre-order、In-order、Post-order)基于递归
- 前序遍历:根-左-右
- 中序:左根右
- 后序遍历:左右根
图
区别:
Linked List(链表)就是特殊的Tree(树)
Tree是特殊化的Graph图(没有环)
1.2 二叉搜索树(Binary Search Tree)
也称为有序二叉树(Ordered Binary Tree)、排序二叉树(Sorted Binary Tree)
普通的树查找结点的时间复杂度为O(n)
搜索树就将二叉树变得有序
特性:
- 左子树所有结点均小于它的根节点
- 右子树所有结点均大于它的根节点
- 以此类推:左右子树也分别为二叉搜索树(重复性)
得出:中序遍历为升序排列
二叉搜索树常见操作
- 查询(log n)
- 插入(log n):插入时先查找,未找到的话那么当前搜索的这个位置就是需要插入的位置
- 创建:把所有结点依次插入空二叉搜索树
- 删除:当删除的结点是根节点时,将此节点最接近的结点填补上去(一般是取第一个大于此结点的结点)
删除65,第一个大于 的结点为72
特殊情况:树退化成只有右结点状态(链表),此时时间复杂度会增加
时间复杂度为(log n)
2. 算法题解
1)二叉树的中序遍历
- 中序遍历:左根右
- 递归,
- 维护栈:遍历数组时将左结点的依次放入栈,直到没有结点后出栈并放入数组
出栈的时候要判断出栈的结点是否有右结点,有的话需要继续入栈已出栈结点的右结点
import java.util.ArrayList;
import java.util.List;
import java.util.Stack;
import javax.swing.tree.TreeNode;
/*
* @lc app=leetcode.cn id=94 lang=java
*
* [94] 二叉树的中序遍历
*/
// @lc code=start
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
// 1.
// public List<Integer> inorderTraversal(TreeNode root) {
// List<Integer> res = new ArrayList<>();
// helper(root, res);
// return res;
// }
// public void helper(TreeNode root, List<Integer> res) {
// if (root != null){
// if (root.left != null) {
// helper(root.left, res); // 左子树存在就一直递归,直到不存在
// }
// res.add(root.val); // 判断左子结点不再存在后就添加当前结点,
// if (root.right != null) {
// helper(root.right, res); // 左子树递归完成后递归有右子树的结点的右子树
// }
// }
// }
// 2.
public List<Integer> inorderTraversal(TreeNode root) {
List<Integer> res = new ArrayList<>();
Stack<TreeNode> stack = new Stack<>();
TreeNode curr = root;
while (curr != null || !stack.isEmpty()){
while (curr != null){ // 左子结点存在时入栈
stack.push(curr);
curr = curr.left;
}
// 左子结点不存在就出栈
curr = stack.pop(); // 保存当前出栈的结点
res.add(curr.val); // 将当前出栈的结点存入数组
curr = curr.right; // 若curr.right为空,那么curr为空 继续pop()出栈
// 只要满足cur !=null 或者 stack.isEmpty()则继续循环出栈
}
return res;
}
}
// @lc code=end
2) 二叉树的前序遍历
前序遍历:根左右
- 递归
- 维护数组:按规则入栈
和中序遍历不一样的是:
中序被遍历后要从最深的那个左子结点开始存入数组
遍历:根-根1-根1左-根1右 != 返回:根1左-根1-根1右-根
而前序遍历时遍历到哪个结点,就存入哪个结点
遍历:根-根1-根1左-根1右 == 返回:
import java.util.List;
import java.util.Stack;
import javax.swing.tree.TreeNode;
import jdk.nashorn.api.tree.Tree;
// @lc code=start
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
// // 1.递归
// public List<Integer> preorderTraversal(TreeNode root) {
// List<Integer> res = new ArrayList<>();
// pre_order(root, res);
// return res;
// }
// public void pre_order(TreeNode root, List<Integer> res) {
// if (root != null) {
// res.add(root.val);
// if (root.left != null) {
// pre_order(root.left, res);
// }
// if (root.right != null) {
// pre_order(root.right, res);
// }
// }
// }
// 2. 维护数组
public List<Integer> preorderTraversal(TreeNode root) {
List<Integer> res = new ArrayList<>();
Stack<TreeNode> stack = new Stack<>();
TreeNode cur = root;
while (cur != null || !stack.isEmpty()) {
while (cur != null) { // 当结点存在
stack.push(cur); // 将此结点入栈
res.add(cur.val); // 规则是被遍历就直接存入数组,和中序遍历不一样的是:中序被遍历后要从最深的那个左子结点开始存入数组
cur = cur.left;
}
cur = stack.pop();
cur = cur.right;
}
return res;
}
}
// @lc code=end
3 ) [590] N叉树的后序遍历
- 递归
子结点是存储在List中的,并且从小到大排序
所以通过依次递归这个列表中的结点实现结果 - 迭代,通过一个链表实现一个栈 入栈为add() 出栈为pollLast();
另一个链表存放结果,出栈的元素加入结果链表的头部:1-4-2-3-6-5 —> 5-6-3-2-4-1
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import org.w3c.dom.Node;
/*
* @lc app=leetcode.cn id=590 lang=java
*
* [590] N叉树的后序遍历
// @lc code=start
/*
// Definition for a Node.
class Node {
public int val;
public List<Node> children;
public Node() {}
public Node(int _val) {
val = _val;
}
public Node(int _val, List<Node> _children) {
val = _val;
children = _children;
}
};
*/
class Solution {
// // 1.递归
// List<Integer> res = new ArrayList<>();
// public List<Integer> postorder(Node root) {
// if (root != null){
// for (Node node: root.children) postorder(node); // node = root.children[i] retrun;
// res.add(root.val); // 5 - 6 - 3
// }
// return res;
// }
// 2. 迭代
public List<Integer> postorder(Node root) {
LinkedList<Node> stack = new LinkedList<>();
LinkedList<Integer> res = new LinkedList<>();
if (root != null) { // 可能给一个空node
stack.add(root);
while (!stack.isEmpty()) {
Node node = stack.pollLast(); // 保存结点后出栈
res.addFirst(node.val); // 出栈的顺序是根-右-左,所以反过来存入结果res就是后序遍历
for (Node item : node.children) {
// 若有子结点,将子结点放入栈,此步骤将入栈所有子结点
// 没有子结点了就继续while循环
// 例如:判断1子结点,入栈3-2-4,判断4-2无子,3有子就将其子5-6入栈
if (item != null) stack.add(item);
}
}
}
return res;
}
}
// @lc code=end
4) [589] N叉树的前序遍历
- 递归:根左右
- 迭代:要点:children的反向排序
- 通过一个链表LinkedList维护一个栈:Collections.reverse(node.children);反转指定列表中元素的顺序。
- 通过Stack维护栈 for (i=children.lenth)实现
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import org.w3c.dom.Node;
/*
* @lc app=leetcode.cn id=589 lang=java
*
*/
// @lc code=start
/*
// Definition for a Node.
class Node {
public int val;
public List<Node> children;
public Node() {}
public Node(int _val) {
val = _val;
}
public Node(int _val, List<Node> _children) {
val = _val;
children = _children;
}
};
*/
class Solution {
// // 1. 递归
// List<Integer> res = new ArrayList<>();
// public List<Integer> preorder(Node root) {
// if (root != null) {
// res.add(root.val); // 先加根
// for (Node node : root.children) preorder(node);
// }
// return res;
// }
// // 2. 维护栈LinkedList
// public List<Integer> preorder(Node root) {
// LinkedList<Node> stack = new LinkedList<>();
// LinkedList<Integer> res = new LinkedList<>();
// if (root != null) {
// stack.add(root);
// while (!stack.isEmpty()) {
// Node node = stack.pollLast();
// res.add(node.val);
// Collections.reverse(node.children); // 反转链表顺序为4-2-3,6-5
// for (Node item : node.children) {
// stack.add(item); // 入栈顺序为4-2-3,3出栈,入栈6,5;
// }
// }
// }
// return res;
// }
// 第二种方法
// Stack维护栈
public List<Integer> preorder(Node root) {
List<Integer> list = new ArrayList<>(); //
if (root == null) return list;
Stack<Node> stack = new Stack<>();
stack.add(root);
while (!stack.empty()) {
root = stack.pop(); //
list.add(root.val);
// 反向将结点的children入栈:4,2,3;出栈3,入栈6,5;出栈5-6;出栈4-2;
for (int i = root.children.size() - 1; i >= 0; i--)
stack.add(root.children.get(i));
}
return list;
}
}
// @lc code=end