數據結構:詳解二叉樹(樹,二叉樹順序結構,堆的實現與應用,二叉樹鏈式結構,鏈式二叉樹的4種遍歷方式)

目錄

1.樹的概念和結構

1.1樹的概念

1.2樹的相關概念

1.3樹的代碼表示

2.二叉樹的概念及結構

2.1二叉樹的概念

2.2特殊的二叉樹

2.3二叉樹的存儲結構

2.3.1順序存儲

2.3.2鏈式存儲

3.二叉樹的順序結構和實現

3.1二叉樹的順序結構

3.2堆的概念和結構

3.3堆的特點

3.4堆的代碼實現

3.4.1堆代碼實現中的算法問題

3.4.1.1向上調整算法

3.4.1.2向下調整算法

3.4.2堆代碼 Heap.h

3.4.3堆代碼Heap.c

3.4.4堆代碼test.c

3.5堆的應用(TOP K問題)

3.5.1舉例

3.5.2解決問題代碼

4.二叉樹的鏈式結構和實現

4.1手搓鏈式二叉樹

4.2遍歷鏈式二叉樹

4.2.1前序遍歷

4.2.2中序遍歷

4.2.3后序遍歷

4.2.4層序遍歷

?編輯

4.3鏈式二叉樹的其他函數

4.3.1二叉樹節點個數,葉子節點個數,高度函數

4.3.2二叉樹第K層節點個數

4.3.3銷毀二叉樹


1.樹的概念和結構

1.1樹的概念

樹與我們之前學過的數據結構都不相同,因為其具有一個重要特征:非線性

樹是一種非線性的數據結構,由一組節點(node)和一組連接節點的邊(edge)組成。樹的基本定義如下:

  1. 每個樹都有一個稱為根(root)的節點,根節點是樹的頂層節點,沒有父節點。
  2. 除了根節點外,每個節點可以有零個或多個子節點,子節點與父節點之間通過邊連接。
  3. 樹中的每個節點都有一個稱為父節點(parent)的節點,除了根節點。
  4. 樹中的節點可以擁有一個或多個子節點,每個子節點都有一個稱為子樹(subtree)的樹,由該子節點及其子節點構成。
  5. 沒有子節點的節點稱為葉節點(leaf),葉節點位于樹的底層。
  6. 從根節點到任意節點的路徑都唯一確定一條邊,該邊稱為該節點的父邊。

?用圖來表示就是:

簡單來說,數據結構中樹就是一種發散性的結構,與我們之前學過的單鏈表,順序表,棧和隊列這類的線性事物完全不同。計算機中和樹有關系的就是文件夾,文件夾一般采用多層次結構( 樹狀結構 )。在這種結構中每一個磁盤有一個根文件夾 ,它包含若干文件和文件夾。

需要注意的一點是:?在樹這種結構,子樹之間不能有交集,要不然就不能構成樹了。

1.2樹的相關概念

在樹這種數據結構中包含了很多概念,這些概念基于樹的結構,利用人類親緣關系和樹木的概念名稱命名。

結點的度:一個結點含有的子樹的個數稱為該結點的度;如上圖:A的為6
葉結點或終端結點:度為0的結點稱為葉結點;如上圖:B、C、H、I...等結點為葉結點
非終端結點或分支結點:度不為0的結點;如上圖:D、E、F、G...等結點為分支結點
雙親結點或父結點:若一個結點含有子結點,則這個結點稱為其子結點的父結點;如上圖:A是B的父結點
孩子結點或子結點:一個結點含有的子樹的根結點稱為該結點的子結點;如上圖:B是A的孩子結點
兄弟結點:具有相同父結點的結點互稱為兄弟結點;如上圖:B、C是兄弟結點
樹的度:一棵樹中,最大的結點的度稱為樹的度;如上圖:樹的度為6
結點的層次:從根開始定義起,根為第1層,根的子結點為第2層,以此類推;
樹的高度或深度:樹中結點的最大層次;如上圖:樹的高度為4
堂兄弟結點:雙親在同一層的結點互為堂兄弟;如上圖:H、I互為兄弟結點
結點的祖先:從根到該結點所經分支上的所有結點;如上圖:A是所有結點的祖先
子孫:以某結點為根的子樹中任一結點都稱為該結點的子孫。如上圖:所有結點都是A的子孫
森林:由m(m>0)棵互不相交的樹的集合稱為森林;

1.3樹的代碼表示

樹的代碼表示和一些其他數據結構類似,都是利用指針聯系不同的數據。而樹這個數據結構特殊之處在于是由一個根數據慢慢擴展至很多其他數據。從前面的介紹我們也得知了不同數據之間大體上可以分為兩種:父子(祖先)關系和兄弟關系,聰明的發明者就是利用這一點設計出了樹的代碼表示:

typedef int DataType;
typedef struct Node
{struct Node* firstChild1; // 第一個孩子結點struct Node* pNextBrother; // 指向其下一個兄弟結點DataType data; // 結點中的數據域
}Node;

注意:第一個孩子默認是父親向下最左邊的子樹。

