二叉樹實現

小輝哥的掘金發表於2019-04-02

二叉樹

樹 是 N(N>=0)個節點的有限集。當N=0時稱為空樹。 在任意一棵非空樹中:

  • 1、有且僅有一個特定的稱為根節點
  • 2、當N>1時,其餘節點可分為M個互不相交的有限集合,其中每一個集合本身又是一棵樹,並且稱為根的子樹

樹.png

結點擁有的子樹樹稱為結點度

度為0的結點稱為葉結點或者終端結點

度不為0的結點稱為非終端結點或者分支結點

除根結點之外,分支節點也稱為內部結點。樹的度是樹內部各結點的度的最大值

樹1.png

節點間的關係

  • 1、結點的子樹的根稱為該結點的孩子(Child),相應的,該結點稱為孩子的雙親(Parent)
  • 2、同一個雙親的孩子之間稱為兄弟

結點的層次從根開始定義起,根為第一層,根的孩子為第二層,依次類推

樹2.png

為什麼要有樹結構

樹結構是一種天然的組織結構,將資料使用樹儲存的時候,出奇的高效

我們生活中有很多樹結構

樹3.png

樹4.png

樹5.png

二叉樹

  • 1、每個結點最多有兩棵樹
  • 2、左樹和右樹是有順序的,次序不能顛倒
  • 3、即使樹中某個結點只有一棵樹,也要區分它是左子樹還是右子樹
  • 4、左斜樹:所有的結點都只有左子斜樹的二叉樹叫左斜樹
  • 5、右斜樹:所有的結點都只有右子斜樹的二叉樹叫右斜樹
  • 6、滿二叉樹:所有的結點都存在左子樹和右子樹,並且所有葉子都在同一層上
  • 7、完全二叉樹:所有的結點都存在左子樹和右子樹,左右兩邊不一定在同一層次

二叉樹的基本資料結構

class Node: NSObject {
  var E:Int 
  var left:Node?
  var right:Node?
}
複製程式碼

二分搜尋樹(Binary Search Tree)

二叉樹.png

二分搜尋樹也是一種二叉樹 二分搜尋樹的每個節點的值

  • 大於其左子樹的所有節點的值
  • 小於其右子樹的所有節點的值

我們現在就來實現一個簡單的二分搜尋樹吧

節點

//節點
class Node: NSObject {
    var E:Int = 0
    var left:Node?
    var right:Node?
    override init() {
        super.init()
    }
    init(E:Int) {
        self.E = E
        self.left = nil
        self.right = nil
    }
   
}
複製程式碼

我們實現的二分搜尋樹實現以下功能

  • 1、判斷樹是否為空
  • 2、新增元素
  • 3、檢視二分搜尋樹中是否包含某個元素
  • 4、二分搜尋樹的前序遍歷、中序遍歷、後序遍歷
  • 5、尋找二分搜尋樹的最小元素,刪除二分搜尋樹的最小元素
  • 6、尋找二分搜尋樹的最大元素、刪除二分搜尋樹的最大元素
  • 7、刪除任意的節點

我們建立一個BTS類,裡面有兩個成員變數

var size = 0     //樹的大小
var root:Node!   //根節點
複製程式碼

1、判斷樹是否為空

    //判斷是否為空
    func isEmpty() -> Bool{
        return size == 0;
    }
複製程式碼

2、新增元素

    //新增元素
    func add(E:Int) {
        if root == nil {
            size += 1
            root = Node(E: E)
        }else{
            addNode(E: E, node: root)
        }
        
    }


 // 向以node為根的二分搜尋樹中插入元素e,遞迴演算法
    private
    func addNode(E:Int,node:Node) {
        //遞迴用法,先判斷結束語句
        if node.E == E {
            return
        }else if((E < node.E) && (node.left == nil)){
            //遍歷到最後,新增左葉子
            node.left = Node(E: E)
            size += 1
            return
        }else if((E > node.E) && (node.right == nil)){
            //遍歷到最後,新增右葉子
            node.right = Node(E: E)
            size += 1
            return
        }
        
        //遞迴呼叫
        if E < node.E {
            addNode(E: E, node: node.left ?? Node())
        }else{
            addNode(E: E, node: node.right ?? Node())
        }
        
    }

