數據結構詳解——堆與二叉樹

?

目錄

  • 樹的概念
  • 樹的表示方法
  • 二叉樹的概念
  • 特殊的二叉樹
  • 二叉樹的性質
  • 二叉樹的存儲結構
    • 順序存儲
    • 鏈式存儲
  • 堆的概念與結構
  • 堆的性質
  • 堆的實現
    • 堆的初始化
    • 入堆
    • 堆的擴容
    • 向上調整算法
    • 出堆(最頂端元素)
    • 向下調整算法
  • 二叉樹的實現
    • 二叉樹的創建
    • 二叉樹的銷毀
    • 二叉樹的元素數
    • 二叉樹的葉子節點數
    • 二叉樹第k層結點個數
    • 二叉樹中尋找值為x的結點
    • 二叉樹的前中后序遍歷
    • 二叉樹的層序遍歷
    • 判斷二叉樹是否為完全二叉樹

樹的概念

樹是一種非線性的數據結構,它是由n(n >= 0)個有限結點組成的一個具有層次關系的集合。它看起來像一個倒掛的樹。
二叉樹最上面的結點被稱為根節點,根節點沒有前驅結點,也就是根結點之上沒有節點。除根結點外,其余結點被分成M(M>0)個互不相交的集合T1、T2、……、Tm,其中每一個集合Ti(1<= i <= m)又是一棵結構與樹類似的子樹。每棵子樹的根結點有且只有一個前驅,可以有0個或多個后繼。樹形結構中的子樹之間不能有交集,也就是一個節點不能有兩個前驅。樹是遞歸定義的。在這里插入圖片描述

樹有很多引申出的概念,下面是一些常用的。

名稱意義
結點的度一個結點含有的子樹的個數稱為該結點的度
葉結點或終端結點度為0的結點稱為葉結點
非終端結點或分支結點度不為0的結點
雙親結點或父結點若一個結點含有子結點,則這個結點稱為其子結點的父結點
孩子結點或子結點一個結點含有的子樹的根結點稱為該結點的子結點
兄弟結點具有相同父結點的結點互稱為兄弟結點
樹的度一棵樹中,最大的結點的度稱為樹的度
結點的層次從根開始定義起,根為第1層,根的子結點為第2層,以此類推
樹的高度或深度樹中結點的最大層次
堂兄弟結點雙親在同一層的結點互為堂兄弟
結點的祖先從根到該結點所經分支上的所有結點
子孫以某結點為根的子樹中任一結點都稱為該結點的子孫
森林由m(m>0)棵互不相交的樹的集合稱為森林

樹的表示方法

由于樹是非線性的數據結構,表示樹時,既要保存樹的值,又要保存結點之間的關系,有一種非常巧妙的方法叫做左孩子右兄弟表示法,定義一個結構體,里面定義一個指向其第一個孩子的指針,再定義一個指向其兄弟的指針。

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

在這里插入圖片描述

二叉樹的概念

二叉樹是一種特殊的樹,它是結點的一個有限集合,該結點要么為空,要么就是由一個根結點加上兩顆分別被稱為左子樹和右子樹的二叉樹組成。二叉樹不存在度大于2的結點,二叉樹的子樹有左右之分,因此二叉樹是有序樹。在這里插入圖片描述

特殊的二叉樹

  1. 滿二叉樹:一個二叉樹,如果每一個層的結點數都達到最大值,則這個二叉樹就是滿二叉樹。也就是說,如果一個二叉樹的層數為K,且結點總數是2^k -1 ,則它就是滿二叉樹。

  2. 完全二叉樹:完全二叉樹是效率很高的數據結構,完全二叉樹是由滿二叉樹而引出來的。對于深度為K的,有n個結點的二叉樹,當且僅當其每一個結點都與深度為K的滿二叉樹中編號從1至n的結點一一對應時稱之為完全二叉樹。 要注意的是滿二叉樹是一種特殊的完全二叉樹。

二叉樹的性質

  1. 若規定根結點的層數為1,則一棵非空二叉樹的第i層上最多有2^(i-1)個結點。

  2. 若規定根結點的層數為1,則深度為h的二叉樹的最大結點數是2^h-1。

  3. 對任何一棵二叉樹, 如果度為0其葉結n0, 度為2的分支結點個數為 n2,則有n0=n2+1,如何去理解這句話呢,其實很簡單,在二叉樹最開始只有一個根節點時,度為0的就有1個,度為2的有0個,每增加一個度為1的就會消滅一個度為0的,產生一個度為1的和一個度為0的,度為0的數目不會變,也因此度為1的結點數我們無法求得,而每增加一個度為2的,就會消滅一個度為0的,增加一個度為2的和兩個度為0的,一前一后相抵消的話相當于度為0的因為度為2的加一自己也加了一。于是乎度為0的永遠比度為2的大1。

  4. 若規定根結點的層數為1,具有n個結點的滿二叉樹的深度,h=log_2(n+1)。 (log_2(n+1)是log以2為底,n+1的對數)

  5. 對于具有n個結點的完全二叉樹,如果按照從上至下從左至右的數組順序對所有結點從0開始編號,則對于序號為i的結點有:

(1)若i>0,i位置結點的雙親序號:(i-1)/2;i=0,i為根結點編號,無雙親結點

(2) 若i>0,i位置的左孩子節點為:2i+1,右孩子結點為:2i+2。

(3)高度為h的完全二叉樹,節點數量范圍為[2^(h-1), 2^h-1]。

二叉樹的存儲結構

順序存儲

順序存儲就是利用數組來存儲,這種存儲方式只適用于完全二叉樹,普通二叉樹會有空間上的浪費,畢竟既然使用數組了,數與數之間必然就要有強邏輯,只有完全二叉樹這種父結點與孩子結點都可一一對應的這種才合適,普通的就要將空出的結點的空間浪費。使用數組表示完全二叉樹的這種數據結構我們稱為堆(Heap)。

鏈式存儲

鏈式存儲就是利用來鏈來存儲,也就是使用指針,使用指針的話就相對好處理很多,對于二叉鏈來說,鏈表中的每個節結點都由三個變量組成,數據和左右指針。

typedef int BTDataType;
// 二叉鏈
struct BinaryTreeNode
{struct BinTreeNode* left;   // 指向當前結點左孩子struct BinTreeNode* right;  // 指向當前結點右孩子BTDataType data;            // 當前結點值域
};

堆的概念與結構

如果有一個關鍵碼的集合K = {0,1, 2,…,n-1},把它的所有元素按完全二叉樹的順序存儲方式存儲在一個一維數組中,并滿足:i<= 2i+1 且 i<= 2i+2(i >= 2i+1 且 i>= 2i+2) i = 0,1,2…,則稱為小堆(或大堆)。將根結點最大的堆叫做最大堆或大根堆,根結點最小的堆叫做最小堆或小根堆。

堆的性質

堆中某個結點的值總是不大于或不小于其父結點的值

堆總是一棵完全二叉樹。

堆的實現

#define _CRT_SECURE_NO_WARNINGS 1#include<stdio.h>#include<stdlib.h>#include<assert.h>#include<errno.h>#include<string.h>typedef int HPDataType;
typedef struct Heap
{HPDataType* _a;int _size;int _capacity;
}Heap;
//堆的創建
void HeapInit(Heap* php);
// 堆的銷毀
void HeapDestory(Heap* hp);
// 堆的插入
void HeapPush(Heap* hp, HPDataType x);
// 堆的刪除
void HeapPop(Heap* hp);
// 取堆頂的數據
HPDataType HeapTop(Heap* hp);
// 堆的數據個數
int HeapSize(Heap* hp);
// 堆的判空
int HeapEmpty(Heap* hp);
//擴容
void Expansion(Heap* hp);
//交換
void swap(int* a, int* b);
//向上交換
void raise(HPDataType* a, int n);
//向下交換
void down(HPDataType* a, int n);

定義了一個結構體用來保存堆的頭指針,堆的尺寸和堆的存儲元素數量

#include"heap.h"void HeapInit(Heap* php)
{assert(php);Heap* ptr = (HPDataType*)calloc(8, sizeof(HPDataType));if (ptr == NULL){perror("HeapInit:calloc");return;}php->_a = ptr;php->_capacity = 8;php->_size = 0;
}void HeapDestory(Heap* hp)
{assert(hp);free(hp->_a);hp->_a = NULL;hp->_capacity = 0;hp->_size = 0;
}void HeapPush(Heap* hp, HPDataType x)
{assert(hp);Expansion(hp);hp->_a[hp->_size] = x;++hp->_size;raise(hp->_a,hp->_size);
}void raise(HPDataType* a, int n)//向上調整算法
{int child = n;int parents = child / 2;while (child > 1){if (a[child - 1] > a[parents - 1]){swap(&(a[child - 1]), &(a[parents - 1]));child = parents;parents = child / 2;}else{break;}}
}void Expansion(Heap* hp)
{assert(hp);if (hp->_capacity == hp->_size){Heap* ptr = (Heap*)realloc(hp->_a, sizeof(HPDataType) * hp->_capacity * 2);if (ptr == NULL){perror("Expansion:realloc");return;}hp->_a = ptr;hp->_capacity *= 2;}
}void HeapPop(Heap* hp)
{assert(hp && hp->_size);swap(hp->_a, &(hp->_a[hp->_size - 1]));--hp->_size;down(hp->_a, hp->_size);
}void down(HPDataType* a, int n)//向下調整算法
{int parents = 1;int child = parents * 2;while (child <= n){if (child + 1 <= n && a[child - 1] < a[child]){++child;}if (a[child - 1] > a[parents - 1]){swap(&(a[child - 1]), &(a[parents - 1]));parents = child;child = parents * 2;}else{break;}}
}void swap(int* a, int* b)//交換算法
{int ex = *a;*a = *b;*b = ex;
}int HeapSize(Heap* hp)
{assert(hp);return hp->_size;
}int HeapEmpty(Heap* hp)
{assert(hp);if (hp->_size == 0){return 0;}return 1;
}HPDataType HeapTop(Heap* hp)
{assert(hp && HeapEmpty(hp));return hp->_a[0];
}

