<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
前面我們已經對堆進行學習,堆就是一個順序結構的二元樹,把陣列看成二元樹,下面一起學習一下鏈式結構的二元樹,這裡是用遞迴實現功能
typedef char BTDataType; typedef struct BinaryTreeNode { BTDataType data; struct BinaryTreeNode* left; struct BinaryTreeNode* right; }BTNode;
首先,我要說明一下遞迴實現;遞迴實現一般分為三個步驟(遞迴三要素):初步明確函數功能,限制條件,找到實現此功能的等式
單項遞迴和二元樹遞迴(多項遞迴)的區別?
單項遞迴並沒有分支,多項遞迴是有分支的,這就意味著二元樹更看中整體,單項遞迴更看重分治。
單項遞迴和二元樹遞迴的共同點?
都是分治思想,子問題再分子問題再分子問題的思想
思想:把樹看成整體:根、左子樹、右子樹,先遍歷根再走左子樹再走右子樹
void BinaryTreePrevOrder(BTNode* root) { //根的情況(到底的限制條件) if (root == NULL) { printf("NULL "); return; } printf("%c ", root->data); BinaryTreePrevOrder(root->left); BinaryTreePrevOrder(root->right); }
思想:把樹看成整體:根、左子樹、右子樹,先遍歷左子樹再走根再走右子樹
void BinaryTreeInOrder(BTNode* root) { //根的情況(到底的限制條件) if (root == NULL) { printf("NULL "); return; } BinaryTreeInOrder(root->left); printf("%c ", root->data); BinaryTreeInOrder(root->right); }
void BinaryTreePostOrder(BTNode* root) { //根的情況(到底的限制條件) if (root == NULL) { printf("NULL "); return; } BinaryTreePostOrder(root->left); BinaryTreePostOrder(root->right); printf("%c ", root->data); }
思想:出上一層的同時帶著下一層入佇列
//鏈式佇列的結構 typedef struct BinaryTreeNode* QueueDataType; typedef struct QueueNode { QueueDataType data; struct QueueNode* next; }QueueNode; //因為要直接得到隊頭的元素和隊尾的元素 typedef struct QueueLinkList { QueueNode* head; //隊頭 QueueNode* tail; //隊尾 int size; //元素總數 }QLL; //佇列初始化 void QLLInit(QLL* queue) { assert(queue); queue->head = NULL; queue->tail = NULL; queue->size = 0; } //進隊 void QLLPush(QLL* queue, QueueDataType x) { assert(queue); QueueNode* newnode = (QueueNode*)malloc(sizeof(QueueNode)); if (newnode == NULL) { perror("QLLPush:malloc is failed!n"); exit(-1); } newnode->data = x; newnode->next = NULL; if (queue->head == NULL) { queue->head = queue->tail = newnode; } else { queue->tail->next = newnode; queue->tail = newnode; } queue->size++; } //出隊 void QLLPop(QLL* queue) { assert(queue != NULL); assert(QLLEmpty(queue) != true); //只有一個結點時 if (queue->head->next == NULL) { free(queue->head); //free的是這個結點的空間,並不是指標 queue->head = queue->tail = NULL; } else { //通常情況 QueueNode* del = queue->head; queue->head = queue->head->next; free(del); //無需置空 } queue->size--; } //拿取隊頭資料 QueueDataType QLLFront(QLL* queue) { assert(queue != NULL); assert(QLLEmpty(queue) != true); return queue->head->data; } //判空 bool QLLEmpty(QLL* queue) { assert(queue); //return queue->size == 0; return queue->head == NULL && queue->tail == NULL; } //銷燬 void QLLDestroy(QLL* queue) { assert(queue); QueueNode* cur = queue->head; while (cur != NULL) { QueueNode* del = cur; cur = cur->next; free(del); del = NULL; } queue->head = queue->tail = NULL; queue->size = 0; } //層序遍歷實現 void BinaryTreeLevelOrder(BTNode* root) { QLL queue; QLLInit(&queue); //根先入佇列 if (root != NULL) { QLLPush(&queue, root); } //佇列不為NULL的時候進行出隊頭帶下層資料入隊操作 while (QLLEmpty(&queue) != true) { //出隊頭操作 BTNode* front = QLLFront(&queue); printf("%c ", front->data); QLLPop(&queue); //帶下一層進隊 if (front->left != NULL) { QLLPush(&queue, front->left); } if (front->right != NULL) { QLLPush(&queue, front->right); } } printf("n"); QLLDestroy(&queue); }
說明:為什麼遞迴不畫圖來解決呢?
多項遞迴畫圖是很難理解的,因為他不是我們邏輯上想的,就拿前序遍歷來說,首先是根,再遍歷左子樹再遍歷右子樹這樣迴圈來走,但是在實際遞迴中邏輯是左子樹走到底,直到NULL時返回存取右子樹,如果說是畫圖是理解不了二元樹遞迴的,這裡我們就要扣住樹的結構:根、左子樹、右子樹,這樣是我們邏輯上的實現,並不是實際中的過程實現,這裡我需要說明一下,畫圖是為了在原有基礎上來進行糾錯,這裡糾正的錯也是和根的限制條件有關,這裡我還會出幾期二元樹的相關練習,到時候希望大佬們看看就能理解了二元樹遞迴!
遞迴三要素解決問題
int BinaryTreeSize(BTNode* root) { if (root == NULL) { return 0; } return BinaryTreeSize(root->left) + BinaryTreeSize(root->right) + 1; }
程式碼分析
上述列出來的思路就是實現思路,這裡注意的是樹的整體結構,我一直扣的就是整體結構,等式中寫的也是整體結構的邏輯;這裡來看程式碼很簡單就是根和左子樹和右子樹結構
為什麼不寫子結構:根、左孩子、右孩子?
原因就是如果寫成子結構的話就不是整體,而是把整體分為多個相同的結構來討論,這裡就不是整體觀念就很容易陷進去,為什麼二元樹遞迴難,難就難在你沒扣住整體,而是扣住的是子結構,如果扣住子結構那就很容易陷進去,只要陷進去了就不是我們自己想的邏輯,而是實際遞迴的過程邏輯,實際遞迴的過程邏輯和我們想的邏輯有很大的區別
為什麼首先要有個前提:樹的結構:根、左子樹、右子樹?
原因很簡單,我們考慮整體就少不了這個結構,這是我們首先要考慮的問題;另外也是因為這裡三要素中的實現是離不開這個整體結構的,如果離開了整體結構就又被陷進去了
限制條件是怎麼來限制的?
首先我們考慮的結構就是樹的整體結構:根、左子樹、右子樹,我們不可能是來對左右子樹來限制吧,因為左右子樹中有很多結點,從整體上來說是考慮不到的,另外你只要考慮左右子樹中的結點恭喜你,你又被陷進去出不來了哈哈,所以這裡的限制條件是針對根來講的:也就是根的起初的結束條件以及和題意的聯絡
遞迴三要素解決問題
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); }
程式碼分析
遞迴三要素解決問題
int BinaryTreeLevelKSize(BTNode* root, int k) { if (root == NULL) { return 0; } if (k == 1) { return 1; } return BinaryTreeLevelKSize(root->left, k - 1) + BinaryTreeLevelKSize(root->right, k - 1); }
程式碼分析
遞迴三要素解決問題
int BinaryTreeDepth(BTNode* root) { if (root == NULL) { return 0; } int LeftTreeDepth = BinaryTreeDepth(root->left); int RightTreeDepth = BinaryTreeDepth(root->right); if (LeftTreeDepth > RightTreeDepth) { return LeftTreeDepth + 1; } else { return RightTreeDepth + 1; } }
程式碼分析
沒進行優化的程式碼:
int BinaryTreeDepth(BTNode* root) { if (root == NULL) { return 0; } if (BinaryTreeDepth(root->left) > BinaryTreeDepth(root->right)) { return BinaryTreeDepth(root->left) + 1; } else { return BinaryTreeDepth(root->right) + 1; } }
這個程式碼也是對的,但是時間複雜就要多了1倍,因為判斷中用到遞迴了,找到了並沒有記錄深度,也就進入判斷中的遞迴,再此遞迴一次,這樣時間複雜度就增了1倍。
思路:
完全二元樹的性質:前K-1層是滿二元樹,最後一層是從左到右是連續的
思路:用層序遍歷來解決,出上一層的同時帶下一層的資料,知道遇到NULL的時候就要進行判斷佇列中是不是還有不為NULL的值,如果有就不是完全二元樹,沒有則是
bool BinaryTreeComplete(BTNode* root) { QLL queue; QLLInit(&queue); if (root != NULL) { QLLPush(&queue, root); } //拿到每層的 while (QLLEmpty(&queue) != true) { BTNode* front = QLLFront(&queue); QLLPop(&queue); //當這層遇到NULL的時候進行判斷 if (front == NULL) { break; } else { QLLPush(&queue, front->left); QLLPush(&queue, front->right); } } //出到NULL進行檢查 //如果後面有非NULL就不是完全二元樹 while (QLLEmpty(&queue) != true) { BTNode* front = QLLFront(&queue); QLLPop(&queue); //不為NULL說明最後一層不是連續的 if (front != NULL) { QLLDestroy(&queue); return false; } } QLLDestroy(&queue); return true; }
遞迴三要素解決問題
前提:樹的結構:根、左子樹、右子樹
函數功能: 在二元樹中查詢值為x的結點
限制條件:root=NULL時結束,root->val=x時找到了就結束
等式:在根裡面找,在左子樹和右子樹裡面找
BTNode* BinaryTreeFind(BTNode* root, BTDataType x) { if (root == NULL) { return NULL; } if (root->data == x) { return root; } BTNode* ret1 = BinaryTreeFind(root->left, x); if (ret1 != NULL) { return ret1; } BTNode* ret2 = BinaryTreeFind(root->right, x); if (ret2 != NULL) { return ret2; } return NULL; }
程式碼分析
錯誤列舉
BTNode* BinaryTreeFind(BTNode* root, BTDataType x) { if (root == NULL) { return NULL; } if (root->data == x) { return root; } return BinaryTreeFind(root->left, x) || BinaryTreeFind(root->right, x); }
為什麼邏輯上是對的,但是是錯的?
最後的return的意思翻譯過來就是在左子樹裡面找,找到了就返回,不進右子樹,如果左子樹中沒找到就進右子樹,找到了返回,如果都沒找到就直接返回NULL;邏輯上是對的,但是呢,這裡我們返回的是指標,指標的的關係不能用邏輯關係來表達,所以是錯的
思路
也是圍繞層序遍歷來寫:記錄每一層的結點樹來出佇列就行了,這裡也是層序遍歷的知識是主要的,就不再進行討論了
void EveryLayer(BTNode* root) { QLL queue; int levelCount = 0; QLLInit(&queue); if (root != NULL) { QLLPush(&queue, root); //第一層就是一個資料 levelCount = 1; } while (QLLEmpty(&queue) != true) { while (levelCount--) { BTNode* front = QLLFront(&queue); printf("%c ", front->data); QLLPop(&queue); if (front->left != NULL) { QLLPush(&queue, front->left); } if (front->right != NULL) { QLLPush(&queue, front->right); } } //下一層的個數 levelCount = QLLSize(&queue); printf("n"); } QLLDestroy(&queue); }
結合上述題就很容易看出實際上我們寫程式碼來劃分的話也是樹的整體結構:根、左子樹、右子樹,時刻把握著樹的整體結構,這個結構充分體現在等式中,同時也影響到了限制條件,限制條件中只用管根的結束條件以及形成條件,其他的不用管,這就是在程式碼中實現的過程,這裡我就不在畫圖,覺得這個過程不能實現我說的對應的功能的時候你就畫圖去嘗試理解一下,謝謝
思路:
這裡用到前序遍歷來建立,也就是陣列的元素按個放進根的資料域中
限制條件:就是當元素為#,代表的是二元樹中的NULL
BTNode* BinaryTreeCreate(BTDataType* a, int* pi) { //形成條件 if (a[(*pi)] == '#') { (*pi)++; return NULL; } BTNode* root = (BTNode*)malloc(sizeof(BTNode)); if (root == NULL) { perror("BinaryTreeCreate: malloc is failed!n"); exit(-1); } //根 root->data = a[(*pi)++]; //左右子樹 root->left = BinaryTreeCreate(a, pi); root->right = BinaryTreeCreate(a, pi); return root; } void Test2() { char str[] = { 'A','B','D','#','#','E','#','H','#','#','C','F','#','#','G','#','#' }; int i = 0; BTNode* root = BinaryTreeCreate(str, &i); }
//二級指標 void BinaryTreeDestory(BTNode** root) { if ((*root) == NULL) { return; } BinaryTreeDestory(&((*root)->left)); BinaryTreeDestory(&((*root)->right)); free((*root)); *root = NULL; }
void FirstPointBinaryTreeDestory(BTNode* root) { if (root == NULL) { return; } FirstPointBinaryTreeDestory(root->left); FirstPointBinaryTreeDestory(root->right); free(root); //root = NULL;(沒必要) }//需要說明的是用這個函數呼叫後要對root置空
為什麼採用後序遍歷來銷燬二元樹?
因為後序遍歷最開始走到的就是左子樹的最後一層,然後逐次向上銷燬,並不會影響每個結點的指向;如果採用前序遍歷呢?採用前序遍歷上來就是free掉了根結點,就找到不到這個根結點的左右孩子了
以上就是C語言實現二元樹鏈式結構的範例詳解的詳細內容,更多關於C語言二元樹鏈式結構的資料請關注it145.com其它相關文章!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45