【資料結構05】紅-黑樹基礎----二叉搜尋樹(Binary Search Tree)
目錄
- 1、二分法引言
- 2、二叉搜尋樹定義
- 3、二叉搜尋樹的CRUD
- 4、二叉搜尋樹的兩種極端情況
- 5、二叉搜尋樹總結
前言
在【演算法04】樹與二叉樹中,已經介紹過了關於樹的一些基本概念以及二叉樹的前中後序遍歷,而這篇文章將是在二叉樹的基礎上來展開講解的二叉搜尋樹,也就是說二叉搜尋樹建立在樹的基礎之上。至於博主為何要花一整篇文章來講這個二叉搜尋樹呢?原因很簡單,紅-黑樹是基於二叉搜尋樹的,如果對二叉搜尋樹不瞭解,那還談何紅-黑樹?紅-黑樹的重要性我想各位沒吃過佩奇肉也肯定看過宜春跑....是的,jdk1.8的Map 就是散列表+紅黑樹實現的!
@
首先要明確的是二叉搜尋樹又稱二叉排序樹、二叉查詢樹,簡統稱BST
1、二分法引言
在正式將二叉搜尋樹之前,宜春還是想先談談人生談談生活從而切入二叉搜尋樹。
一天,程式設計師老方給宜春打電話:靚仔,我今天下單了一雙皮鞋,老靚了,價格不菲啊!
宜春:得了吧你,啥條件啊我還不知道,還皮鞋,老鼠皮的鞋吧,如果是真牛皮的皮鞋我把它吃嘍!!!
老方:你還真別說,這皮鞋還真是比真牛皮還真的假牛皮的皮鞋,老靚了,你猜猜我買它花了多少銀子,反正是100以內,看你能不能再最少次數猜出來,要是五次機會之內猜出來就親自下廚,燉給你吃,嘎嘣脆,嘿嘿...
宜春:你就嘚瑟吧,還是你懂我,知道我好這一口(自黑)....咳咳咳,50塊
老方:不對,價格高了
宜春:25
老方:不對,價格還高了
宜春:12.5
....
一個一個字看這裡的估計在座各位個個都是人才,就一個簡簡單單的二分法猜數字的遊戲,通過對猜測數字“大了”、“小了”的情況判斷,來猜出最終的數字。當然,本改三言兩語就可以描述的,宜春花了這麼長串字串來描述,估計宜春TM也是個人才....實際上呢,宜春就想活躍活躍下氣氛,把各位的腦細胞集中一下下,當然耽誤了各位時間,屬實抱歉,宜春線上捱揍....
不知各位有沒有想過,為何二分法就是有足夠的優勢呢?如果100以內我直接猜25不行麼?這樣豈不是更可能定出價格?其實直接猜25或者更小這是一種極端的猜測,如果確實比25小,那範圍就是在25以內了,那你有沒有想過:如果大於25呢?範圍就直接轉成了75了!隨著這種極端思想的推進,會發現
每次擇半查詢會更加準確和更好的能應對各種不確定的情景!擇半查詢複雜度為 O(log_2 n),即最多需要 O(log_2 n) 次可以猜到最終數字。
到這裡,要開始正式介紹二叉搜尋樹了,實際上二叉搜尋樹就類似於上面提到過的二分查詢,有類似的韻味。
2、二叉搜尋樹定義
二叉查詢樹(Binary Search Tree,BST)是一種特殊的二叉樹,一棵二叉搜尋樹(BST)是一棵二叉樹,其中,對於樹中每個節點而言:
1、若其左子樹存在,則其左子樹中每個節點的值都不大於該節點值;
2、若其右子樹存在,則其右子樹中每個節點的值都不小於該節點值。
3、二叉搜尋樹的CRUD
我想提一下的是:我們知道遍歷樹是使用前中後序遍歷方法,但是遍歷二叉搜尋樹最好是使用中序遍歷法,如果不理解為何使用中序遍歷,那麼你有三種選擇:
一、自行【演算法04】樹與二叉樹進去補補基礎樹基礎
二、留言提問,宜春看到就回
三、前面二者都拒絕 ,那你就優秀了....
3.1、查詢
如果要在二叉查詢樹中查詢任意一個節點,假設它是X ,我們可以分為以下幾步:
1、如果二叉查詢樹為空,則返回空操作,否則,執行一下操作;
2、先取根節點,如果節點 X 等於根節點,則返回;
3、如果節點小於根節點,則遞迴查詢左子樹;
4、如果節點大於根節點,則遞迴查詢右子樹。
//查詢的邏輯程式碼實現:
/**
* @param value 希望查詢結點的值
* @return 如果找到返回該結點,否則返回null
*/
public Node search(int value) {
if(value == this.value) { //找到就是該結點
return this;
} else if(value < this.value) {//如果查詢的值小於當前結點,向左子樹遞迴查詢
//如果左子結點為空
if(this.left == null) {
return null;
}
return this.left.search(value);
} else { //如果查詢的值不小於當前結點,向右子樹遞迴查詢
if(this.right == null) {
return null;
}
return this.right.search(value);
}
}
3.2、插入
在二叉樹中插入一個節點,仔細想想,會發現插入某一個節點一般都是插入到葉節點上,所以只需從根結點開始,依次遍歷比較要插入的資料和節點的大小關係。
==二叉查詢樹有一個很重要的特性就是插入的實現難度和查詢差不多==。插入節點其實可以有如下三種情況:
1、如果樹是空的,則直接將新節點插入,否則,執行下面步驟。
2、要插入的資料比根節點資料大,則到右子樹中插入新資料,如果右子樹為空,則將新資料直接插入到右子節點的位置;不為空,則繼續遍歷右子樹,查詢插入位置。
3、要插入的資料比根節點資料小,則到左子樹中插入資料,如果左子樹為空,則直接將新資料插入到左子節點的位置;不為空,則繼續遍歷左子樹,查詢插入的位置。
//新增結點的邏輯程式碼
//遞迴的形式新增結點,注意需要滿足二叉排序樹的要求
public void add(Node node) {
if(node == null) {
return;
}
if(root == null) {
root = node;//如果root為空則直接讓root指向node
}
//判斷傳入的結點的值,和當前子樹的根結點的值關係
if(node.value < this.value) {
//如果當前結點左子結點為null
if(this.left == null) {
this.left = node;
} else {
//遞迴的向左子樹新增
this.left.add(node);
}
} else { //新增的結點的值大於 當前結點的值
if(this.right == null) {
this.right = node;
} else {
//遞迴的向右子樹新增
this.right.add(node);
}
}
}
3.3、刪除
可以這麼說,刪除相對查詢和插入來說比較複雜一些,為啥會複雜一些呢?因為要刪除某一個節點,首先要查詢到這個節點然後將其刪除,刪除之後還需要將該二叉搜尋樹還原成一顆二叉搜尋樹。因此針對要刪除節點的子節點位置的不同,同樣一般分為三種情況來處理:
1、 第一種情況,如果要刪除的節點沒有子節點,直接將父節點指向要刪除節點的指標指向 null。比如途中要刪除的節點 0。
2、第二種情況,如果要刪除的節點只有一個節點,即只有左子節點或右子節點,則將父節點指向要刪除節點的指標指向要刪除節點的子節點即可。比如途中要刪除的節點1。
3、第三種情況,如果要刪除的節點有兩個子節點,則需要先找到這個節點右子樹中的最小節點或者左子樹中的最大節點,將其替換到要刪除的節點上。然後刪除這個右子樹中的最小節點或左子樹中的最大節點,比如圖中要刪除的節點 6。
//刪除結點邏輯程式碼
public void delNode(int value) {
if(root == null) {
return;
}else {
//1.需求先去找到要刪除的結點 targetNode
Node targetNode = search(value);
//如果沒有找到要刪除的結點
if(targetNode == null) {
return;
}
//如果我們發現當前這顆二叉排序樹只有一個結點
if(root.left == null && root.right == null) {
root = null;
return;
}
//去找到targetNode的父結點
Node parent = searchParent(value);
//如果要刪除的結點是葉子結點
if(targetNode.left == null && targetNode.right == null) {
//判斷targetNode 是父結點的左子結點,還是右子結點
if(parent.left != null && parent.left.value == value) { //是左子結點
parent.left = null;
} else if (parent.right != null && parent.right.value == value) {//是由子結點
parent.right = null;
}
} else if (targetNode.left != null && targetNode.right != null) { //刪除有兩顆子樹的節點
int minVal = delRightTreeMin(targetNode.right);
targetNode.value = minVal;
} else { // 刪除只有一顆子樹的結點
//如果要刪除的結點有左子結點
if(targetNode.left != null) {
if(parent != null) {
//如果 targetNode 是 parent 的左子結點
if(parent.left.value == value) {
parent.left = targetNode.left;
} else { // targetNode 是 parent 的右子結點
parent.right = targetNode.left;
}
} else {
root = targetNode.left;
}
} else { //如果要刪除的結點有右子結點
if(parent != null) {
//如果 targetNode 是 parent 的左子結點
if(parent.left.value == value) {
parent.left = targetNode.right;
} else { //如果 targetNode 是 parent 的右子結點
parent.right = targetNode.right;
}
} else {
root = targetNode.right;
}
}
}
}
}
3.4、整體程式碼
為了連貫一下思維,可以自行編輯main方法進行測試!
package dataStructure;
//建立二叉排序樹
class BinarySortTree {
private Node root;
public Node getRoot() {
return root;
}
//查詢要刪除的結點
public Node search(int value) {
if(root == null) {
return null;
} else {
return root.search(value);
}
}
//查詢父結點
public Node searchParent(int value) {
if(root == null) {
return null;
} else {
return root.searchParent(value);
}
}
//編寫方法:
//1. 返回的 以node 為根結點的二叉排序樹的最小結點的值
//2. 刪除node 為根結點的二叉排序樹的最小結點
/**
*
* @param node 傳入的結點(當做二叉排序樹的根結點)
* @return 返回的 以node 為根結點的二叉排序樹的最小結點的值
*/
public int delRightTreeMin(Node node) {
Node target = node;
//迴圈的查詢左子節點,就會找到最小值
while(target.left != null) {
target = target.left;
}
//這時 target就指向了最小結點
//刪除最小結點
delNode(target.value);
return target.value;
}
//刪除結點
public void delNode(int value) {
if(root == null) {
return;
}else {
//1.需求先去找到要刪除的結點 targetNode
Node targetNode = search(value);
//如果沒有找到要刪除的結點
if(targetNode == null) {
return;
}
//如果我們發現當前這顆二叉排序樹只有一個結點
if(root.left == null && root.right == null) {
root = null;
return;
}
//去找到targetNode的父結點
Node parent = searchParent(value);
//如果要刪除的結點是葉子結點
if(targetNode.left == null && targetNode.right == null) {
//判斷targetNode 是父結點的左子結點,還是右子結點
if(parent.left != null && parent.left.value == value) { //是左子結點
parent.left = null;
} else if (parent.right != null && parent.right.value == value) {//是由子結點
parent.right = null;
}
} else if (targetNode.left != null && targetNode.right != null) { //刪除有兩顆子樹的節點
int minVal = delRightTreeMin(targetNode.right);
targetNode.value = minVal;
} else { // 刪除只有一顆子樹的結點
//如果要刪除的結點有左子結點
if(targetNode.left != null) {
if(parent != null) {
//如果 targetNode 是 parent 的左子結點
if(parent.left.value == value) {
parent.left = targetNode.left;
} else { // targetNode 是 parent 的右子結點
parent.right = targetNode.left;
}
} else {
root = targetNode.left;
}
} else { //如果要刪除的結點有右子結點
if(parent != null) {
//如果 targetNode 是 parent 的左子結點
if(parent.left.value == value) {
parent.left = targetNode.right;
} else { //如果 targetNode 是 parent 的右子結點
parent.right = targetNode.right;
}
} else {
root = targetNode.right;
}
}
}
}
}
//新增結點的方法
public void add(Node node) {
if(root == null) {
root = node;//如果root為空則直接讓root指向node
} else {
root.add(node);
}
}
//中序遍歷
public void infixOrder() {
if(root != null) {
root.infixOrder();
} else {
System.out.println("二叉排序樹為空,不能遍歷");
}
}
}
//建立Node結點
class Node {
int value;
Node left;
Node right;
public Node(int value) {
this.value = value;
}
//查詢要刪除的結點
/**
*
* @param value 希望刪除的結點的值
* @return 如果找到返回該結點,否則返回null
*/
public Node search(int value) {
if(value == this.value) { //找到就是該結點
return this;
} else if(value < this.value) {//如果查詢的值小於當前結點,向左子樹遞迴查詢
//如果左子結點為空
if(this.left == null) {
return null;
}
return this.left.search(value);
} else { //如果查詢的值不小於當前結點,向右子樹遞迴查詢
if(this.right == null) {
return null;
}
return this.right.search(value);
}
}
//查詢要刪除結點的父結點
/**
*
* @param value 要找到的結點的值
* @return 返回的是要刪除的結點的父結點,如果沒有就返回null
*/
public Node searchParent(int value) {
//如果當前結點就是要刪除的結點的父結點,就返回
if((this.left != null && this.left.value == value) ||
(this.right != null && this.right.value == value)) {
return this;
} else {
//如果查詢的值小於當前結點的值, 並且當前結點的左子結點不為空
if(value < this.value && this.left != null) {
return this.left.searchParent(value); //向左子樹遞迴查詢
} else if (value >= this.value && this.right != null) {
return this.right.searchParent(value); //向右子樹遞迴查詢
} else {
return null; // 沒有找到父結點
}
}
}
@Override
public String toString() {
return "Node [value=" + value + "]";
}
//新增結點的方法
//遞迴的形式新增結點,注意需要滿足二叉排序樹的要求
public void add(Node node) {
if(node == null) {
return;
}
//判斷傳入的結點的值,和當前子樹的根結點的值關係
if(node.value < this.value) {
//如果當前結點左子結點為null
if(this.left == null) {
this.left = node;
} else {
//遞迴的向左子樹新增
this.left.add(node);
}
} else { //新增的結點的值大於 當前結點的值
if(this.right == null) {
this.right = node;
} else {
//遞迴的向右子樹新增
this.right.add(node);
}
}
}
//中序遍歷
public void infixOrder() {
if(this.left != null) {
this.left.infixOrder();
}
System.out.println(this);
if(this.right != null) {
this.right.infixOrder();
}
}
}
public class BinarySortTreeDemo { //==========至於main方法的測試程式碼可自行調整測試!!!!!!!!!
public static void main(String[] args) {
int[] arr = {4,7, 2, 13, 11, 5, 1, 9, 3};
BinarySortTree binarySortTree = new BinarySortTree();
for(int i = 0; i< arr.length; i++) {
binarySortTree.add(new Node(arr[i]));
}
binarySortTree.add(new Node(4));
System.out.println("中序遍歷二叉排序樹~");
binarySortTree.infixOrder();
}
}
4、二叉搜尋樹的兩種極端情況
1、變成一顆 完全二叉樹,所有節點儘量填滿樹的每一層,上一層填滿後還有剩餘節點的話,則由左向右儘量填滿下一層。如下圖所示,即為一顆完全二叉樹;
2、每一層只有一個節點的二叉樹。如下圖所示:
我敲,這不是蛇皮怪單鏈表嗎,是的,給我們的感覺就是樹形怪退化為蛇皮怪單鏈表了!在這種情況下,樹中每層只有一個節點,該狀態的樹結構更傾向於一種線性結構,節點的查詢類似於陣列的遍歷,複雜度為 O(n)。
也正是因此,後面就出現了平衡二叉樹,就涉及到了左旋右旋花裡胡哨的蛇皮操作,當然這只是提一下,並不在本文的範疇之內,不過後續應該會寫這方面的文章,儘量吧.....
5、二叉搜尋樹總結
二叉搜尋樹又稱二叉排序樹、二叉查詢樹,簡統稱BST
根據二叉搜尋樹的特性,==可知比較次數等於給定值節點在二叉排序樹中的層數==。遍歷的話使用中序遍歷。如果二叉排序樹是平衡的,則n個節點的二叉排序樹的高度為Log2n+1
,其查詢效率為O(Log2n)
,近似於折半查詢。如果二叉排序樹完全不平衡,則其深度可達到n,查詢效率為O(n)
,退化為順序查詢。一般的,二叉排序樹的查詢效能在O(Log2n)
到O(n)
之間。因此,為了獲得較好的查詢效能,就要構造一棵平衡的二叉排序樹。而平衡二叉樹可能又要涉及到了左旋右旋花裡胡哨的蛇皮操作,當然這只是提一下,並不在本文的範疇之內,不過後續應該會寫這方面的文章,儘量吧.....
如果本文對你有一點點幫助,那麼請點個讚唄,謝謝~
最後,若有不足或者不正之處,歡迎指正批評,感激不盡!如果有疑問歡迎留言,絕對第一時間回覆!
歡迎各位關注我的公眾號,裡面有一些java學習資料和一大波java電子書籍,比如說周志明老師的深入java虛擬機器、java程式設計思想、核心技術卷、大話設計模式、java併發程式設計實戰.....都是java的聖經,不說了快上Tomcat車,咋們走!最主要的是一起探討技術,嚮往技術,追求技術,說好了來了就是盆友喔...
相關推薦
【資料結構05】紅-黑樹基礎----二叉搜尋樹(Binary Search Tree)
目錄 1、二分法引言 2、二叉搜尋樹定義 3、二叉搜尋樹的CRUD 4、二叉搜尋樹的兩種極端情況 5、二叉搜尋樹總結 前言 在【演算法04】樹與二叉樹中,已經介紹
[PTA] 資料結構與演算法題目集 6-12 二叉搜尋樹的操作集
唯一比較需要思考的刪除操作: 被刪除節點有三種情況: 1、葉節點,直接刪除 2、只有一個子節點,將子節點替換為該節點,刪除該節點。 3、有兩個子節點,從右分支中找到最小節點,將其值賦給被刪除節點的位置,接著刪除這個最小節點 // 函式Insert將X插入二叉搜尋樹BST並返
【劍指offer】面試題33:二叉搜尋樹的後序遍歷序列
題目:輸入一個整數陣列,判斷該陣列是不是某二叉搜尋樹的後序遍歷的結果。如果是則返回 true,否則返回 false。假設輸入的陣列的任意兩個數字都互不相同。 牛客網連結: https://blog.csdn.net/jsqfengbao/article/details/4
【劍指offer】面試題36:二叉搜尋樹與雙向連結串列
題目:輸入一棵二叉搜尋樹,將該二叉搜尋樹轉換成一個排序的雙向連結串列。要求不能建立任何新的結點,只能調整樹中結點指標的指向。 比如如下圖中的二叉搜尋樹,則輸出轉換之後的排序雙向連結串列為: 在二叉樹中,每個結點都有兩個指向子節點的指標。在雙向連結串
二叉搜尋樹、AVL以及紅黑自平衡二叉搜尋樹
本文符號意義 q: 插入節點 M: 實際刪除節點(後繼節點) P: 當前節點的父節點 G: 當前節點的爺爺節點 S: 當前節點的叔叔節點(即父節點的兄弟節點) L: 左孩子 R: 右孩子 非空:節點的key值不為空 二叉搜尋樹 二叉搜尋樹的基本操作有search
【劍指offer】面試題54:二叉搜尋樹的第k大節點
題目 給定一顆二叉搜尋樹,請找出其中的第k大的結點。 例如, 5 / \ 3 7 /\ /\ 2 4 6 8 中,按結點數值大小順序第三個結點的值為4。 思路 二叉搜尋樹:左子節點 <= 根節點 <= 右子節點 中序遍歷:左
資料結構(三):非線性邏輯結構-特殊的二叉樹結構:堆、哈夫曼樹、二叉搜尋樹、平衡二叉搜尋樹、紅黑樹、線索二叉樹
/* 性質1. 節點是紅色或黑色 性質2. 根是黑色 性質3. 每個紅色節點的兩個子節點都是黑色 (從每個葉子到根的所有路徑上不能有兩個連續的紅色節點) 性質4. 從任一節點到其每個葉子的所有路徑都包含相同數目的黑色節點 */ #include #include typedef enum
資料結構_樹_二叉搜尋樹
二叉搜尋樹 二叉搜尋樹(BST)又稱為二叉查詢樹、二叉排序樹。 1.特徵 二叉搜尋樹首先是一棵二叉樹; 對任意節點,如果其左子樹不為空,則左子樹上任意節點的值均不大於它的根節點的值; 如果其右子樹不為空,則右子樹上任意節點的值均不大於它的根節點的值; 任意節點的左右子樹也分別是二叉搜尋樹。 2.中序遍
【Java】 劍指offer(33) 二叉搜尋樹的後序遍歷序列 《劍指Offer》Java實現合集 《劍指Offer》Java實現合集
本文參考自《劍指offer》一書,程式碼採用Java語言。 更多:《劍指Offer》Java實現合集 題目 輸入一個整數陣列,判斷該陣列是不是某二叉搜尋樹的後序遍歷的結果。如果是則返回true,否則返回false。假設輸入的陣列的任意兩個數字都互不相同。 思路 二叉
資料結構--伸展樹(伸展樹構建二叉搜尋樹)-學習筆記
/*-----------------------伸展樹----------------------------*/ 伸展數(Splay Tree),又叫分裂樹,是一種二叉排序樹,能在O(log n)內完成插入,查詢,刪除操作; 伸展樹上的一般操作都基於伸展操作: 假設要對一個二叉查詢樹執行一系列
資料結構與演算法學習--二叉樹及二叉搜尋樹
可以看下以前對數的總結https://blog.csdn.net/sjin_1314/article/details/8507490 下面是二叉樹的遍歷,建立及銷燬的函式實現,層次遍歷依賴佇列;佇列實現可以去github上檢視https://github.com/jin13417/al
Java資料結構:中根次序遍歷二叉排序樹
昨天離開了創新創業基地,有點難受,雖然換來了高效,但是總覺的難受,一起度過了半年,昨天離開了。 說正事,今天更新二叉排序樹的中根遍歷。 思想:其實沒啥,類似與二叉樹的非遞迴中
《資料結構》04-樹7 二叉搜尋樹的操作集
題目 本題要求實現給定二叉搜尋樹的5種常用操作。 函式介面定義: BinTree Insert( BinTree BST, ElementType X ); BinTree Delete( BinTree BST, ElementType X ); Positi
資料結構---04-樹7 二叉搜尋樹的操作集(30 分)
#include<cstdio> #include<cstdlib> #include<iostream> using namespace std; typedef int ElementType; typedef struct TNode* Position; typed
Javascript之資料結構與演算法的二叉樹和二叉搜尋樹實現
Javascript之資料結構與演算法的二叉樹和二叉搜尋樹實現 簡介 程式碼實現 簡介 二叉樹中的節點最多隻能有兩個子節點:一個是左側子節點,另一個是右側子節點。 二叉搜尋樹( BST)是二叉樹的一種,但是它只允許你在
二叉排序樹(Binary Sort Tree,二叉查詢樹,二叉搜尋樹)--【演算法導論】
今天的收穫就是二叉搜尋樹,“好記性不如爛筆頭”,寫下來加深一下印象; 1、首先是瞭解了二叉搜尋樹(Binary Sort Tree)又稱二叉查詢樹,亦稱二叉排序樹。 若它的左子樹不空,則左子樹上所有結點的值均小於它的根結點的值; 若它的右子樹不空,則右子樹上所有結點的值均
資料結構(樹,二叉搜尋樹,平衡二叉樹 C++實現)
樹 樹(Tree)是n(n>=0)個結點的有限集合。n = 0時稱為空樹。在任意一顆非空樹中:(1)有且僅有一個特定的稱為根(Root)的結點;(2)當n > 1時,其餘結點可分為m (m > 0)個互不相交的有限集 T1T1 、 T2T2
資料結構中的樹(二叉樹、二叉搜尋樹、AVL樹)
> [資料結構動圖展示網站](https://www.cs.usfca.edu/~galles/visualization/Algorithms.html) ### 樹的概念 樹(英語:tree)是一種抽象資料型別(ADT)或是實作這種抽象資料型別的資料結構,用來模擬具有樹狀結構性質的資料集合。它是由n(
資料機構與演算法:二叉查詢樹(Binary Search Tree)Java實現
個人總結,如有錯誤,感謝指正 二叉查詢樹(Binary Search Tree) 一、簡介 二叉樹(Binary Tree):每個節點最多有兩個子節點的樹。 二叉查詢樹(binary srarch tree):具有如下性質的二叉樹稱為二叉查詢樹
LeetCode98 樹·驗證二叉搜尋樹(C++)
題目描述: 給定一個二叉樹,判斷其是否是一個有效的二叉搜尋樹。 假設一個二叉搜尋樹具有如下特徵: 節點的左子樹只包含小於當前節點的數。 節點的右子樹只包含大於當前節點的數。 所有左子樹和右子樹自身必須也是二叉搜尋樹。 示例 1: 輸入: 2 / \