一个标题哼哼啊啊啊啊啊啊

[数据结构初阶收尾篇]一篇文章带你把二叉树撕成二叉树条_数据


稍微介绍一下自己吧:
双非非科班(其实也沾点边)大一菜鸟,自学C++中,所以不要问咱太复杂的问题啊。
写博客都是记录学习,也是督促自己不要摸鱼,没啥想法,学到哪写到哪,有问题,指,都可以指。
没了。


菜鸡大学生门前有两棵树,一棵是高数,一棵是二叉树。

上学期在高强度预习下,大学生没挂在高数上面,真是万幸。

这学期我看悬。

总之在菜鸡大学生的讲解下,大家都不会挂在二叉树上面的!

我们开始吧!

树是什么

[数据结构初阶收尾篇]一篇文章带你把二叉树撕成二叉树条_二叉树_02

树(左一)

毫无疑问这个是树,但不是我们要讲的树。

我们要讲的树是这个:

[数据结构初阶收尾篇]一篇文章带你把二叉树撕成二叉树条_数据结构_03

由于它长得像一棵倒挂的树所以就给它起名叫树。


注意,子树是不相交的,每一个节点只有一个父节点。
违反上述规则的都不是树。
点名批评这俩。[数据结构初阶收尾篇]一篇文章带你把二叉树撕成二叉树条_c语言_04


树的相关概念

[数据结构初阶收尾篇]一篇文章带你把二叉树撕成二叉树条_数据_05

  • 根结点:简单来说最顶上的就是根节点,根节点没有前驱节点,图示根节点为A
  • 节点的度:一个节点含有的子树的个数称为该节点的度; 如上图:A的度为6。
  • 叶节点或终端节点:度为0的节点称为叶节点; 如上图:B、C、H、I…等节点为叶节点。
  • 双亲节点或父节点:若一个节点含有子节点,则这个节点称为其子节点的父节点; 如上图:A是B的父节点
  • 孩子节点或子节点:父节点概念反过来就是孩子节点,如图,B是A的孩子节点。
  • 树的高度或深度树中节点的最大层次; 如上图:树的高度为4。

树的表示

先简单了解一下孩子兄弟表示法吧,毕竟我们主要讲的是二叉树,树啥的,目前…不重要。

typedef int DataType;

struct Node
{
struct Node* firstChild; // 第一个孩子结点
struct Node* NextBrother; // 指向其下一个兄弟结点
DataType data; // 结点中的数据域
};

实际表示是这样的。

这样可以既兼顾值和节点之间的关系,还是很不错的。

[数据结构初阶收尾篇]一篇文章带你把二叉树撕成二叉树条_php_06

二叉树

啥是二叉树,根据朴素的按照名字去推断的方法,二叉树有两个叉。

再说明白一点,就是一个节点最多有两个子节点。

[数据结构初阶收尾篇]一篇文章带你把二叉树撕成二叉树条_二叉树_07

还有一个特点,左右节点是有顺序的。比如上图左边的二叉树,将4,5两个节点换一个位置,就会变成一个新的二叉树。

一些特殊的二叉树

[数据结构初阶收尾篇]一篇文章带你把二叉树撕成二叉树条_数据_08

  • 满二叉树:一个二叉树,如果每一个层的结点数都达到最大值,则这个二叉树就是满二叉树。
  • 完全二叉树:对于深度为K的有n个结点的二叉树,当且仅当其每一个结点都与深度为K的满二叉树中编号从1至n的结点一一对应时称之为完全二叉树。


满二叉树是一种特殊的完全二叉树。


啥意思?

先放一张满二叉树的图片:

[数据结构初阶收尾篇]一篇文章带你把二叉树撕成二叉树条_php_09

图很好懂,用我的话说就是除了最后一层全是叶子节点其他所有节点都是度为二的节点。

完全二叉树呢?

我们先给这个满二叉树标个号。

[数据结构初阶收尾篇]一篇文章带你把二叉树撕成二叉树条_数据_10

如果一个二叉树有6个节点,且分别对应节点1,2,3,4,5,6就是完全二叉树。

