2024年二叉树_child = parent 2 + 1,洞悉MySQL底层架构

img
img

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

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

对任何一棵二叉树
,
如果度为
0
其叶结点个数为n
,
度为
2
的分支结点个数为m
,
则有

n=m +
1.

若规定根节点的层数为
1
,具有
n
个结点的满二叉树的深度h=㏒₂(n+1).

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


i>0

i
位置节点的双亲序号:
(i-1)/2

i=0

i
为根节点编号,无双亲节点


2i+1<n
,左孩子序号:
2i+1

2i+1>=n
否则无左孩子


2i+2<n
,右孩子序号:
2i+2

2i+2>=n
否则无右孩子

4.二叉树的存储结构

1. 顺序存储

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

在数组中,有父子间下标计算公式关系式:

1.  leftchild = parent*2+1

2.  rightchild = parent*2+2

3.  parent = (child-1) / 2 (奇数偶数余2结果相同,所以(左右孩子-1) / 2结果都相同)

2. 链式存储

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

typedef int BTDataType;
// 二叉链
struct BinaryTreeNode
{
 struct BinTreeNode* _pLeft; // 指向当前节点左孩子
 struct BinTreeNode* _pRight; // 指向当前节点右孩子
 BTDataType _data; // 当前节点值域
}
// 三叉链
struct BinaryTreeNode
{
 struct BinTreeNode* _pParent; // 指向当前节点的双亲
 struct BinTreeNode* _pLeft; // 指向当前节点左孩子
 struct BinTreeNode* _pRight; // 指向当前节点右孩子
 BTDataType _data; // 当前节点值域
};

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

1.堆的概念及结构

堆的性质:

  1. 堆中某个节点的值总是
    不大于

    不小于
    其父节点的值;

  2. 堆总是一棵完全二叉树。

大堆:树中都大于或等于孩子

小堆:树中都小于或等于孩子

2.堆的实现

1.堆的代码实现

Heap.h

#pragma once
#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
#include <stdbool.h>

typedef int HPDataType;

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

//初始化
void HeapInit(HP* php);
//销毁
void HeapDestroy(HP* php);
//打印数据
void HeapPrint(HP* php);
//插入x以后,保持它依旧是(大/小)堆
void HeapPush(HP* php,HPDataType x);
//删除堆顶的数据,(最大\最小)
void HeapPop(HP* php);
//判断是否为空
bool HeapEmpty(HP* php);
//堆大小
size_t HeapSize(HP* php);
//堆顶
HPDataType HeapTop(HP* php);

Heap.c

#include "Heap.h"

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

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

void HeapPrint(HP* php)
{
	assert(php);
	size_t i = 0;
	for (i = 0; i < php->size; i++)
	{
		printf("%d", php->a[i]);
	}
	printf("\n");
}

void SWAP(HPDataType* pa, HPDataType* pb)
{
	HPDataType tmp = *pa;
	*pa = *pb;
	*pb = tmp;
}

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;
		}
	}
}

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 = (HPDataType*)realloc(php->a ,sizeof(HPDataType)*newCapacity);
		if (tmp == NULL)
		{
			printf("ralloc fail\n");
			exit(-1);
		}

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

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

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

}

void AdjustDown(HPDataType* a, size_t size,size_t root)
{
	size_t parent = root;
	size_t child = parent * 2 + 1;
	while (child < size)
	{
		//找出左右孩子中最小的那个
		if (child + 1 < size && a[child + 1] > a[child])//child+1是防止右孩子是NULL,访问失败
		{
			child++;
		}

		//如果孩子小于父亲,则交换,并继续往下调整
		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[php->size - 1], &php->a[0]);
	php->size--;

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

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];
}
2.堆的调整算法

1.向上调整(如上代码实现中的HeapPush)

代码实现:

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;
		}
	}
}

2. 向下调整(如上代码实现中的HeapPop)

代码实现 :

void AdjustDown(HPDataType* a, size_t size,size_t root)
{
	size_t parent = root;
	size_t child = parent * 2 + 1;
	while (child < size)
	{
		//找出左右孩子中最小的那个
		if (child + 1 < size && a[child + 1] > a[child])//child+1是防止右孩子是NULL,访问失败
		{
			child++;
		}

		//跟父亲比较,如果孩子小于父亲,则交换
		if (a[child] < a[parent])
		{
			SWAP(&a[child], &a[parent]);
            //在从交换的孩子位置继续向下调整
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			break;
		}
	}
}

3.堆的应用

1.堆排序

堆排序分两步:

1.建堆(升序:建大堆,降序:建小堆)

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

例:给数组a对里元素排序,遍历一遍复杂度:O(N²)

void HeapSort(int* a, int size)
{
	HP hp;
	HeapInit(&hp);
	int i = 0;
	for (i = 0; i < size; i++)
	{
		HeapPush(&hp, a[i]);//升序
	}

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

	HeapDestroy(&hp);
}

