二叉樹的遍歷
//二叉樹結(jié)點定義
public class TreeNode {
int val;
TreeNode left;
TreeNode right;
TreeNode(int x){
val=x;
}
@Override
public String toString(){
return "val: "+val;
}
}
//訪問結(jié)點方法方法
public void visit(TreeNode node){
System.out.print(node.val+" ");
}
-
前序遍歷
-
遞歸實現(xiàn)
/** * 遞歸先序遍歷 * */ public void preOrderRecursion(TreeNode node){ if(node==null) //如果結(jié)點為空則返回 return; visit(node);//訪問根節(jié)點 preOrderRecursion(node.left);//訪問左孩子 preOrderRecursion(node.right);//訪問右孩子 } -
非遞歸
/** * 非遞歸先序遍歷二叉樹 * */ public List<Integer> preorderTraversal(TreeNode root) { List<Integer> resultList = new ArrayList<>(); Stack<TreeNode> treeStack = new Stack<>(); if (root == null) //如果為空樹則返回 return resultList; treeStack.push(root); while( !treeStack.isEmpty() ){ TreeNode tempNode = treeStack.pop(); if(tempNode != null){ resultList.add(tempNode.val);//訪問根節(jié)點 if (tempNode.right != null) { treeStack.push(tempNode.right); //入棧右孩子 } if (tempNode.left != null) { treeStack.push(tempNode.left);//入棧左孩子 } } } return resultList; }
-
-
中序遍歷
遞歸
-
非遞歸
/** 非遞歸中序遍歷 */ public List<Integer> inorderTraversal(TreeNode root) { List<Integer> list = new ArrayList<Integer>(); Stack<TreeNode> stack = new Stack<TreeNode>(); TreeNode cur = root; while(cur!=null || !stack.empty()){ while(cur!=null){ stack.add(cur); cur = cur.left; } cur = stack.pop(); list.add(cur.val); cur = cur.right; } return list; }
-
后序遍歷
遞歸
-
非遞歸(加一個輔助棧)
public static void posOrderTraverse(Node head) { System.out.println("pos-order"); if(head != null) { Stack<Node> stack1 = new Stack<>(); Stack<Node> stack2 = new Stack<>(); // 輔助棧,存儲 根 -> 右 -> 左 的結(jié)果 stack1.push(head); while(!stack1.isEmpty()) { head = stack1.pop(); stack2.push(head); // 有左孩子就先壓入左孩子 if(head.left != null) stack1.push(head.left); // 有右孩子就后壓入右孩子 if(head.right != null) stack1.push(head.right); } // 逆序打印 根 -> 右 -> 左 的結(jié)果,就是后序遍歷的結(jié)果 while(!stack2.isEmpty()) System.out.print(stack2.pop().val + " "); } }
-
層序遍歷(廣度優(yōu)先,用隊列)
層序遍歷框架:
void traverse(TreeNode root) { if (root == null) return; // 初始化隊列,將 root 加入隊列 Queue<TreeNode> q = new LinkedList<>(); q.offer(root); while (!q.isEmpty()) { TreeNode cur = q.poll(); /* 層級遍歷代碼位置 */ System.out.println(root.val); /*****************/ if (cur.left != null) { q.offer(cur.left); } if (cur.right != null) { q.offer(cur.right); } } }分層來打?。盒枰尤胍粋€Path
public List<List<Integer>> levelOrder(TreeNode root) { List<List<Integer>> res = new ArrayList<>(); if (root == null) return res; Deque<TreeNode> queue = new ArrayDeque<>(); queue.addLast(root); while (queue.size() != 0) { int size = queue.size(); List<Integer> path = new ArrayList<>(); for (int i=0;i<size;i++) { //這個循環(huán)很關(guān)鍵 //按順序出隊列,訪問出隊列的元素的左右結(jié)點 TreeNode temp = queue.removeFirst(); if (temp.left != null) { queue.addLast(temp.left); } if (temp.right != null) { queue.addLast(temp.right); } path.add(temp.val); } res.add(path); } return res; } -
通過先序遍歷和中序遍歷重建二叉樹
[圖片上傳失敗...(image-923cb8-1609223312780)]
TreeNode build(int[] preorder, int preStart, int preEnd, int[] inorder, int inStart, int inEnd) { if (preStart > preEnd) { return null; } // root 節(jié)點對應(yīng)的值就是前序遍歷數(shù)組的第一個元素 int rootVal = preorder[preStart]; // rootVal 在中序遍歷數(shù)組中的索引 int index = 0; for (int i = inStart; i <= inEnd; i++) { if (inorder[i] == rootVal) { index = i; break; } } int leftSize = index - inStart; // 先構(gòu)造出當前根節(jié)點 TreeNode root = new TreeNode(rootVal); // 遞歸構(gòu)造左右子樹,參考下面兩幅圖 root.left = build(preorder, preStart + 1, preStart + leftSize, inorder, inStart, index - 1); root.right = build(preorder, preStart + leftSize + 1, preEnd, inorder, index + 1, inEnd); return root; }image-20201226195602243image-20201226195624346 -
通過中序遍歷和后序遍歷重建二叉樹
image-20201226195723841TreeNode build(int[] inorder, int inStart, int inEnd, int[] postorder, int postStart, int postEnd) { if (inStart > inEnd) { return null; } // root 節(jié)點對應(yīng)的值就是后序遍歷數(shù)組的最后一個元素 int rootVal = postorder[postEnd]; // rootVal 在中序遍歷數(shù)組中的索引 int index = 0; for (int i = inStart; i <= inEnd; i++) { if (inorder[i] == rootVal) { index = i; break; } } // 左子樹的節(jié)點個數(shù) int leftSize = index - inStart; TreeNode root = new TreeNode(rootVal); // 遞歸構(gòu)造左右子樹 root.left = build(inorder, inStart, index - 1, postorder, postStart, postStart + leftSize - 1); root.right = build(inorder, index + 1, inEnd, postorder, postStart + leftSize, postEnd - 1); return root; }image-20201226195748361
總結(jié):主要是要找到正確的起始位置,可以自己舉一個例子畫圖來確定。leftSize是關(guān)鍵,他是中序遍歷左子樹的節(jié)點總數(shù)。
二叉樹的序列化與反序列化
? 二叉樹的序列化可以運用于對象轉(zhuǎn)化為JSON字符串,反序列化可運用于JSON轉(zhuǎn)化為對象。JSON的運用非常廣泛,比如我們經(jīng)常將變成語言中的結(jié)構(gòu)體序列化成 JSON字符串,存入緩存或者通過網(wǎng)絡(luò)發(fā)送給遠端服務(wù),消費者接受JSON字符串然后進行反序列化,就可以得到原始數(shù)據(jù)了。這就是「序列化」和「反序列化」的目的,以某種固定格式組織字符串,使得數(shù)據(jù)可以獨立于編程語言。
-
前序遍歷解法
? 序列化代碼:如果結(jié)點是空結(jié)點,那么在字符串后面拼接一個“#,”;如果不是空結(jié)點,那么拼接“
rootVal,”。最后結(jié)果返回一個把二叉樹“打平”了的字符串String SEP = ","; String NULL = "#"; /* 主函數(shù),將二叉樹序列化為字符串 */ String serialize(TreeNode root) { StringBuilder sb = new StringBuilder(); serialize(root, sb); return sb.toString(); } /* 輔助函數(shù),將二叉樹存入 StringBuilder */ void serialize(TreeNode root, StringBuilder sb) { if (root == null) { sb.append(NULL).append(SEP); return; } /****** 前序遍歷位置 ******/ sb.append(root.val).append(SEP); /***********************/ serialize(root.left, sb); serialize(root.right, sb); }? 反序列化代碼:將序列化的字符串按照“,”進行分割,并轉(zhuǎn)化為鏈表結(jié)構(gòu)。然后通過前序遍歷框架反序列化。
/* 主函數(shù),將字符串反序列化為二叉樹結(jié)構(gòu) */ TreeNode deserialize(String data) { // 將字符串轉(zhuǎn)化成列表 LinkedList<String> nodes = new LinkedList<>(); for (String s : data.split(SEP)) { nodes.addLast(s); } return deserialize(nodes); } /* 輔助函數(shù),通過 nodes 列表構(gòu)造二叉樹 */ TreeNode deserialize(LinkedList<String> nodes) { if (nodes.isEmpty()) return null; /****** 前序遍歷位置 ******/ // 列表最左側(cè)就是根節(jié)點 String first = nodes.removeFirst(); if (first.equals(NULL)) return null; TreeNode root = new TreeNode(Integer.parseInt(first)); /***********************/ //遞歸的去調(diào)用 root.left = deserialize(nodes); root.right = deserialize(nodes); return root; } -
后序遍歷解法
序列化代碼:
/* 輔助函數(shù),將二叉樹存入 StringBuilder */ void serialize(TreeNode root, StringBuilder sb) { if (root == null) { sb.append(NULL).append(SEP); return; } serialize(root.left, sb); serialize(root.right, sb); /****** 后序遍歷位置 ******/ sb.append(root.val).append(SEP); /***********************/ }反序列化代碼:首先要找到根節(jié)點,再按照根-->右-->左的順序還原。
/* 主函數(shù),將字符串反序列化為二叉樹結(jié)構(gòu) */ TreeNode deserialize(String data) { LinkedList<String> nodes = new LinkedList<>(); for (String s : data.split(SEP)) { nodes.addLast(s); } return deserialize(nodes); } /* 輔助函數(shù),通過 nodes 列表構(gòu)造二叉樹 */ TreeNode deserialize(LinkedList<String> nodes) { if (nodes.isEmpty()) return null; // 從后往前取出元素 String last = nodes.removeLast(); if (last.equals(NULL)) return null; TreeNode root = new TreeNode(Integer.parseInt(last)); // 先構(gòu)造右子樹,后構(gòu)造左子樹 root.right = deserialize(nodes); root.left = deserialize(nodes); return root; } -
層序遍歷解法
序列化代碼:
String SEP = ","; String NULL = "#"; /* 將二叉樹序列化為字符串 */ String serialize(TreeNode root) { if (root == null) return ""; StringBuilder sb = new StringBuilder(); // 初始化隊列,將 root 加入隊列 Queue<TreeNode> q = new LinkedList<>(); q.offer(root); while (!q.isEmpty()) { TreeNode cur = q.poll(); /* 層級遍歷代碼位置 */ if (cur == null) { sb.append(NULL).append(SEP); continue; } sb.append(cur.val).append(SEP); /*****************/ q.offer(cur.left); q.offer(cur.right); } return sb.toString(); }序列化后的結(jié)果如圖所示
image-20201226195841319反序列化代碼:
/* 將字符串反序列化為二叉樹結(jié)構(gòu) */ TreeNode deserialize(String data) { if (data.isEmpty()) return null; String[] nodes = data.split(SEP); // 第一個元素就是 root 的值 TreeNode root = new TreeNode(Integer.parseInt(nodes[0])); // 隊列 q 記錄父節(jié)點,將 root 加入隊列 Queue<TreeNode> q = new LinkedList<>(); q.offer(root); for (int i = 1; i < nodes.length; ) { // 隊列中存的都是父節(jié)點 TreeNode parent = q.poll(); // 父節(jié)點對應(yīng)的左側(cè)子節(jié)點的值 String left = nodes[i++]; if (!left.equals(NULL)) { parent.left = new TreeNode(Integer.parseInt(left)); q.offer(parent.left); } else { parent.left = null; } // 父節(jié)點對應(yīng)的右側(cè)子節(jié)點的值 String right = nodes[i++]; if (!right.equals(NULL)) { parent.right = new TreeNode(Integer.parseInt(right)); q.offer(parent.right); } else { parent.right = null; } } return root; } -
總結(jié):
? 注意后序遍歷解法,與前序遍歷、層序遍歷不同,后序遍歷序列化是按照標準后序遍歷框架進行的,而反序列化則是先找到最后一個元素即為根節(jié)點,從后往前(根-->右-->左)進行反序列化。
二叉(排序/搜索)樹 (BST樹)
? 二叉查找樹(Binary Search Tree),(又:二叉搜索樹,二叉排序樹)它或者是一棵空樹,或者是具有下列性質(zhì)的二叉樹:
- 若它的左子樹不空,則左子樹上所有結(jié)點的值均小于它的根結(jié)點的值;
- 若它的右子樹不空,則右子樹上所有結(jié)點的值均大于它的根結(jié)點的值;
- 它的左、右子樹也分別為二叉排序樹。
- 它的中序遍歷是升序的(左-->中-->右),也可以降序(右-->中-->左)
? 二叉搜索樹作為一種經(jīng)典的數(shù)據(jù)結(jié)構(gòu),它既有鏈表的快速插入與刪除操作的特點,又有數(shù)組快速查找的優(yōu)勢;所以應(yīng)用十分廣泛,例如在文件系統(tǒng)和數(shù)據(jù)庫系統(tǒng)一般會采用這種數(shù)據(jù)結(jié)構(gòu)進行高效率的排序與檢索操作。
-
查找
? 可以利用二叉搜索樹的性質(zhì)進行查找(二分查找),O(logn)時間復(fù)雜度,最壞情況O(n)(當樹退化為鏈表時)。
-
插入
? 在查找的基礎(chǔ)上進行插入操作
-
刪除
分為三種情況:刪除葉子節(jié)點、刪除只有一個子結(jié)點的結(jié)點、刪除有兩個子結(jié)點的結(jié)點
TreeNode deleteNode(TreeNode root, int key) { if (root == null) return null; if (root.val == key) { // 這兩個 if 把情況 1 和 2 都正確處理了 if (root.left == null) return root.right; if (root.right == null) return root.left; // 處理情況 3 //minNode 右子樹的最小結(jié)點 TreeNode minNode = getMin(root.right); root.val = minNode.val; root.right = deleteNode(root.right, minNode.val); } else if (root.val > key) { root.left = deleteNode(root.left, key); } else if (root.val < key) { root.right = deleteNode(root.right, key); } return root; } TreeNode getMin(TreeNode node) { // BST 最左邊的就是最小的 while (node.left != null) node = node.left; return node; }
平衡二叉樹(AVL樹)
? 平衡二叉搜索樹(Self-balancing binary search tree)又被稱為AVL樹(有別于AVL算法),且具有以下性質(zhì):它是一 棵空樹或它的左右兩個子樹的高度差的絕對值不超過1,并且左右兩個子樹都是一棵平衡二叉樹。
? 基本代碼準備:
/**
* AVLTree是BST,所以節(jié)點值必須是可比較的
*/
public class AvlTree<E extends Comparable<E>>{
private class Node{
public E e;
public Node left;
public Node right;
public int height;
public Node(E e){
this.e = e;
this.left = null;
this.right = null;
this.height = 1;
}
}
private Node root;
private int size;
public AvlTree(){
root=null;
size=0;
}
//獲取某一結(jié)點的高度
private int getHeight(Node node){
if(node==null){
return 0;
}
return node.height;
}
public int getSize(){
return size;
}
public boolean isEmpty(){
return size == 0;
}
/**
* 獲取節(jié)點的平衡因子
* @param node
* @return
*/
private int getBalanceFactor(Node node){
if(node==null){
return 0;
}
return getHeight(node.left)-getHeight(node.right);
}
//判斷樹是否為平衡二叉樹
public boolean isBalanced(){
return isBalanced(root);
}
private boolean isBalanced(Node node){
if(node==null){
return true;
}
int balanceFactory = Math.abs(getBalanceFactor(node));
if(balanceFactory>1){
return false;
}
return isBalanced(node.left)&&isBalanced(node.right);
}
}
-
LL(右旋)
? LL的意思是向左子樹(L)的左孩子(L)中插入新節(jié)點后導(dǎo)致不平衡,這種情況下需要右旋操作,而不是說LL的意思是右旋,后面的也是一樣。

/**
* 右旋轉(zhuǎn)
*/
private Node rightRotate(Node y){
//x作為新頂點
Node x = y.left;
//因為即將要改變x的右指針,需要保存原來的右孩子
Node t3 = x.right;
x.right = y;
y.left = t3;
//更新height
y.height = Math.max(getHeight(y.left),getHeight(y.right))+1;
x.height = Math.max(getHeight(x.left),getHeight(x.right))+1;
return x;
}
-
RR(左旋)
左旋操作/** * 左旋轉(zhuǎn) */ private Node leftRotate(Node y){ Node x = y.right; Node t2 = x.left; x.left = y; y.right = t2; //更新height y.height = Math.max(getHeight(y.left),getHeight(y.right))+1; x.height = Math.max(getHeight(x.left),getHeight(x.right))+1; return x; }
-
LR(雙旋)
image-20201209203744422private Node LRRotate(Node y){ y.left = leftRotate(y.left); return rightRotate(y); } -
RL(雙旋)image-20201209204242906private Node RLRotate(Node y){ y.right = rightRotate(y.right); return leftRotate(y); } 刪除結(jié)點類似于BST樹,但需要維護平衡性
B樹(又叫B-樹)和B+樹
-
B樹
又叫多路平衡搜索樹,一顆m叉的
BTree特性如下(ceil()向上取整):- 樹中每個節(jié)點最多包含m個孩子。
- 除根節(jié)點與葉子節(jié)點外,每個節(jié)點至少有[ceil(m/2)]個孩子。
- 若根節(jié)點不是葉子節(jié)點,則至少有兩個孩子。
- 所有的葉子節(jié)點都在同一層。
- 每個非葉子節(jié)點由n個key與n+1個指針組成,其中[ceil(m/2)-1] <= n <= m-1
? 以5叉BTree為例,key的數(shù)量:公式推導(dǎo)[ceil(m/2)-1] <= n <= m-1。所以 2 <= n <=4 。當n>4時,中間節(jié)點分裂到父節(jié)點,兩邊節(jié)點分裂。
? 插入 C N G A H E K Q M F W L T Z D P R X Y S 數(shù)據(jù)為例。




-
B+樹
image-20201209222155086MySQL中的B+樹
image-20201209222244742