無論父親有多少孩子節點,child指向左邊第一個孩子。

通過這個設計思路我們能從根出發找到任意一個子樹,下面是舉例:

(1)


要找到D首先利用(第一個孩子節點指針)找到B,再利用(指向下一個兄弟指針)兩次找到D。

(2)


同理,找到L應該走下面這個路線。具體過程不再重復。

2.二叉樹的概念及結構

2.1二叉樹的概念

二叉樹是一種常見的樹狀數據結構,它由一組稱為節點的元素組成,每個節點最多有兩個子節點,分別稱為左子節點和右子節點。根節點是樹的頂部節點,它沒有父節點,而其他節點都有且只有一個父節點。

二叉樹是樹中比較特殊的一種,因為二叉樹中每個父節點最多只能有兩個子樹(左子樹和右子樹)。

從這張圖中我們知道:

1. 二叉樹不存在度大于2的結點
2. 二叉樹的子樹有左右之分,次序不能顛倒,因此二叉樹是有序樹

2.2特殊的二叉樹

實際應用中不是所有的二叉樹我們都研究,主要有下面兩種特殊的二叉樹:

1. 滿二叉樹:一個二叉樹,如果每一個層的結點數都達到最大值,則這個二叉樹就是滿二叉樹。也就是說,如果一個二叉樹的層數為K,且結點總數是 2^k-1 ,則它就是滿二叉樹。
2. 完全二叉樹:完全二叉樹是效率很高的數據結構,完全二叉樹是由滿二叉樹而引出來的。對于深度為K 的,有n個結點的二叉樹,當且僅當其每一個結點都與深度為K的滿二叉樹中編號從1至n的結點一一對 應時稱之為完全二叉樹。 要注意的是 滿二叉樹是一種特殊的完全二叉樹.

2.3二叉樹的存儲結構

二叉樹的儲存結構有兩種,正好對應之前我們學過的兩種數據結構:順序表(底層是數組)和鏈表(底層是結構體和指針)。所以,二叉樹儲存分為順序存儲和鏈式存儲。

2.3.1順序存儲

順序存儲一般只適用于完全二叉樹,因為使用順序存儲要利用到數組。而使用數組存儲二叉樹就必須要用到完全二叉樹(因為這樣會滿足一定的規律,后續會詳細講),所以使用非完全二叉樹時會造成數組空間的浪費(有時需要跳過數組中的一些元素)。如下圖:

假設4位置是空的,數組中E位置就必須空出來,這樣就造成了空間的浪費。

2.3.2鏈式存儲

3.二叉樹的順序結構和實現

3.1二叉樹的順序結構

二叉樹的順序結構是指將二叉樹的節點按照順序存儲在一個數組中,同時利用數組索引來表示節點之間的父子關系。

具體而言,假設二叉樹的根節點存儲在數組索引為0的位置,任意節點在數組中的索引為i,則它的左子節點存儲在索引2i+1的位置,右子節點存儲在索引2i+2的位置。這種方式可以有效地節省空間,但在插入和刪除節點時可能需要進行數組的移動操作,因此不適用于經常需要插入和刪除操作的情況。

在用數組存儲的時候根部用0表示,每個子樹都要-1.這樣就滿足前面所說的:

左子樹=父親*2+1;右子樹=父親*2+2.

3.2堆的概念和結構

在數據結構中,堆(Heap)是一種特殊的樹形數據結構,它滿足以下兩個特性:

  1. 堆是一個完全二叉樹(Complete Binary Tree)。這意味著除了最底層之外的所有層都被完全填滿,并且最底層從左到右填充。

  2. 堆中的每個節點的值都必須滿足堆屬性(Heap Property):“對于大頂堆(或最大堆),父節點的值大于或等于其子節點的值;對于小頂堆(或最小堆),父節點的值小于或等于其子節點的值”。

基于上述屬性,堆可以分為兩種常見的類型:大堆和小堆。

下圖就是大堆和小堆最直觀的區別:

3.3堆的特點

無論是小堆還是大堆,祖先的位置和子樹的位置之間都有一定的關系(這一特點對后面關于對的代碼實現很有用處)。

特點如下:

1.堆是以數組作為底層的,存儲數據的邏輯結構是一顆二叉樹,存儲數據的物理結構是數組。

2.想象中的二叉樹上的每一個數據都是數組中的一個元素,都有自己的編號(就是作為數組元素的下標 )。

3.二叉樹上的數據的編號有一定的規律:

(1)由父親找兒子:左子樹=父親*2+1;右子樹=父親*2+2

(2)由兒子找父親:父親=(兒子-1)/2.(這個公式容易記錯先-1還是先/2的話可以自己畫一個二叉樹的圖去判斷)。

3.4堆的代碼實現

3.4.1堆代碼實現中的算法問題

堆代碼中的算法問題主要解決一個有序的堆中突然插入或刪除一個數據導致有序的堆變為無序的堆的問題,我們需要涉及到的算法有向上調整算法和向下調整算法

下面我們先來講向上調整算法:

3.4.1.1向上調整算法

這個算法主要解決的是當一個有序的堆插入新數據時怎樣重新調整至有序的狀態。

