堆与二叉树的相爱相杀

前言

什么是树

在这里插入图片描述

树中包括了什么、

节点的度:一个节点的子节点

叶子节点: 度为0的节点/没有子节点的节点

非终端节点: 度不为0的节点

父节点:一个节点有分支节点, 这个节点就叫 分支节点的 父节点

子节点:由一个节点分裂的节点 叫 子节点

兄弟节点: 相同的父节点的节点

树的度:一颗树中最大节点的度 就叫树的度

树的高度/深度: 树中节点最大的层次

节点的祖先:从该节点到所经过分支上的所有节点

森林:由m颗树但互不相交的多颗树

什么是二叉树

在这里插入图片描述

二叉树的介绍

二叉树:每一个父节点只有2个子节点

任何一颗二叉树:其度为0的节点个数为N个,度为2的节点个数为M个,则有 N=M+1;

二叉树中的特例:完全二叉树

完全二叉树:高度为H的树中前 h-1层都是满的 最后一层不满,但是从左往右是连续存在节点的

满二叉树:每一层都是满的 上图显示的是就是完全二叉树

二叉树的定义

我们使用左孩子,右兄弟的方法定义二叉树

typedef char BTDataType;

typedef struct BinaryTreeNode
{
	struct BinaryTreeNode* left;//左孩子
	struct BinaryTreeNode* right;//右孩子
	BTDataType data;

}BTNode;

什么是前序、中序、尾序

前序:先遍历根,然后遍历左子树,然后遍历右子树

void PrevOrder(BTNode* root)//前序 二叉树
{
	if (root == NULL)
	{
		printf("NULL ");
		return;
	}
	printf("%c ", root->data);
	PrevOrder(root->left);//左子树递归 递归到NULL时返回到上一个递归的调用
	PrevOrder(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);
}

怎么计算二叉树中节点个数、叶子节点个数、高度

节点个数:算出左子树的个数加上右子树的个数+1(这里的1是根)

int TreeSize(BTNode*root)//计算节点个数
{
	return root == NULL ? 0 : TreeSize(root->left) + TreeSize(root->right) + 1;//左子树个数+右子树个数+1
}

叶子节点个数:左子树为NULL且右子树为NULL才为叶子节点

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

高度:左子树和右子树比大小谁大谁+1

int TreeHeight(BTNode* root)
{
	if (root == NULL)
	{
		return 0;
	}
	int a = TreeHeight(root->left);
	int b = TreeHeight(root->right);
	//return fmax(TreeHeight(root->left), TreeHeight(root->right)) + 1;
	return a > b ? a + 1 : b + 1;
}

查找二叉树有没有为x的节点、销毁二叉树

查找节点:
先在左子树找,如果有返回那个节点的,如果没有则在右子树找,右子树有则返回节点,
没有则返回空
(返回的时候要用变量来保存,不然返回时值会被下一次查找覆盖)

BTNode* TreeFind(BTNode* root, BTDataType x)//二叉树查找值为x的节点
{
	if (root == NULL)//如果找到了 在左子树 但是还是要走右子树 右子树进入返回来一个空
	{
		return NULL;
	}
	if (root->data == x)
	{
		return root;
	}
	BTNode* ret = NULL;
	ret = TreeFind(root->left, x);
	if (ret)
	{
		return ret;
	}
	return TreeFind(root->right, x);
}

销毁二叉树:先释放左子树,然后释放右子树,然后释放根

void TreeDestory(BTNode* root)//销毁程序
{
	if (root == NULL)
	{
		return;
	}
	TreeDestory(root->left);
	TreeDestory(root->right);
	free(root);
}

什么是堆

堆:非线性表,完全二叉树
堆:底层:
物理结构:数组
逻辑勾结:完全二叉树

小堆

小堆:树中任意一个父亲都<=孩子

大堆

大堆:树中任意一个父亲都>=孩子

堆的实现

堆的定义

typedef  int HPDataType;

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

堆中的接口

void Swap(HPDataType* p1, HPDataType* p2);//交换函数

void HeapInit(HP* php);//初始化

void HeapDestory(HP* php);//销毁

void HeapPush(HP* php,HPDataType x);//插入

void HeapPrint(HP* php);//打印

void HeapPop(HP* php);//删除

HPDataType HeapTop(HP* php);//堆顶

bool HeapEmpty(HP* php);//判断为不为空堆

void AdjustUP(HPDataType* a, int child);//向上调整 小堆

void AdjustDown(HPDataType* a, int n, int parent);//向下调整

堆中初始化和销毁

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

	void HeapDestory(HP* php)//销毁
	{
		assert(php);
		free(php->a);
		php->a = NULL;
		php->capacity = php->size = 0;
	}

交换

void Swap(HPDataType* p1, HPDataType* p2)
{
	HPDataType tmp = *p1;
	*p1 = *p2;
	*p2 = tmp;
}

向上调整、向下调整

