Swift數(shù)據(jù)結(jié)構(gòu)-樹Trees 和二叉樹Binary Trees

聲明:算法和數(shù)據(jù)結(jié)構(gòu)的文章均是作者從github上翻譯過來串结,為方便大家閱讀。如果英語閱讀能力強(qiáng)的朋友跪妥,可以直接到swift算法俱樂部查看所有原文鞋喇,以便快速學(xué)習(xí)。作者同時(shí)也在學(xué)習(xí)中骗奖,歡迎交流

樹Trees

樹可以用來表示不同對(duì)象之間的分層關(guān)系确徙。如圖:


一個(gè)樹可以包含多個(gè)節(jié)點(diǎn)node醒串,一個(gè)節(jié)點(diǎn)通常有包含父節(jié)點(diǎn)和子節(jié)點(diǎn)执桌,不同的是,節(jié)點(diǎn)的父節(jié)點(diǎn)只能有一個(gè)芜赌,而子節(jié)點(diǎn)可以包含多個(gè)仰挣。需要注意的是,沒有父節(jié)點(diǎn)的節(jié)點(diǎn)缠沈,我們稱之為根root膘壶,而沒有子節(jié)點(diǎn),我們稱之為葉leaf洲愤。


節(jié)點(diǎn)

樹永遠(yuǎn)不會(huì)產(chǎn)生閉環(huán)颓芭,如下圖,這樣的結(jié)構(gòu)柬赐,我們稱之為圖像亡问,而不是樹。


圖表

目前我們現(xiàn)在描述的樹肛宋,是最簡(jiǎn)單的樹州藕,對(duì)節(jié)點(diǎn)以及葉的數(shù)量沒有任何限制,對(duì)節(jié)點(diǎn)的順序等也沒有特殊要求酝陈。

以下為樹的swift代碼:

public class TreeNode<T> {
  public var value: T

  public weak var parent: TreeNode?
  public var children = [TreeNode<T>]()

  public init(value: T) {
    self.value = value
  }

  public func addChild(_ node: TreeNode<T>) {
    children.append(node)
    node.parent = self
  }
}

以上為對(duì)樹的節(jié)點(diǎn)進(jìn)行描述床玻,這里的數(shù)據(jù)類型為T,可指向父節(jié)點(diǎn)parent沉帮,同時(shí)又含子節(jié)點(diǎn)數(shù)組children

我們可以對(duì)上述代碼進(jìn)行拓展锈死,添加description函數(shù)方便我們打印出完整的樹結(jié)構(gòu):

extension TreeNode: CustomStringConvertible {
  public var description: String {
    var s = "\(value)"
    if !children.isEmpty {
      s += " {" + children.map { $0.description }.joined(separator: ", ") + "}"
    }
    return s
  }
}

在playground里輸入以下代碼:

let tree = TreeNode<String>(value: "beverages")

let hotNode = TreeNode<String>(value: "hot")
let coldNode = TreeNode<String>(value: "cold")

let teaNode = TreeNode<String>(value: "tea")
let coffeeNode = TreeNode<String>(value: "coffee")
let chocolateNode = TreeNode<String>(value: "cocoa")

let blackTeaNode = TreeNode<String>(value: "black")
let greenTeaNode = TreeNode<String>(value: "green")
let chaiTeaNode = TreeNode<String>(value: "chai")

let sodaNode = TreeNode<String>(value: "soda")
let milkNode = TreeNode<String>(value: "milk")

let gingerAleNode = TreeNode<String>(value: "ginger ale")
let bitterLemonNode = TreeNode<String>(value: "bitter lemon")

tree.addChild(hotNode)
tree.addChild(coldNode)

hotNode.addChild(teaNode)
hotNode.addChild(coffeeNode)
hotNode.addChild(chocolateNode)

coldNode.addChild(sodaNode)
coldNode.addChild(milkNode)

teaNode.addChild(blackTeaNode)
teaNode.addChild(greenTeaNode)
teaNode.addChild(chaiTeaNode)

sodaNode.addChild(gingerAleNode)
sodaNode.addChild(bitterLemonNode)

此時(shí)tree的value值為:
beverages {hot {tea {black, green, chai}, coffee, cocoa}, cold {soda {ginger ale, bitter lemon}, milk}}

其對(duì)應(yīng)的結(jié)構(gòu)為:


示例

這里的根是beverages贫堰,因?yàn)樗鼪]有父節(jié)點(diǎn),而葉是black,green,chai,ginger ale,bitter lemon,coffee,cocoa,milk待牵,因?yàn)樗鼈儧]有子節(jié)點(diǎn)严嗜。

對(duì)于每個(gè)節(jié)點(diǎn)來說,你可以通過以下方法去獲取它們的父節(jié)點(diǎn)
teaNode.parent // "hot" 節(jié)點(diǎn) teaNode.parent!.parent // 根

