当前位置: 首页 > news >正文

【数据结构】8. 二叉树

文章目录

  • 一、树的概念及结构
    • 1、树的概念
    • 2、树的相关概念
    • 3、树的表示
    • 4、树的实际运用
  • 二、二叉树的概念及结构
    • 1、二叉树的概念
    • 2、特殊的二叉树
    • 3、二叉树的性质
    • 4、二叉树的存储结构
  • 三、二叉树的顺序结构及实现
    • 1、二叉树的顺序结构
    • 2、堆的概念及结构
    • 3、堆的实现
      • 0)准备工作
      • 1)算法
        • 1.a)数据交换
        • 1.b)向上调整算法
        • 1.c)向下调整算法
        • 1.e)向上/向下调整算法时间复杂度分析
      • 2)初始化和销毁
      • 3)堆的插入
      • 4)堆的删除
      • 5)取堆顶元素
      • 6)堆的判空
    • 4、堆的应用
      • 1)堆排序
      • 2) TOP-K问题
  • 四、二叉树链式结构的实现
    • 1、二叉树的链式结构
      • 0)准备工作
      • 1)二叉树的手动创建
      • 2)二叉树的遍历(前序、中序、后序)
      • 3)节点个数
      • 4)叶子节点个数
      • 5)高度
      • 6)第k层节点数
      • 7)查找值为x的节点
    • 2、二叉树的创建和销毁
      • 1)前序遍历数组构建二叉树
      • 2)二叉树的销毁
      • 3)判断二叉树是否是完全二叉树

一、树的概念及结构

1、树的概念

树是一种非线性的数据结构,它是由n个有限结点组成一个具有层次关系的集合。把它叫做树是因为它看起来像一棵倒挂的树,根在上,叶在下

  • 树上有一个特殊的结点,称为根结点,根结点没有前驱结点。
  • 除根结点外,其余结点被分成M个互不相交的集合T1、T2、……、Tm,其中每个集合又是一棵结构与树类似的子树。每棵子树的根结点有且只有一个前驱,但可以有0个或多个后继。
  • 因此,树是递归定义的。
  • 注意: 在树形结构中,子树间是不相交的,并且除根结点外每个结点有且只有一个父结点,否则就不是树形结构。

2、树的相关概念

★结点的度: 结点的子树个数。 如上图:A的度为6。

★叶结点或终端结点: 度为0的结点。如上图:B、C、H、I等结点为叶结点。
非终端结点或分支结点: 度不为0的结点。 如上图:D、E、F、G等结点为分支结点

★双亲结点或父结点: 含有子结点的结点,这个结点称为其子结点的父结点。 如上图:A是B的父结点。
★孩子结点或子结点: 结点的子树的根结点,称为该结点的子节点。 如上图:B是A的孩子结点。
兄弟结点: 具有相同父结点的结点互称为兄弟结点; 如上图:B、C是兄弟结点

★树的度: 树中所有结点度的最大值。

★树的高度或深度: 树中结点的最大层次。如上图:树的高度为4。

堂兄弟结点: 双亲在同一层的结点互为堂兄弟。如上图:H、I互为兄弟结点。

★结点的祖先: 从根到该结点所经分支上的所有结点。如上图:A->D->H这条分支上,A、D都是H的祖先。
子孙: 以某结点为根的子树中任一结点都称为该结点的子孙。如上图:所有结点都是A的子孙。
森林: 由m棵互不相交的树的集合称为森林。

3、树的表示

树的结构体中既要保存值域,也要保存结点和结点之间的关系。实际中树有很多种表示方式:双亲表示法,孩子表示法,孩子双亲表示法以及孩子兄弟表示法等。

下面是最常用的孩子兄弟表示法

typedef int DataType;
struct TreeNode
{struct TreeNode* leftchild;//左孩子struct TreeNode* rightBrother;//右兄弟DataType val;//值
};

在这里插入图片描述

4、树的实际运用

树在实际中的运用可以用来表示文件系统的目录的树结构。

二、二叉树的概念及结构

1、二叉树的概念

一颗二叉树是由n个结点构成的集合,该集合有两种情况:为空或者由一个根结点加上两棵被称为左子树和右子树的二叉树组成。

从上图可以看出:

①:二叉树不存在度大于2的结点。
②:二叉树的子树有左右之分,次序不能颠倒,因此二叉树是有序树。

注意:对于任意的二叉树都是由以下几种情况复合而成的:

2、特殊的二叉树

  • 满二叉树:二叉树每一层的结点数都达到最大值,这个二叉树就是满二叉树。也就是说,如果一个二叉树的层数为k,且结点总数是2k-1 ,则它就是满二叉树。
  • 完全二叉树:完全二叉树是效率很高的数据结构,完全二叉树是由满二叉树而引出来的。对于深度为k,有n个结点的二叉树,当且仅当其每一个结点都与深度为K的满二叉树中编号从1至n的结点一一对应时称之为完全二叉树。 要注意的是满二叉树是一种特殊的完全二叉树。
    (可以理解为前k-1层都是满的,最后一层不满,最后一层从左到右是连续的)

3、二叉树的性质

①:若规定根结点的层数为1,则一棵非空二叉树的第 i 层上最多有 2(i-1)个结点。

②:若规定根结点的层数为1,则深度为 h 的二叉树的最大结点数是2h-1。

③★:对任何一棵二叉树,如果度为0的叶结点个数为n0n_0n0,度为2的分支结点个数为n2n_2n2,则有n0n_0n0=n2n_2n2+1。注意:总结点N = n0n_0n0+n1n_1n1+n2n_2n2。(当N为偶数时n1n_1n1=1,否则为0)

④★:若规定根结点的层数为1,具有n个结点的满二叉树的深度:h=log⁡2(n+1)\log_2 (n+1)log2(n+1)

⑤:对于具有n个结点的完全二叉树,如果按照从上至下从左至右的数组顺序对所有结点从0开始编号,则对于下标为 i 的结点有:

  • 若i>0,i 位置结点的双亲下标:(i-1)/2
  • 若2i+1<n,左孩子下标:2i+1
  • 若2i+2<n,右孩子下标:2i+2

练习题

1. 某二叉树共有 399 个结点,其中有 199 个度为 2 的结点,则该二叉树中的叶子结点数为(B)  
A. 不存在这样的二叉树  
B. 200  
C. 198  
D. 199  解析:根据性质3,叶子结点等于度为2的分支节点+1,也就是2002. 下列数据结构中,不适合采用顺序存储结构的是(A)  
A. 非完全二叉树  
B. 堆  
C. 队列  
D. 栈  解析:非完全二叉树使用顺序存储会浪费大量的空间。3. 在具有 2n 个结点的完全二叉树中,叶子结点个数为(A)  
A. n  
B. n+1  
C. n-1  
D. n/2  解析:根据性质3,总结点为偶数,n1=1,N=n0+n1+n2,n0=n2+1,所以N=2n0,叶子结点为n。4. 一棵完全二叉树的结点数位为 531 个,那么这棵树的高度为( B)  
A. 11  
B. 10  
C. 8  
D. 12  解析:根据性质2,当n=9时,满二叉树最大有512个节点,多出的节点是下一层的,因此n=10

4、二叉树的存储结构

二叉树一般可以使用两种结构存储,一种顺序结构,一种链式结构。

①:顺序存储
顺序结构存储就是使用数组来存储,一般使用数组只适合表示完全二叉树,因为不是完全二叉树会有空间的浪费。而现实中只有堆才会使用数组来存储。二叉树顺序存储在物理上是一个数组,在逻辑上是一颗二叉树。

如图所示:

下面这里使用数组来存储非完全二叉树,可以看到在数组中许多空间都没有使用到,在数组中其实都是浪费了的。

②:链式存储
二叉树的链式存储就是使用链表来表示一棵二叉树,即用链来指示元素的逻辑关系。 通常的方法是链表中每个结点由三个域组成,数据域和左右指针域,左右指针分别用来给出该结点左孩子和右孩子所在的链结点的存储地址 。链式结构又分为二叉链和三叉链,目前一般都是二叉链,如红黑树等会用到三叉链。

typedef int BTDataType;// 二叉链
struct BinaryTreeNode4
{struct BinTreeNode* left;//左孩子struct BinTreeNode* right;//右孩子BTDataType data;//值
};// 三叉链
struct BinaryTreeNode12
{struct BinTreeNode* parent;//双亲struct BinTreeNode* left;//左孩子struct BinTreeNode* right;//右孩子BTDataType data;//值
};

