【從蛋殼到滿天飛】JAVA 數據結構解析和算法實現,所有文章大概的內容以下:
Arrays(數組)、Stacks(棧)、Queues(隊列)、LinkedList(鏈表)、Recursion(遞歸思想)、BinarySearchTree(二分搜索樹)、Set(集合)、Map(映射)、Heap(堆)、PriorityQueue(優先隊列)、SegmentTree(線段樹)、Trie(字典樹)、UnionFind(並查集)、AVLTree(AVL 平衡樹)、RedBlackTree(紅黑平衡樹)、HashTable(哈希表)php
源代碼有三個:ES6(單個單個的 class 類型的 js 文件) | JS + HTML(一個 js 配合一個 html)| JAVA (一個一個的工程)html
所有源代碼已上傳 github,點擊我吧,光看文章可以掌握兩成,動手敲代碼、動腦思考、畫圖才能夠掌握八成。前端
本文章適合 對數據結構想了解而且感興趣的人羣,文章風格一如既往如此,就以爲手機上看起來比較方便,這樣顯得比較有條理,整理這些筆記加源碼,時間跨度也算將近半年時間了,但願對想學習數據結構的人或者正在學習數據結構的人羣有幫助。java
線性數據結構是把全部的數據排成一排node
樹結構自己是一個種自然的組織結構python
樹結構很是的高效c++
在計算機科學領域不少問題的處理git
二分搜索樹(Binary Search Tree)github
平衡二叉樹:AVL;紅黑樹,面試
算法須要使用一些特殊的操做的時候將數據組織成樹結構
堆
以及並查集
,線段樹、Trie(字典樹、前綴樹)
二叉樹
class Node { E e; Node left; Node right; }
二叉樹也叫多叉樹,
在數據結構領域對應樹結構來講
二叉樹和鏈表同樣具備自然遞歸的結構
二叉樹不必定是「滿」的
二分搜索樹是一棵二叉樹
二分搜索樹的每個節點的值
二分搜索樹的每一棵子樹也是二分搜索樹
爲了可以達到二分搜索樹的性質
二分搜索樹其實不是支持全部的類型
代碼實現
public class MyBinarySearchTree<E extends Comparable<E>> { private class Node { public E e; public Node left, right; public Node (E e) { this.e = e; left = null; right = null; } } private Node root; private int size; public MyBinarySearchTree () { root = null; size = 0; } public int getSize() { return size; } public boolean isEmpty () { return size == 0; } }
若是二分搜索樹的根節點爲空的話
按照這樣的規則,每來一個新元素從根節點開始,
若是遇到兩個元素的值相同,那暫時先不去管,
二分搜索樹添加元素的非遞歸寫法,和鏈表很像
代碼
public class MyBinarySearchTree<E extends Comparable<E>> { private class Node { public E e; public Node left, right; public Node (E e) { this.e = e; left = null; right = null; } } private Node root; private int size; public MyBinarySearchTree () { root = null; size = 0; } public int getSize() { return size; } public boolean isEmpty () { return size == 0; } // 向二分搜索樹中添加一個元素 e public void add (E e) { if (root == null) { root = new Node(e); size ++; } else { add(root, e); } } // 向以node爲根的二分搜索樹種插入元素E,遞歸算法 private void add (Node node, E e) { // node 是對用戶屏蔽的,用戶不用知道二分搜索樹中有怎樣一個節點結構 // 若是出現相同的元素就不進行操做了 if (e.equals(node.e)) { return; } else if (e.compareTo(node.e) < 0 && node.left == null) { // 給左孩子賦值 node.left = new Node(e); size ++; return; } else if (e.compareTo(node.e) > 0 && node.right == null) { // 給右海子賦值 node.right = new Node(e); size ++; return; } // 這裏是處理節點被佔了,那就進入下一個層的二叉樹中 if (e.compareTo(node.e) < 0) { // 去左子樹 add(node.left, e); } else { // e.compareTo(node.e) > 0 // 去右子樹 add(node.right, e); } } }
對於二分搜索的插入操做
改進添加操做
以前的算法
代碼
public class MyBinarySearchTree<E extends Comparable<E>> { private class Node { public E e; public Node left, right; public Node (E e) { this.e = e; left = null; right = null; } } private Node root; private int size; public MyBinarySearchTree () { root = null; size = 0; } public int getSize() { return size; } public boolean isEmpty () { return size == 0; } // 向二分搜索樹中添加一個元素 e // 改進:直接調用add public void add (E e) { root = add(root, e); } // 向以node爲根的二分搜索樹種插入元素E,遞歸算法 // 改進:返回插入的新節點後二分搜索樹的根 private Node add (Node node, E e) { // 處理最基本的問題 if (node == null) { size ++; return new Node(e); } // 空的二叉樹也是叉樹。 if (e.compareTo(node.e) < 0) { // 將處理後的結果賦值給node的左子樹 node.left = add(node.left, e); } else if (e.compareTo(node.e) > 0) { // 將處理後的結果賦值給node的右子樹 node.right = add(node.right, e); } // 若是相同 就什麼都不作 // 最後返回這個node return node; } // // 向二分搜索樹中添加一個元素 e // public void add (E e) { // if (root == null) { // root = new Node(e); // size ++; // } else { // add(root, e); // } // } // // 向以node爲根的二分搜索樹種插入元素E,遞歸算法 // private void add (Node node, E e) { // // node 是對用戶屏蔽的,用戶不用知道二分搜索樹中有怎樣一個節點結構 // // // 若是出現相同的元素就不進行操做了 // if (e.equals(node.e)) { // return; // } else if (e.compareTo(node.e) < 0 && node.left == null) { // // 給左孩子賦值 // node.left = new Node(e); // return; // } else if (e.compareTo(node.e) > 0 && node.right == null) { // // 給右海子賦值 // node.right = new Node(e); // return; // } // // // 這裏是處理節點被佔了,那就進入下一個層的二叉樹中 // if (e.compareTo(node.e) < 0) { // // 去左子樹 // add(node.left, e); // } else { // e.compareTo(node.e) > 0 // // 去右子樹 // add(node.right, e); // } // } }
雖然代碼量更少了,可是也更難理解的了一些
查詢操做很是的容易
和添加元素同樣須要使用遞歸的進行實現
在數組和鏈表中有索引這個概念,
代碼
public class MyBinarySearchTree<E extends Comparable<E>> { private class Node { public E e; public Node left, right; public Node (E e) { this.e = e; left = null; right = null; } } private Node root; private int size; public MyBinarySearchTree () { root = null; size = 0; } public int getSize() { return size; } public boolean isEmpty () { return size == 0; } // 向二分搜索樹中添加一個元素 e // 改進:直接調用add public void add (E e) { root = add(root, e); } // 向以node爲根的二分搜索樹中插入元素E,遞歸算法 // 改進:返回插入的新節點後二分搜索樹的根 private Node add (Node node, E e) { // 處理最基本的問題 if (node == null) { size ++; return new Node(e); } // 空的二叉樹也是叉樹。 if (e.compareTo(node.e) < 0) { // 將處理後的結果賦值給node的左子樹 node.left = add(node.left, e); } else if (e.compareTo(node.e) > 0) { // 將處理後的結果賦值給node的右子樹 node.right = add(node.right, e); } // 若是相同 就什麼都不作 // 最後返回這個node return node; } // 查詢二分搜索數中是否包含某個元素 public boolean contains (E e) { return contains(root, e); } // 向以node爲根的二分搜索樹 進行查找 遞歸算法 public boolean contains (Node node, E e) { // 解決最基本的問題 也就是遍歷完全部節點都沒有找到 if (node == null) { return false; } // 若是 e 小於當前節點的e 則向左子樹進發 if (e.compareTo(node.e) < 0) { return contains(node.left, e); } else if (e.compareTo(node.e) > 0) { // 若是 e 大於當前節點的e 則向右子樹進發 return contains(node.right, e); } else { // 若是e 等於 當前節點 e 則直接返回true return true; } } }
遍歷操做就是把這個數據結構中全部的元素都訪問一遍
訪問數據結構中存儲的全部元素是由於與業務相關,
在線性結構下,遍歷是極其容易的
在樹結構下遍歷操做並無那麼難
對於遍歷操做,兩個子樹都要顧及
// 遍歷以node爲根的二分搜索樹 遞歸算法 function traverse(node) { if (node === null) { return; } // ... 要作的事情 // 訪問該節點 兩邊都要顧及 // 訪問該節點的時候就去作該作的事情, // 如 給全部學生加兩分 traverse(node.left); traverse(node.right); } // 寫法二 這種邏輯也是能夠的 function traverse(node) { if (node !== null) { // ... 要作的事情 // 訪問該節點 兩邊都要顧及 // 訪問該節點的時候就去作該作的事情, // 如 給全部學生加兩分 traverse(node.left); traverse(node.right); } }
(class: MyBinarySearchTree, class: Main)
MyBinarySearchTree
public class MyBinarySearchTree<E extends Comparable<E>> { private class Node { public E e; public Node left, right; public Node (E e) { this.e = e; left = null; right = null; } } private Node root; private int size; public MyBinarySearchTree () { root = null; size = 0; } public int getSize() { return size; } public boolean isEmpty () { return size == 0; } // 向二分搜索樹中添加一個元素 e // 改進:直接調用add public void add (E e) { root = add(root, e); } // 向以node爲根的二分搜索樹中插入元素E,遞歸算法 // 改進:返回插入的新節點後二分搜索樹的根 private Node add (Node node, E e) { // 處理最基本的問題 if (node == null) { size ++; return new Node(e); } // 空的二叉樹也是叉樹。 if (e.compareTo(node.e) < 0) { // 將處理後的結果賦值給node的左子樹 node.left = add(node.left, e); } else if (e.compareTo(node.e) > 0) { // 將處理後的結果賦值給node的右子樹 node.right = add(node.right, e); } // 若是相同 就什麼都不作 // 最後返回這個node return node; } // 查詢二分搜索數中是否包含某個元素 public boolean contains (E e) { return contains(root, e); } // 向以node爲根的二分搜索樹 進行查找 遞歸算法 public boolean contains (Node node, E e) { // 解決最基本的問題 也就是遍歷完全部節點都沒有找到 if (node == null) { return false; } // 若是 e 小於當前節點的e 則向左子樹進發 if (e.compareTo(node.e) < 0) { return contains(node.left, e); } else if (e.compareTo(node.e) > 0) { // 若是 e 大於當前節點的e 則向右子樹進發 return contains(node.right, e); } else { // 若是e 等於 當前節點 e 則直接返回true return true; } } // 二分搜索樹的前序遍歷 public void preOrder () { preOrder(root); } // 前序遍歷以node爲根的二分搜索樹 遞歸算法 public void preOrder (Node node) { if (node == null) { return; } // 輸出 System.out.println(node.e); preOrder(node.left); preOrder(node.right); // // 這種邏輯也是能夠的 // if (node != null) { // // 輸出 // System.out.println(node.e); // // preOrder(node.left); // preOrder(node.right); // } } }
Main
public class Main { public static void main(String[] args) { MyBinarySearchTree<Integer> mbst = new MyBinarySearchTree<Integer>(); int [] nums = {5, 3, 6, 8, 4, 2}; for (int i = 0; i < nums.length ; i++) { mbst.add(nums[i]); } ///////////////// // 5 // // / \ // // 3 6 // // / \ \ // // 2 4 8 // ///////////////// mbst.preOrder(); } }
遍歷輸出二分搜索樹
(class: MyBinarySearchTree, class: Main)
MyBinarySearchTree
public class MyBinarySearchTree<E extends Comparable<E>> { private class Node { public E e; public Node left, right; public Node (E e) { this.e = e; left = null; right = null; } } private Node root; private int size; public MyBinarySearchTree () { root = null; size = 0; } public int getSize() { return size; } public boolean isEmpty () { return size == 0; } // 向二分搜索樹中添加一個元素 e // 改進:直接調用add public void add (E e) { root = add(root, e); } // 向以node爲根的二分搜索樹中插入元素E,遞歸算法 // 改進:返回插入的新節點後二分搜索樹的根 private Node add (Node node, E e) { // 處理最基本的問題 if (node == null) { size ++; return new Node(e); } // 空的二叉樹也是叉樹。 if (e.compareTo(node.e) < 0) { // 將處理後的結果賦值給node的左子樹 node.left = add(node.left, e); } else if (e.compareTo(node.e) > 0) { // 將處理後的結果賦值給node的右子樹 node.right = add(node.right, e); } // 若是相同 就什麼都不作 // 最後返回這個node return node; } // 查詢二分搜索數中是否包含某個元素 public boolean contains (E e) { return contains(root, e); } // 向以node爲根的二分搜索樹 進行查找 遞歸算法 public boolean contains (Node node, E e) { // 解決最基本的問題 也就是遍歷完全部節點都沒有找到 if (node == null) { return false; } // 若是 e 小於當前節點的e 則向左子樹進發 if (e.compareTo(node.e) < 0) { return contains(node.left, e); } else if (e.compareTo(node.e) > 0) { // 若是 e 大於當前節點的e 則向右子樹進發 return contains(node.right, e); } else { // 若是e 等於 當前節點 e 則直接返回true return true; } } // 二分搜索樹的前序遍歷 public void preOrder () { preOrder(root); } // 前序遍歷以node爲根的二分搜索樹 遞歸算法 public void preOrder (Node node) { if (node == null) { return; } // 輸出 System.out.println(node.e); preOrder(node.left); preOrder(node.right); // // 這種邏輯也是能夠的 // if (node != null) { // // 輸出 // System.out.println(node.e); // // preOrder(node.left); // preOrder(node.right); // } } @Override public String toString () { StringBuilder sb = new StringBuilder(); generateBSTString(root, 0, sb); return sb.toString(); } // 生成以node爲根節點,深度爲depth的描述二叉樹的字符串 private void generateBSTString (Node node, int depath, StringBuilder sb) { if (node == null) { sb.append(generateDepthString(depath) + "null\n"); return; } sb.append(generateDepthString(depath) + node.e + "\n"); generateBSTString(node.left, depath + 1, sb); generateBSTString(node.right, depath + 1, sb); } // 生成路徑字符串 private String generateDepthString (int depth) { StringBuilder sb = new StringBuilder(); for (int i = 0; i < depth; i++) { sb.append("-- "); } return sb.toString(); } }
Main
public class Main { public static void main(String[] args) { MyBinarySearchTree<Integer> mbst = new MyBinarySearchTree<Integer>(); int [] nums = {5, 3, 6, 8, 4, 2}; for (int i = 0; i < nums.length ; i++) { mbst.add(nums[i]); } ///////////////// // 5 // // / \ // // 3 6 // // / \ \ // // 2 4 8 // ///////////////// mbst.preOrder(); System.out.println(); // 輸出 調試字符串 System.out.println(mbst.toString()); } }
前序遍歷
前
表示先訪問的這個節點。function preOrder(node) { if (node == null) return; // ... 要作的事情 // 訪問該節點 // 先一直往左,而後不斷返回上一層 再向左、終止, // 最後整個操做循環往復,直到所有終止。 preOrder(node.left); preOrder(node.right); }
中序遍歷
中
表示先訪問左子樹,function inOrder(node) { if (node == null) return; inOrder(node.left); // ... 要作的事情 // 訪問該節點 inOrder(node.right); }
中序遍歷後輸出的結果是排序後的結果。
後序遍歷
後
表示先訪問左子樹,function inOrder(node) { if (node == null) return; inOrder(node.left); inOrder(node.right); // ... 要作的事情 // 訪問該節點 }
二分搜索樹的前序遍歷和後序遍歷並不像中序遍歷那樣進行了排序
java
、c#
這樣的語言都有垃圾回收機制,c++
語言中須要手動的控制內存,二分搜索樹的前中後序遍歷
(class: MyBinarySearchTree, class: Main)
MyBinarySearchTree
public class MyBinarySearchTree<E extends Comparable<E>> { private class Node { public E e; public Node left, right; public Node (E e) { this.e = e; left = null; right = null; } } private Node root; private int size; public MyBinarySearchTree () { root = null; size = 0; } public int getSize() { return size; } public boolean isEmpty () { return size == 0; } // 向二分搜索樹中添加一個元素 e // 改進:直接調用add public void add (E e) { root = add(root, e); } // 向以node爲根的二分搜索樹中插入元素E,遞歸算法 // 改進:返回插入的新節點後二分搜索樹的根 private Node add (Node node, E e) { // 處理最基本的問題 if (node == null) { size ++; return new Node(e); } // 空的二叉樹也是叉樹。 if (e.compareTo(node.e) < 0) { // 將處理後的結果賦值給node的左子樹 node.left = add(node.left, e); } else if (e.compareTo(node.e) > 0) { // 將處理後的結果賦值給node的右子樹 node.right = add(node.right, e); } // 若是相同 就什麼都不作 // 最後返回這個node return node; } // 查詢二分搜索數中是否包含某個元素 public boolean contains (E e) { return contains(root, e); } // 向以node爲根的二分搜索樹 進行查找 遞歸算法 private boolean contains (Node node, E e) { // 解決最基本的問題 也就是遍歷完全部節點都沒有找到 if (node == null) { return false; } // 若是 e 小於當前節點的e 則向左子樹進發 if (e.compareTo(node.e) < 0) { return contains(node.left, e); } else if (e.compareTo(node.e) > 0) { // 若是 e 大於當前節點的e 則向右子樹進發 return contains(node.right, e); } else { // 若是e 等於 當前節點 e 則直接返回true return true; } } // 二分搜索樹的前序遍歷 public void preOrder () { preOrder(root); } // 前序遍歷以node爲根的二分搜索樹 遞歸算法 private void preOrder (Node node) { if (node == null) { return; } // 輸出 System.out.println(node.e); preOrder(node.left); preOrder(node.right); // // 這種邏輯也是能夠的 // if (node != null) { // // 輸出 // System.out.println(node.e); // // preOrder(node.left); // preOrder(node.right); // } } // 二分搜索樹的中序遍歷 public void inOrder () { inOrder(root); } // 中序遍歷以node爲根的二分搜索樹 遞歸算法 private void inOrder (Node node) { if (node == null) return; inOrder(node.left); System.out.println(node.e); inOrder(node.right); } // 二分搜索樹的後序遍歷 public void postOrder () { postOrder(root); } // 後續遍歷以node爲根的二分搜索樹 遞歸算法 private void postOrder (Node node) { if (node == null) return; postOrder(node.left); postOrder(node.right); System.out.println(node.e); } @Override public String toString () { StringBuilder sb = new StringBuilder(); generateBSTString(root, 0, sb); return sb.toString(); } // 生成以node爲根節點,深度爲depth的描述二叉樹的字符串 private void generateBSTString (Node node, int depath, StringBuilder sb) { if (node == null) { sb.append(generateDepthString(depath) + "null\n"); return; } sb.append(generateDepthString(depath) + node.e + "\n"); generateBSTString(node.left, depath + 1, sb); generateBSTString(node.right, depath + 1, sb); } // 生成路徑字符串 private String generateDepthString (int depth) { StringBuilder sb = new StringBuilder(); for (int i = 0; i < depth; i++) { sb.append("-- "); } return sb.toString(); } }
Main
public class Main { public static void main(String[] args) { MyBinarySearchTree<Integer> mbst = new MyBinarySearchTree<Integer>(); int [] nums = {5, 3, 6, 8, 4, 2}; for (int i = 0; i < nums.length ; i++) { mbst.add(nums[i]); } ///////////////// // 5 // // / \ // // 3 6 // // / \ \ // // 2 4 8 // ///////////////// // 前序遍歷 mbst.preOrder(); // 5 3 2 4 6 8 System.out.println(); // 中序遍歷 mbst.inOrder(); // 2 3 4 5 6 8 System.out.println(); // 後序遍歷 mbst.postOrder(); // 2 4 3 8 6 5 System.out.println(); // // 輸出 調試字符串 // System.out.println(mbst.toString()); } }
再看二分搜索樹的遍歷
對二分搜索樹前中後這三種順序的遍歷
function traverse(node) { if (node === null) return; // 1. 第一個訪問的機會 前 traverse(node.left); // 2. 第二個訪問的機會 中 traverse(node.right); // 3. 第三個訪問的機會 後 }
二叉樹前中後序遍歷訪問節點的不一樣
前序遍歷的遞歸寫法
前
表示先訪問的這個節點。function preOrder(node) { if (node == null) return; // ... 要作的事情 // 訪問該節點 // 先一直往左,而後不斷返回上一層 再向左、終止, // 最後整個操做循環往復,直到所有終止。 preOrder(node.left); preOrder(node.right); }
前序遍歷的非遞歸寫法
棧
來模擬遞歸調用時的系統棧。不管是非遞歸仍是遞歸的寫法,結果都是一致的
將遞歸算法轉換爲非遞歸算法
二分搜索樹遍歷的非遞歸實現比遞歸實現複雜不少
二分搜索樹的中序遍歷和後序遍歷的非遞歸實現更復雜
對於前序遍從來說不管是遞歸寫法仍是非遞歸寫法
(class: MyBinarySearchTree, class: Main)
MyBinarySearchTree
import java.util.Stack; public class MyBinarySearchTree<E extends Comparable<E>> { private class Node { public E e; public Node left, right; public Node (E e) { this.e = e; left = null; right = null; } } private Node root; private int size; public MyBinarySearchTree () { root = null; size = 0; } public int getSize() { return size; } public boolean isEmpty () { return size == 0; } // 向二分搜索樹中添加一個元素 e // 改進:直接調用add public void add (E e) { root = add(root, e); } // 向以node爲根的二分搜索樹中插入元素E,遞歸算法 // 改進:返回插入的新節點後二分搜索樹的根 private Node add (Node node, E e) { // 處理最基本的問題 if (node == null) { size ++; return new Node(e); } // 空的二叉樹也是叉樹。 if (e.compareTo(node.e) < 0) { // 將處理後的結果賦值給node的左子樹 node.left = add(node.left, e); } else if (e.compareTo(node.e) > 0) { // 將處理後的結果賦值給node的右子樹 node.right = add(node.right, e); } // 若是相同 就什麼都不作 // 最後返回這個node return node; } // 查詢二分搜索數中是否包含某個元素 public boolean contains (E e) { return contains(root, e); } // 向以node爲根的二分搜索樹 進行查找 遞歸算法 private boolean contains (Node node, E e) { // 解決最基本的問題 也就是遍歷完全部節點都沒有找到 if (node == null) { return false; } // 若是 e 小於當前節點的e 則向左子樹進發 if (e.compareTo(node.e) < 0) { return contains(node.left, e); } else if (e.compareTo(node.e) > 0) { // 若是 e 大於當前節點的e 則向右子樹進發 return contains(node.right, e); } else { // 若是e 等於 當前節點 e 則直接返回true return true; } } // 二分搜索樹的前序遍歷 public void preOrder () { preOrder(root); } // 前序遍歷以node爲根的二分搜索樹 遞歸算法 private void preOrder (Node node) { if (node == null) { return; } // 輸出 System.out.println(node.e); preOrder(node.left); preOrder(node.right); // // 這種邏輯也是能夠的 // if (node != null) { // // 輸出 // System.out.println(node.e); // // preOrder(node.left); // preOrder(node.right); // } } // 二分搜索樹的前序遍歷 非遞歸算法 public void preOrderNonRecursive () { Stack<Node> stack = new Stack<Node>(); stack.push(root); Node node = null; while (!stack.isEmpty()) { node = stack.pop(); // // 第一種寫法 不符合要求也能夠壓入棧,可是不符合要求的在出棧後不處理它 // if (node != null) { // System.out.println(node.e); // stack.push(node.right); // stack.push(node.left); // } // // 第二種寫法 不符合要求也能夠壓入棧,可是不符合要求的在出棧後不處理它 // if (node == null) continue; // // System.out.println(node.e); // stack.push(node.right); // stack.push(node.left); // 寫法三 不符合要求就不壓入棧 System.out.println(node.e); if (node.right != null) { stack.push(node.right); } if (node.left != null) { stack.push(node.left); } } } // 二分搜索樹的中序遍歷 public void inOrder () { inOrder(root); } // 中序遍歷以node爲根的二分搜索樹 遞歸算法 private void inOrder (Node node) { if (node == null) return; inOrder(node.left); System.out.println(node.e); inOrder(node.right); } // 二分搜索樹的中序遍歷 非遞歸算法 public void inOrderNonRecursive () { } // 二分搜索樹的後序遍歷 public void postOrder () { postOrder(root); } // 後續遍歷以node爲根的二分搜索樹 遞歸算法 private void postOrder (Node node) { if (node == null) return; postOrder(node.left); postOrder(node.right); System.out.println(node.e); } @Override public String toString () { StringBuilder sb = new StringBuilder(); generateBSTString(root, 0, sb); return sb.toString(); } // 生成以node爲根節點,深度爲depth的描述二叉樹的字符串 private void generateBSTString (Node node, int depath, StringBuilder sb) { if (node == null) { sb.append(generateDepthString(depath) + "null\n"); return; } sb.append(generateDepthString(depath) + node.e + "\n"); generateBSTString(node.left, depath + 1, sb); generateBSTString(node.right, depath + 1, sb); } // 生成路徑字符串 private String generateDepthString (int depth) { StringBuilder sb = new StringBuilder(); for (int i = 0; i < depth; i++) { sb.append("-- "); } return sb.toString(); } }
Main
public class Main { public static void main(String[] args) { MyBinarySearchTree<Integer> mbst = new MyBinarySearchTree<Integer>(); int [] nums = {5, 3, 6, 8, 4, 2}; for (int i = 0; i < nums.length ; i++) { mbst.add(nums[i]); } ///////////////// // 5 // // / \ // // 3 6 // // / \ \ // // 2 4 8 // ///////////////// // 前序遍歷 mbst.preOrder(); // 5 3 2 4 6 8 System.out.println(); // 中序遍歷 mbst.inOrder(); // 2 3 4 5 6 8 System.out.println(); // 後序遍歷 mbst.postOrder(); // 2 4 3 8 6 5 System.out.println(); // 前序遍歷 非遞歸 mbst.preOrderNonRecursive(); // 5 3 2 4 6 8 System.out.println(); // // 輸出 調試字符串 // System.out.println(mbst.toString()); } }
二分搜索樹的 前序、中序、後序遍歷
對於二分搜索樹來講
先遍歷第 0 層、再遍歷第 1 層、再遍歷下一層,
對於層序遍歷的實現或者廣度優先遍歷的實現
先入隊根節點,而後看隊首是否有元素,
相對於深度優先遍從來說,廣度優先遍歷的優勢
在圖中也是有深度優先遍歷和廣度優先遍歷的
(class: MyBinarySearchTree, class: Main)
MyBinarySearchTree
import java.util.LinkedList; import java.util.Queue; import java.util.Stack; public class MyBinarySearchTree<E extends Comparable<E>> { private class Node { public E e; public Node left, right; public Node (E e) { this.e = e; left = null; right = null; } } private Node root; private int size; public MyBinarySearchTree () { root = null; size = 0; } public int getSize() { return size; } public boolean isEmpty () { return size == 0; } // 向二分搜索樹中添加一個元素 e // 改進:直接調用add public void add (E e) { root = add(root, e); } // 向以node爲根的二分搜索樹中插入元素E,遞歸算法 // 改進:返回插入的新節點後二分搜索樹的根 private Node add (Node node, E e) { // 處理最基本的問題 if (node == null) { size ++; return new Node(e); } // 空的二叉樹也是叉樹。 if (e.compareTo(node.e) < 0) { // 將處理後的結果賦值給node的左子樹 node.left = add(node.left, e); } else if (e.compareTo(node.e) > 0) { // 將處理後的結果賦值給node的右子樹 node.right = add(node.right, e); } // 若是相同 就什麼都不作 // 最後返回這個node return node; } // 查詢二分搜索數中是否包含某個元素 public boolean contains (E e) { return contains(root, e); } // 向以node爲根的二分搜索樹 進行查找 遞歸算法 private boolean contains (Node node, E e) { // 解決最基本的問題 也就是遍歷完全部節點都沒有找到 if (node == null) { return false; } // 若是 e 小於當前節點的e 則向左子樹進發 if (e.compareTo(node.e) < 0) { return contains(node.left, e); } else if (e.compareTo(node.e) > 0) { // 若是 e 大於當前節點的e 則向右子樹進發 return contains(node.right, e); } else { // 若是e 等於 當前節點 e 則直接返回true return true; } } // 二分搜索樹的前序遍歷 public void preOrder () { preOrder(root); } // 前序遍歷以node爲根的二分搜索樹 遞歸算法 private void preOrder (Node node) { if (node == null) { return; } // 輸出 System.out.println(node.e); preOrder(node.left); preOrder(node.right); // // 這種邏輯也是能夠的 // if (node != null) { // // 輸出 // System.out.println(node.e); // // preOrder(node.left); // preOrder(node.right); // } } // 二分搜索樹的前序遍歷 非遞歸算法 public void preOrderNonRecursive () { Stack<Node> stack = new Stack<Node>(); stack.push(root); Node node = null; while (!stack.isEmpty()) { node = stack.pop(); // // 第一種寫法 不符合要求也能夠壓入棧,可是不符合要求的在出棧後不處理它 // if (node != null) { // System.out.println(node.e); // stack.push(node.right); // stack.push(node.left); // } // // 第二種寫法 不符合要求也能夠壓入棧,可是不符合要求的在出棧後不處理它 // if (node == null) continue; // // System.out.println(node.e); // stack.push(node.right); // stack.push(node.left); // 寫法三 不符合要求就不壓入棧 System.out.println(node.e); if (node.right != null) { stack.push(node.right); } if (node.left != null) { stack.push(node.left); } } } // 二分搜索樹的中序遍歷 public void inOrder () { inOrder(root); } // 中序遍歷以node爲根的二分搜索樹 遞歸算法 private void inOrder (Node node) { if (node == null) return; inOrder(node.left); System.out.println(node.e); inOrder(node.right); } // 二分搜索樹的中序遍歷 非遞歸算法 public void inOrderNonRecursive () { } // 二分搜索樹的後序遍歷 public void postOrder () { postOrder(root); } // 後續遍歷以node爲根的二分搜索樹 遞歸算法 private void postOrder (Node node) { if (node == null) return; postOrder(node.left); postOrder(node.right); System.out.println(node.e); } // 二分搜索樹的層序遍歷 public void levelOrder () { // java中的Queue是一個接口,可是它有鏈表和隊列的實現, // 因此你能夠new 一個子類鏈表類來進行進行使用,能夠達到一樣的效果 Queue<Node> queue = new LinkedList<Node>(); queue.add(root); while (!queue.isEmpty()) { Node node = queue.remove(); System.out.println(node.e); if (node.left != null) { queue.add(node.left); } if (node.right != null) { queue.add(node.right); } } } @Override public String toString () { StringBuilder sb = new StringBuilder(); generateBSTString(root, 0, sb); return sb.toString(); } // 生成以node爲根節點,深度爲depth的描述二叉樹的字符串 private void generateBSTString (Node node, int depath, StringBuilder sb) { if (node == null) { sb.append(generateDepthString(depath) + "null\n"); return; } sb.append(generateDepthString(depath) + node.e + "\n"); generateBSTString(node.left, depath + 1, sb); generateBSTString(node.right, depath + 1, sb); } // 生成路徑字符串 private String generateDepthString (int depth) { StringBuilder sb = new StringBuilder(); for (int i = 0; i < depth; i++) { sb.append("-- "); } return sb.toString(); } }
Main
public class Main { public static void main(String[] args) { MyBinarySearchTree<Integer> mbst = new MyBinarySearchTree<Integer>(); int [] nums = {5, 3, 6, 8, 4, 2}; for (int i = 0; i < nums.length ; i++) { mbst.add(nums[i]); } ///////////////// // 5 // // / \ // // 3 6 // // / \ \ // // 2 4 8 // ///////////////// // // 前序遍歷 // mbst.preOrder(); // 5 3 2 4 6 8 // System.out.println(); // // // 中序遍歷 // mbst.inOrder(); // 2 3 4 5 6 8 // System.out.println(); // // // 後序遍歷 // mbst.postOrder(); // 2 4 3 8 6 5 // System.out.println(); // // // 前序遍歷 非遞歸 // mbst.preOrderNonRecursive(); // 5 3 2 4 6 8 // System.out.println(); mbst.levelOrder(); // 5 3 6 2 4 8 System.out.println(); // // 輸出 調試字符串 // System.out.println(mbst.toString()); } }
不少時候學習知識
對於二分搜索樹來講刪除一個節點相對來講是比較複雜的
刪除二分搜索樹的最小值和最大值
找到二分搜索樹中的最大值和最小值是很是容易的
刪除最大元素節點
刪除最小元素節點
(class: MyBinarySearchTree, class: Main)
MyBinarySearchTree
import java.util.LinkedList; import java.util.Queue; import java.util.Stack; public class MyBinarySearchTree<E extends Comparable<E>> { private class Node { public E e; public Node left, right; public Node (E e) { this.e = e; left = null; right = null; } } private Node root; private int size; public MyBinarySearchTree () { root = null; size = 0; } public int getSize() { return size; } public boolean isEmpty () { return size == 0; } // 向二分搜索樹中添加一個元素 e // 改進:直接調用add public void add (E e) { root = add(root, e); } // 向以node爲根的二分搜索樹中插入元素E,遞歸算法 // 改進:返回插入的新節點後二分搜索樹的根 private Node add (Node node, E e) { // 處理最基本的問題 if (node == null) { size ++; return new Node(e); } // 空的二叉樹也是叉樹。 if (e.compareTo(node.e) < 0) { // 將處理後的結果賦值給node的左子樹 node.left = add(node.left, e); } else if (e.compareTo(node.e) > 0) { // 將處理後的結果賦值給node的右子樹 node.right = add(node.right, e); } // 若是相同 就什麼都不作 // 最後返回這個node return node; } // 查詢二分搜索數中是否包含某個元素 public boolean contains (E e) { return contains(root, e); } // 向以node爲根的二分搜索樹 進行查找 遞歸算法 private boolean contains (Node node, E e) { // 解決最基本的問題 也就是遍歷完全部節點都沒有找到 if (node == null) { return false; } // 若是 e 小於當前節點的e 則向左子樹進發 if (e.compareTo(node.e) < 0) { return contains(node.left, e); } else if (e.compareTo(node.e) > 0) { // 若是 e 大於當前節點的e 則向右子樹進發 return contains(node.right, e); } else { // 若是e 等於 當前節點 e 則直接返回true return true; } } // 二分搜索樹的前序遍歷 public void preOrder () { preOrder(root); } // 前序遍歷以node爲根的二分搜索樹 遞歸算法 private void preOrder (Node node) { if (node == null) { return; } // 輸出 System.out.println(node.e); preOrder(node.left); preOrder(node.right); // // 這種邏輯也是能夠的 // if (node != null) { // // 輸出 // System.out.println(node.e); // // preOrder(node.left); // preOrder(node.right); // } } // 二分搜索樹的前序遍歷 非遞歸算法 public void preOrderNonRecursive () { Stack<Node> stack = new Stack<Node>(); stack.push(root); Node node = null; while (!stack.isEmpty()) { node = stack.pop(); // // 第一種寫法 不符合要求也能夠壓入棧,可是不符合要求的在出棧後不處理它 // if (node != null) { // System.out.println(node.e); // stack.push(node.right); // stack.push(node.left); // } // // 第二種寫法 不符合要求也能夠壓入棧,可是不符合要求的在出棧後不處理它 // if (node == null) continue; // // System.out.println(node.e); // stack.push(node.right); // stack.push(node.left); // 寫法三 不符合要求就不壓入棧 System.out.println(node.e); if (node.right != null) { stack.push(node.right); } if (node.left != null) { stack.push(node.left); } } } // 二分搜索樹的中序遍歷 public void inOrder () { inOrder(root); } // 中序遍歷以node爲根的二分搜索樹 遞歸算法 private void inOrder (Node node) { if (node == null) return; inOrder(node.left); System.out.println(node.e); inOrder(node.right); } // 二分搜索樹的中序遍歷 非遞歸算法 public void inOrderNonRecursive () { } // 二分搜索樹的後序遍歷 public void postOrder () { postOrder(root); } // 後續遍歷以node爲根的二分搜索樹 遞歸算法 private void postOrder (Node node) { if (node == null) return; postOrder(node.left); postOrder(node.right); System.out.println(node.e); } // 二分搜索樹的層序遍歷 public void levelOrder () { // java中的Queue是一個接口,可是它有鏈表和隊列的實現, // 因此你能夠new 一個子類鏈表類來進行進行使用,能夠達到一樣的效果 Queue<Node> queue = new LinkedList<Node>(); queue.add(root); while (!queue.isEmpty()) { Node node = queue.remove(); System.out.println(node.e); if (node.left != null) { queue.add(node.left); } if (node.right != null) { queue.add(node.right); } } } // 尋找二分搜索樹的最小值元素 public E minimum () { if (size == 0) { throw new IllegalArgumentException("BST is empty."); } return minimum(root).e; } // 返回以node爲根的二分搜索樹的最小值所在的節點 private Node minimum (Node node) { // 向左走再也走不動了,就返回這個節點。 if (node.left == null) return node; return minimum(node.left); } // 從二分搜索樹種刪除最小值所在節點,返回這個最小值 public E removeMin () { E result = minimum(); // removeMin(root); root = removeMin(root); return result; } // 刪除掉以node爲根的二分搜索樹中的最小節點 // 返回刪除節點後新的二分搜索樹的根 private Node removeMin (Node node) { // if (node.left == null) { // node = node.right; // size --; // return node; // } // // return removeMin(node.left); if (node.left == null) { Node rightNode = node.right; node.right = null; size --; return rightNode; } node.left = removeMin(node.left); return node; } // 尋找二分搜索樹的最大值元素 public E maximum () { if (size == 0) { throw new IllegalArgumentException("BST is empty."); } return maximum(root).e; } // 返回以node爲根的二分搜索樹的最大值所在的節點 private Node maximum (Node node) { // 向右走再也走不動了,就返回這個節點。 if (node.right == null) return node; return maximum(node.right); } // 從二分搜索樹種刪除最大值所在節點,返回這個最大值 public E removeMax () { E result = maximum(); root = removeMax(root); return result; } // 刪除掉以node爲根的二分搜索樹中的最大節點 // 返回刪除節點後新的二分搜索樹的根 private Node removeMax (Node node) { if (node.right == null) { Node leftNode = node.left; node.left = null; size --; return leftNode; } node.right = removeMax(node.right); return node; } @Override public String toString () { StringBuilder sb = new StringBuilder(); generateBSTString(root, 0, sb); return sb.toString(); } // 生成以node爲根節點,深度爲depth的描述二叉樹的字符串 private void generateBSTString (Node node, int depath, StringBuilder sb) { if (node == null) { sb.append(generateDepthString(depath) + "null\n"); return; } sb.append(generateDepthString(depath) + node.e + "\n"); generateBSTString(node.left, depath + 1, sb); generateBSTString(node.right, depath + 1, sb); } // 生成路徑字符串 private String generateDepthString (int depth) { StringBuilder sb = new StringBuilder(); for (int i = 0; i < depth; i++) { sb.append("-- "); } return sb.toString(); } }
Main
import java.util.ArrayList; import java.util.Random; public class Main { public static void main(String[] args) { MyBinarySearchTree<Integer> mbst = new MyBinarySearchTree<Integer>(); Random random = new Random(); int n = 100; for (int i = 0; i < n; i++) { mbst.add(random.nextInt(Integer.MAX_VALUE)); } // 動態數組 ArrayList<Integer> arrayList = new ArrayList<Integer>(); while (!mbst.isEmpty()) { arrayList.add(mbst.removeMin()); // arrayList.add(mbst.removeMax()); } // 數組中就是從小到大排序的 System.out.println(arrayList); // 驗證一下 for (int i = 1; i < arrayList.size() ; i++) { // 若是前面的數大於後面的數就報異常 if (arrayList.get(i - 1) > arrayList.get(i)) { // 若是前面的數小於後面的數就報異常 // if (arrayList.get(i - 1) < arrayList.get(i)) { throw new IllegalArgumentException("error."); } } System.out.println("removeMin test completed."); // System.out.println("removeMax test completed."); } }
在二分搜索樹種刪除最大值最小值的邏輯
刪除二分搜索樹上任意節點會發生的狀況
對於二分搜索樹來講
(class: MyBinarySearchTree, class: Main)
MyBinarySearchTree
import java.util.LinkedList; import java.util.Queue; import java.util.Stack; public class MyBinarySearchTree<E extends Comparable<E>> { private class Node { public E e; public Node left, right; public Node (E e) { this.e = e; left = null; right = null; } } private Node root; private int size; public MyBinarySearchTree () { root = null; size = 0; } public int getSize() { return size; } public boolean isEmpty () { return size == 0; } // 向二分搜索樹中添加一個元素 e // 改進:直接調用add public void add (E e) { root = add(root, e); } // 向以node爲根的二分搜索樹中插入元素E,遞歸算法 // 改進:返回插入的新節點後二分搜索樹的根 private Node add (Node node, E e) { // 處理最基本的問題 if (node == null) { size ++; return new Node(e); } // 空的二叉樹也是叉樹。 if (e.compareTo(node.e) < 0) { // 將處理後的結果賦值給node的左子樹 node.left = add(node.left, e); } else if (e.compareTo(node.e) > 0) { // 將處理後的結果賦值給node的右子樹 node.right = add(node.right, e); } // 若是相同 就什麼都不作 // 最後返回這個node return node; } // 查詢二分搜索數中是否包含某個元素 public boolean contains (E e) { return contains(root, e); } // 向以node爲根的二分搜索樹 進行查找 遞歸算法 private boolean contains (Node node, E e) { // 解決最基本的問題 也就是遍歷完全部節點都沒有找到 if (node == null) { return false; } // 若是 e 小於當前節點的e 則向左子樹進發 if (e.compareTo(node.e) < 0) { return contains(node.left, e); } else if (e.compareTo(node.e) > 0) { // 若是 e 大於當前節點的e 則向右子樹進發 return contains(node.right, e); } else { // 若是e 等於 當前節點 e 則直接返回true return true; } } // 二分搜索樹的前序遍歷 public void preOrder () { preOrder(root); } // 前序遍歷以node爲根的二分搜索樹 遞歸算法 private void preOrder (Node node) { if (node == null) { return; } // 輸出 System.out.println(node.e); preOrder(node.left); preOrder(node.right); // // 這種邏輯也是能夠的 // if (node != null) { // // 輸出 // System.out.println(node.e); // // preOrder(node.left); // preOrder(node.right); // } } // 二分搜索樹的前序遍歷 非遞歸算法 public void preOrderNonRecursive () { Stack<Node> stack = new Stack<Node>(); stack.push(root); Node node = null; while (!stack.isEmpty()) { node = stack.pop(); // // 第一種寫法 不符合要求也能夠壓入棧,可是不符合要求的在出棧後不處理它 // if (node != null) { // System.out.println(node.e); // stack.push(node.right); // stack.push(node.left); // } // // 第二種寫法 不符合要求也能夠壓入棧,可是不符合要求的在出棧後不處理它 // if (node == null) continue; // // System.out.println(node.e); // stack.push(node.right); // stack.push(node.left); // 寫法三 不符合要求就不壓入棧 System.out.println(node.e); if (node.right != null) { stack.push(node.right); } if (node.left != null) { stack.push(node.left); } } } // 二分搜索樹的中序遍歷 public void inOrder () { inOrder(root); } // 中序遍歷以node爲根的二分搜索樹 遞歸算法 private void inOrder (Node node) { if (node == null) return; inOrder(node.left); System.out.println(node.e); inOrder(node.right); } // 二分搜索樹的中序遍歷 非遞歸算法 public void inOrderNonRecursive () { } // 二分搜索樹的後序遍歷 public void postOrder () { postOrder(root); } // 後續遍歷以node爲根的二分搜索樹 遞歸算法 private void postOrder (Node node) { if (node == null) return; postOrder(node.left); postOrder(node.right); System.out.println(node.e); } // 二分搜索樹的層序遍歷 public void levelOrder () { // java中的Queue是一個接口,可是它有鏈表和隊列的實現, // 因此你能夠new 一個子類鏈表類來進行進行使用,能夠達到一樣的效果 Queue<Node> queue = new LinkedList<Node>(); queue.add(root); while (!queue.isEmpty()) { Node node = queue.remove(); System.out.println(node.e); if (node.left != null) { queue.add(node.left); } if (node.right != null) { queue.add(node.right); } } } // 尋找二分搜索樹的最小值元素 public E minimum () { if (size == 0) { throw new IllegalArgumentException("BST is empty."); } return minimum(root).e; } // 返回以node爲根的二分搜索樹的最小值所在的節點 private Node minimum (Node node) { // 向左走再也走不動了,就返回這個節點。 if (node.left == null) return node; return minimum(node.left); } // 從二分搜索樹種刪除最小值所在節點,返回這個最小值 public E removeMin () { E result = minimum(); // removeMin(root); root = removeMin(root); return result; } // 刪除掉以node爲根的二分搜索樹中的最小節點 // 返回刪除節點後新的二分搜索樹的根 private Node removeMin (Node node) { // if (node.left == null) { // node = node.right; // size --; // return node; // } // // return removeMin(node.left); if (node.left == null) { Node rightNode = node.right; node.right = null; size --; return rightNode; } node.left = removeMin(node.left); return node; } // 尋找二分搜索樹的最大值元素 public E maximum () { if (size == 0) { throw new IllegalArgumentException("BST is empty."); } return maximum(root).e; } // 返回以node爲根的二分搜索樹的最大值所在的節點 private Node maximum (Node node) { // 向右走再也走不動了,就返回這個節點。 if (node.right == null) return node; return maximum(node.right); } // 從二分搜索樹種刪除最大值所在節點,返回這個最大值 public E removeMax () { E result = maximum(); root = removeMax(root); return result; } // 刪除掉以node爲根的二分搜索樹中的最大節點 // 返回刪除節點後新的二分搜索樹的根 private Node removeMax (Node node) { if (node.right == null) { Node leftNode = node.left; node.left = null; size --; return leftNode; } node.right = removeMax(node.right); return node; } // 從二分搜索樹中刪除元素e的節點 public void remove (E e) { root = remove(root, e); } // 刪除掉以node爲根的二分搜索樹中值爲e的節點 遞歸算法 // 返回刪除節點後新的二分搜索樹的根 private Node remove(Node node, E e) { if (node == null) return null; if (e.compareTo(node.e) < 0) { node.left = remove(node.left, e); return node; } else if (e.compareTo(node.e) > 0) { node.right = remove(node.right, e); return node; } else { // e == node.e // 待刪除的節點左子樹爲空 if (node.left == null) { Node rightNode = node.right; node.right = null; size --; return rightNode; } // 待刪除的節點右子樹爲空 if (node.right == null) { Node leftNode = node.left; node.left = null; size --; return leftNode; } // 待刪除的節點左右子樹都不爲空的狀況 // 找到比待刪除節點大的最小節點,即待刪除節點右子樹的最小節點 // 用這個節點頂替待刪除節點的位置 Node successor = minimum(node.right); successor.right = removeMin(node.right); // 在removeMin這個操做中維護了一次size --,可是並無刪除節點 // 因此這裏要進行一次size ++操做 size ++; successor.left = node.left; // 讓node這個節點與當前這個二分搜索樹脫離關係 node.left = node.right = null; // 維護一下size size --; return successor; } } @Override public String toString () { StringBuilder sb = new StringBuilder(); generateBSTString(root, 0, sb); return sb.toString(); } // 生成以node爲根節點,深度爲depth的描述二叉樹的字符串 private void generateBSTString (Node node, int depath, StringBuilder sb) { if (node == null) { sb.append(generateDepthString(depath) + "null\n"); return; } sb.append(generateDepthString(depath) + node.e + "\n"); generateBSTString(node.left, depath + 1, sb); generateBSTString(node.right, depath + 1, sb); } // 生成路徑字符串 private String generateDepthString (int depth) { StringBuilder sb = new StringBuilder(); for (int i = 0; i < depth; i++) { sb.append("-- "); } return sb.toString(); } }
Main
import java.util.ArrayList; import java.util.Random; public class Main { public static void main(String[] args) { MyBinarySearchTree<Integer> mbst = new MyBinarySearchTree<Integer>(); // 動態數組 ArrayList<Integer> arrayList = new ArrayList<Integer>(); Random random = new Random(); int n = 10; for (int i = 0; i < n; i++) { int value = random.nextInt(Integer.MAX_VALUE); mbst.add(value); arrayList.add(value); } // 輸出二分搜索樹 System.out.println(mbst.getSize()); // 輸出數組中內容 System.out.println(arrayList); for (int i = 0; i < arrayList.size(); i++) { mbst.remove(arrayList.get(i)); } // 輸出二分搜索樹 System.out.println(mbst.getSize()); System.out.println("remove test completed."); } }
能夠很是方便的拿到二分搜索樹中最大值和最小值,
也由於這個順序性也能夠對它進行 floor 和 ceil 的操做,
相應的二分搜索樹還能夠實現 rank 和 select 方法,
root.size
就行了,很是的方便。維護 depth 的二分搜索樹
支持重複元素的二分搜索樹
還能夠經過維護每個節點的 count 變量來實現重複元素的二分搜索樹,
count++
便可,count--
便可,在二分搜索樹中相應的變種其實大可能是在 node 中維護一些數據
相關的習題能夠去 leetcode 中找到,
https://leetcode-cn.com/tag/tree/
其它