Go:實現二叉搜索樹(BST)

二叉搜索樹是一種支持快速查找、增加和刪除元素的數據結構。二叉搜索樹中的節點是有序的,因此支持快速查找。該數據結構由P.F.Windley、A.D.Booth、A.J.T.Colin和T.N.Hibbard發明的。

二叉搜索樹的平均空間復雜度是O(n),插入、刪除和搜索操作的時間就復雜的是O(logn)。二叉搜索樹節點包含以下屬性:

  • 一個整型key值
  • 一個整型value值
  • 包含左節點leftNode和右節點rightNode
  • 左節點key小于當前節點,右節點key大于當前節點

節點定義代碼如下:

type TreeNode struct {
    key int
    value int
    leftNode *TreeNode
    rightNode *TreeNode
}

二叉搜索樹結構體定義,其包含一個根節點rootNode是TreeNode類型,以及一個互斥鎖sync.RWMutex類型。二叉搜索樹的遍歷是從根節點出發的,然后是左節點和右節點:

type BinarySearchTree struct {
    rootNode *TreeNode
    lock sync.RWMutex
}

定義好了二叉搜索樹結構體類型,接下來實現其方法。

插入方法:InsertElement

該方法作用是將一個包含key和value的節點插入到二叉搜索樹中。插入之前要獲取鎖,插入完成釋放鎖。

func (tree *BinarySearchTree)InsertElement(key int, value int)  {
    tree.lock.Lock()
    defer tree.lock.Unlock()
    var treeNode *TreeNode
    treeNode = &TreeNode{
        key:       key,
        value:     value,
        leftNode:  nil,
        rightNode: nil,
    }
    if tree.rootNode == nil {
        tree.rootNode = treeNode
    } else {
        insertTreeNode(tree.rootNode, treeNode)
    }
}

先根據key,value創建treeNode節點。然后判斷根節點是否為空,如果是空就將新增的節點賦值給根節點。如果根節點不為空,調用insertTreeNode函數找到合適的位置插入。

func insertTreeNode(rootNode *TreeNode, newTreeNode *TreeNode)  {
    if newTreeNode.key < rootNode.key {
        if rootNode.leftNode ==  nil {
            rootNode.leftNode = newTreeNode
        } else {
            insertTreeNode(rootNode.leftNode, newTreeNode)
        }
    } else {
        if rootNode.rightNode == nil{
            rootNode.rightNode = newTreeNode
        } else{
            insertTreeNode(rootNode.rightNode, newTreeNode)
        }
    }
}

根據二叉搜索樹的特點,每個節點的key比左節點key要大,比右節點的key要小。因此先和左節點的key比較,如果左節點為空,就將新增節點賦值給左節點,否則繼續遞歸找。同理如果新增節點key比左節點大,就和右節點比較,為空就賦值,否則遞歸。

inOrderTraverse方法:順序遍歷二叉搜索樹

該方法按順序遍歷樹中的每個節點。先調用RLock()讀鎖,遍歷完后釋放:

func (tree *BinarySearchTree)InOrderTraverseTree(function func(int))  {
    tree.lock.RLock()
    defer tree.lock.RUnlock()
    inOrderTraverseTree(tree.rootNode, function)
}

func inOrderTraverseTree(treeNode *TreeNode, function func(int))  {
    if treeNode != nil {
        inOrderTraverseTree(treeNode.leftNode, function)
        function(treeNode.value)
        inOrderTraverseTree(treeNode.rightNode, function)
    }
}

為了避免數據競爭,這里創建一個內部調用函數來實現有序遍歷二叉搜索樹。根據二叉搜索樹特點,先遍歷左子樹,然后當前節點最后遍歷右子樹,也稱為中序遍歷。

PreOrderTraverse方法:前序遍歷

先遍歷當前節點,然后是左子樹最后右子樹;

func (tree *BinarySearchTree)PreOrderTraverseTree(function func(int))  {
    tree.lock.RLock()
    defer tree.lock.RUnlock()
    preOrderTraverseTree(tree.rootNode, function)
}

func preOrderTraverseTree(treeNode *TreeNode, function func(int))  {
    if treeNode != nil {
        function(treeNode.value)
        preOrderTraverseTree(treeNode.leftNode, function)
        preOrderTraverseTree(treeNode.rightNode, function)
    }
}

PostOrderTraverse方法:后序遍歷

后序遍歷:先左子樹,然后右子樹最后是當前節點

func (tree *BinarySearchTree)PostOrderTraverseTree(function func(int))  {
    tree.lock.RLock()
    defer tree.lock.RUnlock()
    postOrderTraverseTree(tree.rootNode, function)
}