堆的初始化

void HeapInit(Heap* php)
{assert(php);Heap* ptr = (HPDataType*)calloc(8, sizeof(HPDataType));if (ptr == NULL){perror("HeapInit:calloc");return;}php->_a = ptr;php->_capacity = 8;php->_size = 0;
}

由于堆是一個數組,為了充分的利用空間,我采用動態數組的方式,對于傳入的堆指針,calloc動態開辟內存,檢驗之后設置好頭指針,尺寸和元素數量就行了。

入堆

void HeapPush(Heap* hp, HPDataType x)
{assert(hp);Expansion(hp);hp->_a[hp->_size] = x;++hp->_size;raise(hp->_a,hp->_size);
}

對于進入的元素,首先用擴容函數進行擴容,之后將其插入到隊尾,再將存儲的元素數++,之后進入排序函數對插入后的堆進行排序。

堆的擴容

void Expansion(Heap* hp)
{assert(hp);if (hp->_capacity == hp->_size){Heap* ptr = (Heap*)realloc(hp->_a, sizeof(HPDataType) * hp->_capacity * 2);if (ptr == NULL){perror("Expansion:realloc");return;}hp->_a = ptr;hp->_capacity *= 2;}
}

常規的動態數組的擴容方式。

向上調整算法

void raise(HPDataType* a, int n)//向上調整算法
{int child = n;int parents = child / 2;while (child > 1){if (a[child - 1] > a[parents - 1]){swap(&(a[child - 1]), &(a[parents - 1]));child = parents;parents = child / 2;}else{break;}}
}

對于之前的入堆函數,我將數據插入到隊尾,這時要對堆進行排序,就要用到向上調整函數,其原理就要利用到堆的性質,即父節點要比子節點大(大堆)或者小(小堆),對于插入到堆的尾部的元素,我們就是要一步一步地找到其對應的父節點,與之比較,如果父節點比他大(大堆)或比他小(小堆),就交換兩個數,直到父節點比他小(大堆)或者大(小堆)為止,我們就完成了排序。對于具體的實現方法,首先我們需要明白的是,堆的理想樣式與數組是有一些出入的,對于堆,我們會讓其從1開始計數,而數組是從0開始計數的,這兩種計數方式計算父子結點的方式不同。對于從1開始的堆,父節點 = 子節點 / 2,子節點1 = 父節點 * 2 ,子節點2 = 父節點 * 2 + 1。對于從0開始的堆,父節點 = (子節點 - 1) / 2,子節點1 = 父節點 * 2 + 1 ,子節點2 = 父節點 * 2 + 2。所以在排序時我們要注意到這一點,我這里采用的是將父節點和子節點按一般堆的表示方式即從1開始計數,在數組計算時將其減一表示正確的位置,其實也可以在一開始就按從0開始計算,這樣就不用減一了,按習慣來。引入兩個新變量后,將child表示為新入堆元素的位置,parents表示為child的父節點,,之后用while循環反復執行檢驗父子節點并交換的操作,當插入元素來到根結點或者找到比它大(大堆)或者小(小堆)時就停止。

出堆(最頂端元素)

void HeapPop(Heap* hp)
{assert(hp && hp->_size);swap(hp->_a, &(hp->_a[hp->_size - 1]));--hp->_size;down(hp->_a, hp->_size);
}

刪除堆中最頂端的元素,也就是根結點,這其實是有點麻煩的,對于堆來說,刪除其末端的元素,其實是最為簡單的,下一次存入時覆蓋就行,他不會破壞整體的排序。而對于頂端,如果用一般的處理方式,貿然用memmove函數或循環等方法將后面的元素向前移動來覆蓋是不行的,堆中的數據排序有著強邏輯,整體移動這么多元素會破壞這種關系,所以我先將頂段元素與末端元素交換,將size–,之后我們用向下調整算法對其進行排序,完成頂端元素的刪除。

