【數據結構】堆(下)+ 二叉樹

上期回顧:【數據結構】樹(堆)·上

一.堆的應用

1.1堆排序(向下調整在上一期)

向上調整算法建堆:

首先先回顧一下向上調整算法

void AdjustUP(HPDataType* arr, int child)
{int parent = (child - 1) / 2;//找到父結點的位置while (child > 0)//循環,確保父結點一定小于(大于)子結點{//小堆:<//大堆:>//     |//     Vif (arr[child] > arr[parent]){//調整位置Swap(&arr[child], &arr[parent]);child = parent;parent = (child - 1) / 2;}elsebreak;}
}

在上一期我們也學過了向下調整算法建堆,其是通過由下向上循環,通過確保父結點下方的是堆結構,而向上算法建隊=堆就剛好反過來,是由上到下進行循環。

//堆排序
void HeapSort(int *arr,int n)
{//建堆--向下調整for (int i = (n - 1 - 1) / 2; i >= 0; i--){AdjustDOWN(arr, i, n);}//建堆--向上調整for (int i = 0; i < n; i++){AdjustUP(arr, i);}//排序int end = n - 1;while (end > 0){Swap(&arr[end], &arr[0]);AdjustDOWN(arr, 0, end);end--;}
}

1.2.TOP-K問題

TOP-K問題:即求數據結合中前K個最大的元素或者最小的元素,一般情況下數據量都比較大。
比如:專業前10名、世界500強、富豪榜、游戲中前100的活躍玩家等。
對于Top-K問題,能想到的最簡單直接的方式就是排序,但是:如果數據量非常大,排序就不太可取了(可能數據都不能一下子全部加載到內存中)。最佳的方式就是用堆來解決,基本思路如下:

通過前面的學習可了解到,小堆的堆頂是數據的最小值,所以若想通過小堆找最小,就需要遍歷所有的數據,在進行堆排序,這對于大數據肯定是不行的,

那么換一種思路,用小堆找最大數據:將前K個數據直接建小堆,然后再遍歷剩下的n-k個數據,將其與堆頂進行比較,若比堆頂大,則替換堆頂,來回重復,最后構成一個新堆,該堆就是數據中前K大的數。

而找前K小的數據道理相同,所以就可以總結出:

找最大的前K個數據,建小堆

找最小的前K個數據,建大堆

實踐:

先通過代碼生成10萬個數據,并保存在data.txt文件中

隨后實現topK函數:

void TopK()
{int k =0;printf("請輸入k:");scanf("%d", &k);const char* file = "data.txt";FILE* fout = fopen(file, "r");if (fout == NULL){perror("fopen fail");exit(1);}//找最大的前K個數據--建小堆//找最小的前K個數據--建大堆int* minHeap = (int*)malloc(sizeof(int) * k);if (minHeap == NULL){perror("malloc fail");exit(1);}for (int i = 0; i < k; i++){fscanf(fout, "%d", &minHeap[i]);}//minHeap -- 向下調整建堆for (int i = (k - 2) / 2; i >=- 0; i--){//找最大的前K個數據--建小堆//找最小的前K個數據--建大堆AdjustDOWN(minHeap, i, k);}//遍歷剩下的n-k個數據,跟堆頂比較,堆頂小替換堆頂數據int x = 0;while (fscanf(fout, "%d", &x) != EOF){//找最大>//找最小<if (x > minHeap[0]){minHeap[0] = x;AdjustDOWN(minHeap, 0, k);}}for (int i = 0; i < k; i++){printf("%d ", minHeap[i]);}printf("\n");fclose(fout);
}

先讀取文件數據,取前K個保存在數組中,并構成堆,隨后遍歷剩下的n-k個數據,進行比較替換。

最后成功打印

二.實現鏈式結構二叉樹

二叉樹的相關知識需要用到遞歸知識,所以建議先學習遞歸后再來看以下文章!!!


2.1二叉樹的插入與刪除

數據的插入和刪除可以在二叉樹的任何位置,所以此時暫時不寫文章,在后期學習到平衡樹,紅黑樹等在進行編寫。