三、二叉树的顺序结构及实现

1、二叉树的顺序结构

一般针对完全二叉树会使用顺序结构的数组存储。
堆在物理层面上是数组,而在逻辑层面上是完全二叉树,因此可以使用数组来存储堆。

2、堆的概念及结构

如果有一个关键码的集合K = { k0k_0k0k1k_1k1k2k_2k2 , …, kn−1k_{n-1}kn1 },把它的所有元素按完全二叉树的顺序存储方式存储在一个一维数组中,并满足:KiK_iKi <= K2∗i+1K_{2*i+1}K2i+1KiK_iKi<= K2∗i+2K_{2*i+2}K2i+2(KiK_iKi >=K2∗i+1K_{2*i+1}K2i+1KiK_iKi >= K2∗i+2K_{2*i+2}K2i+2) 。 i = 0,1,2…,则称为小堆(或大堆)。
将根结点最大的堆叫做最大堆或大根堆,根结点最小的堆叫做最小堆或小根堆。

堆的性质:
①堆中某个结点的值总是大于等于或小于等于其父结点的值。
②堆是一棵完全二叉树。

如图所示:

堆的练习题:

1. 下列关键字序列为堆的是:(A)
A. 100,60,70,50,32,65
B. 60,70,65,50,32,100  
C. 65,100,70,32,50,60 
D. 70,65,100,32,50,60 
E. 32,50,100,70,65,60
F. 50,100,70,65,60,32  

选项A如图:

2. 已知小根堆为8,15,10,21,34,16,12,删除关键字8之后需重建堆,在此过程中,关键字之间的比较次数是(B)。  
A. 1  
B. 2  
C. 3  
D. 4  

如图所示:

3. 最小堆[0,3,2,5,7,4,6,8],在删除堆顶元素0之后,其结果是()  
A. [3257468]  
B. [2357468]  
C. [2345786]  
D. [2345678]

如图所示:

3、堆的实现

0)准备工作

实现堆之前我们先创建三个文件:
Heap.h:库函数头文件的声明,堆结构体的定义,方法的声明。
Heap.c:方法的实现。
test.c:方法的测试。

先来实现Heap.h文件:

#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>typedef int HPDataType;
typedef struct Heap
{HPDataType* a;//数组int size;int capacity;
}HP;//算法的声明
//交换数据
void Swap(HPDataType* p1, HPDataType* p2);//向上调整算法
void AdjustUP(HPDataType* a, int child);//向下调整算法
void AdjustDown(HPDataType* a, int n, int parent);//方法的声明
//堆的初始化与销毁
void HPInit(HP* php);
void HPDestroy(HP* php);//堆的插入
void HPPush(HP* php, HPDataType x);//堆的删除
void HPPop(HP* php);//取堆顶元素
HPDataType HPTop(HP* php);//堆的判空
bool HPEmpty(HP* php);//堆的排序
void HPSort(HPDataType* a, int n);

1)算法

1.a)数据交换
void Swap(HPDataType* p1, HPDataType* p2)
{HPDataType tmp = *p1;*p1 = *p2;*p2 = tmp;
}
1.b)向上调整算法

在这里插入图片描述

void AdjustUp(HPDataType* a, int child)
{int parent = (child - 1) / 2;while (child > 0){if (a[child] < a[parent])//小堆{Swap(&a[child], &a[parent]);child = parent;parent = (child - 1) / 2;}else{break;}}
}

下面这条代码是创建小堆的判断条件:只要父亲大于孩子,就交换顺序。

if (a[child] < a[parent])

只要改变判断条件:只要父亲小于孩子,就交换顺序。就是创建大堆。

if (a[child] > a[parent])
1.c)向下调整算法

在这里插入图片描述

void AdjustDown(HPDataType* a, int n, int parent)
{//先假设左孩子小int child = parent * 2 + 1;while (child < n){//先找到较小孩子if (child + 1 < n && a[child + 1] < a[child]){++child;}if (a[child] < a[parent]){Swap(&a[child], &a[parent]);parent = child;child = 2 * parent + 1;}else{break;}}
}

如果想要建大堆,那么上述代码要修改的有两点:
①:先找到较大的孩子。
②:改变判断条件。