其余任何情况都不是。

[数据结构初阶收尾篇]一篇文章带你把二叉树撕成二叉树条_数据_11

[数据结构初阶收尾篇]一篇文章带你把二叉树撕成二叉树条_数据结构_12

简单来说只要中间不断就是完全二叉树了。

二叉树的性质

[数据结构初阶收尾篇]一篇文章带你把二叉树撕成二叉树条_数据结构_13

这三条都邦邦重要,做题十分管用,务必要记住。

用性质来做点题目吧

  1. 某二叉树共有 399 个结点,其中有 199 个度为 2 的结点,则该二叉树中的叶子结点数为( )
    A 不存在这样的二叉树
    B 200
    C 198
    D 199


我们根据第三条结论,可以直接算出n0=200


  1. 下列数据结构中,不适合采用顺序存储结构的是( )
    A 非完全二叉树
    B 堆
    C 队列
    D 栈


A. 原因我们下面讲。


  1. 在具有 2n 个结点的完全二叉树中,叶子结点个数为( )
    A n
    B n+1
    C n-1
    D n/2


我们假设n2=x,n0就是x+1n2+n0=2x+1由于完全二叉树,n1的个数不是1就是0,又因为2x+1肯定是奇数,所以n1=12x+1+1=2n,就可以算出n0=n了,选A


  1. 一棵完全二叉树的节点数位为531个,那么这棵树的高度为( )
    A 11
    B 10
    C 8
    D 12


2^9-1<531< 2^10-1高度是10,选B。


  1. 一个具有767个节点的完全二叉树,其叶子节点个数为()
    A 383
    B 384
    C 385
    D 386


和第三题一样的思路,选B


二叉树的顺序结构

假设我们有一棵完全二叉树,[数据结构初阶收尾篇]一篇文章带你把二叉树撕成二叉树条_php_14

康康这完美的序号!这么漂亮的结构不用数组说不过去吧!

我们用数组的下标形式重新标一下:[数据结构初阶收尾篇]一篇文章带你把二叉树撕成二叉树条_二叉树_15

amazing啊,我们可以发现下标和它们的父母孩子都是有关系的。

  • 一个节点的两个孩子等于节点下标*2+1和下标*2+2.
  • 节点的父亲等于(孩子-1)/2.


非完全二叉树也可以这么搞,但肯定没有完全二叉树实用不是吗?


那么我们顺势引入堆的概念。

堆的性质:

  • 堆中某个节点的值总是不大于或不小于其父节点的值;
  • 堆总是一棵完全二叉树。

按照堆的定义可以把堆分成大堆或者小堆。

  • 大堆:堆中父亲都大于等于孩子。
  • 小堆:堆中父亲都小于等于孩子。

[数据结构初阶收尾篇]一篇文章带你把二叉树撕成二叉树条_php_16

堆的实现

既然是数组就好操作了。

先把常规操作整了:

typedef int HPDataType;
typedef struct Heap
{
HPDataType* a;
size_t size;
size_t capacity;
}HP;

// 堆的初始化
void HeapInit(HP* php)
{
assert(php);

php->a = NULL;
php->size = php->capacity = 0;

}

//堆的销毁
void HeapDestroy(HP* php)
{
assert(php);

free(php->a);
php->size = php->capacity = 0;
}

//交换函数
void Swap(HPDataType* pa, HPDataType* pb)
{
HPDataType tmp = *pa;
*pa = *pb;
*pb = tmp;
}

//堆的打印
void HeapPrint(HP* php)
{
assert(php);

for (int i = 0; i < php->size; i++)
{
printf("%d ", php->a[i]);
}
printf("\n");
}

//判断堆是否为空
bool HeapEmpty(HP* php)
{
assert(php);

return php->size == 0;
}

//返回堆的大小
size_t HeapSize(HP* php)
{
assert(php);

return php->size;
}

//返回堆顶数据
HPDataType HeapTop(HP* php)
{
assert(php);
assert(php->size > 0);

return php->a[0];
}

堆的插入