比如下面這個小堆:


它現在是一個有序的小堆存儲,如果這個時候我們插入一個新的編號為9的數據14,就像下圖:


此時它明顯不符合小堆的概念,這個時候就需要向上調整。

小堆排序中向上調整的方法與思路是:

子樹與父親進行比較,如果子樹比父親小,則與父親交換;若子樹比父親大,則不需要變動。

思路用圖來表示就是:


最后,添加了一個數據的無序堆再次變成了有序的小堆。

向上調整算法用代碼來表示就是:

//向上調整算法
void AdjustUp(DataType*a,int child)
{assert(a);int parent = (child - 1) / 2;while (child > 0){if (a[child] <= a[parent]){int tmp = a[child];a[child] = a[parent];a[parent] = tmp;child = parent;parent = (child - 1) / 2;}else{break;}}
}

3.4.1.2向下調整算法

向下調整算法比向上調整算法稍微復雜一點,用于接下來刪除堆頂數據。

還是利用我們剛剛的圖像,如果直接刪除堆頂數據就像這樣:

對于剩下數據的重排就比較麻煩,因為動的數據很多(不像向上調整算法一樣只要動一列從子孫到祖先的疏忽),非常麻煩。

這個時候科學家想到一種很妙的方式,思路如下:

1.先將堆頂元素和堆的最后一個元素互換。

2.刪除堆的最后一個元素(相當于刪除了先前的堆頂)。

3.利用向下調整算法將堆重新恢復至小堆。

我們先來講向下調整算法部分。

小堆排序中向下調整的方法與思路是(剛好與向上調整算法相反):

根數據與子樹進行比較,如果根數據比子樹大,則與子樹交換;若根數據比子樹大,則不需要變動。

代碼實現如下:

//向下調整算法
void AdjustDown (Heap* hp,DataType parent)
{assert(hp);//假設左孩子比右孩子要小int child = parent * 2 + 1;while (child < hp->size){// 找出小的那個孩子if (child + 1 <  hp->size && hp->a[child + 1] < hp->a[child]){++child;}if (hp->a[parent] >= hp->a[child]){int tmp =hp->a[parent];hp->a[parent] = hp->a[child];hp->a[child] = tmp;parent = child;child = parent * 2 + 1;}else{break;}}
}

需要注意的是:這個代碼與向上調整算法的不同之處在于使用了假設法先假設左子樹比右子樹小,假設成功就不需要調整;若假設失敗,在使數組的下標++即可

3.4.2堆代碼 Heap.h

堆底層還是數組,所以構成堆的結構體組成與順序表相似。

#pragma once
#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
#include<stdbool.h>typedef int DataType;typedef struct Heap
{DataType* a;int size;int Capacity;
}Heap;// 堆的構建
void HeapInit(Heap* hp);
// 堆的銷毀
void HeapDestory(Heap* hp);
// 堆的插入
void HeapPush(Heap* hp, DataType x);
// 堆的刪除
void HeapPop(Heap* hp);
// 取堆頂的數據
DataType HeapTop(Heap* hp);
// 堆的數據個數
int HeapSize(Heap* hp);
// 堆的判空
bool HeapEmpty(Heap* hp);
//展示堆的數據
void HeapPrint(Heap* hp);//向上調整算法
void AdjustUp(Heap*hp,DataType parent);//向下調整算法
void AdjustDown(Heap* hp, DataType parent);
3.4.3堆代碼Heap.c

答題的實現方式與順序表比較類似,不同的地方主要有兩個:

(1)堆數據的添加和堆數據的刪除需要引入向上調整算法和向下調整算法。

(2)堆數據的刪除需要三步:

? ? ? ? ? 1.堆頂堆底數據交換。(引入臨時變量)

? ? ? ? ? 2.刪除堆底數據。(size--即可)

? ? ? ? ? 3.向下調整算法。(調用寫好的函數即可)