通常情況下洲敢,我們用以下兩種定義來說明樹:
1.樹高:樹高指樹的根到最底下的葉的距離漫玄,在我們的例子中,樹高為3压彭,因?yàn)閺?code>black到beverages需要跳三次睦优。
2.節(jié)點(diǎn)深度:指任意節(jié)點(diǎn)到根的距離,例如teabeverages的深度為2壮不,而coldbeverages的深度為1.

樹的結(jié)構(gòu)可以多種多樣汗盘。比如,有時(shí)候你只需要每個(gè)節(jié)點(diǎn)只有2個(gè)子節(jié)點(diǎn)询一,而這樣的樹又稱之為二叉樹隐孽。樹的核心意義在于展示數(shù)據(jù)的邏輯層次,并且可以根據(jù)個(gè)人的需要而進(jìn)行調(diào)整健蕊,具有多樣性菱阵。

下面我們會(huì)描述一下如何用TreeNode來決定一個(gè)樹里面是否含有某個(gè)特定值。首先它會(huì)檢查節(jié)點(diǎn)或者根本身的值缩功,如果不匹配晴及,則檢查節(jié)點(diǎn)的子節(jié)點(diǎn),如果不匹配嫡锌,同時(shí)節(jié)點(diǎn)的子節(jié)點(diǎn)仍然是節(jié)點(diǎn)而不是葉虑稼,則繼續(xù)檢索下去,一直到檢索完整個(gè)樹势木。

代碼如下:

extension TreeNode where T: Equatable {
  func search(_ value: T) -> TreeNode? {
    if value == self.value {
      return self
    }
    for child in children {
      if let found = child.search(value) {
        return found
      }
    }
    return nil
  }
}

輸入:

tree.search("cocoa")    // 返回 "cocoa" 節(jié)點(diǎn)
tree.search("chai")     // 返回 "chai" 節(jié)點(diǎn)
tree.search("bubbly")   //返回 nil

二叉樹Binary Trees

二叉樹是節(jié)點(diǎn)只能有0~2個(gè)子節(jié)點(diǎn)的樹蛛倦。二叉樹可以在許多種場(chǎng)景下使用,比如在使用二叉搜索樹的時(shí)候啦桌,我們要求節(jié)點(diǎn)是有順序的溯壶,左邊的數(shù)值要大于右邊,這部分知識(shí)我們會(huì)在下一個(gè)文章中講到震蒋。當(dāng)然茸塞,不是所有的二叉樹都有這樣的要求丰捷。

比如歧寺,我們可以用二叉樹來表示算術(shù)運(yùn)算操作(5 * (a - 10)) + (-4 * (3 / b))

算術(shù)運(yùn)算

代碼

在swift中挚歧,我們可以用以下代碼來表示二叉樹:

public indirect enum BinaryTree<T> {
  case node(BinaryTree<T>, T, BinaryTree<T>)
  case empty
}

實(shí)現(xiàn)上述算數(shù)運(yùn)算:

// leaf nodes
let node5 = BinaryTree.node(.empty, "5", .empty)
let nodeA = BinaryTree.node(.empty, "a", .empty)
let node10 = BinaryTree.node(.empty, "10", .empty)
let node4 = BinaryTree.node(.empty, "4", .empty)
let node3 = BinaryTree.node(.empty, "3", .empty)
let nodeB = BinaryTree.node(.empty, "b", .empty)

// intermediate nodes on the left
let Aminus10 = BinaryTree.node(nodeA, "-", node10)
let timesLeft = BinaryTree.node(node5, "*", Aminus10)

// intermediate nodes on the right
let minus4 = BinaryTree.node(.empty, "-", node4)
let divide3andB = BinaryTree.node(node3, "/", nodeB)
let timesRight = BinaryTree.node(minus4, "*", divide3andB)

// root node
let tree = BinaryTree.node(timesLeft, "+", timesRight)

在創(chuàng)建過程中视事,我們必須從下向上開始創(chuàng)建梢杭,即從葉到根另凌。同時(shí)也可以添加description函數(shù)看幼,方便輸出席覆。

extension BinaryTree: CustomStringConvertible {
  public var description: String {
    switch self {
    case let .node(left, value, right):
      return "value: \(value), left = [\(left.description)], right = [\(right.description)]"
    case .empty:
      return ""
    }
  }
}

我們能得到

value: +, 
    left = [value: *, 
        left = [value: 5, left = [], right = []], 
        right = [value: -, 
            left = [value: a, left = [], right = []], 
            right = [value: 10, left = [], right = []]]], 
    right = [value: *, 
        left = [value: -, 
            left = [], 
            right = [value: 4, left = [], right = []]], 
        right = [value: /, 
            left = [value: 3, left = [], right = []], 
            right = [value: b, left = [], right = []]]]