[数据结构初阶收尾篇]一篇文章带你把二叉树撕成二叉树条_二叉树_17

为了保证堆的结构不被破坏,我们选择讲数据先插入到最后然后慢慢向上调整的方法,如图:

  • 先将数据放在最后。
  • 将数据和父节点对比根据堆的类型判断是否要交换。
  1. 如果是大堆,就是比父亲大,交换。
  2. 如果是小堆,就是比父亲小,交换。
  • 直到交换结束,此时数组依然是个堆。
void AdjustUp(HPDataType* a, size_t child)
{
size_t 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;
}
}
}

// 插入x以后,保持他依旧是(大/小)堆
void HeapPush(HP* php, HPDataType x)
{
assert(php);

if (php->size == php->capacity)
{
size_t newCapacity = php->capacity == 0 ? 4 : php->capacity * 2;
HPDataType* tmp = realloc(php->a, sizeof(HPDataType) * newCapacity);
if (tmp == NULL)
{
printf("realloc failed\n");
exit(-1);
}

php->a = tmp;
php->capacity = newCapacity;
}

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

// 向上调整,控制保持是一个(大/小)堆
AdjustUp(php->a, php->size - 1);
}

堆的删除

一般我们删除是删除堆顶的数据,但是,如果我们把堆顶数据删掉了,谁是堆顶呢?堆的结构是不是就乱了呢?

此时有一个天才般的方法,先把堆顶的数据和最后一个数据交换,删掉最后一个数据,然后将堆顶慢慢往下调整

看图:[数据结构初阶收尾篇]一篇文章带你把二叉树撕成二叉树条_数据_18

  • 先将堆顶和最后一个数据交换。
  • 删除最后一个数据
  • 将数据和子节点对比根据堆的类型判断是否要交换。
  1. 如果是大堆,就是和较大的子节点比,如果子节点大,交换。
  2. 如果是小堆,就是和较小的子节点比,如果子节点小,交换。
  • 直到交换结束,此时数组依然是个堆。
void AdjustDown(HPDataType* a, size_t size, size_t root)
{
size_t parent = root;
size_t child = root*2+1;

while (child < size)
{
// 1、选出左右孩子中小的那个
if (child + 1 < size && a[child] > a[child + 1])
++child;

// 2、如果孩子小于父亲,则交换,并继续往下调整
if (a[child] < a[parent])
{
Swap(&a[child], &a[parent]);
parent = child;
child = parent * 2 + 1;
}
else
{
break;
}
}
}

// 删除堆顶的数据。(最小/最大)
void HeapPop(HP* php)
{
assert(php);
assert(php->size > 0);

Swap(&php->a[0], &php->a[php->size - 1]);
--php->size;

AdjustDown(php->a, php->size, 0);
}

堆排序


堆排序(Heapsort)是指利用堆积树(堆)这种数据结构所设计的一种排序算法,它是选择排序的一种。
它是通过堆来进行选择数据。需要注意的是排升序要建大堆,排降序建小堆。


我们先不管这些,我们可以先用上面的代码写一个粗糙的堆排序试试看。

由于大/小堆堆顶的数据是堆里面最大/小的数据,我们只要不停出堆顶的数据放入数组就可以保证数组有序了。

思路:

  • 建堆。
  • 在队里一个一个插入数据,使之还是一个堆。
  • 出堆顶的数据,然后向下调整。
  • 继续出堆顶的数据直到堆为空。
void HeapSort1(int* a, int size)
{
HP hp;
HeapInit(&hp);

for (int i = 0; i < size; i++)
{
HeapPush(&hp, a[i]);
}

int j = 0;
while (!HeapEmpty(&hp))
{
a[j++] = HeapTop(&hp);
HeapPop(&hp);
}

HeapDestroy(&hp);
}

这个方法不错是吧?可不可以优化呢?

由于我们额外建立了一个堆导致有O(n)的空间复杂度,我们是不是可以考虑直接在数组里面建堆呢?

可以。


再次强调:升序要建大堆,排降序建小堆。