複製程式碼

思路:

  • 1、首先判斷root是否為空,如果root為空的話,第一個新增的元素就是root
  • 2、在root不為空的時候,我們需要迴圈遍歷,E>節點值的時候新增到右子樹,E<節點值的時候新增到左子樹,知道迴圈遍歷到最後一個節點為空的時候,新增上去

3、檢視二分搜尋樹中是否包含某個元素

//檢視二分搜尋樹中是否包含某個元素
func contain(E:Int) -> Bool {
        return containNode(E: E, node: root)
  }

// 看以node為根的二分搜尋樹中是否包含元素e, 遞迴演算法
    private
    func containNode(E:Int,node:Node?) -> Bool {
        if node == nil {
            return false
        }
        
        if node?.E == E{
            return true
        }else if (node?.E)! > E {
            //左
            return containNode(E: E, node: node?.left )
        }else{
            //右
            return containNode(E: E, node: node?.right)
        }
        
    }
複製程式碼

4、前序遍歷、中序遍歷、後序遍歷

前序遍歷:規則是若二叉樹為空,則空操作返回,否則先訪問根節點,然後前續遍歷左子樹,然後再前序遍歷右子樹

前序遍歷.png

//二分搜尋樹的前序遍歷
    func preOrder() {
        preOrder(node: root)
    }
// 前序遍歷以node為根的二分搜尋樹, 遞迴演算法
    private
    func preOrder(node:Node?) {
        //結束條件
        if node == nil {
            return
        }
        print(node?.E ?? "nil")
        preOrder(node: node?.left)
        preOrder(node: node?.right)
    }
複製程式碼

中序遍歷:規則是若樹為空,則空操作返回,否則從根節點開始(注意並不是先訪問根節點),中序遍歷根節點是左子樹,然後是訪問根節點,最後中序遍歷右子樹

中序遍歷.png

// 二分搜尋樹的中序遍歷
    func inOrder() {
        inOrder(node: root)
    }
// 中序遍歷以node為根的二分搜尋樹, 遞迴演算法
    private
    func inOrder(node:Node?) {
        //結束條件
        if node == nil {
            return
        }
        inOrder(node: node?.left)
        print(node?.E ?? "nil")
        inOrder(node: node?.right)
        
    }
複製程式碼

後序遍歷:規則是若樹為空,則空操作返回,否則從左到右先子葉後節點的方式遍歷訪問左右子樹,最後是訪問根節點

後序遍歷.png

// 二分搜尋樹的後序遍歷
func postOrder() {
    postOrder(node: root)
}

// 後序遍歷以node為根的二分搜尋樹, 遞迴演算法
    private
    func postOrder(node:Node?) {
        //結束條件
        if node == nil {
            return
        }
        inOrder(node: node?.left)
        inOrder(node: node?.right)
        print(node?.E ?? "nil")
    }
複製程式碼

5、尋找二分搜尋樹的最小元素,刪除二分搜尋樹的最小元素

尋找二分搜尋樹的最小元素

// 從二分搜尋樹中刪除最小值所在節點, 返回最小值
   func removeMin() -> Int {
       if size == 0 {
           return 10086
       }
       root = removeMin(node: root)
       return minimum()
   }

return minimum(node: root).E
}
//查詢最小資料 遞迴演算法
   private
   func minimum(node:Node?) -> Node{
       if node?.left == nil {
           return node ?? Node()
       }
   
       return minimum(node: node?.left)
   }
複製程式碼

思路:

  • 1、根據二分搜尋樹的定義我們知道,最小值一定在最左側子節點上面,我們一直往最左側子節點遍歷就可以了

刪除二分搜尋樹的最小元素

// 從二分搜尋樹中刪除最小值所在節點, 返回最小值
func removeMin() -> Int {
if size == 0 {
return 10086
}
root = removeMin(node: root)
return minimum()
}