func postOrderTraverseTree(treeNode *TreeNode, function func(int))  {
    if treeNode != nil {
        postOrderTraverseTree(treeNode.leftNode, function)
        postOrderTraverseTree(treeNode.rightNode, function)
        function(treeNode.value)
    }
}

MinNode方法:最小節點

該方法:用于查找二叉搜索樹中key最小的節點值。

func (tree *BinarySearchTree)MinNode() *int {
    tree.lock.RLock()
    defer tree.lock.RUnlock()
    var treeNode *TreeNode
    treeNode = tree.rootNode
    if treeNode == nil {
        return (*int)(nil)
    }
    for  {
                //最小值位于左子樹
        if treeNode.leftNode == nil {
            return &treeNode.value
        }
        treeNode = treeNode.leftNode
    }
}

根據二叉搜索樹的特點:左子樹值都小于當前節點,所以如果當前節點的左節點為空,說明當前節點的key是最小的

MaxNode方法:查詢最大key節點

該方法和MinNode方法相反,最大子位于當前節點右子樹。如果當前節點的右節點為空,說明當前節點是值最大節點。

func (tree *BinarySearchTree)MaxNode() *int {
    tree.lock.RLock()
    defer tree.lock.RUnlock()
    var treeNode *TreeNode
    treeNode = tree.rootNode
    if treeNode == nil {
        return (*int)(nil)
    }
    for  {
        if treeNode.rightNode == nil {
            return &treeNode.value
        }
        treeNode = treeNode.rightNode
    }
}

SearchNode方法:根據key查詢對應節點是否存在。

func (tree *BinarySearchTree)SearchNode(key int) bool {
    tree.lock.RLock()
    defer tree.lock.RUnlock()
    return searchNode(tree.rootNode, key)
}

func searchNode(treeNode *TreeNode, key int) bool {
    if treeNode == nil {
        return false
    }
    if key < treeNode.key{
        return searchNode(treeNode.leftNode, key)
    }
    if key > treeNode.key{
        return searchNode(treeNode.rightNode, key)
    }
    return true
}

二叉搜索樹:當前節點比左子樹所有節點大,比右子樹所有節點小。因此在查找一個key是否存在時,如果當前節點key比搜索key更小,就繼續在左子樹查找;如果要搜索key大于當前節點的key就在右子樹查找。否則當前節點就是要查找的節點。

removeNode方法:刪除節點

func (tree *BinarySearchTree) RemoveNode(key int) {
    tree.lock.Lock()
    defer tree.lock.Unlock()
    removeNode(tree.rootNode, key)
}

func removeNode(treenode *TreeNode, key int) *TreeNode {
    if treenode == nil {
        return nil
    }
    if key < treenode.key {
        treenode.leftNode = removeNode(treenode.leftNode, key)
        return treenode
    }
    if key > treenode.key {
        treenode.rightNode = removeNode(treenode.rightNode, key)
        return treenode
    }
    //key == treeNode.ley
    if treenode.leftNode == nil && treenode.rightNode == nil {
        treenode = nil
        return nil
    }
    //要刪除的節點,左節點為空
    if treenode.leftNode == nil {
        treenode = treenode.rightNode
        return treenode
    }
    //要刪除的節點,右節點為空
    if treenode.rightNode == nil {
        treenode = treenode.leftNode
        return treenode
    }
    //要刪除的節點,左右節點都不為空
    var leftmostrightNode *TreeNode
    leftmostrightNode = treenode.rightNode
    for {
        if leftmostrightNode != nil && leftmostrightNode.leftNode != nil {
            leftmostrightNode = leftmostrightNode.leftNode
        } else {
            break
        }
    }
       //將右子樹最小節點值,替換待刪除節點的值
    treenode.key, treenode.value = leftmostrightNode.key, leftmostrightNode.value
      //遞歸刪除右子樹最小節點
    treenode.rightNode = removeNode(treenode.rightNode, treenode.key)
    return treenode
}

二叉搜索樹刪除節點比較復雜,遞歸的刪除。分4種情況:
1、待刪除節點,左右節點為空,直接刪除。
2、待刪除節點,左節點為空,直接將右節點賦值給刪除節點覆蓋
3、待刪除節點,右節點為空,直接將左節點賦值給刪除節點覆蓋。
4、待刪除節點,左右節點都不為空。用其右子樹的最小結點代替待刪除結點。代碼中的leftmostrightNode節點為右子樹最小節點。

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,412評論 6 532
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,514評論 3 416
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,373評論 0 374
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,975評論 1 312
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,743評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,199評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,262評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,414評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,951評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,780評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,983評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,527評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,218評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,649評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,889評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,673評論 3 391
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,967評論 2 374

推薦閱讀更多精彩內容