數據結構(四):樹

来源:https://www.cnblogs.com/sheshouxin/archive/2019/05/15/10753967.html
-Advertisement-
Play Games

樹 概念:樹是一些節點的集合,一棵樹由稱作根(root)的節點 r 以及0個或多個非空的(子)樹組成,這些子樹中每一棵的根都被來自根 r 的一條有向的邊(edge)連接。每一棵子樹的根叫做根 r 的兒子(child),r 是每一棵子樹的根的父親(parent)。一棵樹是N個 節點和N-1條邊的集合, ...


概念:樹是一些節點的集合,一棵樹由稱作(root)的節點 r 以及0個或多個非空的(子)樹組成,這些子樹中每一棵的根都被來自根 r 的一條有向的(edge)連接。每一棵子樹的根叫做根 r 的兒子(child),r 是每一棵子樹的根的父親(parent)。一棵樹是N個 節點和N-1條邊的集合,其中一個節點叫做根。每條邊都將某個節點連接到它的父親,而除去根節點外每個節點都有一個父親。

特性

  • 沒有兒子的節點稱為樹葉(leaf)
  • 具有相同父親的節點為兄弟(sibling)
  • 在一棵樹中從根到每個節點恰好存在一條路徑,這條路徑上邊的條數稱為路徑的長
  • 從根到任意節點 n 的唯一路徑的長稱為該節點 n 的深度(depth)
  • 任意節點 n 到它的子樹中一片樹葉的最長路徑的長稱為節點 n 的 
  • 所有樹葉的高都是0,一棵樹的高等於它的根的高,也等於它的最深的樹葉的深度

樹的實現:實現一棵樹通常的思路是定義一個節點類,每個節點除數據外還要有指向該節點的每一個兒子的指針。但是,由於節點的兒子數變化很大並且事先不知道有多少個兒子,所以在節點類中不能直接定義指向兒子的指針。解決這個問題,可以把指向兒子的指針定義為一個鏈表,及節點類除數據外,還有一個用於存放該節點所有兒子的鏈表,即可構造一棵樹。

代碼:

 1 from collections import deque
 2 
 3 class TreeNode(object):
 4     def __init__(self, root, f_child=None):
 5         self.root = root
 6         self.depth = 0
 7         self.subtree = deque()
 8         self.f_child = f_child
 9         if self.f_child:
10             self.subtree.append(self.f_child)
11 
12     def insert(self, node):
13         node.depth = self.depth + 1
14         if self.f_child is None:
15             self.f_child = node
16         self.subtree.append(node)

樹的應用:樹的常見應用之一是UNIX等許多操作系統的目錄結構。系統的根目錄 / 即為樹的根節點,根目錄下的所有文件或子目錄都可以看作根結點的兒子,而子目錄下的文件或目錄又可以看作兒子節點的兒子,以此形成了操作系統的整棵目錄樹。

構造文件目錄並分級列印:

 1 """
 2 /
 3     usr
 4         local
 5         bin
 6     root
 7         download
 8             a.txt
 9             c.txt
10         documents
11     bin
12         config
13 """
14 # 構造上面的文件系統目錄並分級列印
15 Dir = TreeNode('/')
16 usr = TreeNode('usr')
17 root = TreeNode('root')
18 bin = TreeNode('bin')
19 download = TreeNode('download')
20 Dir.insert(usr)
21 Dir.insert(root)
22 Dir.insert(bin)
23 usr.insert(TreeNode('local'))
24 usr.insert(TreeNode('bin'))
25 root.insert(download)
26 root.insert(TreeNode('documents'))
27 bin.insert(TreeNode('config'))
28 download.insert(TreeNode('a.txt'))
29 download.insert(TreeNode('c.txt'))
30 
31 def printName(node):
32     for _ in range(node.depth):
33         print('\t', end='')
34     print(node.root)
35 
36 def ListDir(node):
37     printName(node)
38     for n in node.subtree:
39         ListDir(n)
40 
41 if __name__ == '__main__':
42     ListDir(Dir)

二叉樹

概念:每個節點最多有兩個兒子的樹,稱為二叉樹。