1.e)向上/向下调整算法时间复杂度分析

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
由上述公式推导得出

  • 向下调整算法的时间复杂度为:O(N)
  • 向上调整算法的时间复杂度为:0(N*logN)

2)初始化和销毁

void HPInit(HP* php)
{assert(php);php->a = NULL;php->size = php->capacity = 0;
}void HPDestroy(HP* php)
{assert(php);free(php->a);php->a = NULL;php->size = php->capacity = 0;
}

再在test.c中进行测试:

3)堆的插入

在这里插入图片描述

在数据插入之后再调用向上调整算法。

void HPPush(HP* php, HPDataType x)
{assert(php);if (php->size == php->capacity){int newcapacity = php->capacity == 0 ? 4 : 2 * php->capacity;HPDataType* tmp = (HPDataType*)realloc(php->a, newcapacity * sizeof(HPDataType));if (tmp == NULL){perror("realloc fail");return;}php->a = tmp;php->capacity = newcapacity;}//直接插入php->a[php->size] = x;php->size++;//向上调整AdjustUp(php->a, php->size - 1);
}

再进行测试:

void test()
{int a[] = { 4,2,8,1,5,6,9,7,3,2 };int len = sizeof(a) / sizeof(a[0]);HP hp;HPInit(&hp);//依次将数组元素插入到堆上for (int i = 0; i < len; i++){HPPush(&hp, a[i]);}
}int main()
{test();return 0;
}

调试结果:

逻辑结构如图所示:

4)堆的删除

删除堆是删除堆顶的数据,将堆顶的数据跟最后的数据先交换,然后删除数组最后一个数据(原来的堆顶),再进行向下调整算法。

void HPPop(HP* php)
{assert(php);assert(php->size);Swap(&php->a[0], &php->a[php->size - 1]);php->size--;AdjustDown(php->a, php->size, 0);
}

再进行测试:

void test()
{int a[] = { 4,2,8,1,5,6,9,7,3,2 };int len = sizeof(a) / sizeof(a[0]);HP hp;HPInit(&hp);//依次将数组元素插入到堆上for (int i = 0; i < len; i++){HPPush(&hp, a[i]);}HPPop(&hp);HPPop(&hp);HPDestroy(&hp);
}int main()
{test();return 0;
}

调试结果:

5)取堆顶元素

直接返回堆顶元素即可。

HPDataType* HPTop(HP* php)
{assert(php);assert(php->size > 0);return php->a[0];
}

再进行测试;

void test()
{int a[] = { 4,2,8,1,5,6,9,7,3 };int len = sizeof(a) / sizeof(a[0]);HP hp;HPInit(&hp);//依次将数组元素插入到堆上for (int i = 0; i < len; i++){HPPush(&hp, a[i]);}printf("%d\n", HPTop(&hp));HPPop(&hp);printf("%d\n", HPTop(&hp));HPDestroy(&hp);
}int main()
{test();return 0;
}

运行结果:
在这里插入图片描述

6)堆的判空

直接判断size是否等于0即可。

bool HPEmpty(HP* php)
{assert(php);return php->size == 0;
}

再进行测试依次取出所有堆顶的元素:

void test()
{int a[] = { 4,2,8,1,5,6,9,7,3 };int len = sizeof(a) / sizeof(a[0]);HP hp;HPInit(&hp);//依次将数组元素插入到堆上for (int i = 0; i < len; i++){HPPush(&hp, a[i]);}while (!HPEmpty(&hp)){printf("%d ", HPTop(&hp));HPPop(&hp);}
}int main()
{test();return 0;
}

运行结果:
在这里插入图片描述

4、堆的应用

1)堆排序

堆排序即利用堆的思想来进行排序,总共分为两个步骤:
①建堆

  • 升序:建大堆
  • 降序:建小堆

建堆使用向上或者向下调整算法都可以实现,向上调整算法其实套用的就是建堆的方法,如果使用向下调整算法建堆,时间复杂度更小,效率会更高。

在这里插入图片描述
②利用堆删除思想来进行排序。
在堆中被删除的数据,实际上在数组中还存在,并且已经处于有序状态。