向下調整算法

void down(HPDataType* a, int n)//向下調整算法
{int parents = 1;int child = parents * 2;while (child <= n){if (child + 1 <= n && a[child - 1] < a[child]){++child;}if (a[child - 1] > a[parents - 1]){swap(&(a[child - 1]), &(a[parents - 1]));parents = child;child = parents * 2;}else{break;}}
}

對于出堆(頂端元素)函數中,我們需要用到向下調整函數,在入堆操作中,我使用向上調整算法,將入堆元素與其父節點進行比較,向上交換。向下調整函數與之相反,交換首尾元素后,將頂端元素(根結點)與其子節點進行比較,如果比其小(大堆)或者大(小堆)就交換,直到比子節點大(大堆)或者小(小堆)或者超出堆的元素數量為止。具體的實現方式與向上調整方式類似,有一點需要特別注意,在向上調整算法中,元素只需要找到其父節點就行,父節點就一個,而在向下調整算法中,與之比較的子節點有兩個,那我們就要保證與其比較的是最大(大堆)或者最小(小堆),所以在正式進行父子結點比較時,還要加一個if語句選出最大的子節點,與此同時也要時刻注意堆的越界情況,child和child+1都要<=n。

二叉樹的實現

#include "queue.h"#include<stdio.h>#include<stdlib.h>#include<string.h>#include<assert.h>#include<errno.h>typedef char BTDataType;typedef struct BinaryTreeNode
{BTDataType _data;struct BinaryTreeNode* _left;struct BinaryTreeNode* _right;
}BTNode;// 通過前序遍歷的數組"ABD##E#H##CF##G##"構建二叉樹
BTNode* BinaryTreeCreate(BTDataType* a, int* pi);
// 二叉樹銷毀
void BinaryTreeDestory(BTNode** root);
// 二叉樹節點個數
int BinaryTreeSize(BTNode* root);
// 二叉樹葉子節點個數
int BinaryTreeLeafSize(BTNode* root);
// 二叉樹第k層節點個數
int BinaryTreeLevelKSize(BTNode* root, int k);
// 二叉樹查找值為x的節點
BTNode* BinaryTreeFind(BTNode* root, BTDataType x);
// 二叉樹前序遍歷 
void BinaryTreePrevOrder(BTNode* root);
// 二叉樹中序遍歷
void BinaryTreeInOrder(BTNode* root);
// 二叉樹后序遍歷
void BinaryTreePostOrder(BTNode* root);
// 層序遍歷
void BinaryTreeLevelOrder(BTNode* root);
// 判斷二叉樹是否是完全二叉樹
int BinaryTreeComplete(BTNode* root);
#include"BinaryTree.h"BTNode* BinaryTreeCreate(BTDataType* a, int* pi)
{if (a[(*pi)] == '#' || !a){++(*pi);return NULL;}BTNode* newnode = (BTNode*)calloc(1, sizeof(BTNode));newnode->_data = a[(*pi)++];newnode->_left = BinaryTreeCreate(a, pi);newnode->_right = BinaryTreeCreate(a, pi);return newnode;
}void BinaryTreeDestory(BTNode** root)
{if (!*root)return;BinaryTreeDestory(&((*root)->_left));BinaryTreeDestory(&((*root)->_right));free(*root);*root = NULL;
}int BinaryTreeSize(BTNode* root)
{if (!root)return 0;return BinaryTreeSize(root->_left) + BinaryTreeSize(root->_right) + 1;
}int BinaryTreeLeafSize(BTNode* root)
{if (!root)return 0;if (!root->_left && !root->_right)return 1;return BinaryTreeLeafSize(root->_left) + BinaryTreeLeafSize(root->_right);
}int BinaryTreeLevelKSize(BTNode* root, int k)
{--k;if (!root){return 0;}if (k == 0)return 1;return BinaryTreeLevelKSize(root->_left, k) + BinaryTreeLevelKSize(root->_right, k);
}//int BTLKSize(BTNode* root, int* k)
//{
//	--*k;
//	if (*k == 0)
//		return 1;
//	int ck = *k;
//	return BTLKSize(root->_left, &ck) + BTLKSize(root->_right, k);
//}BTNode* BinaryTreeFind(BTNode* root, BTDataType x)
{if (!root)return NULL;if (root->_data == x)return root;BTNode* left = BinaryTreeFind(root->_left, x);if (left)return left;return BinaryTreeFind(root->_right, x);
}void BinaryTreePrevOrder(BTNode* root)//前
{if (!root){printf("# ");return;}printf("%c ", root->_data);BinaryTreePrevOrder(root->_left);BinaryTreePrevOrder(root->_right);
}void BinaryTreeInOrder(BTNode* root)//中
{if (!root){printf("# ");return;}BinaryTreeInOrder(root->_left);printf("%c ", root->_data);BinaryTreeInOrder(root->_right);
}void BinaryTreePostOrder(BTNode* root)//后
{if (!root){printf("# ");return;}BinaryTreePostOrder(root->_left);BinaryTreePostOrder(root->_right);printf("%c ", root->_data);
}void BinaryTreeLevelOrder(BTNode* root)
{if (!root)return;Queue q;QueueInit(&q);QueuePush(&q, root);while (!QueueEmpty(&q)){BTNode* curr = QueueFront(&q);printf("%c ", curr->_data);QueuePop(&q);if(curr->_left)QueuePush(&q, curr->_left);if(curr->_right)QueuePush(&q, curr->_right);}QueueDestory(&q);
}int BinaryTreeComplete(BTNode* root)
{Queue qu;BTNode* cur;int tag = 0;QueueInit(&qu);QueuePush(&qu, root);while (!QueueIsEmpty(&qu)){cur = QueueTop(&qu);putchar(cur->_data);if (cur->_right && !cur->_left)return 0;if (tag && (cur->_right || cur->_left))return 0;if (cur->_left)QueuePush(&qu, cur->_left);if (cur->_right)QueuePush(&qu, cur->_right);elsetag = 1;QueuePop(&qu);}QueueDestory(&qu);return 1;
}

