文章目录

  • 一、树的概念及结构
    • 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.pswp.cn/pingmian/88371.shtml
繁体地址,请注明出处:http://hk.pswp.cn/pingmian/88371.shtml
英文地址,请注明出处:http://en.pswp.cn/pingmian/88371.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Spring MVC中异常处理

1.全局异常处理1.1什么是全局异常处理器全局异常处理器是SpringMVC框架中的一种异常处理机制&#xff0c;用于统一处理由控制器抛出的异常。全局异常处理器可以帮助我们捕获和处理控制器中的异常&#xff0c;并且根据不同的异常类型进行不同的处理操作&#xff0c;从而保障应用…

imx6ull-系统移植篇2—— U-Boot 命令使用(上)

目录 前言 U-Boot 命令 help 信息查询命令 bdinfo printenv version 环境变量操作命令 setenv 和 saveenv 修改环境变量 新建环境变量 删除环境变量 内存操作命令 md nm mm mw cp cmp 网络操作命令 ping 命令 dhcp 命令 nfs 命令 tftp 命令 EMMC 和 S…

vector之动态二维数组的底层

引言&#xff1a;在计算机编程领域&#xff0c;二维动态数组是一种能够在程序运行期间动态调整其大小的二维数组数据结构。它与静态二维数组的关键区别在于&#xff0c;静态二维数组在编译时就需要确定其大小&#xff0c;而二维动态数组的大小可以在程序运行过程中根据实际需求…

第十六天,7月10日,八股

1、mybatis的延迟加载需要时才加载关联对象&#xff0c;而不是查询主对象时&#xff0c;立刻加载所有关联对象&#xff0c;这样可以提高查询性能并减少不必要的数据库访问&#xff0c;例如&#xff1a;一个订单表包含着商品列表&#xff08;一对多&#xff09;&#xff0c;当查…

CSS中的Element语法

1.1 Element语法1.1.1 案例 1. 快速生成10个div,并且每个div里面是从1到10的内容2.生成一个div标签&#xff0c;类名为one,并且同时生成一个id为first的p标签1.1.2 快速生成CSS样式语法 CSS基本采取简写形式即可 比如w22 按住tab键 可以生成 width:200px比如lh26px 按住tab键 可…

Go从入门到精通(21) - 一个简单web项目-添加swagger文档

Go从入门到精通(20)-一个简单web项目-服务搭建 文章目录Go从入门到精通(20)-一个简单web项目-服务搭建前言前期准备为API 添加 Swagger 文档1.安装依赖2.添加 Swagger 注释main.goapp.goapi.gopublic_handler.goauth_handler.gocommon_constant.gocommon_dto.gotoken_utils.go3…

自动驾驶环境感知:天气数据采集与融合技术实战

天气与我们日常各类生活场景密不可分&#xff0c;在驾驶场景里当车主发动汽车准备驶向目的地时&#xff0c;窗外的阴晴或许只是直观感受&#xff0c;而真正影响驾驶安全与行程效率的&#xff0c;可能是几公里外的突发暴雨、桥面的结冰预警&#xff0c;或是前方路段的强侧风等级…

基于svga+uniapp的微信小程序动画组件开发指南

lottie动画指南 效果 概述 本项目使用 svgaplayer.weapp.js 库来实现 SVGA 动画播放功能&#xff0c;支持在微信小程序、H5 等多端环境下播放高质量的矢量动画。SVGA 是一种跨平台的开源动画格式&#xff0c;具有文件小、渲染性能高的特点。 技术栈 核心库: svgaplayer.wea…

数据结构与算法——计算直线的交点数

前言&#xff1a; 这是之前做的一道笔试题&#xff0c;当时没写出来烦恼很久&#xff0c;这次记录一下。 题目链接&#xff1a; Dotcpp--题目 1174: 计算直线的交点数 参考文章&#xff1a; CSDN--槐阳7--计算直线的交点数 题目&#xff1a; 解题思考&#xff1a; 在当时…

大模型及agent开发6 OpenAI Assistant API 高阶应用 - 流式输出功能