时间复杂度:O(N*㏒N) , 空间复杂度:O(N)

优化后(利用堆删除思想):时间复杂度:O(N*㏒N) , 空间复杂度:O(1)

1.1 建堆 (建堆时间复杂度:O(N))

一、向下调整法建堆

	for (i = (size-1-1)/2; i >= 0; i--)//size-1是最后一个结点的位置
	{
		AdjustDown(a, size, i);
	}
	//parent = ( child - 1 )/2

二、向上调整法建堆

for (i = 1; i < size; i++)
	{
		AdjustUp(a, i);
	}

1.2 利用堆删除思想来进行排序

对上面例题优化后:时间复杂度:O(N*㏒N) , 空间复杂度:O(1)

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

void HeapSort(int* a, int size)
{
	int i = 0;
	//向上调整 -- 建堆
	//for (i = 1; i < size; i++)
	//{
	//	AdjustUp(a, i);
	//}

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

	size_t end = size-1;
	while (end > 0)
	{
		SWAP(&a[end], &a[0]);
		AdjustDown(a, end, 0);
		end--;
	}
}

2.TOP-K问题

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

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

对于
Top-K
问题,能想到的最简单直接的方式就是排序,但是:如果数据量非常大,排序就不太可取了
(
可能数据都不能一下子全部加载到内存中)

最佳的方式就是用堆来解决

思路:用前K个数建立一个K个数的小堆,然后剩下的N-K个依次遍历,如果比堆顶的数据大,就替换它进堆, 最后堆里面的K个数就是最大的K个

void PrintTopK(int* a,int size, int k)
{
	//1.建堆 -- 用前a中前k个元素建堆
	int* kminHeap = (int*)malloc(sizeof(int) * k);
	assert(kminHeap);

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

	//建小堆 
	for (i = (k - 1 - 1) / 2; i >= 0; i--)
	{
		AdjustDown(a, size, i);
	}

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

总结:

用数据集合中前K个元素来建堆

求前
k
个最大的元素,则建小堆

求前
k
个最小的元素,则建大堆


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

1.链式二叉树结构说明

typedef int BTDataType;

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

二叉树是:

空树

非空:根节点,根节点的左子树、根节点的右子树组成的

2.二叉树的遍历

手动创建这样一棵树

typedef int BTDataType;

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

BTNode* BuyBTNode(BTDataType x)
{
	BTNode* newnode = (BTNode*)malloc(sizeof(BTNode));
	if (newnode == NULL)
	{
		printf("malloc fail\n");
		exit(-1);
	}

	newnode->data = x;
	newnode->left = newnode->right = NULL;
	return newnode;
}

BTNode* CreateBinaryTree()
{
	BTNode* node1 = BuyBTNode(1);
	BTNode* node2 = BuyBTNode(2);
	BTNode* node3 = BuyBTNode(3);
	BTNode* node4 = BuyBTNode(4);
	BTNode* node5 = BuyBTNode(5);
	BTNode* node6 = BuyBTNode(6);

	node1->left = node2;
	node1->right = node4;
	node2->left = node3;
	node4->left = node5;
	node4->right = node6;

	return node1;
}

int main()
{
	BTNode* tree = CreateBinaryTree();

	return 0;
}

2.1 前序遍历(先根遍历)顺序:    根、左子树、右子树
void PrevOrder(BTNode* root)
{
	if (root == NULL)
	{
		printf("NULL");
		return;
	}

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

int main()
{
	BTNode* tree = CreateBinaryTree();
	PrevOrder(tree);
	return 0;
}

打印出结果: 

2.2 中序遍历(中根遍历)顺序:  左子树,根节点,右子树
void InOrder(BTNode* root)
{
	if (root == NULL)
	{
		printf("NULL ");
		return;
	}

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

int main()
{
	BTNode* tree = CreateBinaryTree();
	InOrder(tree);
	return 0;
}

打印出结果:

2.3   后序遍历(后根遍历)顺序:  左子树,右子树,根节点
void PostOrder(BTNode* root)
{
	if (root == NULL)
	{
		printf("NULL ");
		return;
	}

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

int main()
{
	BTNode* tree = CreateBinaryTree();
	PostOrder(tree);
	return 0;
}

打印出结果:

img
img

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

sdnimg.cn/f44fb74a89434684aaa31a86eb7cc77f.png)**

2.3   后序遍历(后根遍历)顺序:  左子树,右子树,根节点
void PostOrder(BTNode* root)
{
	if (root == NULL)
	{
		printf("NULL ");
		return;
	}

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

int main()
{
	BTNode* tree = CreateBinaryTree();
	PostOrder(tree);
	return 0;
}

打印出结果:

[外链图片转存中…(img-oGpJNpaW-1715592973431)]
[外链图片转存中…(img-hKLvad07-1715592973431)]

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值