// 刪除掉以node為根的二分搜尋樹中的最小節點
// 返回刪除節點後新的二分搜尋樹的根
private
func removeMin(node:Node?) -> Node? {
if node?.left == nil {
size -= 1
let rightNode = node?.right
node?.right = nil
return rightNode
}
node?.left = removeMin(node: node?.left)
return node
}
複製程式碼

刪除最小元素.png

思考: 在刪除最小元素的時候我們需要考慮兩種情況

  • 1、最小元素沒有子樹了
  • 2、最小元素有右子樹

針對上面這種,我們程式碼可以這樣寫

if node?.left == nil {
size -= 1
let rightNode = node?.right
node?.right = nil
return rightNode
}
複製程式碼

在最小元素有有右子樹的時候,我們需要把這個右子樹佔據我們刪除的那個子樹的位置

6、尋找二分搜尋樹的最大元素、刪除二分搜尋樹的最大元素

尋找二分搜尋樹的最大元素

// 尋找二分搜尋樹的最大元素
func maximum() -> Int {
if size == 0 {
return 10086
}

return maximum(node: root)
}

//查詢最大資料 遞迴演算法
private
func maximum(node:Node?) -> Int {
if node?.right == nil {
return node?.E ?? 10086
}
return maximum(node: node?.right)
}
複製程式碼

刪除二分搜尋樹的最大元素

// 從二分搜尋樹中刪除最大值所在節點, 返回最大值
func removeMax() -> Int {
if size == 0 {
return 10086
}
root = removeMax(node: root)
return maximum()
}

// 刪除掉以node為根的二分搜尋樹中的最大節點
// 返回刪除節點後新的二分搜尋樹的根
private
func removeMax(node:Node?) -> Node? {
if node?.right == nil {
size -= 1
let leftNode = node?.left
node?.left = nil
return leftNode
}
node?.right = removeMax(node: node?.right)
return node
}
複製程式碼

7、刪除任意的節點

// 刪除為E的節點
func remove(E:Int) ->Node{
root = remove(E: E, node: root)
return root
}

// 刪除掉以node為根的二分搜尋樹中值為e的節點, 遞迴演算法
// 返回刪除節點後新的二分搜尋樹的根
private
func remove(E:Int,node:Node?) -> Node? {
if node == nil {
return nil
}
if E < (node?.E)! {
node?.left = remove(E: E, node: node?.left)
return node
}else if E > (node?.E)! {
node?.right = remove(E: E, node: node?.right)
return node
}else{
//找到了
// 待刪除節點左子樹為空的情況
if node?.left == nil{
let rightNode = node?.right
node?.right = nil
size -= 1
return rightNode
}
// 待刪除節點右子樹為空的情況
if node?.right == nil{
let leftNode = node?.left
node?.left = nil
size -= 1
return leftNode
}
// 待刪除節點左右子樹均不為空的情況

// 找到比待刪除節點大的最小節點, 即待刪除節點右子樹的最小節點
// 用這個節點頂替待刪除節點的位置
let successor = minimum(node: node?.right)
successor.right = removeMin(node: node?.right)
successor.left = node?.left
node?.right = nil
node?.left = nil

return successor
}
}
複製程式碼

思考:

  • 1、在找到待刪除節點以後,左節點為空時,這個時候跟刪除最小值類似
// 待刪除節點左子樹為空的情況
if node?.left == nil{
let rightNode = node?.right
node?.right = nil
size -= 1
return rightNode
}
複製程式碼
  • 2、在找到待刪除節點以後,右節點為空時,這個時候跟刪除最大值類似
// 待刪除節點右子樹為空的情況
if node?.right == nil{
let leftNode = node?.left
node?.left = nil
size -= 1
return leftNode
}
複製程式碼
  • 3、待刪除節點左右子樹均不為空的情況 找到比待刪除節點大的最小節點, 即待刪除節點右子樹的最小節點;用這個節點頂替待刪除節點的位置

刪除任意節點1.png

刪除任意節點2.png

相關demo,可以在這裡檢視

相關文章