#include"Heap.h"//向上調整算法
void AdjustUp(DataType*a,int child)
{assert(a);int parent = (child - 1) / 2;while (child > 0){if (a[child] <= a[parent]){int tmp = a[child];a[child] = a[parent];a[parent] = tmp;child = parent;parent = (child - 1) / 2;}else{break;}}
}//向下調整算法
void AdjustDown (Heap* hp,DataType parent)
{assert(hp);//假設左孩子比右孩子要小int child = parent * 2 + 1;while (child < hp->size){// 找出小的那個孩子if (child + 1 <  hp->size && hp->a[child + 1] < hp->a[child]){++child;}if (hp->a[parent] >= hp->a[child]){int tmp =hp->a[parent];hp->a[parent] = hp->a[child];hp->a[child] = tmp;parent = child;child = parent * 2 + 1;}else{break;}}
}// 堆的構建
void HeapInit(Heap* hp)
{hp->Capacity = hp->size = 0;hp->a = NULL;
}// 堆的銷毀
void HeapDestory(Heap* hp)
{assert(hp);free(hp->a);hp->a = NULL;hp->Capacity = hp->size = 0;
}// 堆的插入
void HeapPush(Heap* hp, DataType x)
{//擴容if (hp->Capacity == hp->size){int NewCapacity = hp->Capacity == 0 ? 4 : 2 * hp->Capacity;DataType* tmp = (DataType*)realloc(hp->a,NewCapacity*sizeof(DataType));hp->Capacity = NewCapacity;hp->a = tmp;}if (hp->size == 0){hp->a[0] = x;hp->size++;}else{hp->a[hp->size] = x;hp->size++;//數據添加AdjustUp(hp->a, hp->size - 1);}
}// 堆的刪除(刪除堆頂數據)
void HeapPop(Heap* hp)
{//1.交換int tmp = hp->a[0];hp->a[0] = hp->a[hp->size - 1];hp->a[hp->size-1]=tmp;//2.刪除末尾數據hp->size--;//3.向下調整算法AdjustDown(hp,0);
}// 取堆頂的數據
DataType HeapTop(Heap* hp)
{assert(hp);return hp->a[0];
}// 堆的數據個數
int HeapSize(Heap* hp)
{return hp->size;
}// 堆的判空
bool HeapEmpty(Heap* hp)
{if (hp->size == 0){return false;}else{return true;}
}//展示堆的數據
void HeapPrint(Heap* hp)
{for (int i=0;i<hp->size;i++){printf("%d ",hp->a[i]);}
}
3.4.4堆代碼test.c
#include"Heap.h"int main()
{Heap hp;// 堆的構建HeapInit(&hp);// 堆的插入HeapPush(&hp,1);HeapPush(&hp, 6);HeapPush(&hp, 48);HeapPush(&hp, 45);HeapPush(&hp, 17);HeapPush(&hp, 172);HeapPush(&hp, 51);HeapPush(&hp, 5);//展示堆的數據HeapPrint(&hp);// 堆的刪除HeapPop(&hp);printf("\n");HeapPrint(&hp);// 堆的銷毀HeapDestory(&hp);return 0;
}

效果如下:


3.5堆的應用(TOP K問題)

3.5.1舉例

我們知道數據結構的學習就是為了處理數據,堆排序的一個重要應用就是可以解決TOP K問題,下面我們簡單介紹一下TOP K問題。

TOP K問題可以舉出下面的例子:

(1)(實例)假設王者榮耀有幾億個玩家,其中有一般的人都玩過亞瑟這個角色。現在系統要根據每位玩家亞瑟的數據排出全國前十的亞瑟。

(2)現在隨機生成10000個數字,要把最大的5個數字篩選出來。

我們將上述王者榮耀的例子換成整型數字,一億個數字=4億字節≈4G。現在面試官問你能否用1G的內存找出最大的10個數字?解法如下:

將1億個數字分為4堆,分4次堆排序,每次篩選出最大的10個數字,最后再對40個數字來一次堆排序,挑出最大的10個數字即可。

現在面試官改變問法:能否用1KB的內存完成上述的事情呢?方法如下:

先用前10個數字組成一個小堆,之后遍歷1億個數字,將這1億個數字都與小堆頂的數字比較。如果比小堆頂的數字大,就交換,同時再利用向下調整算法重新恢復成小堆;如果比小堆頂的數字還小就不做變動。最后留在小堆的10個數字就是最大的10個數字。這樣就可以利用最少的空間辦最大的事。

3.5.2解決問題代碼

現在我們寫出一段代碼,來證明我們的思路確實有效果。這段代碼大致意思是:給一個長度為10000的數組,生成10000個隨機數,利用代碼篩選出最大的10個數。

Heap.h , Heap.c大體上與堆代碼一致,需要修改的地方如下:

(1)新寫了一個HeapAdd函數,當遍歷數組時用這個函數來判斷數組元素是否要與堆頂元素進行交換。代碼如下:

void HeapAdd(Heap* hp, int x)
{if (x >= hp->a[0]){hp->a[0] = x;AdjustDown(hp, 0);}}

(2)主函數需要改動

#include"Heap.h"int main()
{Heap hp;srand((unsigned)time(NULL));int arr[10000] = { 0 };//隨機生成10000個數字for (int i = 0; i < 10000; i++){arr[i] = rand()%10000+ i;}HeapInit(&hp);//將前10個數組成一個小堆for (int j = 0; j < 10; j++){HeapPush(&hp, arr[j]);}for (int l = 10; l < 10000; l++){HeapAdd(&hp, arr[l]);}HeapPrint(&hp);return 0;
}

效果如下:


但是我們不知道找到的這10個數是不是最大的,我們要再對代碼進行小小的改動:

#include"Heap.h"int main()
{Heap hp;srand((unsigned)time(NULL));int arr[10000] = { 0 };//隨機生成10000個數字for (int i = 0; i < 10000; i++){arr[i] = rand()%10000+ i;//改動位置if (i % 999 == 0){arr[i] += 1000000;}}HeapInit(&hp);//將前10個數組成一個小堆for (int j = 0; j < 10; j++){HeapPush(&hp, arr[j]);}for (int l = 10; l < 10000; l++){HeapAdd(&hp, arr[l]);}HeapPrint(&hp);return 0;
}

