【万字好文】二叉树从入门到精通(树、二叉树、堆、深度广度优先遍历、二叉树练习题(1)


3.2.4 堆的插入(向上调整算法)

先插入一个10到数组的尾上,再进行向上调整算法,直到满足堆。

在这里插入图片描述

代码实现:

//交换函数

void Swap(HPDataType* x, HPDataType* y)

{

HPDataType tmp = *x;

*x = *y;

*y = tmp;

}

//堆的向上调整(小堆)

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;

}

}

}


接下来我们对进行比较全面的实现

3.2.5堆的实现

typedef int HPDataType;

typedef struct Heap

{

HPDataType* a;

int size;

int capacity;

}Hp;

// 堆的构建

void HeapInit(Hp* hp, HPDataType* a, int n);

// 堆的销毁

void HeapDestory(Hp* hp);

// 堆的插入

void HeapPush(Hp* hp, HPDataType x);

// 堆的删除

void HeapPop(Hp* hp);

// 取堆顶的数据

HPDataType HeapTop(Hp* hp);

// 堆的数据个数

int HeapSize(Hp* hp);

// 堆的判空

int HeapEmpty(Hp* hp);

初始化堆

首先,必须创建一个堆类型,该类型中需包含堆的基本信息:存储数据的数组、堆中元素的个数以及当前堆的最大容量。

typedef int HPDataType;//堆中存储数据的类型

typedef struct Heap

{

HPDataType* a;//用于存储数据的数组

int size;//记录堆中已有元素个数

int capacity;//记录堆的容量

}HP;

然后我们需要一个初始化函数,对刚创建的堆进行初始化,并将传入数据实现建堆操作。

//初始化堆

void HeapInit(HP* php, HPDataType* a, int n)

{

assert(php);

HPDataType* tmp = (HPDataType*)malloc(sizeof(HPDataType)*n);//申请一个堆结构

if (tmp == NULL)

{

printf(“malloc fail\n”);

exit(-1);

}

php->a = tmp;

memcpy(php->a, a, sizeof(HPDataType)n);//拷贝数据到堆中–memcpy满足任意类型的拷贝(因为接收f的是void

php->size = n;

php->capacity = n;

int i = 0;

//建堆

for (i = (php->size - 1 - 1) / 2; i >= 0; i–)

{

AdjustDown(php->a, php->size, i);

}

}

销毁堆

为了避免内存泄漏,使用完动态开辟的内存空间后都要及时释放该空间,所以,一个用于释放内存空间的函数是必不可少的。

//销毁堆

void HeapDestroy(HP* php)

{

assert(php);

free(php->a);//释放动态开辟的数组

php->a = NULL;//及时置空

php->size = 0;//元素个数置0

php->capacity = 0;//容量置0

}

打印堆

打印堆中的数据,这里用了两种打印格式。第一种打印格式是按照堆的物理结构进行打印,即打印为一排连续的数字。第二种打印格式是按照堆的逻辑结构进行打印,即打印成树形结构。

//求结点数为n的二叉树的深度

int depth(int n)

{

assert(n >= 0);

if (n>0)

{

int m = 2;

int hight = 1;

while (m < n + 1)

{

m *= 2;

hight++;

}

return hight;

}

else

{

return 0;

}

}

//打印堆

void HeapPrint(HP* php)

{

assert(php);

//按照物理结构进行打印

int i = 0;

for (i = 0; i < php->size; i++)

{

printf("%d ", php->a[i]);

}

printf(“\n”);

//按照树形结构进行打印

int h = depth(php->size);

int N = (int)pow(2, h) - 1;//与该二叉树深度相同的满二叉树的结点总数

int space = N - 1;//记录每一行前面的空格数

int row = 1;//当前打印的行数

int pos = 0;//待打印数据的下标

while (1)

{

//打印前面的空格

int i = 0;

for (i = 0; i < space; i++)

{

printf(" ");

}

//打印数据和间距

int count = (int)pow(2, row - 1);//每一行的数字个数

while (count–)//打印一行

{

printf(“%02d”, php->a[pos++]);//打印数据

if (pos >= php->size)//数据打印完毕

{

printf(“\n”);

return;

}

int distance = (space + 1) * 2;//两个数之间的空格数

while (distance–)//打印两个数之间的空格

{

printf(" ");

}

}

printf(“\n”);

row++;

space = space / 2 - 1;

}

}

