数据结构篇之二叉树(binary tree)的介绍和应用

欢迎光临:

                                   

男神

目录

一·树的介绍和表示:

二·二叉树的介绍及性质:

三·堆的介绍及创建:

1·堆的创建:

2·堆的应用:

四·二叉树的创建:

①// 通过前序遍历的数组"ABD##E#H##CF##G##"构建二叉树BTNode*BinaryTreeCreate(BTDataType* a, int n, int* pi):

 ②void BinaryTreeDestory(BTNode** root):

③int BinaryTreeSize(BTNode* root):

④int BinaryTreeLeafSize(BTNode* root):

⑤int BinaryTreeLevelKSize(BTNode* root, int k):

⑥BTNode* BinaryTreeFind(BTNode* root, BTDataType x):

 

⑦void BinaryTreePrevOrder(BTNode* root):

⑧void BinaryTreeInOrder(BTNode* root):

⑨void BinaryTreePostOrder(BTNode* root):

⑩void BinaryTreeLevelOrder(BTNode* root):

⑪int BinaryTreeComplete(BTNode* root):

⑫int maxDepth(struct TreeNode* root):

五·二叉树的oj刷题:

1·对称二叉树: 

2·另一棵树的子树:

3·平衡二叉树:

4·翻转二叉树:


一·树的介绍和表示:

首先,要知道树是一种非线性结构,然后第一个是它的根节点,类似于树一样往下面展开。

大致长这个样子。

那这样子的树要怎表示呢?

我们用一种叫孩子兄弟表示法:

也就是我们在结构体里面创建的分别为孩子,兄弟,数据这几个项,然后把它们连接起来

    typedef int DataType;
    struct treenode
    {
         struct Node* Child; 
         struct Node* Brother; 
         DataType data; 
    };

当然,这种研究起来比较复杂,接下来介绍一下二叉树。 

 

二·二叉树的介绍及性质:

二叉树就是一个根引出两个支,然后延续下去,成为二叉树的左右子树,然后这就给了它一个限制,也就是每个节点只能引出两条边,也就是度最大为2。

然后二叉树又分为:

1·不完全二叉树:不完全的话就是完全的反面如上面的这棵树就是不完全二叉树。

2·完全二叉树:完全的话就是每一行从左到右应该是持续排列的,即不能左支无而右支有。如:

3·满二叉树: 这就是完全二叉树的一种类型,它是每个节点都连接了两个分支,也就是除了叶子层每个节点度都是2.如:

性质:简单说比较重要的就是任何一个二叉树n(度为2)+1=n(度为1) ;下面还有一些性质可做参考:

 

三·堆的介绍及创建:

堆其实也可以看做一颗完全二叉树,它是非线性结构。然后又可以分为大堆和小堆。

1·小堆:树中父亲节点的值小于或等于孩子节点值。

2·大堆:树中父亲节点的值大于会等于孩子节点的值。

因此我们创建堆可以分这两类。

那这个有什么用途呢?我们可以用它topk(在很多数据中找到前k个数据)以及排序问题。

1·堆的创建:

那么下面我们先来实现一下堆:

首先我们先创建这样的结构体,利用数组来实现。

typedef struct Heap {
	int capacity;
	int size;
	headpdata* a;
}Hp;

下面介绍一下,向上调整和向下调整。首先对于堆来说要知道孩子还父亲在数组里对应的关系。

int parent = (child - 1) / 2;
int child = parent * 2 + 1;

上调:就是从孩子节点往上调整,也就是如果我们要建立小堆的话,如果孩子比父亲小那么就让两者互换得到新的孩子一直循环下去,直到孩子的下标大于0即完成,大堆变动一下即可。

下调:还是拿小堆为例子,拿到父亲节点值与两个孩子比较,得到两个孩子中最小的交换,再次得到新的父亲节点,再求下一个孩子节点,直到孩子节点到达数组最大长度n终止。 

然后这里我们在每一次上调或者下调的时候要确保这个数组构成的堆是有序的。

然后我们每次删除堆顶元素,需要让它和尾部元素交换,然后得到一个除了堆顶元素外剩下都有序的堆然后下调,同理插入的时候,每次插入都进行上调。

