二叉尋找樹的尋找、插入和刪除 - Java實現

來源:互聯網
上載者:User

標籤:

http://www.cnblogs.com/yangecnu/p/Introduce-Binary-Search-Tree.html

 yangecnu(yangecnu‘s Blog on 部落格園) 
出處:http://www.cnblogs.com/yangecnu/ 

英文原文的出處:
http://algs4.cs.princeton.edu/32bst/

前文介紹了符號表的兩種實現,無序鏈表和有序數組,無序鏈表在插入的時候具有較高的靈活性,而有序數組在尋找時具有較高的效率,本文介紹的二叉尋找樹(Binary Search Tree,BST)這一資料結構綜合了以上兩種資料結構的優點。

二叉尋找樹具有很高的靈活性,對其最佳化可以產生平衡二叉樹,紅/黑樹狀結構等高效的尋找和插入資料結構,後文會一一介紹。

一 定義

二叉尋找樹(Binary Search Tree),也稱有序二叉樹(ordered binary tree),排序二叉樹(sorted binary tree),是指一棵空樹或者具有下列性質的二叉樹:

1. 若任意節點的左子樹不空,則左子樹上所有結點的值均小於它的根結點的值;

2. 若任意節點的右子樹不空,則右子樹上所有結點的值均大於它的根結點的值;

3. 任意節點的左、右子樹也分別為二叉尋找樹。

4. 沒有索引值相等的節點(no duplicate nodes)。

如,這個是普通的二叉樹:

在此基礎上,加上節點之間的大小關係,就是二叉尋找樹:

二 實現

在實現中,我們需要定義一個內部類Node,它包含兩個分別指向左右節點的Node,一個用於排序的Key,以及該節點包含的值Value,還有一個記錄該節點及所有子節點個數的值Number。

public class BinarySearchTreeSymbolTable<TKey, TValue> : SymbolTables<TKey, TValue> where TKey : IComparable<TKey>, IEquatable<TValue>{    private Node root;    private class Node    {        public Node Left { get; set; }        public Node Right { get; set; }        public int Number { get; set; }        public TKey Key { get; set; }        public TValue Value { get; set; }        public Node(TKey key, TValue value, int number)        {            this.Key = key;            this.Value = value;            this.Number = number;        }    }...}
尋找

尋找操作和二分尋找類似,將key和節點的key比較,如果小於,那麼就在Left Node節點尋找,如果大於,則在Right Node節點尋找,如果相等,直接返回Value。

 

該方法實現有迭代和遞迴兩種。

遞迴的方式實現如下:

public override TValue Get(TKey key){    TValue result = default(TValue);    Node node = root;    while (node != null)    {        if (key.CompareTo(node.Key) > 0)        {            node = node.Right;        }        else if (key.CompareTo(node.Key) < 0)        {            node = node.Left;        }        else        {            result = node.Value;            break;        }    }    return result;}

迭代的如下:

public TValue Get(TKey key){    return GetValue(root, key);}private TValue GetValue(Node root, TKey key){    if (root == null) return default(TValue);    int cmp = key.CompareTo(root.Key);    if (cmp > 0) return GetValue(root.Right, key);    else if (cmp < 0) return GetValue(root.Left, key);    else return root.Value;}
插入

插入和尋找類似,首先尋找有沒有和key相同的,如果有,更新;如果沒有找到,那麼建立新的節點。並更新每個節點的Number值,代碼實現如下:

public override void Put(TKey key, TValue value){    root = Put(root, key, value);}private Node Put(Node x, TKey key, TValue value){    //如果節點為空白,則建立新的節點,並返回    //否則比較根據大小判斷是左節點還是右節點,然後繼續尋找左子樹還是右子樹    //同時更新節點的Number的值    if (x == null) return new Node(key, value, 1);    int cmp = key.CompareTo(x.Key);    if (cmp < 0) x.Left = Put(x.Left, key, value);    else if (cmp > 0) x.Right = Put(x.Right, key, value);    else x.Value = value;    x.Number = Size(x.Left) + Size(x.Right) + 1;    return x;}private int Size(Node node){    if (node == null) return 0;    else return node.Number;}

  插入操作圖示如下:

下面是插入動畫效果:

隨機插入形成樹的動畫如下,可以看到,插入的時候樹還是能夠保持近似平衡狀態:

最大最小值

如可以看出,二叉尋找樹的最大最小值是有規律的:

可以看出,二叉尋找樹中,最左和最右節點即為最小值和最大值,所以我們只需迭代調用即可。

public override TKey GetMax(){    TKey maxItem = default(TKey);    Node s = root;    while (s.Right != null)    {        s = s.Right;    }    maxItem = s.Key;    return maxItem;}public override TKey GetMin(){    TKey minItem = default(TKey);    Node s = root;    while (s.Left != null)    {        s = s.Left;    }    minItem = s.Key;    return minItem;}

以下是遞迴的版本:

public TKey GetMaxRecursive(){    return GetMaxRecursive(root);}private TKey GetMaxRecursive(Node root){    if (root.Right == null) return root.Key;    return GetMaxRecursive(root.Right);}public TKey GetMinRecursive(){    return GetMinRecursive(root);}private TKey GetMinRecursive(Node root){    if (root.Left == null) return root.Key;    return GetMinRecursive(root.Left);}
Floor和Ceiling

尋找Floor(key)的值就是所有<=key的最大值,相反尋找Ceiling的值就是所有>=key的最小值,是Floor函數的尋找:

以尋找Floor為例,我們首先將key和root元素比較,如果key比root的key小,則floor值一定在左子樹上;如果比root的key大,則有可能在右子樹上,若且唯若其右子樹有一個節點的key值要小於等於該key;如果和root的key相等,則floor值就是key。根據以上分析,Floor方法的代碼如下,Ceiling方法的代碼類似,只需要把符號換一下即可:

public TKey Floor(TKey key){    Node x = Floor(root, key);    if (x != null) return x.Key;    else return default(TKey);}private Node Floor(Node x, TKey key){    if (x == null) return null;    int cmp = key.CompareTo(x.Key);    if (cmp == 0) return x;    if (cmp < 0) return Floor(x.Left, key);    else    {        Node right = Floor(x.Right, key);        if (right == null) return x;        else return right;    }}
刪除

刪除元素操作在二叉樹的操作中應該是比較複雜的。首先來看下比較簡單的刪除最大最小值得方法。

以刪除最小值為例,我們首先找到最小值,及最左邊左子樹為空白的節點,然後返回其右子樹作為新的左子樹。操作如下:

代碼實現如下:

public void DelMin(){    root = DelMin(root);}private Node DelMin(Node root){    if (root.Left == null) return root.Right;    root.Left = DelMin(root.Left);    root.Number = Size(root.Left) + Size(root.Right) + 1;    return root;}

刪除最大值也是類似。

現在來分析一般情況,假定我們要刪除指定key的某一個節點。這個問題的痛點在於:刪除最大最小值的操作,刪除的節點只有1個子節點或者沒有子節點,這樣比較簡單。但是如果刪除任意節點,就有可能出現刪除的節點有0個,1 個,2個子節點的情況,現在來逐一分析。

當刪除的節點沒有子節點時,直接將該父節點指向該節點的link設定為null。

 

當刪除的節點只有1個子節點時,將該自己點替換為要刪除的節點即可。

當刪除的節點有2個子節點時,問題就變複雜了。

假設我們刪除的節點t具有兩個子節點。因為t具有右子節點,所以我們需要找到其右子節點中的最小節點,替換t節點的位置。這裡有四個步驟:

1. 儲存帶刪除的節點到臨時變數t

2. 將t的右節點的最小節點min(t.right)儲存到臨時節點x

3. 將x的右節點設定為deleteMin(t.right),該右節點是刪除後,所有比x.key最大的節點。

4. 將x的做節點設定為t的左節點。

整個過程如:

對應代碼如下:

public void Delete(TKey key){    root =Delete(root, key);        }private Node Delete(Node x, TKey key){    int cmp = key.CompareTo(x.Key);    if (cmp > 0) x.Right = Delete(x.Right, key);    else if (cmp < 0) x.Left = Delete(x.Left, key);    else    {        if (x.Left == null) return x.Right;        else if (x.Right == null) return x.Left;        else        {            Node t = x;            x = GetMinNode(t.Right);            x.Right = DelMin(t.Right);            x.Left = t.Left;        }    }    x.Number = Size(x.Left) + Size(x.Right) + 1;    return x;}private Node GetMinNode(Node x){    if (x.Left == null) return x;    else return GetMinNode(x.Left); }

以上二叉尋找樹的刪除節點的演算法不是完美的,因為隨著刪除的進行,二叉樹會變得不太平衡,下面是動畫示範。

三 分析

二叉尋找樹的已耗用時間和樹的形狀有關,樹的形狀又和插入元素的順序有關。在最好的情況下,節點完全平衡,從根節點到最底層葉子節點只有lgN個節點。在最差的情況下,根節點到最底層葉子節點會有N各節點。在一般情況下,樹的形狀和最好的情況接近。

在分析二叉尋找樹的時候,我們通常會假設插入的元素順序是隨機的。對BST的分析類似與快速排序中的尋找:

BST中位於頂部的元素就是快速排序中的第一個劃分的元素,該元素左邊的元素全部小於該元素,右邊的元素均大於該元素。

對於N個不同元素,隨機插入的二叉尋找樹來說,其平均尋找/插入的時間複雜度大約為2lnN,這個和快速排序的分析一樣,具體的證明方法不再贅述,參照快速排序。

 

四 總結

有了前篇文章 二分尋找的分析,對二叉尋找樹的理解應該比較容易。下面是二叉尋找樹的時間複雜度:

它和二分尋找一樣,插入和尋找的時間複雜度均為lgN,但是在最壞的情況下仍然會有N的時間複雜度。原因在於插入和刪除元素的時候,樹沒有保持平衡。我們追求的是在最壞的情況下仍然有較好的時間複雜度,這就是後面要講的平衡尋找樹的內容了。下文首先講解平衡尋找樹的最簡單的一種:2-3尋找樹。

希望本文對您瞭解二叉尋找樹有所協助。

二叉尋找樹的尋找、插入和刪除 - Java實現

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.