堆的插入

数据插入时是插入到数组的末尾,即树形结构的最后一层的最后一个结点,所以插入数据后我们需要运用堆的向上调整算法对堆进行调整,使其在插入数据后仍然保持堆的结构。

//堆的插入

void HeapPush(HP* php, HPDataType x)

{

assert(php);

if (php->size == php->capacity)

{

HPDataType* tmp = (HPDataType*)realloc(php->a, 2 * php->capacity*sizeof(HPDataType));

if (tmp == NULL)

{

printf(“realloc fail\n”);

exit(-1);

}

php->a = tmp;

php->capacity *= 2;

}

php->a[php->size] = x;

php->size++;

//向上调整

AdjustUp(php->a, php->size - 1);

}

堆的删除

堆的删除,删除的是堆顶的元素,但是这个删除过程可并不是直接删除堆顶的数据,而是先将堆顶的数据与最后一个结点的位置交换,然后再删除最后一个结点,再对堆进行一次向下调整。

原因:我们若是直接删除堆顶的数据,那么原堆后面数据的父子关系就全部打乱了,需要全体重新建堆,时间复杂度为 O ( N ) 。若是用上述方法,那么只需要对堆进行一次向下调整即可,因为此时根结点的左右子树都是小堆,我们只需要在根结点处进行一次向下调整即可,时间复杂度为 O ( log ⁡ ( N ) ) 。

//堆的删除

void HeapPop(HP* php)

{

assert(php);

assert(!HeapEmpty(php));

Swap(&php->a[0], &php->a[php->size - 1]);//交换堆顶和最后一个结点的位置

php->size–;//删除最后一个结点(也就是删除原来堆顶的元素)

AdjustDown(php->a, php->size, 0);//向下调整

}

获取堆顶的数据

获取堆顶的数据,即返回数组下标为0的数据。

//获取堆顶的数据

HPDataType HeapTop(HP* php)

{

assert(php);

assert(!HeapEmpty(php));

return php->a[0];//返回堆顶数据

}

获取堆的数据个数

获取堆的数据个数,即返回堆结构体中的size变量。

//获取堆中数据个数

int HeapSize(HP* php)

{

assert(php);

return php->size;//返回堆中数据个数

}

堆的判空

堆的判空,即判断堆结构体中的size变量是否为0。

//堆的判空

bool HeapEmpty(HP* php)

{

assert(php);

return php->size == 0;//判断堆中数据是否为0

}

3.3 堆的应用

3.3.1 堆排序

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

  1. 建堆
  • 排升序:建大堆
  • 排降序:建小堆
  1. 利用堆删除思想来进行排序

建堆和堆删除中都用到了向下调整,因此掌握了向下调整,就可以完成堆排序

以排升序(建大堆)为例:

1.先利用向下调整法建堆:

//建堆(大堆)

for (int i = (n - 1 - 1) / 2; i >= 0; i–)

{

AdjustDown(php->a, php->size, i);

}

2.再利用堆删除思想来进行排序:

步骤如下:

1、将堆顶数据与堆的最后一个数据交换,然后对根位置进行一次堆的向下调整,但是调整时被交换到最后的那个最大的数不参与向下调整。

2、完成步骤1后,这棵树除最后一个数之外,其余数又成一个大堆,然后又将堆顶数据与堆的最后一个数据交换,这样一来,第二大的数就被放到了倒数第二个位置上,然后该数又不参与堆的向下调整…反复执行下去,直到堆中只有一个数据时便结束。此时该序列就是一个升序。

实例:

将下面这组数先建大堆,再排升序