二叉樹的創建

BTNode* BinaryTreeCreate(BTDataType* a, int* pi)
{if (a[(*pi)] == '#' || !a){++(*pi);return NULL;}BTNode* newnode = (BTNode*)calloc(1, sizeof(BTNode));newnode->_data = a[(*pi)++];newnode->_left = BinaryTreeCreate(a, pi);newnode->_right = BinaryTreeCreate(a, pi);return newnode;
}

在二叉樹的實現過程中需要時刻用到兩路歸并的思想,也就是分成兩路的遞歸,傳入的參數是字符串數組和計數用的整形指針,他在初始時解引用的值為0,以此來計數。對于遞歸函數,其開頭當然是寫遞歸的結束條件,當數組為字符’#‘或’\0’時結束遞歸('#'表示空結點),返回NULL。之后就是創建結點,給結點賦值,之后引用函數本身給左右子節點地址賦值以此形成遞歸。

二叉樹的銷毀

void BinaryTreeDestory(BTNode** root)
{if (!*root)return;BinaryTreeDestory(&((*root)->_left));BinaryTreeDestory(&((*root)->_right));free(*root);*root = NULL;
}

對于二叉樹的銷毀,傳參有兩種形式,一種是傳根節點的指針,一種是傳根節點的二級指針,這兩者的區別是傳二級指針的話可以在函數內就完成指針free后的置空,而傳一級指針就不行,需要手動置空,我這里用的是二級指針,用二級指針的話就要注意解引用。這個函數也是一個二路歸并函數,先判斷遞歸結束的條件(指針為空),引用自己對左右指針進行銷毀,注意先要引用自己銷毀左右指針,再銷毀自己,先銷毀自己的話就i丟失了左右指針的地址了。

二叉樹的元素數

int BinaryTreeSize(BTNode* root)
{if (!root)return 0;return BinaryTreeSize(root->_left) + BinaryTreeSize(root->_right) + 1;
}

二叉樹沒法像堆一樣直接得出元素的數量,需要遍歷求和才行,這里我同樣采用二路歸并,先給出遞歸結束的條件,返回0,之后再return語句中引用自己求和就行。

二叉樹的葉子節點數

int BinaryTreeLeafSize(BTNode* root)
{if (!root)return 0;if (!root->_left && !root->_right)return 1;return BinaryTreeLeafSize(root->_left) + BinaryTreeLeafSize(root->_right);
}

葉子節點就是沒有子節點的結點,同樣的,遞歸先給出遞歸結束的條件,這里有兩種情況,一種是遇到了左右子節點都為空節點的葉子節點返回1,一種是遇到了空節點返回0,這里有人會疑惑為什么判斷為葉子節點就返回了為什么還會遇到空結點,這是因為會有一個子節點為空另一個子節點正常的情況。之后我們同樣利用return語句計算總數。

二叉樹第k層結點個數

