線索二叉樹構造及遍歷算法

線索二叉樹構造以及遍歷算法

  • 線索二叉樹(中序遍歷版)
    • 構造線索二叉樹
    • 構造雙向線索鏈表
    • 遍歷中序線索二叉樹

線索二叉樹(中序遍歷版)

中序遍歷找到對應結點的前驅(土方法)

visit 函數
q 是否等于 p
傳入節點 q
final = pre
pre = q
返回
開始
調用 InOrder T
樹 T 是否為空
結束
遞歸調用 InOrder T->lchild
調用 visit T
遞歸調用 InOrder T->rchild
visit
typedef struct BiNode{int data;BiNode *lchild, *rchild;
}BiNode, *BiTree;BiNode *p;  // 指向目標節點
BiNode *pre = NULL; // 指向當前訪問節點的前驅
BiNode *final = NULL; // 記錄最終結果void InOrder(BiTree T){if(T != NULL){InOrder(T -> lchild);visit(T);InOrder(T -> rchild);}
}// 找到目標節點的前驅節點
void visit(BiNode *q){  // q代表當前訪問節點if(p == q)final = pre;   // 如果當前訪問節點和目標節點一致了,那么pre就是我們需要找的前驅elsepre = q;       // 如果不一致,那么更新前驅節點
}

由上述代碼我們可以知道,如果使用土方法來尋找目標節點的前驅節點,那么每找一次,就需要對二叉樹進行一次遍歷,這樣對資源的浪費是不言而喻的,所以我們需要采用線索二叉樹來更加快速地尋找對應節點的前驅后繼,通過線索二叉樹,我們可以實現對二叉樹的隨機訪問。

問題1:為什么在visit函數中不需要對q進行迭代?

回答1:因為 q q q的迭代是在 I n O r d e r InOrder InOrder中進行的,在每一次對 I n O r d e r InOrder InOrder的遞歸中,傳入 v i s i t visit visit的節點會不會·不斷變化,也就實現了對 q q q的迭代。

線索二叉樹實際就是用空的 n + 1 個空指針指向直接前驅和直接后繼。如果一個節點的左孩子為空,則左孩子指針指向當前節點的前驅,改 l t a g ltag ltag為1;如果一個節點的右孩子為空,則用右孩子指針指向當前節點的后繼,改 r t a g rtag rtag為1。

lchildltagdatartagrchild
指示左孩子00指示右孩子
指示直接前驅11指示直接后繼
// 線索二叉樹的存儲結構
typedef struct ThreadTree{int data;struct ThreadTree *lchild, *rchild;int ltag, rtag;
}ThreadNode, *ThreadTree;

構造線索二叉樹

通過中序遍歷對二叉樹線索化