下面展示一下堆的代码:

heap.h


#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>
typedef int headpdata;
typedef struct Heap {
	int capacity;
	int size;
	headpdata* a;
}Hp;
void Hpinit(Hp* p);//初始化
void Hpdestroy(Hp* p);//销毁
void Hppush(Hp* p, headpdata x);//数据入堆
void Hpupadjust(headpdata* a, int child);//数据在堆中上调
void Hpdownadjust(headpdata* a, int parent, int n);//下调
bool Hpempty(Hp* p);//判空
headpdata  Hptop(Hp* p);//返回堆顶元素;
void Hppop(Hp* p);//删除堆顶元素


heap.c

#define _CRT_SECURE_NO_WARNINGS
#include"heap.h"
void swap(headpdata* a, headpdata* b) {
	headpdata tmp = *a;
	*a = *b;
	*b = tmp;
}
void Hpinit(Hp* p) {
	assert(p);
	p->capacity = p->size = 0;
	p->a = NULL;
}

void Hppush(Hp* p, headpdata x) {
	assert(p);

	if (p->capacity == p->size) {
		int newcapacity = p->capacity == 0 ? 4 : 2 * p->capacity;
		headpdata* m = (headpdata*)realloc(p->a, sizeof(headpdata) * newcapacity);
		if (m != NULL) {

			p->a = m;
			p->capacity = newcapacity;

		}
		else {
			perror(realloc);

		}
	}
	p->a[p->size] = x;
	p->size++;
	Hpupadjust(p->a, p->size - 1);
}
void Hpupadjust(headpdata* 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 {
			return;
		}
	}
}
void Hpdownadjust(headpdata* a, int parent, int n) {
	int child = parent * 2 + 1;


	while (child < n) {
		//假设孩子为左孩子
		if (child + 1 < n && a[child] > a[child + 1]) {
			child++;
		}
		//小堆:child>child+1
		//此时的child一定是最小的一个
		if (a[child] < a[parent]) /*小堆(<)*/
		{
			swap(&a[child], &a[parent]);
			parent = child;
			child = parent * 2 + 1;
		}
		else {
			return;
		}
	}
}
bool Hpempty(Hp* p) {
	assert(p);
	return p->size == 0;
}
headpdata  Hptop(Hp* p) {
	assert(p);
	return p->a[0];
}
void Hppop(Hp* p) {
	assert(p);
	swap(&p->a[0], &p->a[p->size - 1]);
	p->size--;
	Hpdownadjust(p->a, 0, p->size);
}
void Hpdestroy(Hp* p) {
	assert(p);
	free(p->a);
	p->a = NULL;
	p->capacity = p->size = 0;

}


test.c


#define _CRT_SECURE_NO_WARNINGS
#include"heap.h"
void test1() {
	Hp hp;
	Hpinit(&hp);
	Hppush(&hp, 2);
	Hppush(&hp, 11);
	Hppush(&hp, 9);
	Hppush(&hp, 8);
	Hppush(&hp, 5);
	Hppush(&hp, 2);
	Hppush(&hp, 4);
	for (int i = 0; i < 7; i++)
	{

		printf("%d ", Hptop(&hp));
		Hppop(&hp);

	}
	Hpdestroy(&hp);
}
int main() {
	test1();
	return 0;
}

这样就建好堆了,至于每次上下调传的是数组的指针而不是结构体指针方便下面我们可以直接调用数组进行堆排序。

2·堆的应用:

堆排序:以小堆为例介绍,其实就是我们创建好数组然后给它传递过去,上调的话就是从第一个开始遍历这个数组每次都调用一次上调,最后这个数组就会是一个小堆,接着每次让堆顶元素跟尾部元素交换然后尾指针--;然后对这个堆(由于除了堆顶元素其他都有序,故下调,无论建大堆还是小堆均这样)最后就得到了降序的数组。

然后由于时间复杂度,通常选用下调建堆。

堆排序也通常要记住:升序建大堆,降序建小堆。

代码如下:

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>
//数组内部堆排序:
//数组升序则大堆排列,数组降序则小堆排列