2.2二叉樹的遍歷

二叉樹的遍歷有前序/中序/后序的遍歷的遞歸結構遍歷:

1)前序遍歷:訪問根節點的操作在遍歷其左右子樹之間

?訪問順序為:根結點、左子樹、右子樹

2)中序遍歷:訪問根結點的操作發生在遍歷其左右子樹之中(間)

訪問順序為:左子樹、根結點、右子樹

3)后序遍歷:訪問根結點的操作發生在遍歷其左右子樹之后

訪問順序為:左子樹、右子樹、根結點

遍歷的實現:

通過函數的遞歸實現遍歷,需要先判斷遞歸條件,當目前根節點為NULL時,遞歸中止,拿前序遍歷為例,在目前根節點時,先訪問根節點的data,隨后根據前序遍歷的順序,分別訪問左子樹,右子樹,進行遞歸。那么中序遍歷與后序遍歷也就很好寫出來了。

構造文章上文的二叉樹例子,然后進行遍歷輸出:

2.3二叉樹的結點個數

1.運用二叉樹遍歷的知識,通過遞歸遍歷每一個結點,然后size++,那么結果會是什么呢?

int BinaryTreeSize(BTNode* root)
{int size = 0;if (root == NULL){return;}//結點非空,+1size++;BinaryTreeSize(root->left);BinaryTreeSize(root->right);return size;
}

運行后可以發現,不管調用幾次,size都為1,原因是因為,每次遞歸size都被初始化為0

2.那么把size設為全局變量后是不是就可以避免了?

int size = 0;
int BinaryTreeSize(BTNode* root)
{if (root == NULL){return;}//結點非空,+1size++;BinaryTreeSize(root->left);BinaryTreeSize(root->right);return size;
}

運行后發現,雖然第一次調用size的結果是正確的,但是經過多次調用,size的值就會越加越大。

3.這次換一種思路,直接把size設為參數進行調用。

int BinaryTreeSize(BTNode* root, int size)
{if (root == NULL){return;}//結點非空,+1size++;BinaryTreeSize(root->left,size);BinaryTreeSize(root->right,size);return size;
}

這一次發現結果又變為三個1,分析原因,當遞歸回溯的時候,size的值會返回到上一步的值,例如當遞歸到4結點時,size=3,但是回溯到2結點時,size又回到了2

4.要解決這個問題,把傳值調用改為傳址調用就可解決。

int BinaryTreeSize(BTNode* root,int*size)
{if (root == NULL){return 0;}//結點非空,+1(*size)++ ;BinaryTreeSize(root->left,size);BinaryTreeSize(root->right,size);return *size;
}

這時結果就正確了,但是,每次調用參數,都需要把size初始化為0,有一點麻煩,在對函數進行改造

分析二叉樹可知,結點數 = 根結點+左子樹的結點數+右子樹的結點數?

最終版:

//二叉樹節點個數
int BinaryTreeSize(BTNode* root)
{if (root == NULL){return 0;}//結點非空,+1return 	1 + BinaryTreeSize(root->left) + BinaryTreeSize(root->right);
}

2.4二叉樹葉子結點數

葉子結點數 = 左子樹葉子節點數 + 右子樹葉子結點數

結合剛才學到的結點數的知識,可以寫出:

//二叉樹葉子節點數
int BinaryTreeLeafSize(BTNode* root)
{if (root == NULL){return 0;}if (root->left == NULL && root->right == NULL){return 1;}return BinaryTreeLeafSize(root->left) + BinaryTreeLeafSize(root->right);
}

2.5二叉樹第K層結點個數

//二叉樹第K層結點個數
int BinaryTreeLevelSize(BTNode* root, int k)
{if (root == NULL){return 0;}if (k == 1){return 1;}return BinaryTreeLevelSize(root->left, k - 1) + BinaryTreeLevelSize(root->right, k - 1);
}

2.6二叉樹的深度

深度 = 根結點 + max(左子樹的深度+右子樹的深度)