int BinaryTreeLevelKSize(BTNode* root, int k)
{--k;if (!root){return 0;}if (k == 0)return 1;return BinaryTreeLevelKSize(root->_left, k) + BinaryTreeLevelKSize(root->_right, k);
}

我們計算二叉樹第k層結點個數的原理就是每訪問一層就將k–,當k減為0就到達了k層,此時如果不為空結點就返回1。 首先為了避免k值過大出現非法訪問的情況,設置一個遇到空結點return 0的if語句,之后設置遞歸結束的條件,即k值減為0時返回1。

二叉樹中尋找值為x的結點

BTNode* BinaryTreeFind(BTNode* root, BTDataType x)
{if (!root)return NULL;if (root->_data == x)return root;BTNode* left = BinaryTreeFind(root->_left, x);if (left)return left;return BinaryTreeFind(root->_right, x);
}

同樣的,先給出遞歸結束的條件,當結點為空時返回NULL,當結點為要找的數時返回結點地址,之后我們先引用自己對左子樹進行尋找,返回結果賦值給left,if語句判斷left不為空就返回left,不是就返回引用自己對右子樹的尋找結果,因為已經找完了左子樹了,右子樹就算沒找到也是返回NULL,所以直接返回右子樹的結果就行。

二叉樹的前中后序遍歷

void BinaryTreePrevOrder(BTNode* root)//前
{if (!root){printf("# ");return;}printf("%c ", root->_data);BinaryTreePrevOrder(root->_left);BinaryTreePrevOrder(root->_right);
}void BinaryTreeInOrder(BTNode* root)//中
{if (!root){printf("# ");return;}BinaryTreeInOrder(root->_left);printf("%c ", root->_data);BinaryTreeInOrder(root->_right);
}void BinaryTreePostOrder(BTNode* root)//后
{if (!root){printf("# ");return;}BinaryTreePostOrder(root->_left);BinaryTreePostOrder(root->_right);printf("%c ", root->_data);
}

所謂前中后序遍歷,就是對父結點,左子樹,右子樹的訪問順序,前序遍歷就是先訪問根結點,再從左至右訪問左右子樹,中序遍歷就是根結點在中間,左子樹在左,右子樹在右,后序遍歷就是先從左至右訪問左右子樹,最后再訪問根結點。前中后序遍歷的實現很簡單,理解了之前的二路歸并就很容易實現。

二叉樹的層序遍歷

void BinaryTreeLevelOrder(BTNode* root)
{if (!root)return;Queue q;QueueInit(&q);QueuePush(&q, root);while (!QueueEmpty(&q)){BTNode* curr = QueueFront(&q);printf("%c ", curr->_data);QueuePop(&q);if(curr->_left)QueuePush(&q, curr->_left);if(curr->_right)QueuePush(&q, curr->_right);}
}

層序遍歷顧名思義就是就是按照層的順序逐層遍歷二叉樹,這相比較于二叉樹的前中后序遍歷要麻煩得多,因為二叉樹只層與層之間有鏈接,同層元素之間的并沒有強聯系。這里有一種巧妙的方法,那就是用隊列來實現。先引入隊列的數據結構,我引入了我之前完成的隊列。先將二叉樹的根結點入隊列,然后根據隊列先進先出的特點,每出一個元素就在隊列尾部按從左到右插入左右子節點,如此往復,知道隊列為空,如此就完成了二叉樹的層序遍歷。下面是圖解。在這里插入圖片描述

判斷二叉樹是否為完全二叉樹

int BinaryTreeComplete(BTNode* root)
{Queue qu;BTNode* cur;int tag = 0;QueueInit(&qu);QueuePush(&qu, root);while (!QueueIsEmpty(&qu)){cur = QueueTop(&qu);putchar(cur->_data);if (cur->_right && !cur->_left)return 0;if (tag && (cur->_right || cur->_left))return 0;if (cur->_left)QueuePush(&qu, cur->_left);if (cur->_right)QueuePush(&qu, cur->_right);elsetag = 1;QueuePop(&qu);}QueueDestory(&qu);return 1;
}

根據完全二叉樹的定義,我們明白完全二叉樹直到結束為止到要排滿的二叉樹,對于如何檢驗,我們首先要明白在這種情況下使用隊列來遍歷是一個很好的選擇,因為隊列是層序遍歷,完全二叉的檢驗也是按層序檢驗有沒有缺子節點,之后要明確何種情況下才能判斷其不是完全二叉樹,一種是出現左節點為空時,完全二叉要么左右節點都為空,要么只有右為空,所以左為空一定不是完全二叉;再者就是右為空的情況,但我們明白右為空本身并不一定就不是完全二叉,但右為空一定意味著完全二叉已經到了末尾,之后如果出現不為空的節點,就一定能判斷其不是完全二叉了,我使用了一個tag變量來記錄,將其初始化為0,第一次遇到右為0時將其賦值為1,之后當tag為1且左右子節點有一個不為空時就可以認定其不為完全二叉樹了。