如果是建小堆的话第一个就已经是最小的了,后面的数据还需要重新建堆,那么堆排序的意义就不存在了。

思路:

  • 假设要排的数据有n个。
  • 建大堆找到最大的数据和最后一个数据交换。
  • 堆顶数据向下调整,此时新的堆顶数据是n-1个数据里面最大的数。
  • 堆顶和第n-1个数据交换。
  • 直到只剩一个数据。

我们先不急着建堆,先把后面的代码写出来。

for (size_t i = n - 1; i > 0; i--)
{
Swap(&a[0], &a[i]);
AdjustDown(a, i, 0);
}

顺便画个不太好理解的图。

[数据结构初阶收尾篇]一篇文章带你把二叉树撕成二叉树条_二叉树_19

然后在尝试建堆康康。

我们可以采取向上建堆和向下建堆两种建堆方式。

向上建堆就类似于一个个向数组里面插入数据。

向下建堆从倒数第一个非叶子节点开始向下调整

[数据结构初阶收尾篇]一篇文章带你把二叉树撕成二叉树条_c语言_20

//建大堆
// 向上建堆
for (int i = 0; i < n; i++)
{
AdjustUp(a, i);
}

//向下建堆
for (int i = (n-1-1)/2; i >= 0; i--)
{
AdjustDown(a, n, i);
}

既然有两种建堆方式我们就要比较一下效率了。

我们假设是满二叉树:

[数据结构初阶收尾篇]一篇文章带你把二叉树撕成二叉树条_c语言_21

显然向下建堆效率更高。

将代码整合一下:

void HeapSort(int* a, int n)
{
//向下建堆
for (int i = (n-1-1)/2; i >= 0; i--)
{
AdjustDown(a, n, i);
}

for (size_t i = n - 1; i > 0; i--)
{
Swap(&a[0], &a[i]);
AdjustDown(a, i, 0);
}
}

topK问题


即求数据结合中前K个最大的元素或者最小的元素,一般情况下数据量都比较大


如果数据量比较小的话我们可以使用排序,但是如果数据很大呢?

这个时候我们可以用堆解决:

  • 用数据集合中前K个元素来建堆
  1. 前k个最大的元素,则建小堆
  2. 前k个最小的元素,则建大堆
  • 用剩余的N-K个元素依次与堆顶元素来比较,决定是否替换:
  1. 前k个最大的元素,如果堆顶元素比比较元素小,就替换。
  2. 前k个最小的元素,如果堆顶元素比比较元素大,就替换。
  • 将剩余N-K个元素依次与堆顶元素比完之后,堆中剩余的K个元素就是所求的前K个最小或者最大的元素。

代码:

void PrintTopK(int* a, int n, int k)
{
int* kminHeap = (int*)malloc(sizeof(int) * k);
assert(kminHeap);

for (int i = 0; i < k; ++i)
{
kminHeap[i] = a[i];
}

// 建小堆
for (int j = (k - 1 - 1) / 2; j >= 0; --j)
{
AdjustDown(kminHeap, k, j);
}

// 2. 将剩余n-k个元素依次与堆顶元素交换,不满则则替换
for (int i = k; i < n; ++i)
{
if (a[i] > kminHeap[0])
{
kminHeap[0] = a[i];
AdjustDown(kminHeap, k, 0);
}
}

for (int j = 0; j < k; ++j)
{
printf("%d ", kminHeap[j]);
}
printf("\n");
free(kminHeap);
}

注:时间复杂度:O(K+logK*(N-K))) 空间复杂度:O(K)。


如果N非常大,K很小,时间复杂度就可以看作O(N)。


二叉树的链式结构

我们可以考虑用链表来存储二叉树,如图:

[数据结构初阶收尾篇]一篇文章带你把二叉树撕成二叉树条_数据结构_22

也就是说每个节点包含三个部分:

  1. 该节点保存的数据。
  2. 指向左孩子节点的指针。
  3. 指向右孩子节点的指针。
typedef int BTDataType;

typedef struct BinaryTreeNode {
struct BinaryTreeNode* left; //指向左孩子的指针
struct BinaryTreeNode* right; //指向右孩子的指针
BTDataType data; //数据
}BTNode;