InThread函數
p 是否為空?
傳入 p 和 pre
返回
遞歸調用 InThread p->lchild, pre
p->lchild 是否為空?
p->lchild = pre p->ltag = 1
不做操作
pre 是否非空且 pre->rchild 是否為空?
pre->rchild = p pre->rtag = 1
不做操作
pre = p
遞歸調用 InThread p->rchild, pre
開始
調用 CreateInThread T
樹 T 是否為空?
結束
初始化 pre = NULL
調用 InThread T, pre
pre->rchild = NULL pre->rtag = 1
void InThread(ThreadTree &p, ThreadTree &pre){ // p是當前訪問節點,pre為當前訪問節點的前驅if(p != NULL){InThread(p -> lchild);if(p -> lchild == NULL){  // 如果左孩子為空,則更新左孩子為前驅,ltag為1p -> lchild = pre;ltag = 1;}if(pre != NULL && pre -> rchild == NULL){ // 若前驅節點非空且其右子樹為空,則更新其右孩子為后繼,rtag為1pre -> rchild = p;pre -> rtag = 1;}pre = p;InThread(p -> rchild);}
}
void CreateInThread(ThreadTree T){Thread pre = NULL;if(T != NULL){InThread(T, pre);pre -> rchild = NULL; // 處理最后一個節點pre -> rtag = 1;}
}

問題2:為什么在創建二叉樹的時候需要判斷pre是否為空?

回答2為了避免空指針引用,我們在創建線索二叉樹的時候,會把pre初始化為NULL(也就是其實并沒有這個節點),因為第一個節點沒有直接的前驅,而如果我們不對空指針進行判斷的話,那么pre的后繼就會是當前節點p,那么究竟誰才是第一個節點呢?運行起來就會導致程序崩潰。只有當pre不為空時,才有意義去判斷其右子樹是否為空,為它建立線索二叉樹才有意義。

構造雙向線索鏈表

但是這樣的線索二叉樹還是存在一些問題,比如沒有辦法從第一個節點直接遍歷到最后一個節點,為此我們可以建立一個頭節點,讓其lchild指向二叉樹的根節點,其rchild指向中序遍歷時訪問的最后一個節點。令中序遍歷的第一個節點的lchild指向頭節點,也就是第一個節點的前驅不再是NULL,而是head;令中序遍歷的最后一個節點的rchild指向頭節點,也就是最后一個節點的后繼也不再是NULL,而是head。這樣一來,我們就獲得了一個雙向線索鏈表。

InThread函數
p 是否為空?
傳入 p 和 pre
返回
遞歸調用 InThread p->lchild, pre
p->lchild 是否為空?
p->lchild = pre p->ltag = 1
不做操作
pre 是否非空且 pre->rchild 是否為空?
pre->rchild = p pre->rtag = 1
不做操作
pre = p
遞歸調用 InThread p->rchild, pre
開始
調用 CreateInThread T
分配頭節點 head
head 分配成功?
結束
設置 head->ltag = 0
設置 head->rtag = 1
設置 head->rchild = head
T 是否為空?
設置 head->lchild = head
設置 head->lchild = T
初始化 pre = head
調用 InThread T, pre
設置 pre->rchild = head pre->rtag = 1
初始化 first = head->lchild
first->ltag == 0?
first = first->lchild
設置 first->lchild = head
void InThread(ThreadTree &p, ThreadTree &pre){ // p是當前訪問節點,pre為當前訪問節點的前驅if(p != NULL){InThread(p -> lchild);if(p -> lchild == NULL){  // 如果左孩子為空,則更新左孩子為前驅,ltag為1p -> lchild = pre;ltag = 1;}if(pre != NULL && pre -> rchild == NULL){ // 若前驅節點非空且其右子樹為空,則更新其右孩子為后繼,rtag為1pre -> rchild = p;pre -> rtag = 1;}pre = p;InThread(p -> rchild);}
}
void CreateInThread(ThreadTree T){ThreadNode *head = (ThreadTree*)malloc(sizeof(ThreadTree));if(head == NULL)return ;head -> ltag = 0; // 指向根節點head -> rtag = 1; // 指向最后一個節點head -> rchild = head; // 初始化右指針指向自己if(T == NULL){head -> lchild = head;}else {head -> lchild = T;ThreadTree pre = head;InTread(T, pre);// 處理最后一個節點pre -> rchild = head;pre -> rtag = 1;// 處理第一個節點ThreadNode *first = head -> lchild; // 初始化第一個節點為根節點,方便找到第一個節點// 尋找中序遍歷的第一個節點while(first -> ltag == 0) // 如果lchild是指向左孩子則迭代first = first -> lchild;first -> lchild = head;}
}

遍歷中序線索二叉樹

只要先找到序列中的第一個節點,然后依次找節點的后繼,直到其后繼為空便可完成遍歷;

1. 求第一個節點

ThreadNode *FirstNode(ThreadNode *p){while(p -> ltag == 0) p = p -> lchild;return p;
}

2. 求中序線索二叉樹中節點p在中序序列下的后繼

ThreadNode *NextNode(ThreadNode *p){if(p -> rtag == 0) return FirstNode(p -> rchild); // 右子樹中最左下節點else return p -> rchild;
}

3. 求中序線索二叉樹的最后一個節點

ThreadNode *LastNode(ThreadNode *p){while(p -> rtag == 0) p = p -> rchild;return p;
}

4. 求節點p前驅

ThreadNode *PreNode(ThreadNode *p){if(p -> ltag == 0) return LastNode(p -> lchild);return p;
}

利用上述1.2.兩個算法,我們可以寫出不含頭節點的中序線索二叉樹的中序遍歷算法:

void InOrder(ThreadNode *T){for(ThreadNode *p = FirstNode(T); p != NULL; p = NextNode(p))visit(p); // 訪問節點,可自由設定
}

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/897586.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/897586.shtml
英文地址,請注明出處:http://en.pswp.cn/news/897586.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

基于SpringBoot的“體育購物商城”的設計與實現(源碼+數據庫+文檔+PPT)

基于SpringBoot的“體育購物商城”的設計與實現(源碼數據庫文檔PPT) 開發語言:Java 數據庫:MySQL 技術:SpringBoot 工具:IDEA/Ecilpse、Navicat、Maven 系統展示 系統總體模塊設計 前臺用戶登錄界面 系統首頁界面…

數據篇| App爬蟲入門(一)

App 的爬取相比 Web 端爬取更加容易,反爬蟲能力沒有那么強,而且數據大多是以 JSON 形式傳輸的,解析更加簡單。在 Web 端,我們可以通過瀏覽器的開發者工具監聽到各個網絡請求和響應過程,在 App 端如果想要查看這些內容就需要借助抓包軟件。常見抓包軟件有: ?工具名稱??…

go context學習

1.Context接口2.emptyCtx3.Deadline()方法4.Done()方法5.Err方法6.Value方法()7.contex應用場景8.其他context方法 1.Context接口 Context接口只有四個方法,以下是context源碼。 type Context interface {Deadline() (deadline time.Time, …

在VMware Workstation Pro上輕松部署CentOS7 Linux虛擬機

首先我們需要下載VM虛擬機和Centos7的鏡像 下載并安裝VMware Workstation Pro 訪問VMware Workstation Pro官網下載 https://www.vmware.com/ 第二步:下載centos7鏡像 訪問centos官網下載 https://www.centos.org/ 開始部署Centos7 點擊創建新的虛擬機 這里是Cen…

Jsoup 解析商品信息時需要注意哪些細節?

在使用Jsoup解析商品信息時,需要注意以下細節和最佳實踐,以確保爬蟲的穩定性和數據的準確性: 1. 檢查HTML文檔的合法性 在解析之前,需要確認所解析的文檔是否是一份合法正確的HTML文檔。如果HTML結構不完整或存在錯誤&#xff0…

Android AudioFlinger(五)—— 揭開AudioMixer面紗

前言: 在 Android 音頻系統中,AudioMixer 是音頻框架中一個關鍵的組件,用于處理多路音頻流的混音操作。它主要存在于音頻回放路徑中,是 AudioFlinger 服務的一部分。 上一節我們講threadloop的時候,提到了一個函數pr…

go的”ambiguous import in multiple modules”

執行“go mod tidy”報如下錯誤: go mod tidy -compat1.17 go: finding module for package github.com/gomooon/goredis go: found github.com/gomooon/goredis in github.com/gomooon/goredis v0.3.5 go: github.com/gomooon/core importsgithub.com/gomooon/gor…

從0開始的操作系統手搓教程27:下一步,實現我們的用戶進程

目錄 第一步:添加用戶進程虛擬空間 準備沖向我們的特權級3(用戶特權級) 討論下我們創建用戶線程的基本步驟 更加詳細的分析代碼 用戶進程的視圖 說一說BSS段 繼續看process.c中的函數 添加用戶線程激活 現在,我們做好了TSS…

Java線程池深度解析,從源碼到面試熱點

Java線程池深度解析,從源碼到面試熱點 一、線程池的核心價值與設計哲學 在開始討論多線程編程之前,可以先思考一個問題?多線程編程的原理是什么? 我們知道,現在的CUP是多核CPU,假設你的機器是4核的&#x…

大數據技術在土地利用規劃中的應用分析

大數據技術在土地利用規劃中的應用分析 一、引言 土地利用規劃是對一定區域內的土地開發、利用、整治和保護所作出的統籌安排與戰略部署,對于實現土地資源的優化配置、保障社會經濟的可持續發展具有關鍵意義。在當今數字化時代,大數據技術憑借其海量數據處理、高效信息挖掘等…

Node 使用 SSE 結合redis 推送數據(echarts 圖表實時更新)

1、實時通信有哪些實現方式? 特性輪詢(Polling)WebSocketSSE (Server-Sent Events)通信方向單向(客戶端 → 服務端)雙向(客戶端 ? 服務端)單向(服務端 → 客戶端)連接方…

Android Native 之 文件系統掛載

一、文件系統掛載流程概述 二、文件系統掛載流程細節 1、Init啟動階段 眾所周知,init進程為android系統的第一個進程,也是native世界的開端,要想讓整個android世界能夠穩定的運行,文件系統的創建和初始化是必不可少的&#xff…

Redis--Set類型

目錄 一、引言 二、介紹 三、命令 1.sadd,smembers,sismember 2.spop,srandmember 3.smove,srem 4.sinter,sinterstore 5.sunion,sunionstore,sdiff,sdiffstore 四、內部編碼 1.intset 2.hashtable 五、應用場景 1.使用Set保存用…

for...of的用法與介紹

一、定義 for...of 是 ES6(ECMAScript 2015)引入的一種用于 遍歷可迭代對象(Iterable)的循環語句 二、語法 for (const item of iterable) {// 代碼塊 }參數: iterable:一個可迭代對象(如數組…

Faster R-CNN原理詳解以及Pytorch實現模型訓練與推理

《------往期經典推薦------》 一、AI應用軟件開發實戰專欄【鏈接】 項目名稱項目名稱1.【人臉識別與管理系統開發】2.【車牌識別與自動收費管理系統開發】3.【手勢識別系統開發】4.【人臉面部活體檢測系統開發】5.【圖片風格快速遷移軟件開發】6.【人臉表表情識別系統】7.【…

使用dockerfile創建鏡像

1.什么是Dockerfile Dockerfile 是一個用于指導 Docker 鏡像構建過程的腳本文件。它通過一系列指令來詳細描述了構建鏡像所需的步驟和配置細節。利用 Dockerfile,我們可以精確地設定容器的運行環境,安裝必要的軟件,復制項目文件,…

在CentOS系統上安裝Conda的詳細指南

前言 Conda 是一個開源的包管理系統和環境管理系統,廣泛應用于數據科學和機器學習領域。本文將詳細介紹如何在 CentOS 系統上安裝 Conda,幫助您快速搭建開發環境。 準備工作 在開始安裝之前,請確保您的 CentOS 系統已經滿足以下條件&#x…

大腦宏觀結構中的富集俱樂部:圖論分析視角

摘要 大腦是一個高度復雜的網絡。越來越多的證據支持大腦網絡中一組重要腦區的關鍵作用,這些腦區通常被稱為大腦的“核心”或“樞紐”區域。這些區域不僅能量消耗較高,而且在神經信息傳遞方面的效率也極高,因此被稱為“富集俱樂部”。富集俱樂…

Redis7——進階篇(五)

前言:此篇文章系本人學習過程中記錄下來的筆記,里面難免會有不少欠缺的地方,誠心期待大家多多給予指教。 基礎篇: Redis(一)Redis(二)Redis(三)Redis&#x…

Reflect.get和target[key]有何不同?

主要區別在this指向不同,下面輸出張三還是李四?: const person{name:張三,get FullName(){return this.name;},};let personProxynew Proxy(person,{get(target,key){return Reflect.get(target,key)//或者return target[key]}});const p1{__proto__:pe…