?

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

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

相關文章

【藍橋杯】43694.正則問題

題目描述 考慮一種簡單的正則表達式&#xff1a; 只由 x ( ) | 組成的正則表達式。 小明想求出這個正則表達式能接受的最長字符串的長度。 例如 ((xx|xxx)x|(x|xx))xx 能接受的最長字符串是&#xff1a; xxxxxx&#xff0c;長度是 6。 輸入描述 一個由 x()| 組成的正則表達式。…

mac m1下載maven安裝并配置環境變量

下載地址&#xff1a;Download Apache Maven – Maven 解壓到一個沒有中文和空格的文件夾 輸入pwd查看安裝路徑 輸入cd返回根目錄再輸入 code .zshrc 若顯示 command not found: code你可以通過以下步驟來安裝和配置 code 命令&#xff1a; 1. 確保你已經安裝了 Visual Studio…

【自己動手開發Webpack插件:開啟前端構建工具的個性化定制之旅】

在前端開發的世界里&#xff0c;Webpack無疑是構建工具中的“明星”。它強大的功能可以幫助我們高效地打包和管理前端資源。然而&#xff0c;有時候默認的Webpack功能可能無法完全滿足我們的特定需求&#xff0c;這時候就需要自定義Webpack插件來大展身手啦&#xff01;今天&am…

移遠通信多模衛星通信模組BG95-S5獲得Skylo網絡認證,進一步拓展全球衛星物聯網市場

近日&#xff0c;全球領先的物聯網整體解決方案供應商移遠通信正式宣布&#xff0c;其支持“衛星蜂窩”多模式的高集成度NTN衛星通信模組BG95-S5已成功獲得NTN網絡運營商Skylo的網絡認證。BG95-S5也成為了獲得該認證的最新款移遠衛星通信模組。 BG95-S5模組順利獲得Skylo認證&a…

1.3.淺層神經網絡

目錄 1.3.淺層神經網絡 1.3.1 淺層神經網絡表示 1.3.2 單個樣本的向量化表示 1.3.4 激活函數的選擇 1.3.5 修改激活函數 1.3.5 練習??????? 1.3.淺層神經網絡 1.3.1 淺層神經網絡表示 之前已經說過神經網絡的結構了&#xff0c;在這不重復敘述。假設我們有如下…

StarRocks強大的實時數據分析

代碼倉庫&#xff1a;https://github.com/StarRocks/starrocks?tabreadme-ov-file StarRocks | A High-Performance Analytical Database 快速開始&#xff1a;StarRocks | StarRocks StarRocks 是一款高性能分析型數據倉庫&#xff0c;使用向量化、MPP 架構、CBO、智能物化…

2024年博客之星主題創作|貓頭虎分享AI技術洞察:2025年AI發展趨勢前瞻與展望

2025年AI發展趨勢前瞻&#xff1a;貓頭虎深度解析未來科技與商業機遇 摘要 2024年&#xff0c;AI技術迎來爆發式增長&#xff0c;AIGC、智能體、AIRPA、AI搜索、推理模型等技術不斷突破&#xff0c;AI應用場景持續擴展。2025年&#xff0c;AI將進入全新發展階段&#xff0c;W…

PG vs MySQL mvcc機制實現的異同

MVCC實現方法比較 MySQL 寫新數據時&#xff0c;把舊數據寫入回滾段中&#xff0c;其他人讀數據時&#xff0c;從回滾段中把舊的數據讀出來 PostgreSQL 寫新數據時&#xff0c;舊數據不刪除&#xff0c;直接插入新數據。 MVCC實現的原理 PG的MVCC實現原理 定義多版本的數據…

Android SystemUI——CarSystemBar視圖解析(十一)

前面文章我們已經把 CarSystemBar 從啟動到構建視圖,再到將視圖添加到 Window 的流程分析完畢,我們知道默認情況下在車載系統中只顯示頂部欄和底部欄視圖的。這里我們在前面文章的基礎上以頂部欄為例具體解析其視圖的結構。 一、頂部欄解析 通過《CarSystemBar車載狀態欄》這…

51c~ONNX~合集1

我自己的原文哦~ https://blog.51cto.com/whaosoft/11608027 一、使用Pytorch進行簡單的自定義圖像分類 ~ONNX 推理 圖像分類是計算機視覺中的一項基本任務&#xff0c;涉及訓練模型將圖像分類為預定義類別。本文中&#xff0c;我們將探討如何使用 PyTorch 構建一個簡單的自定…