完全二叉树相当于一个数组

向上调整:因为堆可以看成一个数组,父节点的下标=(子节点-1)/2,
如果子节点比父节点小那么交换(小堆),大堆则改小于成大于

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

向下调整:我们默认左子树比右子树小,如果左子树比右子树大那么让取左子树的下标++一下
如果父节点比子节点大那么交换(小堆),如果要变成大堆那么父节点小于子节点

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+1这个右子树不存在那么 则直接输出左子树
		//假设做孩子小 如果比右孩子小的话 ++换成右孩子
		{
			++child;
		}
		if (a[child] < a[parent])//小孩比父亲大那么交换(大堆)
			//小的孩子比 父亲小 那么交换(小堆)
		{
			Swap(&a[child], &a[parent]);
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			break;
		}
	}

}

堆的插入与删除

插入:先查看空间够不够,不够则扩容空间,然后尾插到堆中
然后进行向上调整,调整到合适位置

void HeapPush(HP* php,HPDataType x)//插入
{
	assert(php);
	//扩容
	if (php->capacity == php->size)
	{
		int newCapacity = php->capacity == 0 ? 4 : php->capacity * 2;
		HPDataType* tmp = (HPDataType*)realloc(php->a, sizeof(HPDataType)* newCapacity);
		if (tmp == NULL)
		{
			perror("relloc fail");
			exit(-1);
		}
		php->a = tmp;
		php->capacity = newCapacity;
	}
	php->a[php->size] = x;
	php->size++;
	AdjustUP(php->a, php->size - 1);
}

删除:先让尾节点跟根交换,然后让有效长度–,最后向下调整让整个二叉树成为堆

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

堆顶与判断堆为不为空与打印堆

堆顶:

HPDataType HeapTop(HP* php)//堆顶
{
	assert(php);
	assert(php->size > 0);
	return php->a[0];
}

判断为不为空:

bool HeapEmpty(HP* php)
{
	assert(php);
	return php->size != NULL;
}

打印:

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

堆排序

堆排序的时候要取大堆(建小堆 取小的可能不是堆了)

向上调整(缺点时间复杂度为O(N*logN)):

for (int i = 1; i < n; i++)//向上调整
{
	adjustup(a, i);
}

向下调整(时间复杂度为O(N))

int end = n - 1;
for (int i = (end-1)/2; i >=0; i--)//i= 父亲节点
{
	adjustdown(a, i, 0);
}

排序完之后 再向下调整(时间复杂度为O(N*logN))

while (end >0)
{
	swap(&a[0], &a[end]);
	adjustdown( a, end, 0);
	end--;
}

找堆中前K个最大的数据

void PrintTopk(int* a, int n, int k)
{
	if (a == NULL)
	{
		perror("a fail");
		return;
	}
	int* minheap = (int*)malloc(sizeof(int) * k);
	if (minheap == NULL)
	{
		perror("malloc fail");
		return;
	}
	//保存前k个数据
	for (int i = 0; i < k; i++)
	{
		minheap[i] = a[i];
	}
	for (int i = (k - 2) / 2; i >= 0; i--)//建小堆
	{
		AdjustDown(minheap, k, i);
	}
	/*int end = k - 1;*/
	//while (end >0)//向下调整
	//{
	//	Swap(&minheap[0], &minheap[end]);
	//	AdjustDown( minheap, end, 0);
	//	end--;
	//}
	int x = 10;
	while (x<=100)
	{
		if(a[x] > minheap[0])
		{
			minheap[0] = a[x];
			AdjustDown(minheap, k,0);
		}
		x++;
	}
	int b = 0;
	while (k--)
	{
		printf("%d ", minheap[k]);
	}
}

void TestTopk()
{
	int n = 100;
	int* a = malloc(sizeof(int) * n);
	srand(time(0));
	for (int i = 0; i < n; i++)
	{
		a[i] = rand() % 100;
	}
	a[9] = 100 + 1;
	a[19] = 100 + 2;
	a[29] = 100 + 3;
	a[39] = 100 + 4;
	a[49] = 100 + 5;
	a[59] = 100 + 6;
	a[69] = 100 + 7;
	a[79] = 100 + 8;
	a[89] = 100 + 9;
	a[99] = 100 + 10;
	PrintTopk(a, n, 10);
}

int main()
{
	//10亿个数据
	//前k个最大的数
	//k==100
	//数组中存储前100个数建小堆
	//如果堆顶的数据比剩下的数据下 则交换 再向下调整
	//读完所有数据 ,堆中100个数是最大的前100个
	//时间复杂度O(N*logK)
	//空间复杂度(K)

	TestTopk();
	return 0;
}

什么叫深度优先遍历与广度优先遍历

深度优先遍历

完全二叉树中的前序就是深度优先遍历

广度优先遍历

完全二叉树中的层数遍历就是广度优先遍历

  • 23
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 11
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

dabai__a

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值