我們看到留了很多空白的地方就是改動位置,主要是為了創造出最大的10個數(其他的數都在20000以內,而這10個數在100000以上)。效果如下:

證明我們的代碼沒有問題,TOP K問題被成功解決。?

4.二叉樹的鏈式結構和實現

二叉樹鏈式結構的基礎結構體代碼是:

typedef struct BinaryNode
{int data;struct BinaryNode* LeftTree;struct BinaryNode* RightTree;
}BN;

每棵樹都有左右兩顆子樹,使用兩個指針分別指向他們即可。

4.1手搓鏈式二叉樹

?為了方便操作,我們手動創造一個二叉樹,后續方便寫有關二叉樹的函數。

假設我們要實現下面這個鏈式二叉樹:

BN* BuyNode(int a)
{BN* NewCapacity = (BN*)malloc(sizeof(BN));if (NewCapacity == NULL){perror("malloc fail");}NewCapacity->data = a;NewCapacity->LeftTree = NULL;NewCapacity->RightTree = NULL;return NewCapacity;
}BN* CreatCapacity()
{BN* Node1 = BuyNode(1);BN* Node2 = BuyNode(2);BN* Node3 = BuyNode(3);BN* Node4 = BuyNode(4);BN* Node5 = BuyNode(5);BN* Node6 = BuyNode(6);Node1->LeftTree = Node2;Node1->RightTree = Node4;Node2->LeftTree = Node3;Node4->LeftTree = Node5;Node4->RightTree = Node6;return Node1;
}

主函數內代碼如下:

int main()
{BN *pret=CreatCapacity();PreOrder(pret);return 0;
}

4.2遍歷鏈式二叉樹

二叉樹有前序遍歷,中序遍歷,后序遍歷和層序遍歷。一般將前序,中序,后序放在一起講。下面我們就利用上面的二叉樹圖完成下面幾個遍歷。

前序,中序,后序遍歷代碼簡單且比較相似,但是理解起來有一定難度,需要用到遞歸的知識。

4.2.1前序遍歷
void PreOrder(BN* root)
{if (root == NULL){printf("N ");return;}printf("%d ", root->data);PreOrder(root->LeftTree);PreOrder(root->RightTree);
}

前序遍歷是? 根---->左邊的子樹----->右邊的子樹?這個順序來遍歷的。按照補全空子樹的順序(如下圖)應該是:1 2 3 N N N 4 5 N N 6 N N

如右圖:紅色箭頭代表的是指向節點,綠色的線代表指向節點為空時函數return?到上一層函數并且換向(從指向左子樹換成指向右子樹,即代碼中PreOrder(root->LeftTree)?和 PreOrder(root->RightTree)?的變換)。