在这里插入图片描述

在这里插入图片描述

动图演示:(动图来自菜鸟网站->堆排序)

在这里插入图片描述

堆排序代码:

//堆排序

void HeapSort(int* a, int n)

{

//排升序,建大堆

//从第一个非叶子结点开始向下调整,一直到根

int i = 0;

for (i = (n - 1 - 1) / 2; i >= 0; i–)

{

AdjustDown(a, n, i);

}

int end = n - 1;//记录堆的最后一个数据的下标

while (end)

{

Swap(&a[0], &a[end]);//将堆顶的数据和堆的最后一个数据交换

AdjustDown(a, end, 0);//对根进行一次向下调整

end–;//堆的最后一个数据的下标减一

}

}

时间复杂度: O ( N l o g N )  空间复杂度: O ( 1 )

3.3.2 TOP-K问题

TOP-K问题:即求数据集合中前K个最大的元素或者最小的元素,一般情况下数据量都比较大。

比如:专业前10名、世界500强、富豪榜、游戏中前100的活跃玩家等

我们从时间和空间的角度逐步来看:

假设当前我们输入数组arr[2,7,4,6,2,3,9,8],找出其中最大的k个数。

例如,将k设为4,则在这8个数字中,最大的k个数是6、7、8 、9。

这就是所谓的TOP-K问题

角度一

对于Top-K问题,能想到的最简单直接的方式就是排序,利用时间复杂度较低的堆排序将数组排为降序,然后输出前k个数就可以了

代码:

//交换函数

void Swap(int* x, int* y)

{

int tmp = *x;

*x = *y;

*y = tmp;

}

//堆的向下调整(小堆)

void AdjustDown(int* a, int n, int parent)

{

//child记录左右孩子中值较小的孩子的下标

int child = 2 * parent + 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;

}

}

}

int* getLeastNumbers(int* arr, int arrSize, int k, int* returnSize)

{

*returnSize = k;

int i = 0;

//建小堆

for (i = (arrSize - 1 - 1) / 2; i >= 0; i–)

{

AdjustDown(arr, arrSize, i);

}

//排降序

int end = arrSize - 1;

while (end > 0)

{

Swap(&arr[0], &arr[end]);

AdjustDown(arr, end, 0);

end–;

}

//将最大的k个数存入数组

int* retArr = (int*)malloc(sizeof(int)*k);

for (i = 0; i < k; i++)

{

retArr[i] = arr[i];

}

return retArr;//返回最大的k个数

}

时间复杂度: O ( N + N l o g N )

【建堆为N,向下调整一次为log N,调整N次为NlogN,所以时间复杂度为O ( N + N l o g N ) 】

空间复杂度: O ( N )

————————————————

角度二

在角度一的基础上,我们可不可以将时间复杂度再降低一些?

我们可以将数组建成一个大堆,因为堆顶的元素最大,所以我们取k次堆顶的元素就可以实现要求了,即把N个数建堆,取出前k个

注意:

1.取出数据后要让其与最后的元素替换,因为你已经取出这个元素了,所以不需要它了,这时让它去堆尾,不让它算入堆的个数中就行了。

2. 如果在取到堆顶数据后直接删除数据,那么就要重新建堆了。正确的做法应该是上面所说的方法,因为那样只要进行一次向下调整,就可以保证堆的结构了。要知道建堆的复杂度为O(N),而一次向下调整的复杂度仅为O(logn),这样大大提升了效率。

代码:

//交换函数

void Swap(int* x, int* y)

{

int tmp = *x;

*x = *y;

*y = tmp;

}

//堆的向下调整(大堆)

void AdjustDown(int* a, int n, int parent)

{

//child记录左右孩子中值较大的孩子的下标

int child = 2 * parent + 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;

}

}

}

int* getLeastNumbers(int* arr, int arrSize, int k, int* returnSize)