1.Assistant API 的主要优点&#xff1a; 减少编码工作量、自动管理上下文窗口、安全的访问控制、工具和文档的轻松集成 本节讲应用设计和性能流式输出&#xff1a;借助流式输出&#xff0c;可以让应用程序实时处理和响应用户输入。具体来说&#xff0c;这种技术允许数据在生成…

React Native安卓刘海屏适配终极方案:仅需修改 AndroidManifest.xml!

&#x1f4cc; 问题背景在 React Native 开发中&#xff0c;我们经常会遇到安卓设备刘海屏&#xff08;Notch&#xff09;适配问题。即使正确使用了 react-native-safe-area-context 和 react-navigation&#xff0c;在一些安卓设备&#xff08;如小米、华为、OPPO 等&#xff…

Spring Boot整合MyBatis+MySQL实战指南(Java 1.8 + 单元测试)

一、环境准备 开发工具&#xff1a;IntelliJ IDEA 2023.1 JDK 1.8.0_382 Maven3.6.3数据库&#xff1a;MySQL 8.0.21依赖版本&#xff1a;<parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifact…

游戏开发日记

如何用数据表来储存&#xff0c;位置坐标&#xff08;XYZ&#xff09;&#xff1a;决定了对象在世界中的摆放资源ID / 图片URL&#xff1a;决定了使用什么模型或贴图事件ID / 特效&#xff1a;是否触发某些事件&#xff08;例如点击、交互&#xff09;逻辑索引&#xff08;Grid…

如何使用xmind编写测试用例

如何使用xmind编写测试用例为什么要使用xmind&#xff1f;使用xmind编写测试用例是为了梳理我们的思路。使用xmind编写测试用例的思路是什么&#xff1f;先进行分析再提取测试用例。 例如下面的注册功能的测试用例的分析&#xff1a; 分析&#xff1a; 先提取出需要测试的功能点…

使用LLaMA-Factory微调Qwen2.5-VL-3B 的目标检测任务-数据集格式转换(voc 转 ShareGPT)

一、LLaMA-Factory Qwen2.5-VL ShareGPT 格式要求ShareGPT 格式就是多轮对话的 list&#xff0c;每条数据如下&#xff1a;[{"conversations": [{"from": "user", "value": "<image>\n请标注图片中的所有目标及其类别和位…

【SkyWalking】服务端部署与微服务无侵入接入实战指南

【SkyWalking】服务端部署与微服务无侵入接入实战指南 &#x1f4a1; SkyWalking 系列总引导 在微服务架构快速演进的今天&#xff0c;如何有效实现服务链路追踪、性能分析、日志采集与自动化告警&#xff0c;成为系统稳定性的关键保障手段。 SkyWalking&#xff0c;作为 Apa…

LVDS系列20:Xilinx 7系ISERDESE2原语(一)

Xilinx 7系FPGA bank的io单元如下&#xff1a;Hr bank比hp bank少odelaye2组件&#xff0c;两者的idelaye2组件后面&#xff0c;都有iserdese2组件&#xff1b; iserdese2组件是一种专用的串并转换器或称解串器&#xff0c;用于高速源同步应用&#xff0c;如大部分LVDS信号解析…

【U-Boot】Shell指令

目录 U-Boot 三个Shell U-Boot Shell Linux Shell shell脚本 总结 U-Boot Shell命令 帮助命令 部分命令分类与功能说明 一、基础操作与信息查询 二、内存操作 三、启动管理 四、文件系统操作 五、设备与分区管理 六、环境变量 七、诊断与调试 八、特殊功能 九…

《Revisiting Generative Replay for Class Incremental Object Detection》阅读笔记

摘要Abstract部分 原文 Generative replay has gained significant attention in class-incremental learning; however, its application to Class Incremental Object Detection (CIOD) remains limited due to the challenges in generating complex images with precise …

Mysql: Bin log原理以及三种格式

目录 一、什么是 Binlog&#xff1f; 二、Binlog 的应用场景与案例 1. 数据恢复 (Point-in-Time Recovery) 2. 主从复制 (Master-Slave Replication) 3. 数据审计 三、Binlog 的三种格式 1. STATEMENT 模式 (Statement-Based Logging - SBL) 2. ROW 模式 (Row-Based Log…