void HPSort(HPDataType* a, int n)
{//建堆   //向上调整//for (int i = 1; i < n; i++)//{//	AdjustUp(a, i);//}//向下调整   O(N)for (int i = (n - 1 - 1) / 2; i >= 0; i--){AdjustDown(a, n, i);}//堆删除   O(N*logN)int end = n - 1;while (end > 0){Swap(&a[0], &a[end]);AdjustDown(a, end, 0);--end;}
}

再进行测试:

int main()
{int arr[] = { 4,2,8,1,5,6,9,7,3 };int len = sizeof(arr) / sizeof(arr[0]);HPSort(arr, len);for (int i = 0; i < len; i++){printf("%d ", arr[i]);}return 0;
}

运行结果:
在这里插入图片描述

堆排序的时间复杂度分析:
如果建堆时采用向上调整算法,那么堆排序的时间复杂度为:O(N*logNlog^NlogN)。相比于冒泡排序的时间复杂度O(N2)已经大大减少了。

如果建堆时采用向下调整算法,时间复杂度将锐减为O(N),效率大大提升。

2) TOP-K问题

TOP-K问题:即求数据结合中前K个最大的元素或者最小的元素,一般情况下数据量都比较大。比如:专业前10名、世界500强、富豪榜、游戏中前100的活跃玩家等。
对于Top-K问题,能想到的最简单直接的方式就是排序,但是:如果数据量非常大,排序就不太可取了(可能数据都不能一下子全部加载到内存中)。最佳的方式就是用堆来解决,基本思路如下:
①用数据集合中前K个元素来建堆

  • 前k个最大的元素,建小堆。
  • 前k个最小的元素,建大堆。

②用剩余的N-K个元素依次与堆顶元素来比较,不满足则替换堆顶元素。将剩余N-K个元素依次与堆顶元素比完之后,堆中剩余的K个元素就是所求的前K个最小或者最大的元素。

void testHeap2()
{int k;printf("请输入k:");scanf("%d", &k);//开辟k个大小的数组int* kminheap = (int*)malloc(sizeof(int) * k);if (kminheap == NULL){perror("malloc fail");return;}//打开文件const char* file = "data.txt";FILE* fout = fopen(file, "r");if (fout == NULL){perror("fopen error");return;}//读取文件前k个数for (int i = 0; i < k; i++){fscanf(fout, "%d", &kminheap[i]);}//建k个数的小堆  (向下调整建堆) O(N)for (int i = (k - 1 - 1) / 2; i >= 0; i--){AdjustDown(kminheap, k, i);}//读取剩下的N-K个数,并依次与堆顶元素比较int x = 0;while (fscanf(fout, "%d", &x) > 0){if (x > kminheap[0]){kminheap[0] = x;AdjustDown(kminheap, k, 0);}}//打印printf("最大的前%d个数:", k);for (int i = 0; i < k; i++){printf("%d ", kminheap[i]);}printf("\n");
}

接着再进行测试:
首先创建一个100000个随机整数的文件data.txt

void CreateNDate()
{//造数据int n = 100000;srand(time(0));const char* file = "data.txt";FILE* fin = fopen(file, "w");if (fin == NULL){perror("fopen fail");return;}for (int i = 0; i < n; i++){//生成10000000以内的数int x = (rand() + i) % 10000000;fprintf(fin, "%d\n", x);}fclose(fin);
}

为了方便我们查看是否查找正确,我们可以在data.txt中将部分数据改得突出一点。

现在就可以测试了。

int main()
{//CreateNDate();testHeap2();return 0;
}

运行结果:
在这里插入图片描述

四、二叉树链式结构的实现

1、二叉树的链式结构

0)准备工作

我们在这里创建3个文件:
BT.h:二叉树的定义,方法的声明
BT.c:方法的实现
test.c:测试

先在BT.h中实现二叉树的定义以及方法的声明:

#include<stdio.h.>
#include<stdlib.h>typedef int BTDataType;typedef struct BinaryTreeNode
{BTDataType data;struct BinaryTreeNode* left;struct BinaryTreeNode* right;
} BTNode;//手动创建二叉树
BTNode* CreateBinaryTree();//前序
void PrevOrder(BTNode* root);//中序
void InOrder(BTNode* root);//后序
void PostOrder(BTNode* root);

1)二叉树的手动创建

在手动创建二叉树之前,需要先编写创建二叉树单独节点的函数。

