什么是二叉樹
我們了解了什么是樹(一對(duì)多的邏輯結(jié)構(gòu)),那么對(duì)于二叉樹簡(jiǎn)單地理解,滿足以下兩個(gè)條件的樹就是二叉樹:
- 本身是有序樹;
- 樹中包含的各個(gè)節(jié)點(diǎn)的度不能超過 2,即只能是 0、1 或者 2;
例如,圖 1a) 就是一棵二叉樹,而圖 1b) 則不是。
二叉樹的性質(zhì)
經(jīng)過前人的總結(jié),二叉樹具有以下幾個(gè)性質(zhì):
- 二叉樹中,第 i 層最多有 2i-1 個(gè)結(jié)點(diǎn)。
- 如果二叉樹的深度為 K,那么此二叉樹最多有 2K-1 個(gè)結(jié)點(diǎn)。
- 二叉樹中,終端結(jié)點(diǎn)數(shù)(葉子結(jié)點(diǎn)數(shù))為 n0,度為 2 的結(jié)點(diǎn)數(shù)為 n2,則 n0=n2+1。
- 性質(zhì) 3 的計(jì)算方法為:對(duì)于一個(gè)二叉樹來說,除了度為 0 的葉子結(jié)點(diǎn)和度為 2 的結(jié)點(diǎn),剩下的就是度為 1 的結(jié)點(diǎn)(設(shè)為 n1),那么總結(jié)點(diǎn) n=n0+n1+n2。
同時(shí),對(duì)于每一個(gè)結(jié)點(diǎn)來說都是由其父結(jié)點(diǎn)分支表示的,假設(shè)樹中分枝數(shù)為 B,那么總結(jié)點(diǎn)數(shù) n=B+1。而分枝數(shù)是可以通過 n1 和 n2 表示的,即 B=n1+2n2。所以,n 用另外一種方式表示為 n=n1+2n2+1。
兩種方式得到的 n 值組成一個(gè)方程組,就可以得出 n0=n2+1。
二叉樹還可以繼續(xù)分類,衍生出滿二叉樹和完全二叉樹。
滿二叉樹
如果二叉樹中除了葉子結(jié)點(diǎn),每個(gè)結(jié)點(diǎn)的度都為 2,則此二叉樹稱為滿二叉樹。
如圖 2 所示就是一棵滿二叉樹。
滿二叉樹除了滿足普通二叉樹的性質(zhì),還具有以下性質(zhì):
- 滿二叉樹中第 i 層的節(jié)點(diǎn)數(shù)為 2n-1 個(gè)。
- 深度為 k 的滿二叉樹必有 2k-1 個(gè)節(jié)點(diǎn) ,葉子數(shù)為 2k-1。
- 滿二叉樹中不存在度為 1 的節(jié)點(diǎn),每一個(gè)分支點(diǎn)中都兩棵深度相同的子樹,且葉子節(jié)點(diǎn)都在最底層。
- 具有 n 個(gè)節(jié)點(diǎn)的滿二叉樹的深度為 log2(n+1)。
完全二叉樹
如果二叉樹中除去最后一層節(jié)點(diǎn)為滿二叉樹,且最后一層的結(jié)點(diǎn)依次從左到右分布,則此二叉樹被稱為完全二叉樹。
如圖 3a) 所示是一棵完全二叉樹,圖 3b) 由于最后一層的節(jié)點(diǎn)沒有按照從左向右分布,因此只能算作是普通的二叉樹。
完全二叉樹除了具有普通二叉樹的性質(zhì),它自身也具有一些獨(dú)特的性質(zhì),比如說,n 個(gè)結(jié)點(diǎn)的完全二叉樹的深度為 ?log2n?+1。
- ?log2n? 表示取小于 log2n 的最大整數(shù)。例如,?log24? = 2,而 ?log25? 結(jié)果也是 2。
對(duì)于任意一個(gè)完全二叉樹來說,如果將含有的結(jié)點(diǎn)按照層次從左到右依次標(biāo)號(hào)(如圖 3a)),對(duì)于任意一個(gè)結(jié)點(diǎn) i ,完全二叉樹還有以下幾個(gè)結(jié)論成立:
- 當(dāng) i>1 時(shí),父親結(jié)點(diǎn)為結(jié)點(diǎn) [i/2] 。(i=1 時(shí),表示的是根結(jié)點(diǎn),無父親結(jié)點(diǎn))
- 如果 2i>n(總結(jié)點(diǎn)的個(gè)數(shù)) ,則結(jié)點(diǎn) i 肯定沒有左孩子(為葉子結(jié)點(diǎn));否則其左孩子是結(jié)點(diǎn) 2i 。
- 如果 2i+1>n ,則結(jié)點(diǎn) i 肯定沒有右孩子;否則右孩子是結(jié)點(diǎn) 2i+1 。
順序存儲(chǔ)(數(shù)組)實(shí)現(xiàn)二叉樹
二叉樹的順序存儲(chǔ),指的是使用順序表(數(shù)組)存儲(chǔ)二叉樹。需要注意的是,順序存儲(chǔ)只適用于完全二叉樹。換句話說,只有完全二叉樹才可以使用順序表存儲(chǔ)。因此,如果我們想順序存儲(chǔ)普通二叉樹,需要提前將普通二叉樹轉(zhuǎn)化為完全二叉樹。
- 滿二叉樹也可以使用順序存儲(chǔ)。要知道,滿二叉樹也是完全二叉樹,因?yàn)樗鼭M足完全二叉樹的所有特征。
普通二叉樹轉(zhuǎn)完全二叉樹的方法很簡(jiǎn)單,只需給二叉樹額外添加一些節(jié)點(diǎn),將其"拼湊"成完全二叉樹即可。如圖所示:
解決了二叉樹的轉(zhuǎn)化問題,接下來學(xué)習(xí)如何順序存儲(chǔ)完全(滿)二叉樹。
完全二叉樹的順序存儲(chǔ),僅需從根節(jié)點(diǎn)開始,按照層次依次將樹中節(jié)點(diǎn)存儲(chǔ)到數(shù)組即可。例如,存儲(chǔ)所示的完全二叉樹:
其存儲(chǔ)狀態(tài)如下圖所示:
由此,我們就實(shí)現(xiàn)了完全二叉樹的順序存儲(chǔ)。
不僅如此,從順序表中還原完全二叉樹也很簡(jiǎn)單。我們知道,完全二叉樹具有這樣的性質(zhì),將樹中節(jié)點(diǎn)按照層次并從左到右依次標(biāo)號(hào)(1,2,3,...),若節(jié)點(diǎn) i 有左右孩子,則其左孩子節(jié)點(diǎn)為 2i,右孩子節(jié)點(diǎn)為 2i+1。此性質(zhì)可用于還原數(shù)組中存儲(chǔ)的完全二叉樹。
實(shí)現(xiàn)代碼如下:
定義二叉樹結(jié)構(gòu)
#define MAX_NODE_SIZE 100 // 二叉樹的最大結(jié)點(diǎn)數(shù)
typedef int ElemType;//樹結(jié)點(diǎn)的數(shù)據(jù)類型
typedef ElemType HjBiTree[MAX_NODE_SIZE];//定義樹
ElemType Nil = 0;//定義一個(gè)空值,為空則沒有節(jié)點(diǎn)
typedef struct {
int level;//層級(jí)
int order;//在當(dāng)前層的序號(hào)
} Position;
1、初始化二叉樹
void initBinaryTree(HjBiTree biTree){
for (int i = 0; i < MAX_NODE_SIZE; i++) {
//每個(gè)節(jié)點(diǎn)置空
biTree[i] = Nil;
}
}
2、按層序給二叉樹的節(jié)點(diǎn)賦值
Status createBinaryTree(HjBiTree biTree){
int i = 0;
for (; i < 10; i++) {
biTree[i] = i + 1;
if (i != 0 && biTree[i] != Nil) {
if (biTree[(i + 1) / 2 - 1] == Nil) {
return ERROR;
}
}
}
while (i < MAX_NODE_SIZE) {
biTree[i] = Nil;
I++;
}
return OK;
}
3、層序遍歷
void printfNode(ElemType data){
printf("%d ",data);
}
void levelOrderTraverse(HjBiTree biTree){
printf("層序遍歷:");
for (int i = 0; i < MAX_NODE_SIZE; i++) {
ElemType data = biTree[I];
if (data != Nil) {
printfNode(data);
}
}
printf("\n");
}
4、前序遍歷
void preTraverse(HjBiTree biTree, int index){
if (index >= MAX_NODE_SIZE || index < 0) {
return;
}
ElemType data = biTree[index];
if (data != Nil) {
printfNode(data);
preTraverse(biTree, index * 2 + 1);
preTraverse(biTree, index * 2 + 2);
}
}
void preOrderTraverse(HjBiTree biTree){
printf("前序遍歷:");
preTraverse(biTree, 0);
printf("\n");
}
5、中序遍歷
void inTraverse(HjBiTree biTree, int index){
if (index >= MAX_NODE_SIZE || index < 0) {
return;
}
ElemType data = biTree[index];
if (data != Nil) {
inTraverse(biTree, index * 2 + 1);
printfNode(data);
inTraverse(biTree, index * 2 + 2);
}
}
void inOrderTraverse(HjBiTree biTree){
printf("中序遍歷:");
inTraverse(biTree, 0);
printf("\n");
}
6、后序遍歷
void postTraverse(HjBiTree biTree, int index){
if (index >= MAX_NODE_SIZE || index < 0) {
return;
}
ElemType data = biTree[index];
if (data != Nil) {
postTraverse(biTree, index * 2 + 1);
postTraverse(biTree, index * 2 + 2);
printfNode(data);
}
}
void postOrderTraverse(HjBiTree biTree){
printf("后序遍歷:");
postTraverse(biTree, 0);
printf("\n");
}
7、判斷樹是否是空樹
Status isBiTreeEmpty(HjBiTree biTree){
//根結(jié)點(diǎn)為空,則二叉樹為空
return biTree[0] == Nil;
}
8、獲取二叉樹的深度
int getBiTreeDepth(HjBiTree biTree){
int i = MAX_NODE_SIZE - 1;
for (; i >= 0; i --) {
if (biTree[i] != Nil) {
break;;
}
}
int j = -1;
do {
j++;
} while (pow(2, j) <= i);
return j;
}
9 返回處于位置pos的結(jié)點(diǎn)值
//(層從1開始,序號(hào)從1開始)
ElemType getNodeValue(HjBiTree biTree,Position pos){
int index = pow(2, pos.level - 1) - 2;
index += pos.order;
if (index >= MAX_NODE_SIZE || index < 0) {
return Nil;
}
return biTree[index];
}
10、獲取二叉樹根結(jié)點(diǎn)的值
Status getRootNode(HjBiTree biTree, ElemType *data){
if (isBiTreeEmpty(biTree)) {
return ERROR;
}
*data = biTree[0];
return OK;
}
11、給處于位置pos的結(jié)點(diǎn)賦值
Status setValueForPos(HjBiTree biTree,Position pos,ElemType data){
int index = pow(2, pos.level - 1) - 2;
index += pos.order;
if (index >= MAX_NODE_SIZE || index < 0) {
return ERROR;
}
biTree[index] = data;
return OK;
}
12、獲取節(jié)點(diǎn)的雙親的值
ElemType getParentValue(HjBiTree biTree, ElemType data){
if (biTree[0] == Nil) {
return Nil;
}
for (int i = 1 ; i < MAX_NODE_SIZE; i++) {
if (biTree[i] == data) {
return biTree[ (i + 1) / 2 - 1];
}
}
return Nil;
}
13、獲取某個(gè)結(jié)點(diǎn)的左孩子的值
ElemType getLeftChild(HjBiTree biTree,ElemType data){
if (biTree[0] == Nil) {
return Nil;
}
for (int i = 0; i < MAX_NODE_SIZE; i++) {
if (biTree[i] == data) {
int leftChildIndex = i * 2 + 1;
if (leftChildIndex < MAX_NODE_SIZE) {
return biTree[leftChildIndex];
}
}
}
return Nil;
}
14、獲取某個(gè)結(jié)點(diǎn)的右孩子的值
ElemType getRightChild(HjBiTree biTree,ElemType data){
if (biTree[0] == Nil) {
return Nil;
}
for (int i = 0; i < MAX_NODE_SIZE; i++) {
if (biTree[i] == data) {
int rightChildIndex = i * 2 + 2;
if (rightChildIndex < MAX_NODE_SIZE) {
return biTree[rightChildIndex];
}
}
}
return Nil;
}
其它輔助代碼
#include "stdlib.h"
#include "math.h"
#include "time.h"
#define OK 1
#define ERROR 0
#define TRUE 1
#define FALSE 0
typedef int Status;
int main(int argc, const char * argv[]) {
printf("---二叉樹順序存儲(chǔ)結(jié)構(gòu)實(shí)現(xiàn)!---\n");
Status iStatus;
Position p;
ElemType e;
HjBiTree biTree;
initBinaryTree(biTree);
createBinaryTree(biTree);
printf("建立二叉樹后,樹空否?%d(1:是 0:否) \n",isBiTreeEmpty(biTree));
printf("樹的深度 = %d\n",getBiTreeDepth(biTree));
levelOrderTraverse(biTree);
preOrderTraverse(biTree);
inOrderTraverse(biTree);
postOrderTraverse(biTree);
p.level = 3;
p.order = 2;
e = getNodeValue(biTree, p);
printf("第%d層第%d個(gè)結(jié)點(diǎn)的值: %d\n",p.level,p.order,e);
iStatus = getRootNode(biTree, &e);
if (iStatus) {
printf("二叉樹的根為:%d\n",e);
}
else {
printf("樹為空,無根!\n");
}
//向樹中3層第2個(gè)結(jié)點(diǎn)位置上結(jié)點(diǎn)賦值99
e = 99;
setValueForPos(biTree, p, e);
//獲取樹中3層第2個(gè)結(jié)點(diǎn)位置結(jié)點(diǎn)的值是多少:
e = getNodeValue(biTree,p);
printf("第%d層第%d個(gè)結(jié)點(diǎn)的值: %d\n",p.level,p.order,e);
//找到e這個(gè)結(jié)點(diǎn)的雙親;
printf("結(jié)點(diǎn)%d的雙親為:%d ",e,getParentValue(biTree, e));
//找到e這個(gè)結(jié)點(diǎn)的左右孩子;
printf("左右孩子分別為:%d,%d\n",getLeftChild(biTree, e),getRightChild(biTree, e));
printf("\n");
return 0;
}
輸出結(jié)果
鏈?zhǔn)酱鎯?chǔ)(鏈表)實(shí)現(xiàn)二叉樹
如圖所示,此為一棵普通的二叉樹,若將其采用鏈?zhǔn)酱鎯?chǔ),則只需從樹的根節(jié)點(diǎn)開始,將各個(gè)節(jié)點(diǎn)及其左右孩子使用鏈表存儲(chǔ)即可。
對(duì)應(yīng)的鏈?zhǔn)酱鎯?chǔ)結(jié)構(gòu)如下圖所示:
由圖可知,采用鏈?zhǔn)酱鎯?chǔ)二叉樹時(shí),其節(jié)點(diǎn)結(jié)構(gòu)由 3 部分構(gòu)成:
- 指向左孩子節(jié)點(diǎn)的指針(Lchild);
- 節(jié)點(diǎn)存儲(chǔ)的數(shù)據(jù)(data);
-
指向右孩子節(jié)點(diǎn)的指針(Rchild);
二叉樹鏈?zhǔn)焦?jié)點(diǎn).png
實(shí)現(xiàn)代碼如下:
定義二叉樹結(jié)構(gòu)
typedef char ElemType;
ElemType Nil = ' ';
typedef struct BiTreeNode {
ElemType data;
struct BiTreeNode *leftChild, *rightChild;
}HjTreeNode, *HjBiTree;
1、初始化二叉樹
void initBinaryTree(HjBiTree *biTree){
*biTree = NULL;
}
2、創(chuàng)建二叉樹
void createBinaryTree(HjBiTree *biTree, String str, int *index){
ElemType data = str[*index];
*index = *index + 1;
//判斷是不是空節(jié)點(diǎn)
if (data == '#') {
*biTree = NULL;
}
else {
*biTree = malloc(sizeof(HjTreeNode));
if (!*biTree) {
exit(OVERFLOW);
}
(*biTree)->data = data;
(*biTree)->leftChild = NULL;
(*biTree)->rightChild = NULL;
//構(gòu)造左子樹
createBinaryTree(&(*biTree)->leftChild, str, &(*index));
//構(gòu)造右子樹
createBinaryTree(&(*biTree)->rightChild, str, &(*index));
}
}
3、銷毀二叉樹
void destoryBinaryTree(HjBiTree *biTree){
if (!*biTree) {
return;
}
//銷毀左子樹
if ((*biTree)->leftChild) {
destoryBinaryTree(&(*biTree)->leftChild);
}
//銷毀右子樹
if ((*biTree)->rightChild) {
destoryBinaryTree(&(*biTree)->rightChild);
}
free(*biTree);
*biTree = NULL;
}
4、獲取二叉樹的深度
int getBinaryTreeDepth(HjBiTree biTree){
if (!biTree) {
return 0;
}
int i = 0;
int j = 0;
if (biTree->leftChild) {
i = getBinaryTreeDepth(biTree->leftChild);
}
if (biTree->rightChild) {
j = getBinaryTreeDepth(biTree->rightChild);
}
return i > j ? i + 1 : j + 1;
}
5、前序遍歷
void preTraverse(HjBiTree biTree){
if (!biTree) {
return;
}
ElemType data = biTree->data;
if (data != Nil) {
printfNode(data);
preTraverse(biTree->leftChild);
preTraverse(biTree->rightChild);
}
}
6、中序遍歷
void inTraverse(HjBiTree biTree){
if (!biTree) {
return;
}
ElemType data = biTree->data;
if (data != Nil) {
inTraverse(biTree->leftChild);
printfNode(data);
inTraverse(biTree->rightChild);
}
}
7、后序遍歷
void postTraverse(HjBiTree biTree){
if (!biTree) {
return;
}
ElemType data = biTree->data;
if (data != Nil) {
postTraverse(biTree->leftChild);
postTraverse(biTree->rightChild);
printfNode(data);
}
}
其它輔助代碼
#include "stdlib.h"
#include "math.h"
#include "time.h"
#include "string.h"
#define OK 1
#define ERROR 0
#define TRUE 1
#define FALSE 0
//狀態(tài)碼
typedef int Status;
#define MAX_SIZE 100
//定義二叉樹字符串,0號(hào)單元存長度
typedef char String[MAX_SIZE];
Status assignString(String str, char *chars){
long length = strlen(chars);
if (length > MAX_SIZE) {
return ERROR;
}
str[0] = length;
for (int i = 1; i <= length; i++) {
// str[i] = chars[i - 1];
str[i] = *(chars + i - 1);
}
return OK;
}
int main(int argc, const char * argv[]) {
printf("---二叉樹鏈?zhǔn)酱鎯?chǔ)結(jié)構(gòu)實(shí)現(xiàn)!---\n");
HjBiTree biTree;
String str;
int index = 1;
initBinaryTree(&biTree);
assignString(str,"ABDH#K###E##CFI###G#J##");
printf("創(chuàng)建二叉樹:%s",str);
createBinaryTree(&biTree, str, &index);
printf("\n二叉樹的深度:%d",getBinaryTreeDepth(biTree));
printf("\n前序遍歷二叉樹:");
preTraverse(biTree);
printf("\n中序遍歷二叉樹:");
inTraverse(biTree);
printf("\n后序遍歷二叉樹:");
postTraverse(biTree);
printf("\n");
return 0;
}
輸出結(jié)果
如有不對(duì)的地方,請(qǐng)指正,謝謝您的閱讀~