簡介
樹是類似於連結串列的資料結構,和連結串列的線性結構不同的是,樹是具有層次結構的非線性的資料結構。
樹是由很多個節點組成的,每個節點可以指向很多個節點。
如果一個樹中的每個節點都只有0,1,2個子節點的話,這顆樹就被稱為二叉樹,如果我們對二叉樹進行一定的排序。
比如,對於二叉樹中的每個節點,如果左子樹節點的元素都小於根節點,而右子樹的節點的元素都大於根節點,那麼這樣的樹被叫做二叉搜尋樹(Binary Search Tree)簡稱BST。
今天我們來探討一下BST的性質和對BST的基本操作。
BST的基本性質
剛剛我們已經講過BST的基本特徵了,現在我們再來總結一下:
- BST中任意節點的左子樹一定要比該節點的值要小
- BST中任意節點的右子樹一定要比該節點的值要大
- BST中任意節點的左右子樹一定要是一個BST。
看一張圖直觀的感受一下BST:
BST的構建
怎麼用程式碼來構建一個BST呢?
首先,BST是由一個一個的節點Node組成的,Node節點除了儲存節點的資料之外,還需要指向左右兩個子節點,這樣我們的BST完全可以由Node連線而成。
另外我們還需要一個root節點來表示BST的根節點。
相應的程式碼如下:
public class BinarySearchTree {
//根節點
Node root;
class Node {
int data;
Node left;
Node right;
public Node(int data) {
this.data = data;
left = right = null;
}
}
}
BST的搜尋
先看下BST的搜尋,如果是上面的BST,我們想搜尋32這個節點應該是什麼樣的步驟呢?
先上圖:
搜尋的基本步驟是:
- 從根節點41出發,比較根節點和搜尋值的大小
- 如果搜尋值小於節點值,那麼遞迴搜尋左側樹
- 如果搜尋值大於節點值,那麼遞迴搜尋右側樹
- 如果節點匹配,則直接返回即可。
相應的java程式碼如下:
//搜尋方法,預設從根節點搜尋
public Node search(int data){
return search(root,data);
}
//遞迴搜尋節點
private Node search(Node node, int data)
{
// 如果節點匹配,則返回節點
if (node==null || node.data==data)
return node;
// 節點資料大於要搜尋的資料,則繼續搜尋左邊節點
if (node.data > data)
return search(node.left, data);
// 如果節點資料小於要搜素的資料,則繼續搜尋右邊節點
return search(node.right, data);
}
BST的插入
搜尋講完了,我們再講BST的插入。
先看一個動畫:
上的例子中,我們向BST中插入兩個節點30和55。
插入的邏輯是這樣的:
- 從根節點出發,比較節點資料和要插入的資料
- 如果要插入的資料小於節點資料,則遞迴左子樹插入
- 如果要插入的資料大於節點資料,則遞迴右子樹插入
- 如果根節點為空,則插入當前資料作為根節點
相應的java程式碼如下:
// 插入新節點,從根節點開始插入
public void insert(int data) {
root = insert(root, data);
}
//遞迴插入新節點
private Node insert(Node node, int data) {
//如果節點為空,則建立新的節點
if (node == null) {
node = new Node(data);
return node;
}
//節點不為空,則進行比較,從而遞迴進行左側插入或者右側插入
if (data < node.data)
node.left = insert(node.left, data);
else if (data > node.data)
node.right = insert(node.right, data);
//返回插入後的節點
return node;
}
BST的刪除
BST的刪除要比插入複雜一點,因為插入總是插入到葉子節點,而刪除可能刪除的是非葉子節點。
我們先看一個刪除葉子節點的例子:
上面的例子中,我們刪除了30和55這兩個節點。
可以看到,刪除葉子節點是相對簡單的,找到之後刪除即可。
我們再來看一個比較複雜的例子,比如我們要刪除65這個節點:
可以看到需要找到65這個節點的右子樹中最小的那個,替換掉65這個節點即可(當然也可以找到左子樹中最大的那個)。
所以刪除邏輯是這樣的:
- 從根節點開始,比較要刪除節點和根節點的大小
- 如果要刪除節點比根節點小,則遞迴刪除左子樹
- 如果要刪除節點比根節點大,則遞迴刪除右子樹
- 如果節點匹配,又有兩種情況
- 如果是單邊節點,直接返回節點的另外一邊
- 如果是雙邊節點,則先找出右邊最小的值,作為根節點,然後將刪除最小值過後的右邊的節點,作為根節點的右節點
看下程式碼的實現:
// 刪除新節點,從根節點開始刪除
void delete(int data)
{
root = delete(root, data);
}
//遞迴刪除節點
Node delete(Node node, int data)
{
//如果節點為空,直接返回
if (node == null) return node;
//遍歷左右兩邊的節點
if (data < node.data)
node.left = delete(node.left, data);
else if (data > root.data)
node.right = delete(node.right, data);
//如果節點匹配
else
{
//如果是單邊節點,直接返回其下面的節點
if (node.left == null)
return node.right;
else if (node.right == null)
return node.left;
//如果是雙邊節點,則先找出右邊最小的值,作為根節點,然後將刪除最小值過後的右邊的節點,作為根節點的右節點
node.data = minValue(node.right);
// 從右邊刪除最小的節點
node.right = delete(node.right, node.data);
}
return node;
}
這裡我們使用遞迴來實現的刪除雙邊節點,大家可以考慮一下有沒有其他的方式來刪除呢?
本文的程式碼地址:
本文收錄於 www.flydean.com
最通俗的解讀,最深刻的乾貨,最簡潔的教程,眾多你不知道的小技巧等你來發現!
歡迎關注我的公眾號:「程式那些事」,懂技術,更懂你!