你以为是增删查改吧?不,是遍历!

二叉树增删查改没啥价值,我的评价是不如线性表。

题目也不会出增删查改。

总之题目出啥我学啥(骄傲)。

[数据结构初阶收尾篇]一篇文章带你把二叉树撕成二叉树条_数据结构_23

二叉树的遍历主要可以分为前序遍历,中序遍历,后序遍历和层序遍历。

我们一个个来讲。

前序、中序以及后序遍历

  • 前序遍历的顺序是:根节点,左子树,右子树
  • 中序遍历的顺序是:左子树,根节点,右子树
  • 后序遍历的顺序是:左子树,右子树,根节点


通过观察我们发现所谓的前中后其实就是根节点在顺序中的位置
前序就是根在前,中序就是根在中间,后序就是根在最后。


[数据结构初阶收尾篇]一篇文章带你把二叉树撕成二叉树条_php_24

明白了思想之后,我们就可以考虑写代码了。

在遍历的时候我们考虑使用递归的方法。

每一个问题拆成更小的问题,直到我们可以轻松解决。

  • 对于二叉树来说,什么是最小的问题。
  • 只要递归到NULL一切的问题都不是问题。

前中后序遍历唯一的区别就是打印根的时机,所以代码一并放出来:

//前序遍历
void PrevOrder(BTNode* root)
{
if (root == NULL)
{
printf("NULL ");
return NULL;
}

printf("%d ", root->data);
PrevOrder(root->left);
PrevOrder(root->right);
}

//中序遍历
void InOrder(BTNode* root) {
if (root == NULL) {
printf("NULL ");
return;
}
InOrder(root->left);
printf("%d ", root->data);
InOrder(root->right);
}

//后序遍历
void PostOrder(BTNode* root) {
if (root == NULL) {
printf("NULL ");
return;
}
PostOrder(root->left);
PostOrder(root->right);
printf("%d ", root->data);
}

也许有好朋友没看懂。

这个时候就需要画图了,我们以前序为例:

[数据结构初阶收尾篇]一篇文章带你把二叉树撕成二叉树条_数据结构_25

递归题目看不懂的话就多画画递归展开图,会有奇效。

层序遍历

啥是层序遍历捏?

就是一层一层遍历。

[数据结构初阶收尾篇]一篇文章带你把二叉树撕成二叉树条_数据结构_26

以上图为例,层序遍历结果是:A,B,C,D,E,F。

解题思路:

  • 根节点先入队。
  • 开始循环:
  1. 保存队头节点地址front。
  2. 出队头节点。
  3. front如果有子节点,则入队。
  4. 队为空则结束循环。

[数据结构初阶收尾篇]一篇文章带你把二叉树撕成二叉树条_二叉树_27

//层序遍历
void LevelOrder(BTNode* root)
{
Queue q;
QueueInit(&q);
if (root)
{
QueuePush(&q, root);
}

while (!QueueEmpty(&q))
{
BTNode* front = QueueFront(&q);
QueuePop(&q);

printf("%d ", front->data);
if (front->left)
{
QueuePush(&q, front->left);
}

if (front->right)
{
QueuePush(&q, front->right);
}
}

printf("\n");
QueueDestory(&q);
}

劈里啪啦吓人的题目

做点题玩玩吧,会讲一些大致思路或者直接摆烂放代码。

递归这个东西很玄,要是绕不过来就画图吧。

[数据结构初阶收尾篇]一篇文章带你把二叉树撕成二叉树条_二叉树_28

树的节点个数

树的节点个数等于左子树+右子树+1.

空节点直接返回0.

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

树的叶子节点个数

还是把它分成左子树和右子树。

只有左子树和右子树都是NULL是叶子节点。

//叶子节点个数
int BTreeLeafSize(BTNode* root)
{
if (root == NULL)
return 0;

if (root->left == NULL && root->right == NULL)
return 1;

return BTreeLeafSize(root->left) + BTreeLeafSize(root->right);
}