{

*returnSize = k;

int i = 0;

//建大堆

for (i = (arrSize - 1 - 1) / 2; i >= 0; i–)

{

AdjustDown(arr, arrSize, i);

}

//将最大的k个数存入数组

int* retArr = (int*)malloc(sizeof(int)*k);

int end = arrSize - 1;

for (i = 0; i < k; i++)

{

retArr[i] = arr[0];//取堆顶数据

Swap(&arr[0], &arr[end]);//交换堆顶数据与最后一个数据

//进行一次向下调整,不把最后一个数据看作待调整的数据,所以待调整数据为end=arrSize-1

AdjustDown(arr, end, 0);

end–;//最后一个数据的下标改变

}

return retArr;//返回最大的k个数

}

时间复杂度:O(N+klogN)

空间复杂度:O(N)

角度三

如果数据量非常大,将会占用的内存是巨大的,上面的排序就不太可取了。

我们可以用下面的方法:

  1. 用数据集合中前K个元素来建堆
  • 前k个最大的元素,则建小堆
  • 前k个最小的元素,则建大堆
  1. 用剩余的N-K个元素依次与堆顶元素来比较,不满足则替换堆顶元素

我们以找出最大的k个数为例:先建一个k个数的小堆,然后将数组中n-k个元素依次与堆顶的元素比较,若比堆顶元素大,则把堆顶元素换为该元素,然后再进行一次向下调整,使其仍为小堆。那么问题来了,为什么不用大堆呢,其实这很容易理解,如果建一个大堆,万一堆顶的数据就是我们所求的k个数中的一个,那么我们所求的k个数中比它小的数就永远不可能入堆,因此要用小堆来解决这个问题

代码:

//交换函数

void Swap(int* x, int* y)

{

int tmp = *x;

*x = *y;

*y = tmp;

}

//堆的向下调整(小堆)

void AdjustDown(int* a, int n, int parent)

{

//child记录左右孩子中值较小的孩子的下标

int child = 2 * parent + 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;

}

}

}

int* getLeastNumbers(int* arr, int arrSize, int k, int* returnSize)

{

*returnSize = k;

if (k == 0)

return NULL;

//用数组的前K个数建小堆

int i = 0;

int* retArr = (int*)malloc(sizeof(int)*k);

for (i = 0; i < k; i++)

{

retArr[i] = arr[i];

}

for (i = (k - 1 - 1) / 2; i >= 0; i–)

{

AdjustDown(retArr, k, i);

}

//剩下的N-k个数依次与堆顶数据比较

for (i = k; i < arrSize; i++)

{

if (arr[i]>retArr[0])

{

retArr[0] = arr[i];//堆顶数据替换

}

AdjustDown(retArr, k, 0);//进行一次向下调整

}

return retArr;//返回最大的k个数

}

时间复杂度:O(k+n*logk)

空间复杂度:O(n)


与之前两种方法对比,这种方法大大提高了效率。

4.二叉树链式结构的实现


链式二叉树,那必须得有自己的结点类型,以下是链式二叉树结点类型的定义,下面的问题都统一使用该结点类型。

//二叉树的链式结构

typedef int BTDataType;//结点中存储的元素类型(以int为例)

typedef struct BinaryTreeNode

{

BTDataType data;//结点中存储的元素类型

struct BinaryTreeNode* left;//左指针域(指向左孩子)

struct BinaryTreeNode* right;//右指针域(指向右孩子)

}BTNode;


在学习二叉树的基本操作前,需先要创建一棵二叉树,然后才能学习其相关的基本操作。由于现在大家对二叉树结构掌握还不够深入,此处先手动快速创建一棵简单的二叉树,方便我们学习。

BTNode* BuyNode(BTDataType x)

{

BTNode* node = (BTNode*)malloc(sizeof(BTNode));

node->data = x;

node->left = NULL;

node->right = NULL;

return node;

}

//自建二叉树

BTNode* CreatBinaryTree()