完滿二叉樹:如果二叉樹的所有節點中,除葉節點外都有兩個兒子,這樣的二叉樹稱為完滿二叉樹

完美二叉樹:如果完滿二叉樹的葉節點都在同一層,這樣的二叉樹稱為完美二叉樹

完全二叉樹:如果二叉樹從根節點到倒數第二層滿足完美二叉樹,最後一層的葉節點不完全填充,但靠左對齊,這樣的二叉樹稱為完全二叉樹

樹的遍歷

  • 先序遍歷:在遍歷樹節點的過程中,先處理根結點,再遞歸的處理子樹,這種遍歷方式稱為先序遍歷
  • 後序遍歷:在遍歷樹節點的過程中,先遞歸的處理子子樹,再處理根結點,這種遍歷方式稱為後序遍歷
  • 中序遍歷:對於二叉樹的遍歷,先遞歸的處理左子樹,再處理根結點,最後遞歸的處理右子樹,這種遍歷方式稱為中序遍歷

二叉查找樹:二叉樹的一個重要應用是其在查找中的使用。假設樹中的每個節點的值被用來存儲整數數據,且所有數據間不重覆(重覆的數據較複雜,以後討論)。為了是二叉樹具有便於查找的性質,通常規定每個節點的左子樹上的值都比它小,右子樹上的值都比它大。

實現:構造一棵二叉查找樹,通常要實現以下方法:

  • make_empty:清空二叉樹
  • find:查找某個節點
  • find_min:查找值最小的節點
  • find_max:查找值最大的節點
  • insert:插入節點
  • delete:刪除節點

插入節點的思路分析:二叉查找樹節點的插入是一個沿著樹的根節點遞歸向下查找插入位置的過程,即如果插入節點的值大於根結點,則從根結點的右兒子中查找;如果插入節點的值小於根結點,則從根結點的左兒子中查找。如果要插入的節點在樹中已存在,則什麼都不做,否則將節點插入到遍歷的路徑上的最後一個節點上。

刪除節點的思路分析:對於樹結構來說,刪除節點是最複雜的操作。要刪除某個樹節點,首先要對樹進行遍歷,找到目標節點。如果目標節點為葉節點(沒有孩子),則可直接刪除;如果目標節點有孩子,為了維持二叉查找樹的結構及特性不變,需要用目標節點的右子樹中的最小節點或左子樹中的最大節點來替換目標節點。

在實現這些操作的基礎上,如果想進一步維持一些節點的屬性,如節點的高度和深度,則插入和刪除操作複雜度又會增加,因為這兩個操作會改變一些樹節點高度、深度,每進行一次插入或刪除操作,就要重新計算、修正一次節點屬性,這往往使操作更加耗時。下麵分別分析:

  • 插入操作:可能(因為樹節點的高度由左、右兩個兒子中高度較大的那個決定)會改變根結點到插入節點路徑上所有節點的高度;插入後要計算自身的高度和深度
  • 刪除操作:可能會改變根結點到刪除節點路徑上所有節點的高度;如果刪除的節點不是葉節點,那麼從替換節點往下的整棵樹上的節點(如果有的話)的深度都減1