//数据往上下调时候,原堆应该是有序的
void swap(int* a, int* b) {
	int tmp = *a;
	*a = *b;
	*b = tmp;
}

void Hpupadjust(int* 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 {
			return;
		}

	}
}
void Hpdownadjust(int * a, int parent, int n) {
	int child = parent * 2 + 1;
	//小堆(<)
	while (child < n) {
		//假设孩子为左孩子
		if (child + 1 < n && a[child] > a[child + 1]) {
			child++;
		}
	//大堆child<child+1
		if (a[child] <a[parent])/*小堆(<) */{
			swap(&a[child], &a[parent]);
			parent = child;
			child = parent * 2 + 1;
		}
		else {
			return;
		}
	}
}
/升序大堆,降序小堆
void sort_upadjust(int* a, int n) {
	for (int i = 1; i < n; i++) {
		Hpupadjust(a, i);//上调
	}
	/*for (int i = 0; i < n; i++) {
		printf("%d ", a[i]);
	}
	printf("\n");*/
	int end = n - 1;
	while (end > 0) {
		swap(&a[0], &a[end]);
		Hpdownadjust(a, 0,end);
		end--;
		
		
		

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

void sort_downadjust(int* a, int n) {
	for (int i = (n-2)/2; i >=0; i--) {
		Hpdownadjust(a, 0, i);//下调
	}
	int end = n - 1;
	while (end > 0) {
		swap(&a[0], &a[end]);
		Hpdownadjust(a, 0, end);
		end--;
	}

		for (int i = 0; i < n; i++) {
			printf("%d ", a[i]);
		}
}
int main() {
	int a[] = { 4,6,28,99,34,66,1,1,2,377,4 };
	int r=sizeof(a)/sizeof(a[0]);
	sort_upadjust(a,r);//上调生成堆的排序
	sort_downadjust(a, r);//下调生成堆的排序


	return 0;
}

还有一个应用就是在大量数据中找出前k个:

这里我们在文件内生成十万个数据模拟一下,然后为了节省空间选择建立一个k个数的小堆(假设k=5);然后把它建成小堆,先从文件中读取5个数建好,然后接下来每次读取一个跟堆顶元素比较比它大就覆盖它重新下调,这样每次我们都能选出6个数中最大的5个并且淘汰一个已知不合要求的数字,然后循环下去得到最终的前五。

 

如果我们要得到有序的k个最大的数,可以再调用一下上面的堆排序:

代码如下:

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<time.h>
#include<stdlib.h>
void swap(int* a, int* b) {
	int tmp = *a;
	*a = *b;
	*b = tmp;
}
void Hpdownadjust(int* a, int parent, int n) {
	int child = parent * 2 + 1;
	//小堆(<)
	while (child < n) {
		//假设孩子为左孩子
		if (child + 1 < n && a[child] > a[child + 1]) {
			child++;
		}
		//大堆child<child+1
		if (a[child] < a[parent])/*小堆(<) */ {
			swap(&a[child], &a[parent]);
			parent = child;
			child = parent * 2 + 1;
		}
		else {
			return;
		}
	}
}
void createdata() {
	FILE* file = fopen("data.txt", "w");
	if (file == NULL) {
		perror(fopen);
		return;
	}
	int j = 0;
	srand((unsigned int)time(NULL));
	for (int i = 0; i < 100000; i++) {
		j = (rand() + i) % 100000;//srand产生的随机数会有重复,给他+i可以减少重复数据
		fprintf(file, "%d\n", j);
	}
	fclose(file);
}
int main() {
	/*createdata();*///创作10w个数据
	//读入数组内
	printf("请输入要找的最大的前k个数>: ");
	int k = 0;
	scanf("%d", &k);
	int* a = (int*)malloc(sizeof(int) * k);
	FILE* fout = fopen("data.txt", "r");
	if (fout == NULL) {
		perror(fopen);
		return;
	}
	for (int i = 0; i < k; i++) {
		fscanf(fout, "%d", &a[i]);
	}
	
	/*for (int i = 0; i < k; i++) {
		printf("%d ", a[i]);
	}*/
	//向下调整建立小堆:
	for (int i = (k - 2) / 2; i >=0; i--) {
		Hpdownadjust(a, i, k);
	}
	//一个个把剩下的n-k个数据读出来与a[0]比大小,大则替换:
	// 可以确保小堆里面的数据一定比替换下去的数据大
	//这样就可以一次舍去不合要求数据
	int x = 0;
	while (fscanf(fout, "%d", &x)!=EOF) {//fscanf输出异常为EOF否则
		if (x > a[0]) {
			a[0] = x;
			Hpdownadjust(a, 0, k);
		}
		/*printf("%d ", fscanf(fout, "%d", &x));*///返回的为实际读取数据的个数:1
	}
	fclose(fout);
	//逆序输出最大前k个数:
	int end = k - 1;
	while (end) {
		swap(&a[0], &a[end]);
		end--;
		Hpdownadjust(a, 0, end+1);
	}
	for (int i = 0; i < k; i++) {
		printf("%d ", a[i]);
	}
	return 0;
}

以上就是堆的一些应用。 

 

四·二叉树的创建:

首先创建一个存放字符数据的树,先定义结构体:

typedef char BTDataType;

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

①// 通过前序遍历的数组"ABD##E#H##CF##G##"构建二叉树BTNode*BinaryTreeCreate(BTDataType* a, int n, int* pi):

这里首先创建一个返回可放入数据的一个节点,然后每次判断只要是#就返回NULL,否则放入数据,然后再左子树右子树走下去,每当判断完或者放完数据就让指针++;然后函数不断调用自己,直到归到返回的最开始的根节点。(由于函数调用自己要想改变下标的值必须传地址用指针接收)

BTNode* BinaryTreeCreate(BTDataType* a, int n, int* pi) {
	
		if (a[*pi] == '#') {
			

			return NULL;
		}

		BTNode* node = createnode(a[*pi]);
		

		(*pi)++;
		node->left = BinaryTreeCreate(a, n, pi);
			(*pi)++;

		node->right = BinaryTreeCreate(a, n,pi);
	

			return node;
	
}

 ②void BinaryTreeDestory(BTNode** root):

这里为了销毁的完整选择的是只要不为空左右子树遍历下去,然后当左右子树都为空时,把这个根释放掉。

void BinaryTreeDestory(BTNode** root) {
	if (*root != NULL) {
		BinaryTreeDestory(&((*root)->left));
		BinaryTreeDestory(&((*root)->right));
		free(*root);
		*root=NULL;
	}
}

 

③int BinaryTreeSize(BTNode* root):

就是节点的个数,可以从根(不为NULL)开始左右子树遍历,每遍历一次就加1,故就是1+遍历的左子树+遍历的右子树,为NULL就返回0。

int BinaryTreeSize(BTNode* root) {
	if (root == NULL) {
		return 0;
	}
	return 1 + BinaryTreeSize(root->left) + BinaryTreeSize(root->right);
}

④int BinaryTreeLeafSize(BTNode* root):

首先要知道只有当它的左右子树都为NULL,那么就是叶子,故可以加一,否则在不为空的情况下就递归返回它的左子树加右子树,直到为空就返回0。

int BinaryTreeLeafSize(BTNode* root) {
	if (root == NULL) {
		return 0;
	}
	if (root->left==NULL && root->right == NULL) {
		return 1;
	}
	return  BinaryTreeLeafSize(root->left) + BinaryTreeLeafSize(root->right);
}

 

⑤int BinaryTreeLevelKSize(BTNode* root, int k):

可以通过每次遍历左右子树就让k的值-1直到达到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);
}

 