另一個(gè)有用的拓展則是計(jì)算樹中的節(jié)點(diǎn)數(shù):

  public var count: Int {
    switch self {
    case let .node(left, _, right):
      return left.count + 1 + right.count
    case .empty:
      return 0
    }
  }

在上述示例中,tree.count得到的結(jié)果為12.

在平時(shí)使用中菌仁,我們會(huì)需要遍歷整個(gè)樹結(jié)構(gòu)從而達(dá)到某些目的浩习。比如上述示例的運(yùn)算過程,我們可以在樹中按照一定順序遍歷而讀取出它的方程式济丘。通常情況下谱秽,遍歷的方式有三種:
1.中序遍歷:先看節(jié)點(diǎn)的左子節(jié)點(diǎn),再看節(jié)點(diǎn)本身摹迷,然后它的右子節(jié)點(diǎn)疟赊。
2.先序遍歷:先看節(jié)點(diǎn),再看節(jié)點(diǎn)的左右子節(jié)點(diǎn)峡碉。
3.后序遍歷:先看左右子節(jié)點(diǎn)近哟,再看節(jié)點(diǎn)本身。

這三種遍歷方法實(shí)現(xiàn)代碼如下:

public func traverseInOrder(process: (T) -> Void) {
    if case let .node(left, value, right) = self {
      left.traverseInOrder(process: process)
      process(value)
      right.traverseInOrder(process: process)
    }
  }

  public func traversePreOrder(process: (T) -> Void) {
    if case let .node(left, value, right) = self {
      process(value)
      left.traversePreOrder(process: process)
      right.traversePreOrder(process: process)
    }
  }

  public func traversePostOrder(process: (T) -> Void) {
    if case let .node(left, value, right) = self {
      left.traversePostOrder(process: process)
      right.traversePostOrder(process: process)
      process(value)
    }
  }

如果我們用后序遍歷來遍歷示例的運(yùn)算鲫寄,我們會(huì)得到以下順序的結(jié)果:

5
a
10
-
*
4
-
3
b
/
*
+

最先出現(xiàn)的是左邊的葉吉执,最晚出現(xiàn)的是頂部的根。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末地来,一起剝皮案震驚了整個(gè)濱河市戳玫,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌靠抑,老刑警劉巖量九,帶你破解...
    沈念sama閱讀 211,265評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件适掰,死亡現(xiàn)場(chǎng)離奇詭異颂碧,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)类浪,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,078評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門载城,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人费就,你說我怎么就攤上這事诉瓦。” “怎么了力细?”我有些...
    開封第一講書人閱讀 156,852評(píng)論 0 347
  • 文/不壞的土叔 我叫張陵睬澡,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我眠蚂,道長(zhǎng)煞聪,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,408評(píng)論 1 283
  • 正文 為了忘掉前任逝慧,我火速辦了婚禮昔脯,結(jié)果婚禮上啄糙,老公的妹妹穿的比我還像新娘。我一直安慰自己云稚,他們只是感情好隧饼,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,445評(píng)論 5 384
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著静陈,像睡著了一般燕雁。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上鲸拥,一...
    開封第一講書人閱讀 49,772評(píng)論 1 290
  • 那天贵白,我揣著相機(jī)與錄音,去河邊找鬼崩泡。 笑死禁荒,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的角撞。 我是一名探鬼主播呛伴,決...
    沈念sama閱讀 38,921評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼谒所!你這毒婦竟也來了热康?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,688評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤劣领,失蹤者是張志新(化名)和其女友劉穎姐军,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體尖淘,經(jīng)...
    沈念sama閱讀 44,130評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡奕锌,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,467評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了村生。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片惊暴。...
    茶點(diǎn)故事閱讀 38,617評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖趁桃,靈堂內(nèi)的尸體忽然破棺而出辽话,到底是詐尸還是另有隱情,我是刑警寧澤卫病,帶...
    沈念sama閱讀 34,276評(píng)論 4 329
  • 正文 年R本政府宣布油啤,位于F島的核電站,受9級(jí)特大地震影響蟀苛,放射性物質(zhì)發(fā)生泄漏益咬。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,882評(píng)論 3 312
  • 文/蒙蒙 一屹逛、第九天 我趴在偏房一處隱蔽的房頂上張望础废。 院中可真熱鬧汛骂,春花似錦、人聲如沸评腺。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,740評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)蒿讥。三九已至蝶念,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間芋绸,已是汗流浹背媒殉。 一陣腳步聲響...
    開封第一講書人閱讀 31,967評(píng)論 1 265
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留摔敛,地道東北人廷蓉。 一個(gè)月前我還...
    沈念sama閱讀 46,315評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像马昙,于是被迫代替她去往敵國(guó)和親桃犬。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,486評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容