每打開一個chrome頁面都會【自動打開F12開發者模式】,原因是 使用HBuilderX會影響谷歌瀏覽器的瀏覽模式

打開 HBuilderX&#xff0c;點擊 運行 -> 運行到瀏覽器 -> 設置web服務器 -> 添加chrome瀏覽器安裝路徑 chrome谷歌瀏覽器插件 B站視頻下載助手插件&#xff1a; 參考地址&#xff1a;Chrome插件 - B站下載助手&#xff08;輕松下載bilibili嗶哩嗶哩視頻&#xff09…

go語言之OOP特性和演示

一、OOP特性 Go語言中的OOP特性 結構體&#xff1a;在Go中&#xff0c;結構體用于定義復合類型&#xff0c;類似于其他語言中的類。它可以包含字段&#xff08;屬性&#xff09;和方法&#xff08;行為&#xff09;。方法&#xff1a;Go允許為任何自定義類型&#xff08;包括…

USB3020任意波形發生器4路16位同步模擬量輸出卡1MS/s頻率 阿爾泰科技

信息社會的發展&#xff0c;在很大程度上取決于信息與信號處理技術的先進性。數字信號處理技術的出現改變了信息 與信號處理技術的整個面貌&#xff0c;而數據采集作為數字信號處理的必不可少的前期工作在整個數字系統中起到關鍵 性、乃至決定性的作用&#xff0c;其應用已經深…

ChatGPT大模型極簡應用開發-目錄

引言 要理解 ChatGPT&#xff0c;了解其背后的 Transformer 架構和 GPT 技術一路的演進則變得非常必要。 ChatGPT 背后的 LLM 技術使普通人能夠通過自然語言完成過去只能由程序員通過編程語言實現的任務&#xff0c;這是一場巨大的變革。然而&#xff0c;人類通常容易高估技術…

C++入門基礎篇:域、C++的輸入輸出、缺省參數、函數重載、引用、inline、nullptr

本篇文章是對C學習前期的一些基礎部分的學習分享&#xff0c;希望也能夠對你有所幫助。 那咱們廢話不多說&#xff0c;直接開始吧&#xff01; 目錄 1.第一個C程序 2. 域 3. namespace 3.1 namespace的作用 3.2 namespace的定義 3.3 namespace使用說明 4.C的輸入和輸出…

RabbitMQ---TTL與死信

&#xff08;一&#xff09;TTL 1.TTL概念 TTL又叫過期時間 RabbitMQ可以對隊列和消息設置TTL&#xff0c;當消息到達過期時間還沒有被消費時就會自動刪除 注&#xff1a;這里我們說的對隊列設置TTL,是對隊列上的消息設置TTL并不是對隊列本身&#xff0c;不是說隊列過期時間…

先進制造aps專題二十七 西門子opcenter aps架構分析

歐美的商業aps&#xff0c;主要就是sap apo,西門子opcenter aps,達索quintiq 從技術的層面&#xff0c;西門子aps是不如sap apo的&#xff0c;但是西門子aps是西門子數字化工廠產品的核心&#xff0c;有很多特色&#xff0c;所以分析 西門子aps主要分計劃器和排產器兩個部分 計…

WPF如何跨線程更新界面

WPF如何跨線程更新界面 在WPF中&#xff0c;類似于WinForms&#xff0c;UI控件只能在UI線程&#xff08;即主線程&#xff09;上進行更新。WPF通過Dispatcher機制提供了跨線程更新UI的方式。由于WPF的界面基于Dispatcher線程模型&#xff0c;當你在非UI線程&#xff08;例如后…

ingress-nginx代理tcp使其能外部訪問mysql

一、helm部署mysql主從復制 helm repo add bitnami https://charts.bitnami.com/bitnami helm repo updatehelm pull bitnami/mysql 解壓后編輯values.yaml文件&#xff0c;修改如下&#xff08;storageclass已設置默認類&#xff09; 117 ## param architecture MySQL archit…

macOS Sequoia 15.3 beta3(24D5055b)發布,附黑、白蘋果鏡像下載地址

“ 鏡像&#xff08;黑蘋果引導鏡像、白蘋果Mac鏡像、黑蘋果虛擬機鏡像&#xff09;下載地址&#xff1a;黑果魏叔官網。” 關于macOS Sequoia 15.3 beta3&#xff08;24D5055b&#xff09;&#xff0c;以下是對其的詳細介紹&#xff1a; 一、版本發布信息 發布時間 &#xf…