{

BTNode* node1 = BuyNode(‘A’);

BTNode* node2 = BuyNode(‘B’);

BTNode* node3 = BuyNode(‘C’);

BTNode* node4 = BuyNode(‘D’);

BTNode* node5 = BuyNode(‘E’);

BTNode* node6 = BuyNode(‘F’);

BTNode* node7 = BuyNode(‘G’);

node1->left = node2;

node1->right = node3;

node2->left = node4;

node3->left = node5;

node3->right = node6;

node4->left = node7;

return node1;

}

在这里插入图片描述

下面的学习以上面自建的二叉树为准

4.1二叉树的深度优先遍历

前序遍历

前序遍历,又叫先根遍历。

遍历顺序:根 -> 左子树 -> 右子树

代码:

//二叉树前序遍历

void PreOrder(BTNode* root) {

if (root == NULL)

{

printf("NULL ");

return;

}

printf("%c ", root->data);

PreOrder(root->left);

PreOrder(root->right);

}


中序遍历

中序遍历,又叫中根遍历。

遍历顺序:左子树 -> 根 -> 右子树

代码:

// 二叉树中序遍历

void InOrder(BTNode* root)

{

if (root == NULL) {

printf("NULL ");

return;

}

InOrder(root->left);

printf("%c ", root->data);

InOrder(root->right);

}


后序遍历

后序遍历,又叫后根遍历。

遍历顺序:左子树 -> 右子树 -> 根

代码:

// 二叉树后序遍历

void PostOrder(BTNode* root)

{

if (root == NULL) {

printf("NULL ");

return;

}

PostOrder(root->left);

PostOrder(root->right);

printf("%c ", root->data);

}

4.2二叉树的广度优先遍历

层序遍历

层序遍历,从左往右逐层访问树的结点的过程就是层序遍历。

设二叉树的根节点所在层数为1,层序遍历就是从所在二叉树的根节点出发,首先访问第一层的树根节点,然后从左到右访问第2层上的节点,接着是第三层的节点,以此类推。

在这里插入图片描述

思路(借助一个队列):

借助队列先进先出的性质

1.先把根入队列,然后开始从队头出数据。

2.出队头的数据,把它的左孩子和右孩子依次从队尾入队列(NULL不入队列)。

3.重复进行步骤2,直到队列为空为止。

在这里插入图片描述

代码:(自己先写队列及相关功能)

//层序遍历

void BinaryLevelOrder(BTNode* root)

{

Queue q;

QueueInit(&q);//初始化队列

if (root != NULL)

QueuePush(&q, root);

while (!QueueEmpty(&q))//当队列不为空时,循环继续

{

BTNode* front = QueueFront(&q);//读取队头元素

QueuePop(&q);//删除队头元素

printf("%c ", front->data);//打印出队的元素

if (front->left)

{

QueuePush(&q, front->left);//出队元素的左孩子入队列

}

if (front->right)

{

QueuePush(&q, front->right);//出队元素的右孩子入队列

}

}

QueueDestroy(&q);//销毁队列

}


4.3 求节点个数以及高度等

4.3.1求二叉树节点个数

//1、遍历(前序) – 全局变量

int size = 0;

void BinaryTreeSize(BTNode* root)

{

if (root == NULL)

return;

else

++size;

BinaryTreeSize(root->left);

BinaryTreeSize(root->right);

}

int main()

{

BTNode* root = CreatBinaryTree();

//1.全局–缺点:第二次使用(例如查看另一颗树节点个数),size值会继承上一次

BinaryTreeSize(root);

printf(“BinaryTreeSize:%d\n”, size);

BinaryTreeSize(root);

printf(“BinaryTreeSize:%d\n”, size);

}

在这里插入图片描述


// 2、遍历(前序) – 局部变量

传参时注意不能使用传值传参

void BinaryTreeSize(BTNode* root, int* psize)

{

if (root == NULL)

return;

else

++(*psize);

BinaryTreeSize(root->left, psize);

BinaryTreeSize(root->right, psize);

}

int main()