代碼實現:

  1 class TreeNode(object):
  2     def __init__(self, data, left=None, right=None):
  3         self.data, self.left, self.right = data, left, right
  4         self.depth = 0
  5         self.height = 0
  6 
  7 
  8 class SearchTree(object):
  9     def __init__(self, value):
 10         self.root = TreeNode(value)
 11         self.size = 1
 12         self.min = self.root
 13         self.max = self.root
 14 
 15     def _del_node(self, node):
 16         if node.right is not None:
 17             self._del_node(node.right)
 18         if node.left is not None:
 19             self._del_node(node.left)
 20         del node
 21 
 22     def make_empty(self):
 23         if self.root is not None:
 24             self._del_node(self.root)
 25         self.root = None
 26         self.size = 0
 27         self.min = None
 28         self.max = None
 29 
 30     def _height(self, node):
 31         if node is not None:
 32             return node.height
 33         else:
 34             return 0
 35 
 36     def _maxheight(self, left, right):
 37         if left is None:
 38             return self._height(right)
 39         if right is None:
 40             return self._height(left)
 41         return left.height if left.height > right.height else right.height
 42 
 43     def _add(self, node, root):
 44         if node.data > root.data:
 45             if root.right is None:
 46                 root.right = node
 47                 node.depth = root.depth + 1
 48             else:
 49                 self._add(node, root.right)
 50         if node.data < root.data:
 51             if root.left is None:
 52                 root.left = node
 53                 node.depth = root.depth + 1
 54             else:
 55                 self._add(node, root.left)
 56         root.height = self._maxheight(root.left, root.right) + 1
 57 
 58     def _search(self, value, node):
 59         if node is None:
 60             return None
 61         else:
 62             if value > node.data:
 63                 return self._search(value, node.right)
 64             if value < node.data:
 65                 return self._search(value, node.left)
 66             else:
 67                 return node
 68 
 69     def _search_parent(self, value, node):
 70         if value < node.data and node.left is not None:
 71             if value ==  node.left.data:
 72                 return node
 73             else:
 74                 return self._search_parent(value, node.left)
 75         if value > node.data and node.right is not None:
 76             if value == node.right.data:
 77                 return node
 78             else:
 79                 return self._search_parent(value, node.right)
 80         return None
 81 
 82     def insert(self, value):
 83         if self.size == 0:
 84             self.__init__(value)
 85         else:
 86             node = TreeNode(value)
 87             self._add(node, self.root)
 88             self.size += 1
 89             if value > self.max.data:
 90                 self.max = node
 91             if value < self.min.data:
 92                 self.min = node
 93 
 94     def _find_max(self, node):
 95         if node.right is None:
 96             return node
 97         else:
 98             return self._find_max(node.right)
 99 