用遞歸的思路解釋就是下面這張圖:(紅線表示跳入函數綠線表示跳出函數

當遇到NULL的時候函數會沿著綠線向上跳一層,當本函數的所有代碼都走完了之后,該函數也會向上跳一層(這就是為什么出現了綠線連續的情況)。

4.2.2中序遍歷

中序遍歷在代碼上的改變非常小,就是把printf和指向左子樹的PreOrder調換了一下順序。

void MiddleOrder(BN* root)
{if (root == NULL){printf("N ");return;}MiddleOrder(root->LeftTree);printf("%d ", root->data);MiddleOrder(root->RightTree);
}

按照中序遍歷的思路輸出的內容應該是:N 3 N 2 N 1 N 5 N 4 N 6 N?

遞歸的思路如下圖:

4.2.3后序遍歷

后序遍歷代碼如下:

void BackOrder(BN* root)
{if (root == NULL){printf("N ");return;}BackOrder(root->LeftTree);BackOrder(root->RightTree);printf("%d ", root->data);
}

按照后序遍歷的思路輸出的內容應該是:N N 3 N 2 N N 5 N N 6 4 1

遞歸的思路如下圖:

三種遍歷方式最大的不同就在于執行printf的順序不相同。

最后三種遍歷的所有代碼以及運行結果如下:

#include"BinaryTree.h"BN* BuyNode(int a)
{BN* NewCapacity = (BN*)malloc(sizeof(BN));if (NewCapacity == NULL){perror("malloc fail");}NewCapacity->data = a;NewCapacity->LeftTree = NULL;NewCapacity->RightTree = NULL;return NewCapacity;
}BN* CreatCapacity()
{BN* Node1 = BuyNode(1);BN* Node2 = BuyNode(2);BN* Node3 = BuyNode(3);BN* Node4 = BuyNode(4);BN* Node5 = BuyNode(5);BN* Node6 = BuyNode(6);Node1->LeftTree = Node2;Node1->RightTree = Node4;Node2->LeftTree = Node3;Node4->LeftTree = Node5;Node4->RightTree = Node6;return Node1;
}void PreOrder(BN* root)
{if (root == NULL){printf("N ");return;}printf("%d ", root->data);PreOrder(root->LeftTree);PreOrder(root->RightTree);
}void MiddleOrder(BN* root)
{if (root == NULL){printf("N ");return;}MiddleOrder(root->LeftTree);printf("%d ", root->data);MiddleOrder(root->RightTree);
}void BackOrder(BN* root)
{if (root == NULL){printf("N ");return;}BackOrder(root->LeftTree);BackOrder(root->RightTree);printf("%d ", root->data);
}int TreeSize(BN* root)
{return root == NULL ? 0 :TreeSize(root->LeftTree ) + TreeSize(root->RightTree) + 1;
}int TreeLeafSize(BN* root)
{if (root = NULL){return 0;}if (root->LeftTree == NULL && root->RightTree == NULL){return 1;}return TreeLeafSize(root->LeftTree) + TreeLeafSize(root->RightTree);}int TreeHeight()
{}int main()
{BN *pret=CreatCapacity();PreOrder(pret);printf("\n");MiddleOrder(pret);printf("\n");BackOrder(pret);return 0;
}


4.2.4層序遍歷

層序遍歷顧名思義就是一層一層地讀取數據遍歷二叉樹。其概念如下:

層序遍歷是二叉樹遍歷的一種方法,也稱為廣度優先遍歷。它按照樹的層級順序逐層遍歷樹的節點。具體操作是從根節點開始,先將根節點加入隊列,然后依次取出隊列中的節點,訪問該節點,并將其左右子節點加入隊列。重復該過程,直到隊列為空為止。這樣可以保證按照從上到下,從左到右的順序遍歷整個二叉樹。

像下面這種遍歷順序:


這種遍歷方式不能再用遞歸的方式寫出,聰明的科學家想到了用隊列實現層序遍歷。具體的操作流程如下:

圖上展示的只是一次循環,完整循環我們來用文字表示:

第1步: 1? ? ? ? ? (打印1)

第2步: 1 2 4? ? (1的左右子樹入列)

第3步: 2 4? ? ? ?(打印并刪除1)

第4步: 2 4?3? ? (2的非空子樹入列)

第5步: 4 3? ? ? ?(打印并刪除2)

第6步: 4 3 5 6 (4的左右子樹入列)

第7步: 3 5 6? ? (打印并刪除4)

第8步: 5 6? ? ? ?(打印并刪除3)

第9步:6? ? ? ? ? ?(打印并刪除5)

第10步:NULL? (打印并刪除6)

代碼實現如下:

void LayerOrder(BN* root,Queue*q)
{QueueInit(q);if (root == 0){return ;}else{QueuePush(q,root);printf("%d ", root->data);}while (q->size){if (root->LeftTree != NULL){QueuePush(q, root->LeftTree);}if (root->RightTree!= NULL){QueuePush(q, root->RightTree);}QueuePop(q);if (q->Fir != NULL){root = q->Fir->root;}else{break;}printf("%d ", QueueFront(q));}printf("\n");}

?入列函數與我們之前寫的函數有一些改動,一個參數改成了指針,修改之后的入列函數如下:

// 隊尾入隊列
void QueuePush(Queue* q, BN*Node)
{assert(q);QNode* Newnode = (QNode*)malloc(sizeof(QNode));Newnode->val = Node->data;Newnode->root = Node;//判斷隊列是否有元素if (q->Las == NULL){q->Fir = q->Las = Newnode;}else{q->Las->next = Newnode;q->Las = Newnode;//和下面的代碼一個意思/*q->Las->next = Newnode;q->Las =q->Las->next;*/}q->size++;
}

我們來對層序遍歷進行檢驗:

現在再添加一個數據“7”(加在3的右子樹上):

BN* CreatCapacity()
{BN* Node1 = BuyNode(1);BN* Node2 = BuyNode(2);BN* Node3 = BuyNode(3);BN* Node4 = BuyNode(4);BN* Node5 = BuyNode(5);BN* Node6 = BuyNode(6);BN* Node7 = BuyNode(7);Node1->LeftTree = Node2;Node1->RightTree = Node4;Node2->LeftTree = Node3;Node4->LeftTree = Node5;Node4->RightTree = Node6;Node3->RightTree = Node7;return Node1;
}

結果如下:

4.3鏈式二叉樹的其他函數

4.3.1二叉樹節點個數,葉子節點個數,高度函數

除了遍歷,鏈式二叉樹還有其他函數。

二叉樹節點個數,二叉樹葉子節點個數,二叉樹高度函數

代碼如下:


int TreeSize(BN* root)
{return root == NULL ? 0 :TreeSize(root->LeftTree ) + TreeSize(root->RightTree) + 1;
}int TreeLeafSize(BN* root)
{if (root == NULL){return 0;}if (root->LeftTree == NULL && root->RightTree == NULL){return 1;}return TreeLeafSize(root->LeftTree) + TreeLeafSize(root->RightTree);}int count = 1; int max = 0;
int TreeHeight(BN*root)
{if (root == NULL){return  0;}if (root->LeftTree == NULL && root->RightTree == NULL){if (max <= count){max = count;}return 1;}count++;TreeHeight(root->LeftTree);TreeHeight(root->RightTree);return 1;}

這3個函數主要用的就是遞歸的方法,在判斷條件和返回值上注意一下就能分析出來。

1.二叉樹節點個數函數:當節點不為空時就重復調用函數(先是根節點的左子樹再是右子樹)并且每次+1,相當于把二叉樹遍歷一遍。

2.二叉樹葉子節點函數。葉子就是外層兩個子樹都為空的節點。當根就是空的時候,直接返回0,根的左右子樹均為0的時候代表已經到葉子處了,于是返回1.再運用遞歸的手法不斷深入遍歷二叉樹直至找到所有葉子為止,并返回葉子的個數。

3.二叉樹高度函數。二叉樹的高度就是最深的節點的深度。要創建兩個全局變量來記錄每次最深節點的深度,最后篩選出最大的那個。深入方式就是每調用一次函數count++,當走到葉子時(到底的時候),就做判斷,如果count比max大就賦值給max,最后就能取得最大值了。

測試效果如下:

再改動一下數據,將數據7掛在6的右子樹,將8掛在7的左子樹,測試結果如下:

4.3.2二叉樹第K層節點個數

代碼如下:

int TreeLevelKSize(BN* root, int k)
{assert(k > 0);    //斷言從根節點算起不存在第0層的節點if (root == NULL)return 0;if (k == 1)return 1;return  TreeLevelKSize(root->LeftTree, k - 1)+   TreeLevelKSize(root->RightTree, k - 1);//從根節點開始,分別向左子樹和右子樹深入k-1層;//當二叉樹的最深處都比所要查找的層數大則使用assert報錯。
}
4.3.3銷毀二叉樹

代碼如下:?

// 二叉樹銷毀
void BinaryTreeDestory(BN* root)
{if (root == NULL)return;BinaryTreeDestory(root->LeftTree);BinaryTreeDestory(root->RightTree);free(root);
}


結語:二叉樹一些基本內容幾乎都在本篇文章中,謝謝閱讀,如有錯誤請批評指正?

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

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

相關文章

k-means聚類算法

在Python中&#xff0c;可以使用scikit-learn庫來實現k-means聚類算法。scikit-learn是一個強大的機器學習庫&#xff0c;提供了許多算法的實現&#xff0c;包括k-means聚類。 以下是使用scikit-learn實現k-means聚類的基本步驟&#xff1a; 安裝scikit-learn&#xff1a; 如果…

一文掌握JavaScript 中類的用法

文章導讀&#xff1a;AI 輔助學習前端&#xff0c;包含入門、進階、高級部分前端系列內容&#xff0c;當前是 JavaScript 的部分&#xff0c;瑤琴會持續更新&#xff0c;適合零基礎的朋友&#xff0c;已有前端工作經驗的可以不看&#xff0c;也可以當作基礎知識回顧。 這篇文章…

SQL常用語句--模糊查詢LIKE

like模糊查詢&#xff0c;支持%和下劃線匹配&#xff0c;%匹配多個字符&#xff0c;_下劃線&#xff1a;任意一個字符&#xff0c;示例&#xff1a; 1&#xff09;查詢名字中含有張的學生信息 select * from student where sname like ‘%張%’&#xff1b; 2&#xff09;查…

MySQL統計字符長度:CHAR_LENGTH(str)

對于SQL表&#xff0c;用于計算字符串中字符數的最佳函數是 CHAR_LENGTH(str)&#xff0c;它返回字符串 str 的長度。 另一個常用的函數 LENGTH(str) 在這個問題中也適用&#xff0c;因為列 content 只包含英文字符&#xff0c;沒有特殊字符。否則&#xff0c;LENGTH() 可能會返…

django使用fetch上傳文件

在上一篇文章中&#xff0c;我包裝了fetch方法&#xff0c;使其攜帶cookie。但是之前fetch傳遞的是json數據&#xff0c;現在有了一個上傳文件的需求&#xff0c;因此需要進行修改&#xff1a; const sendRequest (url, method, data) > {const csrftoken Cookies.get(cs…

discuz如何添加主導航

大家好&#xff0c;今天教大家怎么樣給discuz添加主導航。方法其實很簡單&#xff0c;大家跟著我操作既可。一個網站的導航欄是非常重要的&#xff0c;一般用戶進入網站的第一印象就是看網站的導航欄。如果大家想看效果的話可以搜索下網創有方&#xff0c;或者直接點擊查看效果…

精選免費在線工具與資源推薦20240531

精選免費在線工具與資源推薦 引言 在互聯網高速發展的今天&#xff0c;我們身處一個信息爆炸的時代。為了更好地應對工作和學習中的挑戰&#xff0c;我們時常需要借助各種工具和資源來提高效率。幸運的是&#xff0c;網絡上存在著大量免費且高效的在線工具和資源&#xff0c;…

Google VertexAI API 接入

import vertexai import os #此步非常重要&#xff0c;否則無法訪問&#xff0c;去GCP創建服務賬號密鑰。 os.environ["GOOGLE_APPLICATION_CREDENTIALS"] "服務賬號json格式key" from vertexai.generative_models import GenerativeModel, Part # TO…

嵌入式學習——4——c++ 結構體+類

1、數據類型 基本數據類型&#xff1a;char、int 、float、 double、string、bool 構造數據類型&#xff1a;數組、指針、結構體、共用體、枚舉、類 2、引用 引用就是 別名 數據類型 &引用名 同類型的變量名 &#xff08;&引用符號&#xff09; int a 10;int &…

標準發布 | 反滲透和納濾水處理膜修復再利用技術要求

本文件由浙江大學、中華環保聯合會水環境治理專業委員會提出。 本文件由中華環保聯合會歸口。 本文件主編單位&#xff1a;浙江大學、河南一膜環保技術有限公司、安徽精高水處理有限公司、國能龍源環保有限公司、湖南沁森高科新材料有限公司。 本文件參編單位&#xff1a;深…

rtl8723DU移植 android4.4 4418

一、 linux 的移植。 首先編譯一遍確保沒有問題。 將驅動拷貝到 driver/net/wireless 目錄下。 使用的是&#xff1a; 改寫 makefile Kconfig 去改寫 8723 的makefile 設置menuconfig 使能固有的 庫。 使能USB部分 ieee 部分 編譯一遍 有報錯。 解決&#xff1a; …

MATLAB R2024a下載安裝

目錄 前言 下載安裝教程 資源 前言 一個很好的資源&#xff0c;我自己是一遍過了&#xff0c;非常順利&#xff0c;不說廢話&#xff0c;直接上菜。 下載安裝教程 MATLAB R2024a下載及安裝演示_嗶哩嗶哩_bilibili 資源 MATLAB R2024a網盤資源

Java對sqlserver表的image字段圖片讀取和輸出本地

Java代碼實現對sqlserver數據庫表的image字段圖片的讀取&#xff0c;和輸出存儲到本地 由于表image字段圖片存的內容是二進制值&#xff0c;如何輸出保存到本地&#xff1a; 代碼示例&#xff1a;&#xff08;注&#xff1a;連接sqlserver數據庫需配置其驅動文件&#xff09; …

Linux【工具 03】Telnet服務安裝使用(安全性較差 非特殊情況盡量不要使用)

Telnet服務安裝使用 1.說明2.安裝 1.說明 現在大多數服務器的遠程連接基本都是走的SSH協議&#xff0c;也就是常用的22端口【默認端口可以自行調整】。在升級OpenSSH的過程中要卸載老版本&#xff0c;安裝新版本&#xff0c;也就意味著升級過程中如果出現了問題&#xff0c;且…

Spring MVC 應?分層

什么是應用分層 引用分層是一種軟件開發思想 將應用程序分為N個層次每個層次負責各個職責 其中MVC是常見的設計模式這就是應用分層的具體體現 目前主流的開發方式是前后段分離后端開發工程師不再需要關注前端的實現,對此就需要分為表現層&#xff0c;數據層&#xff0c;業務邏…

FPGA DMA IP核使用指南

摘要 本文旨在介紹FPGA中DMA(Direct Memory Access)IP核的使用,包括其基本框架、測試代碼編寫以及仿真波形的分析。DMA是一種允許外圍設備直接與內存進行數據交換的技術,無需CPU的介入,從而提高了數據傳輸的效率。 1. 引言 在現代FPGA設計中,DMA IP核因其…

Day15—圖像爬蟲與簡單處理

圖像爬蟲是一種專門用于從互聯網上下載圖像的網絡爬蟲。除了文本內容,圖像也是網站中的重要組成部分,它們可以用于多種目的,如圖像識別、內容分析、數據備份等。 環境準備 首先,確保你的環境中已安裝Python和必要的庫。如果沒有安裝Pillow庫,可以通過以下命令安裝:pip in…

Leetcode刷題筆記6

34. 在排序數組中查找元素的第一個和最后一個位置 34. 在排序數組中查找元素的第一個和最后一個位置 - 力扣&#xff08;LeetCode&#xff09; 解法一&#xff1a;暴力查找 [1, 2, 3, 3, 3, 4, 5] t 3 從前往后掃描暴力查找&#xff0c;最壞情況下O(N) 優化 利用數組有序的…

【LLM多模態】綜述Visual Instruction Tuning towards General-Purpose Multimodal Model

note 文章目錄 note論文1. 論文試圖解決什么問題2. 這是否是一個新的問題3. 這篇文章要驗證一個什么科學假設4. 有哪些相關研究&#xff1f;如何歸類&#xff1f;誰是這一課題在領域內值得關注的研究員&#xff1f;5. 論文中提到的解決方案之關鍵是什么&#xff1f;6. 論文中的…

隨想錄 Day45 1049. 最后一塊石頭的重量 II 494. 目標和 474.一和零

隨想錄 Day45 1049. 最后一塊石頭的重量 II 494. 目標和 474.一和零 1049. 最后一塊石頭的重量 II 題目鏈接 有一堆石頭&#xff0c;用整數數組 stones 表示。其中 stones[i] 表示第 i 塊石頭的重量。 每一回合&#xff0c;從中選出任意兩塊石頭&#xff0c;然后將它們一起…