//二叉樹的深度
int BinaryTreeDepth(BTNode* root)
{if (root == NULL){return 0;}return 1 + max(BinaryTreeDepth(root->left), BinaryTreeDepth(root->right));//return 1 + (BinaryTreeDepth(root->left) > BinaryTreeDepth(root->right) ? BinaryTreeDepth(root->left) : BinaryTreeDepth(root->right));
}

2.7二叉樹查找值為x的結點

//二叉樹查找值為x的結點
BTNode* BinaryTreeFind(BTNode* root, BTDtatatype x)
{if (root == NULL){return NULL;}if (root->data == x){return root;}BTNode*leftFind =  BinaryTreeFind(root->left, x);if (leftFind){return leftFind;}BTNode*rightFind =  BinaryTreeFind(root->right, x);if (rightFind){return rightFind;}
}

2.8二叉樹的銷毀

//二叉樹的銷毀
void BinaryTreeDestroy(BTNode** root)
{if (root == NULL){return;}BinaryTreeDestroy(&((*root)->left));BinaryTreeDestroy(&((*root)->right));free(*root);*root = NULL;
}

二叉樹的層序遍歷

借助數據結構--隊列

首先將之前所編寫過的隊列的.h和.c文件導入進來

注意:

1.要包含隊列的頭文件

2.修改隊列的頭文件:修改隊列的保存類型,務必要加struct,為了告訴編譯器BinaryTreeNode是個結構體。

2.9層序編列實現:

//層序遍歷
void levelOrder(BTNode* root)
{Queue q;QueueInit(&q);QueuePush(&q, root);while (!QueueEmpty(&q)){//取隊頭,出隊頭BTNode*top =  QueueFront(&q);QueuePop(&q);printf("%c ", top->data);//將左右孩子入隊列if (top->left)QueuePush(&q, top->left);if (top->right)QueuePush(&q, top->right);}QueueDestrory(&q);
}

2.10判斷是否為完全二叉樹

前置知識:完全二叉樹的特點:

1)最后一層節點個數不一定達到最大

2)結點從左到右依次排列

與二叉樹的層序遍歷相同,借助隊列進行實現:

在第一個循環中,取隊頭,出對頭,直到遇到第一個空結點出循環,并進入到第二個循環。

若在第二個循環中,從非空隊列中取到了非空的隊頭結點,那么該二叉樹一定不是完全二叉樹,否則一定是完全二文樹