BTNode* BuyNode(int x)
{BTNode* node = (BTNode*)malloc(sizeof(BTNode));if (node == NULL){perror("malloc fail");return NULL;}node->data = x;node->left = NULL;node->right = NULL;return node;
}

接着再进行创建:

BTNode* CreateBinaryTree()
{BTNode* node1 = BuyNode(1);BTNode* node2 = BuyNode(2);BTNode* node3 = BuyNode(3);BTNode* node4 = BuyNode(4);BTNode* node5 = BuyNode(5);BTNode* node6 = BuyNode(6);node1->left = node2;node1->right = node4;node2->left = node3;node4->left = node5;node4->right = node6;return node1;
}

创建的二叉树的逻辑结构如下所示:

2)二叉树的遍历(前序、中序、后序)

三种遍历如下所示:

//前序
void PrevOrder(BTNode* root)
{//访问到空节点if (root == NULL){printf("N ");return;}printf("%d ", root->data);PrevOrder(root->left);PrevOrder(root->right);
}//中序
void InOrder(BTNode* root)
{//访问到空节点if (root == NULL){printf("N ");return;}InOrder(root->left);printf("%d ", root->data);InOrder(root->right);
}//后序
void PostOrder(BTNode* root)
{//访问到空节点if (root == NULL){printf("N ");return;}PostOrder(root->left);PostOrder(root->right);printf("%d ", root->data);
}

再来进行测试:

int main()
{//创建一颗二叉树BTNode* root = CreateBinaryTree();//前序printf("前序遍历:");PrevOrder(root);printf("\n");//中序printf("中序遍历:");InOrder(root);printf("\n");//后序printf("后序遍历:");PostOrder(root);printf("\n");return 0;
}

运行结果:

因此遍历节点的先后顺序如下所示:

前序遍历结果:1 2 3 4 5 6
中序遍历结果:3 2 1 5 4 6
后序遍历结果:3 2 5 6 4 1

3)节点个数

//节点个数
int TreeSize(BTNode* root)
{return root == NULL ? 0 :TreeSize(root->left) + TreeSize(root->right) + 1;
}

再进行测试;

int main()
{BTNode* root = CreateBinaryTree();printf("节点个数:%d\n", TreeSize(root));return 0;
}

运行结果:
在这里插入图片描述

4)叶子节点个数

在这里插入图片描述

//叶子节点个数
int TreeLeafSize(BTNode* root)
{if (root == NULL)return 0;if (root->left == NULL && root->right == NULL)return 1;return TreeLeafSize(root->left) + TreeLeafSize(root->right);
}

再进行测试:

int main()
{BTNode* root = CreateBinaryTree();printf("叶子节点个数:%d\n", TreeLeafSize(root));return 0;
}

运行结果:
在这里插入图片描述

5)高度

在这里插入图片描述

int TreeHeight(BTNode* root)
{if (root == NULL)return 0;int leftHeight = TreeHeight(root->left);int rightHeight = TreeHeight(root->right);return leftHeight > rightHeight ?leftHeight + 1 : rightHeight + 1;
}

再进行测试:

int main()
{BTNode* root = CreateBinaryTree();printf("二叉树的高度为:%d\n", TreeHeight(root));return 0;
}

运行结果:
在这里插入图片描述
这里测试的二叉树依旧是之前的创建的二叉树,高度为3,上面的图片仅作方法的演示。

6)第k层节点数

在这里插入图片描述

int TreeLevelKSize(BTNode* root, int k)
{if (root == NULL)return 0;if (k == 1)return 1;//子问题return TreeLevelKSize(root->left, k - 1) + TreeLevelKSize(root->right, k - 1);
}

进行测试:

int main()
{BTNode* root = CreateBinaryTree();printf("二叉树第3层的节点个数为:%d\n", TreeLevelKSize(root,3));return 0;
}

运行结果:
在这里插入图片描述

7)查找值为x的节点

在这里插入图片描述

BTNode* TreeFind(BTNode* root,int x)
{if (root == NULL)return NULL;if (root->data == x)return root;//先找左子树BTNode* ret1 = TreeFind(root->left, x);//找到了if (ret1)return ret1;//再找右子树BTNode* ret2 = TreeFind(root->right, x);//找到了if (ret2)return ret2;//遍历整个二叉树都没有找到return NULL;
}

