平衡二叉樹

来源:https://www.cnblogs.com/UnknowCodeMaker/archive/2018/12/16/10009804.html
-Advertisement-
Play Games

1.平衡二叉樹定義:如果一棵樹不為空,其任意節點的左子樹高度與右子樹高度之差不超過1,那麼滿足這樣條件的樹就是平衡二叉樹 2.平衡二叉樹節點定義: 這裡平衡二叉樹節點定義使用了模版,這樣就可以任意自定義節點,using BINNODE = tagNode<T>則是為節點類型起一個別名, using ...


1.平衡二叉樹定義:如果一棵樹不為空,其任意節點的左子樹高度與右子樹高度之差不超過1,那麼滿足這樣條件的樹就是平衡二叉樹

2.平衡二叉樹節點定義:

 1 template<typename T>
 2 struct tagNode
 3 {
 4   T                   m_element;     //樹節點所存儲的元素節點
 5   struct tagNode<T> * m_pLeftNode;   //指向左孩子節點
 6   struct tagNode<T> * m_pRightNode;  //指向右孩子節點
 7   struct tagNode<T> * m_pParentNode; //指向父節點
 8   int                 m_nHeightOfNode;//節點高度
 9   tagNode(T value,
10           tagNode<T> * pLeftNode = nullptr,
11           tagNode<T> *pRightNode = nullptr,
12           tagNode<T> *pParentNode = nullptr);
13 };
14 
15 template<typename T>
16 tagNode<T>::tagNode(T value,
17                     tagNode<T> * pLeftNode,
18                     tagNode<T> *pRightNode,
19                     tagNode<T> *pParentNode):
20 m_element(value),
21 m_pLeftNode(pLeftNode),
22 m_pRightNode(pRightNode),
23 m_pParentNode(pParentNode),
24 m_nHeightOfNode(0)
25 {
26 
27 }
28 
29 
30 template<typename T>
31 using BINNODE = tagNode<T>;
32 
33 template<typename T>
34 using PBINNODE = tagNode<T>*;

   這裡平衡二叉樹節點定義使用了模版,這樣就可以任意自定義節點,using BINNODE = tagNode<T>則是為節點類型起一個別名,

  using PBINNODE = tagNode<T>*則使為該節點類型的指針起一個別名,因為該節點類型的定義使用了模版,

  所以只能用using關鍵字來起別名,不能使用typedef來起別名,有點扯遠了。。。。

3.平衡二叉樹的遍歷:

  這裡限定左子樹比右子樹優先遍歷,所以一共有三種遍歷:先序遍歷,中序遍歷,後序遍歷

    

    下麵將結合圖3-1,來解釋這三種遍歷方式

    先序遍歷:訪問當前的根節點節點,然後遍歷左子樹,如果左子樹為空則遍歷右子樹,

         如果右子樹為空,則向上尋找到一個可以繼續向右遍歷的節點重覆該過程,

         直到所有節點遍歷完成

               對於上圖,先序遍歷的結果是:4->2->1->3->9->7->5->8->10->12

    下麵分別給出先序遍歷的遞歸實現和非遞歸實現:

     遞歸先序遍歷:

 1 //************************************************************************
 2 // 函數名稱:  CBinTree<T>::PreOrderRecuursiveTraversal
 3 // 函數功能:  先序遍歷二叉樹(遞歸實現)
 4 // 所屬許可權:  public 
 5 // 返回的值:  bool
 6 // 函數參數:  CTraversalFunction<T> & FuncOfTravs:訪問節點時使用的函數對象
 7 // 函數參數:  PBINNODE<T> pTravsralPos:遍歷位置
 8 // 註意事項: 
 9 //************************************************************************
10 template<typename T>
11 bool CBinTree<T>::PreOrderRecuursiveTraversal(CTraversalFunction<T> & FuncOfTravs, PBINNODE<T> pTravsralPos)
12 {
13     if (pTravsralPos != nullptr)
14     {
15         FuncOfTravs(pTravsralPos);
16         PreOrderRecuursiveTraversal(FuncOfTravs, pTravsralPos->m_pLeftNode);
17         PreOrderRecuursiveTraversal(FuncOfTravs, pTravsralPos->m_pRightNode);
18     }
19     return true;
20 }

 

  這裡函數第一個參數FuncOfTravs是一個函數對象,其定義如下:

//用於遍歷時所用的函數對象,由子類實現函數調用運算符的重載,
//在函數調用運算符中子類實現對節點進行如何操作
template<typename T>
class CTraversalFunction
{
public:
  virtual void operator()(PBINNODE<T> pNode) = 0;
};

  下麵手工建立一顆二叉樹(現在還不是平衡二叉樹,僅僅是顆二叉樹):

 1  CBinTree<int> binTree;
 2   BINNODE<int> node1(1);
 3   BINNODE<int> node2(2);
 4   BINNODE<int> node3(3);
 5   BINNODE<int> node4(4);
 6   BINNODE<int> node5(5);
 7   BINNODE<int> node6(6);
 8   BINNODE<int> node7(7);
 9   BINNODE<int> node8(8);
10   BINNODE<int> node9(9);
11   BINNODE<int> node10(10);
12   BINNODE<int> node11(11);
13   BINNODE<int> node12(12);
14 
15   node1.m_pLeftNode = &node2;
16   node1.m_pRightNode = &node5;
17   node2.m_pLeftNode = &node3;
18   node2.m_pRightNode = &node4;
19   node3.m_pLeftNode = &node10;
20   node3.m_pRightNode = &node11;
21   node11.m_pLeftNode = &node12;
22   node4.m_pRightNode = &node6;
23   node5.m_pLeftNode = &node7;
24   node5.m_pRightNode = &node9;
25   node7.m_pRightNode = &node8;
View Code

   上述代碼建立的二叉樹大概長這個樣子:

  

 

  現在用上面的遞歸先序遍歷遍歷這個樹,訪問節點時輸出每個節點值,

  所以我繼承一下類模版CTraversalFunction並實現函數調用運算符:

 1 class MyTraversalFunc :public CTraversalFunction<int>
 2 {
 3   virtual void operator()(PBINNODE<int>  pNode);
 4 };
 5 
 6 
 7 void MyTraversalFunc::operator()(PBINNODE<int>  pNode)
 8 {
 9   std::cout << pNode->m_element <<" ";
10 }
View Code

  然後用下麵代碼測試下遞歸先序遍歷:

1   binTree.m_pRoot = &node1;
2   MyTraversalFunc funcObj;
3   binTree.PreOrderRecuursiveTraversal(funcObj, binTree.m_pRoot);
View Code

  結果:

  

  順便提下函數對象,好像是C++11標準才有的東西,當一個函數重載了函數調用運算符後,

  這個類產生的對象也稱為為函數對象,例如在上面代碼的第15行直接通過函數調用運算符調用函數,

  而無需通過FuncOfTravs.xxxx(....)的方式來調用成員函數,此時類對象FuncOfTravs仿佛就像一個函數一樣。

  傳統的C函數只能通過函數傳參來攜帶信息,但是函數對象可以不用通過參數就能攜帶任意信息,

  這讓我想起了Windows的線程回調函數,當我們使用CreateThread或者更安全的_beginThreadEx時,

  線程回調函數只能攜帶一個參數,所以一般都定義一個結構體,將這個結構體變數作為參數傳遞給線程回調函數,

  但是個人感覺函數對象攜帶參數的方式比這些回調函數更優雅些,嗯.......又扯遠了。

  上述的函數調用操作符為我定義成純虛函數,對於任何自定義的數據類型,只要繼承

  該類模版,並實現虛函數調用運算符即可實現對自定義節點的遍歷。

        遞歸遍歷看起來代碼比較簡潔,但是每執行一次遞歸調用對線程棧都會消耗一點,

  程式運行時每個線程的棧空間的大小都是固定的,例如,在我的VS中:項目屬性->鏈接器->系統,頁面如下

  

  預設情況下棧保留大小為1MB,也可以設置為我們想要的大小。

  如果遞歸比較深,耗盡了當前的線程棧,那麼程式會被終止。

  其實,我們也可以使用數據結構中的棧,棧中節點從堆中分配,

  用迴圈來模擬遞歸調用,也能實現想要的功能,下麵給出先序

  遍歷的非遞歸調用代碼:

 1 //************************************************************************
 2 // 函數名稱:  PreOrderTraversal
 3 // 函數功能:  先序遍歷二叉樹(非遞歸實現)
 4 // 所屬許可權:  public 
 5 // 返回的值:  bool
 6 // 函數參數:  CTraversalFunction<T> & FuncOfTravs:訪問節點時使用的函數對象
 7 // 函數參數:  PBINNODE<T> pTravsralPos:遍歷位置
 8 // 註意事項: 
 9 //************************************************************************