⑥BTNode* BinaryTreeFind(BTNode* root, BTDataType x):

法一:这里,可以考虑把它按照层序查找入栈,然后一个个去访问栈中的元素,一旦发现就返回这个指针,到最后也没发现就返回NULL;思路同

代码:

BTNode* BinaryTreeFind(BTNode* root, BTDataType x) {
	queue q;
	Queueinit(&q);
	Queuebackpush(&q, root);
	while (!QueueEmpty(&q)) {
		BTNode* ret = Queuefrontdata(&q);
		Queuefrontpop(&q);
		if (ret->data == x) {
			Queuedestroy(&q);
			return ret;
		}
		if (ret->left != NULL) {

			Queuebackpush(&q, ret->left);
		}
		if (ret->right != NULL) {

			Queuebackpush(&q, ret->right);
		}
	}
	
	Queuedestroy(&q);
	return NULL;


}

法二:在前提首根不为NULL情况下遍历它的左右子树,如果发现了x就返回这个根,否则接着

左右子树接着调用找,用指针记录它们的返回值,并保存,等到归回去的时候,如果不为空就把它返回最终得到x所在节点的指针。

BTNode* BinaryTreeFind(BTNode* root, BTDataType x) {
  
if(root==NULL){
return NULL;
 if(root->val==x){
return root;
    
       }
    BTNode*ret1=BinaryTreeFind(root->left);
if(ret1){
      return ret1;}
     BTNode*ret2=BinaryTreeFind(root->right);
if(ret2){
      return ret2;}
}

 

 

 

⑦void BinaryTreePrevOrder(BTNode* root):

也就是根左右方式去遍历,打印的话就是遇到根就把它打印,然后再分别去遍历左右两支。

void BinaryTreePrevOrder(BTNode* root) {
	if (root == NULL) {
		printf("# ");
		return;
	}
	printf("%c ", root->data);
	BinaryTreePrevOrder(root->left);
	BinaryTreePrevOrder(root->right);


}

 

⑧void BinaryTreeInOrder(BTNode* root):

也就是左根右方式去遍历,一直遍历左树后打印然后再调用右树。

与前序相类似:

void BinaryTreeInOrder(BTNode* root) {
	if (root == NULL) {
		printf("# ");
         return;
 
	}
	BinaryTreeInOrder(root->left);
	printf("%c ", root->data);
	BinaryTreeInOrder(root->right);
}

⑨void BinaryTreePostOrder(BTNode* root):

也就是左右根方式去遍历,一直遍历右树后打印然后再调用左树,与中序打印换一下位置。

void BinaryTreePostOrder(BTNode* root) {
	if (root == NULL) {
		printf("# ");
         return;
 
	}
	BinaryTreePostOrder(root->left);
	BinaryTreePostOrder(root->right);
	printf("%c ", root->data);
}

⑩void BinaryTreeLevelOrder(BTNode* root):

首先建立一个队列然后入队列的数据类型是这个树节点的类型,然后先把树的头根入进去,之后把出队列,然后保存并打印,只要它的左右子树不为空就入进去,循环下去,为空就退,最后就能都打印出。

void BinaryTreeLevelOrder(BTNode* root) {
	queue q;
	Queueinit(&q);
	Queuebackpush(&q, root);
	while (!QueueEmpty(&q)) {
		BTNode*ret=Queuefrontdata(&q);
		Queuefrontpop(&q);
		printf("%c ", ret->data);
		if (ret->left!= NULL) {
			
			Queuebackpush(&q, ret->left);
		}
		if (ret->right!= NULL) {
			
			Queuebackpush(&q, ret->right);
		}
	}
	Queuedestroy(&q);

}

 

这里会不会一个疑问,就是队列为空时候可不可能还有树的节点指针没有进去,然而要知道只要父亲被移除了队列后那么他的孩子一定能进去。

⑪int BinaryTreeComplete(BTNode* root):

还是把它放入队列,然后有进有出,当我们把空节点的指针放进去,那么只要我们在出队列时候读取数据,只要读到了空,就停止,时刻记录队列内数据的个数,然后依次去出队列剩下的数据,如果读到了不是空的指针就证明不是完全二叉树直接返回,如果读到size为0还为读到有数据,即完全二叉树。

int BinaryTreeComplete(BTNode* root) {
	int is = 1;
	int size = 0;
	queue q;
	Queueinit(&q);
	Queuebackpush(&q, root);
	size = 1;
	BTNode* ret = NULL;
	while (!QueueEmpty(&q)) {
		ret = Queuefrontdata(&q);
		Queuefrontpop(&q);
		size--;
		if (ret != NULL) {
			Queuebackpush(&q, ret->left);
			Queuebackpush(&q, ret->right);
			size += 2;
		}
		else {
			break;
		}
		
	}
	while (size--) {
		ret = Queuefrontdata(&q);
		Queuefrontpop(&q);
		if (ret != NULL) {
			 is = 0;
			break;
		}

	}
	Queuedestroy(&q);

	return is;
}

 

⑫int maxDepth(struct TreeNode* root):

即树的高度,就是1+左右子树最长的树,然后一次递归下去(在前提首根不为NULL情况下)。

注:这里由于多次递归复杂问题,可以把之前递归得到的值保存,在进行比较。

int maxDepth(struct TreeNode* root) {
    if (root == NULL) {
        return  0;
    }
    int lefthigh = maxDepth(root->left);
    int righthigh = maxDepth(root->right);//这里把计算的子树的深度保存防止下一次再次递归
    return lefthigh > righthigh ? (lefthigh + 1) : (righthigh + 1);

}

 

五·二叉树的oj刷题:

1·对称二叉树: 

链接:. - 力扣(LeetCode)

思路:如果数都为null那么肯定不对称,不是null那么就可以类比判断两颗树是否相同,即每次比较左右子树,但是是左子树左支和右子树右支比,左子树右支和右子树左支比,然后递归下去,只有每次判断都是true才成立,用&&

bool issimilar(struct TreeNode* p, struct TreeNode* q){
   if(p==NULL&&q==NULL){
        return true;//判断位置合适也是终止条件(true)
    }
    if(p==NULL||q==NULL){
        return false;//上面已经判断了q p不可能同时为空,那么其中至少一个为空就false
    }
    if(q->val!=p->val){
        return false;//判断数值是否合适
    }
    return  issimilar( p->left,q->right)&&issimilar( p->right,q->left);

 }
bool isSymmetric(struct TreeNode* root) {
    if(root==NULL){
        return false;
    }//判断一开始就不为空

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

 

 

2·另一棵树的子树:

链接:. - 力扣(LeetCode) 

思路:首先root如果为NULL肯定不成立,如果root不为空,就遍历root这棵树,然后每次可以得到他的一个节点,就调用比较两棵树是否相同的函数,每到root的一个节点就用一下 isSameTree,然后每次调用遍历函数 只要发现了相同就是出现了true即成立,故这里用||

 

 bool isSameTree(struct TreeNode* p, struct TreeNode* q) {
    if(q==NULL&&p==NULL){
        return true;//判断位置合适也是终止条件(true)
    }
    // if(p==NULL&&q!=NULL){
    //     return false;
    // }
    // if(p!=NULL&&q==NULL){
    //     return false;
    // }
    if(p==NULL||q==NULL){
        return false;//上面已经判断了q p不可能同时为空,那么其中至少一个为空就false
    }
    if(q->val!=p->val){
        return false;//判断数值是否合适
    }

    return  isSameTree(p->left,q->left)&& isSameTree(p->right,q->right);//由于为bool类型,只有遍历的两棵树
    // 左右都返回的true才能证明完全相等,故yong&&
}
 bool prefind_istree(struct TreeNode*p,struct TreeNode*q){
   if(p==NULL){
    return false;
   }
      
      if( isSameTree(p,q)==true){
        return true;
      }//证明找到子树
   return  prefind_istree(p->left,q)||prefind_istree(p->right,q);//找到了就可以返回存在一个就可

 }

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

 

3·平衡二叉树:

链接:. - 力扣(LeetCode) 

思路:先建立一个比较树高度的函数,然后让整颗树以及它的左右子树互相比较left与right如果发现小于等于一就返回false否则继续下去直到为空

 

int maxDepth(struct TreeNode* root) {
    if (root == NULL) {
        return  0;
    }
    int lefthigh = maxDepth(root->left);
    int righthigh = maxDepth(root->right);//这里把计算的子树的深度保存防止下一次再次递归
    return lefthigh > righthigh ? (lefthigh + 1) : (righthigh + 1);

}

bool isBalanced(struct TreeNode* root) {
    if(root==NULL){
        return true;
    }//只能判断首个根是否为NULL
    
    if(abs(maxDepth(root->left)-maxDepth(root->right))>1){
        return false;//这里判断>1好写一些
    }
    return isBalanced(root->left)&&isBalanced(root->right);
   
   
}

 

 

4·翻转二叉树:

链接:. - 力扣(LeetCode) 

 思路:依次交换树的左右两支,不断递归直到得到的为叶子,然后用本次递归调用函数的root->左右交换,最后返回的root就是第一次调用函数的root。

 

struct TreeNode* invertTree(struct TreeNode* root) {
    if(root==NULL){
        return NULL;
    }
    struct TreeNode*ret1=invertTree(root->left);
    struct TreeNode*ret2=invertTree(root->right);
    root->right=ret1;
    root->left=ret2;

    return root;

}

 

 ☺剧终☺

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/648646.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

2024年中国电机工程学会杯数学建模思路 - 案例:最短时间生产计划安排

# 前言 2024电工杯(中国电机工程学会杯)数学建模思路解析 最新思路更新(看最新发布的文章即可): https://blog.csdn.net/dc_sinor/article/details/138726153 最短时间生产计划模型 该模型出现在好几个竞赛赛题上&#xff0c;预测2022今年国赛也会与该模型相关。 1 模型描…

Flink常见面试题总结

文章目录 1. 简单介绍一下Flink2. Flink 的运行必须依赖Hadoop组件吗?3. Flink 和 Spark Streaming 的区别&#xff1f;4. Flink集群角色5. Flink核心概念5.1 并行度5.2 算子链&#xff08;Operator Chain&#xff09;5.3 任务槽&#xff08;Task Slots&#xff09;5.4 任务槽…

【PCI】PCIe高级错误上报能力AER(十二)

本文参考PCIe协议 5.0&#xff1a;https://download.csdn.net/download/zz2633105/89204842 本文参考intel IP用户指南&#xff1a;https://www.intel.cn/content/www/cn/zh/docs/programmable/683501/23-2-10-0-0/debugging-data-transfer-and-performance-25123.html 本文参…

技术前沿:三品PLM系统引领工程变更管理新趋势

引言 在当今快速变化的制造行业&#xff0c;产品生命周期管理&#xff08;PLM&#xff09;系统已成为企业不可或缺的工具之一。PLM系统不仅帮助企业优化产品开发流程&#xff0c;还对工程变更管理&#xff08;ECM&#xff09;起着至关重要的作用。本文将探讨PLM系统在工程变更…

机器学习之词袋模型

目录 1 词袋模型基本概念 2 词袋模型的表示方法 2.1 三大方法 1 独热表示法&#xff08;One-Hot&#xff09; 2 词频表示法&#xff08;Term Frequency, TF&#xff09; 3 词频-逆文档频率表示法&#xff08;TF-IDF&#xff09; 2.2 例子 1 词袋模型基本概念 词袋模型&a…

SpringValidation

一、概述&#xff1a; ​ JSR 303中提出了Bean Validation&#xff0c;表示JavaBean的校验&#xff0c;Hibernate Validation是其具体实现&#xff0c;并对其进行了一些扩展&#xff0c;添加了一些实用的自定义校验注解。 ​ Spring中集成了这些内容&#xff0c;你可以在Spri…

【深度学习基础】NumPy数组库的使用

目录 写在开头 一、数组的类型与维度 数组的类型 数组的维度 二、数组的创建 递增数组 同值数组 随机数数组 三、数组的索引 访问/修改单个元素 花式索引 数组的切片 四、数组的变形 数组的转置 数组的翻转 数组的形状改变 数组的拼接 五、数组的运算 数…

Simulate Ring Resonator in INTERCONNECT

Simulate Ring Resonator in INTERCONNECT 正文正文 首先,我们采用 Interconnect 模块的工作流程 一文中介绍的方法添加一个直波导器件。接着,我们需要对它的名称进行更改,此时我们看左侧 Property View - Root Element 中的 General 属性,我们发现 name 属性是灰色的,无…

旅游推荐管理系统

代码位置:旅游管理系统: 根据若依模版的一个旅游管理系统 - Gitee.com 分支dev 项目介绍 项目目的 随着社会的高速发展&#xff0c;人们生活水平的不断提高&#xff0c;以及工作节奏的加快&#xff0c;旅游逐渐成为一个热门的话题&#xff0c;因为其形式的多样&#xff0c;涉…

Android version与 SDK API level对应表

1.Android 版本: 截至目前主要是从android1 到 android15 Android版本对应的SDK API版本就比较多了。下面是一份来自官网的对照表&#xff0c;在此把它牢记下来: 2.如果需要查看具体信息&#xff0c;请访问API Levels | Android versions, SDK/API levels, version codes, co…

一文掌握Go性能分析、采样分析、链路追踪分析、生成火焰图、打点对比分析、压力测试分析、生成网页报告web查看

一文掌握Go性能分析、采样分析、链路追踪分析、生成火焰图、打点对比分析、压力测试分析、生成网页报告web查看。 Go性能分析 1. 准备工作 1.1 下载go-wrk 这个是用来进行http接口压测的, 官网地址&#xff1a;https://github.com/tsliwowicz/go-wrk 七牛云下载 使用 go-wr…

springboot vue 开源 会员收银系统 (5) 分类及商品模块开发

前言 完整版演示 前面我们对会员系统 门店模块开发的开发 完成了门店的基础管理 并与会员相关联 下面我们将开发门店的分类及商品管理 我们分析以下几个重点 分类可以随时禁用不用单独下架某个商品便于管理商品添加应该有图片上传商品设置会员价和散客价便于营销商品应该参与…

【工具分享】Annabelle勒索病毒解密工具

前言 Annabelle勒索病毒灵感来自恐怖电影系列 Annabelle。除了文件加密功能外&#xff0c;Annabelle 勒索软件还会试图禁用防火墙&#xff0c;强制停止一系列正在运行程序&#xff0c;通过连接的 USB 驱动器进行传播。 特征 勒索内容&#xff1a; Annabelle 使用 AES256 CBC 加…

喜讯!宝兰德斩获2024数字中国创新大赛·信创赛道全国总决赛三等奖

5月24日&#xff0c;由国家发展和改革委员会、国家数据局、国家互联网信息办公室、科学技术部、国务院国有资产监督管理委员会和福建省人民政府共同主办的2024数字中国创新大赛信创赛道全国总决赛颁奖典礼暨闭幕式大会在福州海峡国际会展中心圆满落幕。依托专业技术研发能力及丰…

IP地址在广告行业中的重要地位

新时代&#xff0c;广告已经成为了企业推广产品的必要手段&#xff0c;而企业想要广告效果好&#xff0c;就要做到精准投放营销广告&#xff0c;将“花钱”的广告精准送到产品的受众用户面前&#xff0c;让收益大于花销&#xff0c;而归根究底就是广告转化率与回报率是否达到预…

SwiftUI中TabView的基本用法及外观样式设置

TabView提供了一个交互式界面&#xff0c;允许用户在其内部的子界面间自由的切换&#xff0c;TabView有两种显示模式&#xff0c;一个是DefaultTabViewStyle&#xff0c;另一个是PageTabViewStyle&#xff0c;本文将对DefaultTabViewStyle这种类型的基本使用和外观样式设置进行…

Android消息机制回顾(Handler、Looper、MessageQueue源码解析)

回顾&#xff1a; Android消息机制 Android消息机制主要指的是Handler的运行机制以及Handler所附带的MessageQueue和Looper的工作机制。 介绍 通过Handler 消息机制来解决线程之间通信问题&#xff0c;或者用来切换线程。特别是在更新UI界面时&#xff0c;确保了线程间的数…

Swift使用JSONDecoder处理json数据,实现json序列化和反序列化

Json数据处理是开发中不可获取的一项技能&#xff0c;如果你不会处理json数据&#xff0c;那你离失业就不远了&#xff0c;所以学完了swift基础教程&#xff0c;还是先老老实实学习一下json处理吧&#xff0c;有了这项技能&#xff0c;你才可以继续下一个网络请求阶段的开发&am…