“樹”據結構一:二叉搜尋樹(Binary Search Tree, BST)
前言
想寫兩篇關於AVL樹和B樹的較為詳細的介紹,發現需要先介紹二叉搜尋樹作為先導。
定義
二叉搜尋樹(Binary Search Thee, BST),也被稱為二叉排序樹(Binary Sort Tree, BST),無論哪種定義,都能表明其特點:有序,能夠用於快速搜尋。個人更傾向於稱其為二叉搜尋樹。
二叉搜尋樹,指的是這樣的一顆二叉樹:一個節點的左子節點小於(小於等於,如果允許存在相等元素的話)它,右子節點大於它。同樣地道理適用於其左子樹和右子樹。
來源
之所以有二叉搜尋樹,是為了搜尋方便。對於n個節點,一般情況下僅需要
演算法
(由於主要想寫的是AVL樹和B樹,二叉搜尋樹的演算法這裡不詳細介紹了,哪天有陽光了再好好寫寫。)
資料結構
class Node<T extends Comparable<T>> {
protected T id = null;
protected Node<T> parent = null ;
protected Node<T> lesser = null;
protected Node<T> greater = null;
/**
* Node constructor.
*
* @param parent
* Parent link in tree. parent can be NULL.
* @param id
* T representing the node in the tree.
*/
protected Node(Node<T> parent, T id) {
this.parent = parent;
this.id = id;
}
/**
* {@inheritDoc}
*/
@Override
public String toString() {
return "id=" + id + " parent=" + ((parent != null) ? parent.id : "NULL") + " lesser="
+ ((lesser != null) ? lesser.id : "NULL") + " greater=" + ((greater != null) ? greater.id : "NULL");
}
}
二叉搜尋樹的節點比較簡單,最基礎的是記錄其節點的值、左子節點、右子節點。當然,在實現的時候往往還保留父節點,這會給一些處理帶來很大的便利。
查
/**
* Locate T in the tree.
*
* @param value
* T to locate in the tree.
* @return Node<T> representing first reference of value in tree or NULL if
* not found.
*/
protected Node<T> getNode(T value) {
Node<T> node = root;
while (node != null && node.id != null) {
if (value.compareTo(node.id) < 0) {
node = node.lesser;
} else if (value.compareTo(node.id) > 0) {
node = node.greater;
} else if (value.compareTo(node.id) == 0) {
return node;
}
}
return null;
}
二叉搜尋樹最重要的就是查。可以採用遞迴式查詢和非遞迴式查詢(一般用佇列實現)。這裡使用的是非遞迴方式。
遍歷
二叉搜尋樹的便利有三種方式:
- 前序遍歷:per-order,即根在前,然後左,最後右;
- 中序遍歷:in-order,即左在前,根在中,最後有。之所以稱其為in-order
(按序),是因為對於一個二叉搜尋樹來說,中序遍歷就是按照其節點值的大小順序遍歷;
- 後序遍歷:post-order,即左在前,然後右,最後根。
所以遍歷的命名方式其實就是看中間節點到底是“前”、“中”還是“後”被訪問。
增
/**
* Add value to the tree and return the Node that was added. Tree can
* contain multiple equal values.
*
* @param value
* T to add to the tree.
* @return Node<T> which was added to the tree.
*/
protected Node<T> addValue(T value) {
Node<T> newNode = this.creator.createNewNode(null, value);
// If root is null, assign
if (root == null) {
root = newNode;
size++;
return newNode;
}
Node<T> node = root;
while (node != null) {
if (newNode.id.compareTo(node.id) <= 0) {
// Less than or equal to goes left
if (node.lesser == null) {
// New left node
node.lesser = newNode;
newNode.parent = node;
size++;
return newNode;
}
node = node.lesser;
} else {
// Greater than goes right
if (node.greater == null) {
// New right node
node.greater = newNode;
newNode.parent = node;
size++;
return newNode;
}
node = node.greater;
}
}
return newNode;
}
增加一個節點也比較簡單,關鍵是跟節點進行比較,找到應該新增的位置(找到null為止),然後歸位(調整一下幾個指標的指向)即可。
刪
刪節點值得好好說一說。當刪除一個節點的時候,往往需要對樹結構進行調整,根據維基百科的介紹,刪節點主要分為以下幾種情況:
1. 刪除一個沒有孩子的節點:直接刪了就行了(孤家寡人,揮一揮衣袖,不帶走一片雲彩);
2. 刪除一個只有一個孩子的節點:刪了之後用孩子取代其位置就行了(有點兒像繼承家產);
3. 刪除一個有兩個孩子的節點:比較麻煩。
對於第三種情況,如果稱被刪除的節點為D,可以選擇其中序遍歷的前驅E(左子樹的最右節點),或者中序遍歷的後繼E(右子樹的最左節點)來取代其位置。然後讓E的孩子來取代E的位置(E一定只有一個孩子,要不然E就不能被稱之為子樹的最右或最左節點)。
同時要注意的是,不能一直只選擇前驅/後繼,這樣的話相當於是讓一棵二叉搜尋樹往連結串列的方向發展。所以可以採用輪流的方式。
刪除一個節點:首先找到其替代節點,然後刪除該節點,並用替代節點替代該節點。
/**
* Remove the node using a replacement
*
* @param nodeToRemoved
* Node<T> to remove from the tree.
* @return nodeRemove
* Node<T> removed from the tree, it can be different
* then the parameter in some cases.
*/
protected Node<T> removeNode(Node<T> nodeToRemoved) {
if (nodeToRemoved != null) {
Node<T> replacementNode = this.getReplacementNode(nodeToRemoved);
replaceNodeWithNode(nodeToRemoved, replacementNode);
}
return nodeToRemoved;
}
尋找替代節點:
/**
* Get the proper replacement node according to the binary search tree
* algorithm from the tree.
*
* @param nodeToRemoved
* Node<T> to find a replacement for.
* @return Node<T> which can be used to replace nodeToRemoved. nodeToRemoved
* should NOT be NULL.
*/
protected Node<T> getReplacementNode(Node<T> nodeToRemoved) {
Node<T> replacement = null;
// I. the node has two children
if (nodeToRemoved.greater != null && nodeToRemoved.lesser != null) {
// Two children.
// Add some randomness to deletions, so we don't always use the
// greatest/least on deletion
// always choose the successor or predecessor will lead to an unbalanced tree
if (modifications % 2 != 0) {
replacement = this.getGreatest(nodeToRemoved.lesser);
if (replacement == null)
replacement = nodeToRemoved.lesser;
} else {
replacement = this.getLeast(nodeToRemoved.greater);
if (replacement == null)
replacement = nodeToRemoved.greater;
}
modifications++;
// II. the node has only one child
} else if (nodeToRemoved.lesser != null && nodeToRemoved.greater == null) {
// Using the less subtree
replacement = nodeToRemoved.lesser;
} else if (nodeToRemoved.greater != null && nodeToRemoved.lesser == null) {
// Using the greater subtree (there is no lesser subtree, no refactoring)
replacement = nodeToRemoved.greater;
}
// III. the node has no children
return replacement;
}
尋找前驅:
/**
* Get greatest node in sub-tree rooted at startingNode. The search does not
* include startingNode in it's results.
*
* @param startingNode
* Root of tree to search.
* @return Node<T> which represents the greatest node in the startingNode
* sub-tree or NULL if startingNode has no greater children.
*/
protected Node<T> getGreatest(Node<T> startingNode) {
if (startingNode == null)
return null;
Node<T> greater = startingNode.greater;
while (greater != null && greater.id != null) {
Node<T> node = greater.greater;
if (node != null && node.id != null)
greater = node;
else
break;
}
return greater;
}
尋找後繼:
/**
* Get least node in sub-tree rooted at startingNode. The search does not
* include startingNode in it's results.
*
* @param startingNode
* Root of tree to search.
* @return Node<T> which represents the least node in the startingNode
* sub-tree or NULL if startingNode has no lesser children.
*/
protected Node<T> getLeast(Node<T> startingNode) {
if (startingNode == null)
return null;
Node<T> lesser = startingNode.lesser;
while (lesser != null && lesser.id != null) {
Node<T> node = lesser.lesser;
if (node != null && node.id != null)
lesser = node;
else
break;
}
return lesser;
}
刪除節點,並用替代節點取代其位置:
/**
* Replace a with b in the tree.
*
* @param a
* Node<T> to remove replace in the tree. a should
* NOT be NULL.
* @param b
* Node<T> to replace a in the tree. b
* can be NULL.
*/
protected void replaceNodeWithNode(Node<T> a, Node<T> b) {
if (b != null) {
// Save for later
Node<T> bLesser = b.lesser;
Node<T> bGreater = b.greater;
// I.
// b regards a's children as his children
// a's children regard b as their parent
// (but a still regards his children as his children)
// Replace b's branches with a's branches
Node<T> aLesser = a.lesser;
if (aLesser != null && aLesser != b) {
b.lesser = aLesser;
aLesser.parent = b;
}
Node<T> aGreater = a.greater;
if (aGreater != null && aGreater != b) {
b.greater = aGreater;
aGreater.parent = b;
}
// II.
// b's children and b's parent know about each other
// (and b has no longer relation with them )
// Remove link from b's parent to b
Node<T> bParent = b.parent;
if (bParent != null && bParent != a) {
Node<T> bParentLesser = bParent.lesser;
Node<T> bParentGreater = bParent.greater;
// b is left child, then it at most has a right child(or its left child'll be the replacementNode)
if (bParentLesser != null && bParentLesser == b) {
bParent.lesser = bGreater;
if (bGreater != null)
bGreater.parent = bParent;
// b is right child, then it at most has a left child
} else if (bParentGreater != null && bParentGreater == b) {
bParent.greater = bLesser;
if (bLesser != null)
bLesser.parent = bParent;
}
}
}
// III.
// b regards a's parent as his parent
// a's parent regards b as his child(but the parent should know about b is it's lesser or greater child)
// (but a still regards his parent as his parent)
// Update the link in the tree from a to b
Node<T> parent = a.parent;
if (parent == null) {
// Replacing the root node
root = b;
if (root != null)
root.parent = null;
} else if (parent.lesser != null && (parent.lesser.id.compareTo(a.id) == 0)) {
parent.lesser = b;
if (b != null)
b.parent = parent;
} else if (parent.greater != null && (parent.greater.id.compareTo(a.id) == 0)) {
parent.greater = b;
if (b != null)
b.parent = parent;
}
size--;
// FINALLY.
// node a should be released
a = null;
}
總結
以上就是二叉搜尋樹的大致用法,但是實際情況中,二叉搜尋樹用的並非很廣泛,因為很難保證資料的來源是絕對無序的。所以構造出來的樹自然就是非平衡的。因此AVL的使用才是大勢所趨。
參閱
相關推薦
“樹”據結構一:二叉搜尋樹(Binary Search Tree, BST)
前言 定義 來源 演算法 資料結構 查 遍歷 增 刪 總結 參閱 前言 想寫兩篇關於AVL樹和B樹的較為詳細的介紹,發現需要先介紹二叉搜尋樹作為先導。 定義 二叉搜尋樹(Binary Search Thee, BST),也被稱為二
數據結構35:二叉樹前序遍歷、中序遍歷和後序遍歷
tdi 代碼 nod 完成 循環 同時 reat pan 設置 遞歸算法底層的實現使用的是棧存儲結構,所以可以直接使用棧寫出相應的非遞歸算法。 先序遍歷的非遞歸算法 從樹的根結點出發,遍歷左孩子的同時,先將每個結點的右孩子壓棧。當遇到結點沒有左孩子的時候,取棧頂的右
資料結構實驗之查詢一:二叉排序樹 (SDUT 3373)
二叉排序樹(Binary Sort Tree),又稱二叉查詢樹(Binary Search Tree),也稱二叉搜尋樹。 #include <stdio.h> #include <string.h> #include <stdlib.h> struct nod
SDUT3374資料結構實驗之查詢一:二叉排序樹
判斷是否為同一棵二叉排序樹 解決這個問題需要兩步: 1.建立二叉排序樹 2.判斷兩棵樹是否相同 詳情看程式碼和註釋,懶人程式碼 #include <iostream> #include <cstring> using namespace std; type
演算法題(三十一):二叉搜尋樹(BST)的後序遍歷序列
題目描述 輸入一個整數陣列,判斷該陣列是不是某二叉搜尋樹的後序遍歷的結果。如果是則輸出Yes,否則輸出No。假設輸入的陣列的任意兩個數字都互不相同。 分析 後序遍歷序列中,最右邊數字也就是根結點,會把數集分為左右兩部分,左邊數集都小於root,右邊數集都大於root。左
資料結構:二叉搜尋樹
實現的操作 1.0 插入:找待插入的節點的位置(data<_root->_data左子樹,data>_root->_data 右子樹)data為待插入節點的值。 2.0 尋找:找節點的位置(data<_root->_data左子樹,
資料結構——4.1 二叉搜尋樹
一、二叉搜尋樹 一棵二叉樹,可以為空;如果不為空,滿足以下性質: 1)非空左子樹的所有鍵值小於其根結點的鍵值 2)非空右子樹的所有鍵值大於其根結點的鍵值 3)左右子樹都是二叉搜尋樹 二、二叉搜尋樹操作的特別函式 1、查詢 1)Find ① 查詢從根結點開始,如果樹
資料結構作業:二叉排序樹及其相關操作
寫了一個簡單的。 因為自己對泛型瞭解的還是不夠到位,所以只能寫個demo版的。 這課樹沒辦法維持平衡,希望以後學一下紅黑樹,替罪羊樹etc. /* * 簡單的二叉查詢樹 * 沒有自帶旋轉平衡 * 寫完這個我學一下 * avl樹還有紅黑樹 */ public c
Leetcode 938:二叉搜尋樹的範圍和(最詳細的解法!!!)
給定二叉搜尋樹的根結點 root,返回 L 和 R(含)之間的所有結點的值的和。 二叉搜尋樹保證具有唯一的值。 示例 1: 輸入:root = [10,5,15,3,7,null,18], L = 7, R = 15 輸出:32 示例 2: 輸入:root = [1
連結串列六:二叉搜尋樹與雙向連結串列
/** * 題目:二叉搜尋樹與雙向連結串列 * 描述:輸入一棵二叉搜尋樹,將該二叉搜尋樹轉換成一個排序的雙向連結串列。要求不能建立任何新的結點,只能調整樹中結點指標的指向 * 方案:在中序遍歷中新增前驅結點 * */ public class Six {
樹五:二叉搜尋樹的後序遍歷序列
/** * 題目:二叉搜尋樹的後序遍歷序列 * 描述:輸入一個整數陣列,判斷該陣列是不是某二叉搜尋樹的後序遍歷的結果。如果是則輸出Yes,否則輸出No。假設輸入的陣列的任意兩個數字都互不相同 * 二叉搜尋樹,又叫二叉排序樹,它或者是一顆空樹,或者具有以下性
演算法題(十九):二叉搜尋樹轉雙鏈表
題目描述 輸入一棵二叉搜尋樹,將該二叉搜尋樹轉換成一個排序的雙向連結串列。要求不能建立任何新的結點,只能調整樹中結點指標的指向。 輸入輸出示例: {10,6,4,8,14,12,16} from left to right:4,6,8,10,12,14,16 from right
PTA 資料結構——是否完全二叉搜尋樹
7-2 是否完全二叉搜尋樹 (30 分) 將一系列給定數字順序插入一個初始為空的二叉搜尋樹(定義為左子樹鍵值大,右子樹鍵值小),你需要判斷最後的樹是否一棵完全二叉樹,並且給出其層序遍歷的結果。 輸入格式: 輸入第一行給出一個不超過20的正整數N;第
輸入一棵二叉搜尋樹,將該二叉搜尋樹轉換成一個排序的雙向連結串列(劍指offer)
題目 輸入一棵二叉搜尋樹,將該二叉搜尋樹轉換成一個排序的雙向連結串列。要求不能建立任何新的結點,只能調整樹中結點指標的指向。 分析: 在二叉搜尋樹中,每個結點都有兩個分別指向其左、右子樹的指標,左子樹結點的值總是小於父結點的值,右子樹結點的值總是大於父結點的值。在雙向連結串列中,每個結點
面試題54:二叉搜尋樹的第k個結點
題意:給定一棵二叉搜尋樹,請找出其中的第k小的結點。例如, (5,3,7,2,4,6,8) 中,按結點數值大小順序第三小結點的值為4。 思路:中序遍歷就是從小到大排序,直接中序遍歷一下就好了。
劍指offer:二叉搜尋樹的後序遍歷序列
# -*- coding:utf-8 -*- class Solution: def VerifySquenceOfBST(self, sequence): # write code here ''' if sequen
劍指offer{面試題24:二叉搜尋樹的後序遍歷序列}
這個題似曾相識,之前劍指offer有一道題是判斷該子樹是否是樹的一部分,有異曲同工之妙,看到這種題,上倆就應該想遞迴。 public class Solution { public boolean VerifySquenceOfBST(int [] sequence) {
資料結構與演算法 -- 二叉搜尋樹(java實現)
package com.huang.test.datastructure; import java.util.*; /** * 二叉搜尋樹 */ abstract class BstData<T> { BstData<T> left;
劍指 Offer - 26:二叉搜尋樹與雙向連結串列
題目描述 輸入一棵二叉搜尋樹,將該二叉搜尋樹轉換成一個排序的雙向連結串列。要求不能建立任何新的結點,只能調整樹中結點指標的指向 題目連結:https://www.nowcoder.com/practice/947f6eb80d944a84850b0538bf0ec3a5
劍指 Offer - 23:二叉搜尋樹的後序遍歷序列
題目描述 輸入一個整數陣列,判斷該陣列是不是某二叉搜尋樹的後序遍歷的結果。如果是則輸出Yes,否則輸出No。假設輸入的陣列的任意兩個數字都互不相同 題目連結:https://www.nowcoder.com/practice/a861533d45854474ac791d