//判斷是否為完全二叉樹
bool BinaryTreeComlete(BTNode* root)
{Queue q;QueueInit(&q);QueuePush(&q, root);while (!QueueEmpty(&q)){//取隊頭,出隊頭BTNode* top = QueueFront(&q);QueuePop(&q);if (top == NULL){break;}QueuePush(&q, top->left);QueuePush(&q, top->right);}//隊列不一定為空while(!QueueEmpty(&q)){BTNode* top = QueueFront(&q);QueuePop(&q);if (top != NULL){return false;}}QueueDestrory(&q);return true;
}

以上圖做例子,應不是完全二叉樹:符合預期

把G H I結點去掉后應為完全二叉樹:符合預期

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

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

相關文章

Elasticsearch MCP 服務器現已在 AWS Marketplace 上提供

作者&#xff1a;來自 Elastic Udayasimha Theepireddy (Uday), Matt Ryan, Srinivas Pendyala 我們很高興地宣布&#xff0c;Elasticsearch Model Context Protocol&#xff08; MCP &#xff09;服務器現已在 AWS Marketplace 上提供。 使用 MCP 將代理連接到 Elasticsearch …

【Linux】Makefile(一)-介紹

Makefile 本篇博客是作者在學習Linux方面知識過程中&#xff0c;對Makefile片面的了解&#xff0c;從而產生了對Makefile有一個全面的認識的想法&#xff0c;在知道《跟我一起寫Makefile》此書后&#xff0c;作者學習閱讀過程中整理出的筆記。 目錄Makefilemakefile介紹:規則&…

Java爬蟲與正則表達式——用正則來爬取數據

APIJava幫我們寫好的各種功能的Java類。這些Java類統稱為API。正則表達式就是API幫我們寫好的類。正則表達式例子&#xff1a; 字符類&#xff1a;[abc]&#xff1a;只能是a&#xff0c;b或c[^abc]&#xff1a;除了a&#xff0c;b&#xff0c;c之外的任何字符[a-zA-Z]&#xff…

【后端】.NET Core API框架搭建(8) --配置使用RabbitMQ

目錄 1.添加包 2. 連接配置 2.1.連接字符串 2.2.連接對象 3.創建連接服務 3.1.添加配置獲取方法 3.2.服務實現類 3.3.服務接口 4.創建生產者服務 4.1.生產者實現類 4.2.生產者接口 5.創建消費者服務 5.1.消費者服務接口 5.2.消費者接口 6.注冊 7.簡單使用案例 7.1.實現…

Apache SeaTunnel配置使用案例

前置操作 Apache SeaTunnel詳解與部署&#xff08;最新版本2.3.11&#xff09;-CSDN博客 mkdir /usr/local/soft/apache-seatunnel-2.3.11/job/ 一、MySQL to HDFS 官方配置參考&#xff1a; MySQL | Apache SeaTunnel Hdfs文件 | Apache SeaTunnel 1、配置確認 將mysq…

GitCode 使用高頻問題及解決方案

GitCode 作為一款強大的版本控制系統&#xff0c;在軟件開發流程中起著舉足輕重的作用。然而&#xff0c;在使用過程中&#xff0c;開發者們常常會遇到各種各樣的問題。本文將匯總 GitCode 使用中的高頻問題&#xff0c;并提供詳細的解決方案&#xff0c;幫助開發者們更順暢地使…

在FreeBSD系統使用chroot進入Ubuntu仿真環境使用Localsend軟件發送和接受文件

LocalSend是一款非常實用的在不同系統&#xff08;Windows、MacOS、Linux、Android和IOS&#xff09;傳遞文件的程序。我們這次的實踐&#xff0c;就是要在FreeBSD下也能發送和接收文件。 安裝LocalSend 跟在Ubuntu下安裝非常類似&#xff0c;只是不需要下面的第一步&#xf…

交叉熵損失F.cross_entropy在分類模型中的應用

一、核心思想&#xff1a;通過概率分布懲罰錯誤交叉熵損失的本質是&#xff1a; 比較模型預測的概率分布 vs 真實標簽的概率分布&#xff0c;懲罰兩者之間的差異。例如&#xff1a;真實標簽&#xff1a;圖像 0 → 文本 0&#xff08;獨熱編碼 [1, 0, 0, ...]&#xff09;模型預…

測試學習之——Pytest Day3

引言Pytest 作為 Python 中最受歡迎的測試框架之一&#xff0c;以其簡潔的語法、強大的功能和豐富的插件生態系統&#xff0c;極大地提升了自動化測試的效率和可維護性。在本文中&#xff0c;我們將深入探討 Pytest 的兩大核心特性&#xff1a;Fixture 和插件管理&#xff0c;幫…

控制Vue對話框顯示隱藏

正確做法 — 使用 Vue 數據驅動控制顯隱你不需要手動設置 display: block&#xff0c;因為 Element Plus 的 <el-dialog> 是基于 v-model 或 :visible.sync 控制的。&#x1f527; 修改模板部分&#xff1a;將原來的&#xff1a;<el-dialog title"報文詳情"…

直播帶貨與開源AI智能名片鏈動2+1模式S2B2C商城小程序:重塑電商營銷新格局

摘要&#xff1a;本文聚焦于直播帶貨對互聯網供需關系的深刻影響&#xff0c;分析其如何改變傳統電商營銷模式&#xff0c;實現從“人找貨”到“貨找人”的轉變。同時&#xff0c;引入開源AI智能名片鏈動21模式S2B2C商城小程序這一創新概念&#xff0c;探討其在直播帶貨背景下的…

Jmeter 性能測試響應時間過長怎么辦?

當 JMeter 性能測試中出現 響應時間過長 的問題時&#xff0c;需要從 測試腳本、服務器、網絡、JMeter配置 等多方面排查和優化。以下是詳細的解決步驟和思路&#xff1a; B站最新性能進階&#xff0c;學會這些jmeter性能測試技能&#xff0c;更助于正確設計、執行和分析性能測…

COZE官方文檔基礎知識解讀第三期 —— prompt(提示詞)

COZE官方文檔基礎知識解讀第三期 —— prompt&#xff08;提示詞&#xff09; 對于初步接觸PE&#xff08;prompt engineering&#xff09; 的小伙伴們&#xff0c;你們可以去火山方舟提供的prompt工具&#xff0c;用工具&#xff08;其余的prompt網站https://www.promptinggu…

代碼隨想錄算法訓練營第三十二天|動態規劃理論基礎、LeetCode 509. 斐波那契數、70. 爬樓梯、746. 使用最小花費爬樓梯

目錄 LeetCode 509. 斐波那契數 70. 爬樓梯 746. 使用最小花費爬樓梯 感想 文檔講解&#xff1a;代碼隨想錄 動態規劃&#xff0c;英文&#xff1a;Dynamic Programming&#xff0c;簡稱DP&#xff0c;如果某一問題有很多重疊子問題&#xff0c;使用動態規劃是最有效的。 …

SpringMVC3

一、JSON 與參數傳遞1.1JSON 是什么- JSON 是字符串&#xff1a;比如 {"name":"zhangsan","password":"123456","age":15} 就是一個 JSON 字符串&#xff0c;它用來在前后端、服務間傳遞數據。- JSON 庫&#xff1a;Fastj…

查看.bin二進制文件的方式(HxD十六進制編輯器的安裝)

文章目錄Windows 系統上安裝 HxD 十六進制編輯器的步驟。**HxD 是一款免費、輕量級的工具&#xff0c;適合查看和編輯 .bin 等二進制文件。****PS:實際安裝過程中會發現找不到Windows11的版本&#xff0c;安裝windows10的即可&#xff0c;并且沒有區別setup版和portable版**安裝…

Linux系統性能優化與監控

系統性能優化與監控是保障 Linux 服務器穩定運行的核心技術&#xff0c;涉及 ??CPU、內存、磁盤 I/O、網絡、進程?? 等多維度的指標分析、問題定位與優化策略。以下從??監控工具與指標??、??常見問題診斷??、??優化方法??三個層面詳細講解&#xff0c;并結合?…

如何在 React + TypeScript 中實現 JSON 格式化功能

如何在 React TypeScript 中實現 JSON 格式化功能 作為前端開發者&#xff0c;我們經常需要處理 JSON 數據。無論是 API 調試、配置文件編輯還是數據轉換&#xff0c;能夠格式化 JSON 是一項基本但非常有用的技能。本文將詳細介紹如何在 React 和 TypeScript 環境中實現 JSON…

Mac連接服務器Docker容器全攻略

蘋果電腦( macOS 系統 )連接服務器、配置容器,整體思路和 Linux 終端操作更貼近,以下結合 macOS 特點,詳細分步說明,以 Docker 容器 + 常見 Linux 服務器( 如 CentOS、Ubuntu )為例: 一、連接服務器(SSH 方式, macOS 終端原生支持 ) 1. 準備信息 找運維或云平臺…

【字節跳動】數據挖掘面試題0019:帶貨直播間推薦:現在有一個帶貨的直播間,怎么把它精準地推送給有需要的用戶

文章大綱 帶貨直播間推薦系統:原理、算法與實踐 一、推薦系統在帶貨直播中的重要性 二、數據收集與處理 1. 用戶數據 2. 直播間數據 3. 用戶行為數據 4. 數據處理與特征工程 三、推薦算法實現 1. 基于內容的推薦 2. 基于協同過濾的推薦 3. 基于知識圖譜的推薦 4. 混合推薦算法…