第k层的节点个数

// 第k层的节点的个数,k >= 1
int BTreeKLevelSize(BTNode* root, int k)
{
assert(k >= 1);

if (root == NULL)
return 0;

if (k == 1)
return 1;

return BTreeKLevelSize(root->left, k - 1)
+ BTreeKLevelSize(root->right, k - 1);
}

二叉树销毁

分治啊,分治。

累了,毁灭吧。

// 二叉树销毁
void BTreeDestory(BTNode* root)
{
if (root == NULL)
{
return;
}

BTreeDestory(root->left);
BTreeDestory(root->right);
free(root);
}

二叉树的最大深度

左子树的最大深度和右子树的最大深度中最大的那一个。

int maxDepth(struct TreeNode* root){
if(root==NULL)
return 0;
int left=maxDepth(root->left);
int right=maxDepth(root->right);

return left>right?left+1:right+1;
}

二叉树寻找值为x的节点

// 二叉树查找值为x的结点
BTNode* BTreeFind(BTNode* root, BTDataType x)
{
if (root == NULL)
return NULL;

if (root->data == x)
return root;

BTNode* ret1 = BTreeFind(root->left, x);
if (ret1)
return ret1;

//return BTreeFind(root->right, x);
BTNode* ret2 = BTreeFind(root->right, x);
if (ret2)
return ret2;

return NULL;
}

单值二叉树

链接:​​965. 单值二叉树 - 力扣(LeetCode)​

如果二叉树每个节点都具有相同的值,那么该二叉树就是_单值_二叉树。

只有给定的树是单值二叉树时,才返回 ​​true​​;否则返回 ​​false​​。

这道题是的情况比不是的情况要多很多,所以我们只要在意不是的情况就行。

不是的情况很简单:父节点和子节点不一样。(子节点存在)

bool isUnivalTree(struct TreeNode* root){
if(root==NULL)
return true;

if(root->right&&root->val!=root->right->val)
return false;

if(root->left&&root->val!=root->left->val)
return false;

return isUnivalTree(root->left)&&isUnivalTree(root->right);
}

相同的树

链接:​​100. 相同的树 - 力扣(LeetCode)​

给你两棵二叉树的根节点 ​​p​​ 和 ​​q​​ ,编写一个函数来检验这两棵树是否相同。

如果两个树在结构上相同,并且节点具有相同的值,则认为它们是相同的。

  • 如果俩节点都是空,肯定是相同的。
  • 如果一个是空,一个不是,肯定不相同。
  • 如果值不相等,肯定不是。
bool isSameTree(struct TreeNode* p, struct TreeNode* q){
if(p==NULL&&q==NULL)
return true;

//此时两个节点肯定都不为空
if(p==NULL||q==NULL)
return false;

if(p->val!=q->val)
return false;

return isSameTree(p->left,q->left)&&isSameTree(q->right,p->right);
}

对称二叉树

链接:​​101. 对称二叉树 - 力扣(LeetCode)​

给你一个二叉树的根节点 ​​root​​ , 检查它是否轴对称。

什么是对称?

简单来说,除了根节点,左子树的左子树等于右子树的右子树。

所以我们要先撇掉根节点再写。

思路与上一题一致。

bool _isSymmetric(struct TreeNode* p,struct TreeNode* q)
{
if(p==NULL&&q==NULL)
return true;

if(p==NULL||q==NULL)
return false;

if(p->val!=q->val)
return false;

return _isSymmetric(q->left,p->right)&&_isSymmetric(q->right,p->left);
}

bool isSymmetric(struct TreeNode* root){
if(root==NULL)
return true;

return _isSymmetric(root->left,root->right);
}

反转二叉树

链接:​​226. 翻转二叉树 - 力扣(LeetCode)​

给你一棵二叉树的根节点 ​​root​​ ,翻转这棵二叉树,并返回其根节点。

也和上一题有点像,二叉树的左节点和右节点互换就行,用分治就能换掉整棵树。