再进行测试:

int main()
{BTNode* root = CreateBinaryTree();int k = 0;printf("请输入你要查找的值:");scanf("%d", &k);int find = TreeFind(root, k);if (find)printf("找到了!\n");elseprintf("没有找到...\n");return 0;
}

运行结果:

2、二叉树的创建和销毁

1)前序遍历数组构建二叉树

例如:通过前序遍历的数组"ABD##E#H##CF##G##"构建二叉树。

在这里插入图片描述

 BTNode* CreateTree(char* a, int* pi){if (a[*pi] == '#'){(*pi)++;return NULL;}BTNode* root = (BTNode*)malloc(sizeof(BTNode));root->val = a[*pi];(*pi)++;root->left = CreateTree(a, pi);root->right = CreateTree(a, pi);return root;}

我们可以通过前序遍历这个二叉树并打印,观察是否成功创建:

 void PrevPrint(BTNode* root){if (root == NULL){printf("# ");return;}printf("%c ", root->val);PrevPrint(root->left);PrevPrint(root->right);}int main(){char a[30] = "ABD##E#H##CF##G## ";int i = 0;BTNode* root = CreateTree(a, &i);PrevPrint(root);return 0;}

运行结果:
在这里插入图片描述

可以看到已经成功创建二叉树。

2)二叉树的销毁

在这里插入图片描述

 void TreeDestroy(BTNode* root){if (root == NULL)return;TreeDestroy(root->left);TreeDestroy(root->right);free(root);}

3)判断二叉树是否是完全二叉树

在这里插入图片描述

 bool TreeComplete(BTNode* root){Queue q;QueueInit(&q);if (root)QueuePush(&q, root);//第一次层序遍历while (!QueueEmpty(&q)){BTNode* front = QueueFront(&q);QueuePop(&q);//遇到第一个空if (front == NULL){break;}QueuePush(&q, front->left);QueuePush(&q, front->right);}//接着进行第二次层序遍历while (!QueueEmpty(&q)){BTNode* front = QueueFront(&q);QueuePop(&q);//遇到非空->不是完全二叉树  直接销毁返回falseif (front){QueueDestroy(&q);return false;}}//是完全二叉树QueueDestroy(&q);return true;}
http://www.dtcms.com/a/274103.html

相关文章:

  • 【LeetCode 热题 100】24. 两两交换链表中的节点——(解法一)迭代+哨兵
  • leetcode106深度解析:从中序与后序遍历序列构造二叉树
  • leetcode:518. 零钱兑换 II[完全背包]
  • 【网络】Linux 内核优化实战 - net.ipv4.tcp_congestion_control
  • (LeetCode 每日一题) 3169. 无需开会的工作日 ( 排序+贪心 )
  • 力扣_二叉搜索树_python版本
  • 1965–2022年中国大陆高分辨率分部门用水数据集,包含:灌溉用水、工业制造用水、生活用水和火电冷却
  • 【unitrix】 4.21 类型级二进制数基本结构体(types.rs)
  • 李沐动手学深度学习Pytorch-v2笔记【07自动求导代码实现】
  • 进程管理中的队列调度与内存交换机制
  • Jenkins 系统管理与配置
  • 排序算法与前端交互优化
  • 持续集成 简介环境搭建
  • 14 TryHackMe 靶场 Wireshark: The Basics
  • CIU32L051系列 DMA串口无阻塞性收发的实现
  • CentOS 安装 JDK+ NGINX+ Tomcat + Redis + MySQL搭建项目环境
  • Redis5.0.5 漏洞
  • redis的一些疑问
  • windows下安装 redis
  • Redis全栈技术导航:从基础架构到实战案例的完整指南
  • 创客匠人:AI 时代创始人 IP 打造与知识变现的范式迁移
  • 什么是IP关联?跨境卖家如何有效避免IP关联?
  • LeetCode--43.字符串相乘
  • 软件过程模型核心特征与开发流程对照表
  • Android Glide使用与底层机制详解
  • 上位机知识篇---安装包架构
  • imx6ull-系统移植篇2—— U-Boot 命令使用(上)
  • Java 中线程通信方式笔记
  • tailwindCSS === 使用插件自动类名排序
  • ssm框架整合全攻略:从环境搭建到功能实现