{

BTNode* root = CreatBinaryTree();

int size1 = 0;//局部

BinaryTreeSize(root, &size1);

printf(“BinaryTreeSize:%d\n”, size1);

int size2 = 0;//局部

BinaryTreeSize(root, &size2);

printf(“BinaryTreeSize:%d\n”, size2);

}

在这里插入图片描述


// 3.分治–利用递归(推荐使用)

int BinaryTreeSize(BTNode* root)

{

return root == NULL ? 0 : 1

  • BinaryTreeSize(root->left)

  • BinaryTreeSize(root->right);

}

int main()

{

BTNode* root = CreatBinaryTree();

BinaryTreeSize(root);

printf(“BinaryTreeSize:%d\n”, BinaryTreeSize(root));

BinaryTreeSize(root);

printf(“BinaryTreeSize:%d\n”, BinaryTreeSize(root));

}

在这里插入图片描述


4.3.2二叉树叶子节点个数

int BinaryTreeLeafSize(BTNode* root)

{

if (root == NULL)//空树无叶子结点

{

return 0;

}

else if (root->left == NULL && root->right == NULL)//是叶子结点

{

return 1;

}

else

{

return BinaryTreeLeafSize(root->left)

  • BinaryTreeLeafSize(root->right);

}

}

int main()

{

BTNode* root = CreatBinaryTree();

printf(“BinaryTreeLeafSize:%d\n”, BinaryTreeLeafSize(root));

}

在这里插入图片描述

4.3.2二叉树第k层节点个数

思路:

相对于根结点的第k层结点的个数 = 相对于以其左孩子为根的第k-1层结点的个数 + 相对于以其右孩子为根的第k-1层结点的个数。

int BinaryTreeLevelKSize(BTNode* root, int k)

{

if (root == NULL)

return 0;

if (k == 1)

return 1;

return BinaryTreeLevelKSize(root->left, k - 1)

  • BinaryTreeLevelKSize(root->right, k - 1);

}

int main()

{

BTNode* root = CreatBinaryTree();

printf(“BinaryTreeLevelKSize:%d\n”, BinaryTreeLevelKSize(root, 4));

}

在这里插入图片描述

4.3.3 二叉树深度/高度

核心思想:max(左子树的深度,右子树的深度)+1

int BinaryTreeDepth(BTNode* root)

{
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)

img

最后

给大家送一个小福利

附高清脑图,高清知识点讲解教程,以及一些面试真题及答案解析。送给需要的提升技术、准备面试跳槽、自身职业规划迷茫的朋友们。

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!
process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBARW5qb3kgc29saXR1ZGUt,size_13,color_FFFFFF,t_70,g_se,x_16)

4.3.2二叉树第k层节点个数

思路:

相对于根结点的第k层结点的个数 = 相对于以其左孩子为根的第k-1层结点的个数 + 相对于以其右孩子为根的第k-1层结点的个数。

int BinaryTreeLevelKSize(BTNode* root, int k)

{

if (root == NULL)

return 0;

if (k == 1)

return 1;

return BinaryTreeLevelKSize(root->left, k - 1)

  • BinaryTreeLevelKSize(root->right, k - 1);

}

int main()

{

BTNode* root = CreatBinaryTree();

printf(“BinaryTreeLevelKSize:%d\n”, BinaryTreeLevelKSize(root, 4));

}

在这里插入图片描述

4.3.3 二叉树深度/高度

核心思想:max(左子树的深度,右子树的深度)+1

int BinaryTreeDepth(BTNode* root)

{
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。[外链图片转存中…(img-OJDGGcyX-1713408181016)]

[外链图片转存中…(img-wi9udUOm-1713408181017)]

[外链图片转存中…(img-nd7tqGIk-1713408181017)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)

img

最后

给大家送一个小福利

[外链图片转存中…(img-g68O8qqy-1713408181018)]

附高清脑图,高清知识点讲解教程,以及一些面试真题及答案解析。送给需要的提升技术、准备面试跳槽、自身职业规划迷茫的朋友们。

[外链图片转存中…(img-SGxbCVC1-1713408181018)]

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

  • 4
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值