struct TreeNode* invertTree(struct TreeNode* root){
if(root==NULL)
return NULL;

invertTree(root->left);
invertTree(root->right);

struct TreeNode* tmp=root->left;
root->left=root->right;
root->right=tmp;

return root;
}

二叉树的前序遍历

之前讲过了对吧,但是题目稍微变了一下。

链接:​​144. 二叉树的前序遍历 - 力扣(LeetCode)​

它要求你把值放到数组里面了。

所以我们要先计算一下节点个数再去操作。

注意注释部分的代码。

int TreeSize(struct TreeNode* root)
{
if(root==NULL)
return 0;

return TreeSize(root->left)+TreeSize(root->right)+1;
}

void PreOrder(struct TreeNode* root, int* a,int* i)
{
if(root==NULL)
return ;

a[(*i)++]=root->val; //
PreOrder(root->left,a,i);
PreOrder(root->right,a,i);
}

int* preorderTraversal(struct TreeNode* root, int* returnSize){
int size=TreeSize(root);
int* arr=(int*)malloc(sizeof(int)*size);
int i=0;
PreOrder(root,arr,&i); //
*returnSize=size;
return arr;
}

另一棵树的子树

链接:​​572. 另一棵树的子树 - 力扣(LeetCode)​

给你两棵二叉树 root 和 subRoot 。检验 root 中是否包含和 subRoot 具有相同结构和节点值的子树。如果存在,返回 true ;否则,返回 false 。

二叉树 tree 的一棵子树包括 tree 的某个节点和这个节点的所有后代节点。tree 也可以看做它自身的一棵子树。

相同的树promax版。

我们只要康康它是不是有子树和subroot相等就行。

bool isSameTree(struct TreeNode* p, struct TreeNode* q){
if(p==NULL&&q==NULL)
return true;

if(p==NULL||q==NULL)
return false;

if(p->val!=q->val)
return false;

return isSameTree(p->left,q->left)&&isSameTree(q->right,p->right);
}

bool isSubtree(struct TreeNode* root, struct TreeNode* subRoot){
if(root==NULL)
return NULL;

return isSameTree(root,subRoot)||isSubtree(root->left,subRoot)||isSubtree(root->right,subRoot);
}

判断二叉树是不是完全二叉树

这题和层序遍历有关。

我们只需要:

  • 将层序遍历里面的空节点也存进队列。
  • 出队列时遇到空节点跳出循环
  • 检查队列里面有无非空节点,有就不是完全二叉树。
// 判断二叉树是否是完全二叉树
bool BTreeComplete(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);
// 空后面出到非空,那么说明不是完全二叉树
if (front)
{
QueueDestory(&q);
return false;
}
}

QueueDestory(&q);
return true;
}

二叉树遍历

链接:​​二叉树遍历_牛客网 ​

编一个程序,读入用户输入的一串先序遍历字符串,根据此字符串建立一个二叉树(以指针方式存储)。 例如如下的先序遍历字符串: ABC##DE#G##F### 其中“#”表示的是空格,空格字符代表空树。建立起此二叉树以后,再对二叉树进行中序遍历,输出遍历结果。

前序遍历力扣版本的变种。

typedef struct BinaryTreeNode
{
char data;
struct BinaryTreeNode* left;
struct BinaryTreeNode* right;
}BTNode;

BTNode* BinaryCreate(char* s,int* i)
{
if(s[*i]=='#')
{
(*i)++;
return NULL;
}

BTNode* Node=(BTNode*)malloc(sizeof(BTNode));
Node->data=s[(*i)++];

Node->left=BinaryCreate(s, i);
Node->right=BinaryCreate(s, i);

return Node;
}

void InOrder(BTNode* root)
{
if(root==NULL)
return;

InOrder(root->left);
printf("%c ",root->data);
InOrder(root->right);
}

int main()
{
char arr[100];
scanf("%s",arr);
int len=strlen(arr);
int i=0;

BTNode* root=BinaryCreate(arr,&i);
InOrder(root);
return 0;
}

最后

下一篇就是C++篇了。

向那些有着伟大浪漫主义色彩的事业致敬,劳动节快乐!