100     def _find_min(self, node):
101         if node.left is None:
102             return node
103         else:
104             return self._find_min(node.left)
105 
106     def find(self, value):
107         return self._search(value, self.root)
108 
109     def find_max(self):
110         if self.size > 0:
111             return self._find_max(self.root)
112         return None
113 
114     def find_min(self):
115         if self.size > 0:
116             return self._find_min(self.root)
117         return None
118 
119     def _decrease_depth(self, node):
120         node.depth -= 1
121         if node.right is not None:
122             self._decrease_depth(node.right)
123         if node.left is not None:
124             self._decrease_depth(node.left)
125 
126     def _update_path_height(self, start_node, end_node):
127         if end_node.data > start_node.data:
128             self._update_path_height(start_node.right, end_node)
129             start_node.height = self._maxheight(start_node.left, start_node.right) + 1
130         if end_node.data < start_node.data:
131             self._update_path_height(start_node.left, end_node)
132             start_node.height = self._maxheight(start_node.left, start_node.right) + 1
133 
134     def delete(self, value):
135         # 情況一:節點不存在
136         # 情況二:節點存在且有孩子
137         # 情況三:節點存在,沒孩子
138         # 刪除節點的同時,保證樹上每個節點的高度和深度正確:
139         # 兩步走:1.頂替節點的所有子節點深度減1;
140         # 2.如果頂替節點的父節點的高度發生變化,則更新從根到父節點路徑上所有節點的高度
141         del_node = self._search(value, self.root)
142         if del_node is not None:
143             max_value = self.max.data
144             min_value = self.min.data
145             del_value = del_node.data
146             if del_node.right is not None:
147                 replace_node = self._find_min(del_node.right)
148                 replace_parent = self._search_parent(replace_node.data, del_node)
149                 del_node.data = replace_node.data  # 用替換節點覆蓋被刪除節點的值,相當於刪掉了目標節點
150                 if replace_node.right is not None: # 如果替換節點有孩子,更新所有孩子節點的深度
151                     self._decrease_depth(replace_node.right)
152                     # 將替換節點的孩子嫁接到父節點的樹上,此時替換節點已從原樹中摘除
153                     if replace_parent == del_node:
154                         replace_parent.right = replace_node.right # 替換節點是其父節點的右兒子
155                     else:
156                         replace_parent.left = replace_node.right  # 替換節點是其父節點的左兒子
157                 if replace_parent.height != self._maxheight(replace_parent.left, replace_parent.right) + 1:  # 如果替換節點的父節點高度發生了變化,更新從根到父節點路徑上所有節點的高度
158                     replace_parent.height = self._maxheight(replace_parent.left, replace_parent.right) + 1
159                     self._update_path_height(self.root, replace_parent)
160                 del replace_node
161             elif del_node.left is not None:
162                 replace_node = self._find_max(del_node.left)
163                 replace_parent = self._search_parent(replace_node.data, del_node)
164                 del_node.data = replace_node.data
165                 if replace_node.left is not None:
166                     self._decrease_depth(replace_node.left)
167                     if replace_parent == del_node:
168                         replace_parent.left = replace_node.left
169                     else:
170                         replace_parent.right = replace_node.left
171                 if replace_parent.height != self._maxheight(replace_parent.left, replace_parent.right) + 1:
172                     replace_parent.height = self._maxheight(replace_parent.left, replace_parent.right) + 1
173                     self._update_path_height(self.root, replace_parent)
174                 del replace_node
175             else:
176                 del_node.height = -1  # 目標節點沒有孩子,其父節點的高度必然-1,為了避免再次搜索其父,直接更新其自身路徑上所有節點的高度
177                 self._update_path_height(self.root, del_node)
178                 del del_node
179             self.size -= 1
180             if del_value == max_value:
181                 self.max = self.find_max()
182             if del_value == min_value:
183                 self.min = self.find_min()

 

 

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 1. 內容 1. 匿名函數:一句話函數,比較簡單的函數。 函數名 = lambda 參數 : 返回值 1. 此函數不是沒有名字,他是有名字的,他的名字就是你給其設置的變數,比如func。 func() 函數執行 2. lambda 是定義匿名函數的關鍵字,相當於函數的def. 3. lambda 後 ...
  • 一、unittest模塊官方文檔: https://docs.python.org/3/library/unittest.html 二、一張圖看懂unittest: 三、Unittest主要方法屬性: 1.unittest.TestCase:TestCase類,所有測試用例繼承的基本類: class ...
  • // 導包 import java.util.Scanner; public class Sort { public static void main(String[] args) { // 創建鍵盤輸入對象 Scanner sc = new Scanner(System.out.println); ...
  • 1. 可能是開始也可能是結束 RadonDB是國內知名雲服務提供商青雲開源的一款產品,下麵是一段來自官方的介紹: QingCloud RadonDB 是基於 MySQL 研發的新一代分散式關係型資料庫,可無限水平擴展,支持分散式事務,具備金融級數據強一致性,滿足企業級核心資料庫對大容量、高併發、高可 ...
  • #include <conio.h>int getch(void);// 從控制台得到下一個字元,以ASCII值返回,並不在屏幕顯示該字元int getche(void);// 從控制台得到下一個字元,以ASCII值返回int kbhit(void);// 判斷控制台是否仍有未輸入的字元。若有,則返 ...
  • 在OOP(面向對象)語言中,最重要的一個概念就是:萬事萬物皆對象。 在java中,類也是一個對象,是java.lang.Class的實例對象,官網稱該對象為類的類類型。 Class 類的實例表示正在運行的 Java 應用程式中的類和介面。基本的 Java 類型(boolean、byte、char、s ...
  • 力扣題目解答自我總結(二) 一.迴文數 1.題目描述 判斷一個整數是否是迴文數。迴文數是指正序(從左向右)和倒序(從右向左)讀都是一樣的整數。 示例 1: 示例 2: 示例 3: 2.解答 二.寶石和石頭 1.題目描述 給定字元串 代表石頭中寶石的類型,和字元串 代表你擁有的石頭。 中每個字元代表了 ...
  • 加Q[965546358]獲取資源 第1章 課程導學 第2章 小程式開發入門 從幾個方面介紹小程式開發相關的內容,包括小程式開發者賬號註冊、小程式開發流程、小程式開發規範、小程式常用的API,例如網路請求、本地緩存等API,以及小程式組件等等的知識點。 第3章 深入Django視圖層 分層次介紹Dj ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...