10 template<typename T>
11 bool CBinTree<T>::PreOrderTraversal(CTraversalFunction<T> & FuncOfTravs, 
12                                     PBINNODE<T> pTravsralPos)
13 {
14     std::stack<PBINNODE<T>> tmpStack;
15     tmpStack.push(pTravsralPos);
16     PBINNODE<T> pCurrNode = nullptr;
17     while (!tmpStack.empty())
18     {
19         pCurrNode = tmpStack.top();
20         tmpStack.pop();
21         FuncOfTravs(pCurrNode);
22 
23         //在遍歷過程中先將以pCurrNode為根節點的右孩子節點指針入棧,
24         //在將其左孩子指針入棧,這樣的入棧順序可以達到先根遍歷的目的
25         if (pCurrNode->m_pRightNode != nullptr)
26         {
27             //當前已經訪問節點的右孩子存在則入棧
28             tmpStack.push(pCurrNode->m_pRightNode);
29         }
30         
31         if (pCurrNode->m_pLeftNode != nullptr)
32         {
33           tmpStack.push(pCurrNode->m_pLeftNode);
34         }
35     }
36     return true;
37 }
View Code

  運行結果:

  

  運行結果與遞歸實現的先序遍歷相同,為了模擬遞歸調用這裡用到迴圈,

  先將根節點入棧,在迴圈中先將根節點出棧並訪問,先序遍歷是先訪問

  根節點,然後訪問左子樹,在右子樹,而數據結構中的棧是先入後出的,

  所以入棧時要先將右孩子入棧,在入棧左孩子,這樣出棧時是先出左孩

  子,在出右孩子,就能實現先序遍歷。

  先寫到這,睡覺。

  

 


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

-Advertisement-
Play Games
更多相關文章
  • 2.帶getter和setter屬性 3.對象私有欄位 在Scala中,方法可以訪問該類的所有對象的私有欄位 4.Bean屬性 當你將Scala欄位標註為@BeanProperty時,會自動生成四個方法 5.輔助構造器 6.主構造器 7.嵌套類 ...
  • 你們可能會想,棧長這麼菜的嗎?5分鐘都堅持不了? 本文說起來會有點尷尬,畢竟這是棧長我曾經經歷過的故事。。。 那時候的棧長還真菜,每天寫著 if/ for 及一些簡單的業務邏輯代碼,雖工作有些日子了,但技術水平還停留在剛畢業的起步階段。。。 記得,那是一個周末,棧長去某知名互聯網公司面試,好像不到五 ...
  • 超時 網路請求不可避免會遇上請求超時的情況,在 requests 中,如果不設置你的程式可能會永遠失去響應。超時又可分為連接超時和讀取超時。 連接超時 連接超時指的是在你的客戶端實現到遠端機器埠的連接時(對應的是connect()),Request 等待的秒數。 import timeimport ...
  • 本文介紹python中的while迴圈、for迴圈。在python中for可以用於迴圈,也可用於另一種近親的列表解析,列表解析是python中非常重要的特性,詳細內容見後面的文章。 一般來說,python寫for迴圈比寫while更容易、方便,而且python中的for比while效率要更高,如果可 ...
  • Java記憶體模型雖說是一個老生常談的問題 ,也是大廠面試中繞不過的,甚至初級面試也會問到。但是真正要理解起來,還是相當困難,主要這個東西看不見,摸不著。網上已經有大量的博客,但是人家的終究是人家的,自己也要好好的去理解,去消化。今天我也來班門弄斧,說下Java記憶體模型。 說到Java記憶體模型,不得不 ...
  • while-else 1、break立即打斷迴圈並且不再執行else 2、while迴圈後會執行else ...
  • @Resource,@Autowired,@Inject 這3種都是用來註入bean的,它們屬於不同的程式中。詳情參見下表: ...
  • 題目內容: 下圖為國內主要城市之間的公路里程: 你的程式要讀入這樣的一張表,然後,根據輸入的兩個城市的名稱,給出這兩個城市之間的里程。 註意:任何兩個城市之間的里程都已經給出,不需要計算經第三地中轉。 註意:你並不需要去錄入上圖的數據,數據是在程式輸入中給的。 輸入格式: 首先,你會讀到若幹個城市的 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...