数据结构——树、二叉树、堆

一、树

树是一种非线性数据结构,它可以看作是由 n(n⩾0) 个具有父子关系的节点组成的有限集合。
在这里插入图片描述

  • 根:树的最顶端节点,是整个树的起点。在上面的示例树中,节点 A 是根。
  • 节点的度:一个节点拥有的子树数目称为该节点的度。如节点 A 的度是 6。
  • 叶节点:度为零的节点成为叶节点。如P、Q、K等
  • 父节点:一个节点的直接上级节点称为其父节点。如节点 B 和 C 的父节点是节点 A。
  • 子节点:一个节点的直接下级节点称为其子节点。例如,在示例树中,节点 B 和 C 是节点 A 的子节点。
  • 兄弟节点:同一父节点下的子节点互为兄弟关系。如节点 B 和 C 是兄弟节点。
  • 祖先:从根节点到某节点路径中的所有节点都是该节点的祖先。例如,节点 J的祖先是 A 和 E。
  • 子孙节点:一个节点的所有子节点、子节点的子节点等等都是它的子孙。在示例树中,节点 A 的子孙包括 B、C、D、E、F、I、Q。
  • 树的深度:树中节点的最大层次称为树的深度。在示例树中,根节点 A 处于第 1 层,节点 B、C 处于第 2 层,节点 H、I处于第 3 层,节点P、Q处于第4层。因此,该树的深度是 4。
  • 数的度:一棵树中,最大度的结点的度称为树的度。该树的度为6。
  • 森林:由m(m>0)不相交的树的集合称为森林。
    树的性质
  • 子树是不相交的
  • 除了根节点外,每个节点仅有一个父节点
  • 一个有n个结点的树有n-1条边

二、二叉树

二叉树是一种特殊的树形数据结构,每个节点最多有两个子节点,通常被称为左子节点和右子节点。
满二叉树
所有非叶子节点都有两个子节点,并且所有的叶子节点都在同一层的二叉树。满二叉树具有最大的节点数和最紧凑的结构。
完全二叉树
除最后一层外,每一层都被完全填满,并且最后一层的节点是连续的。
在这里插入图片描述

  • 对于任何一棵二叉树,度为0的节点比度为2的节点多一个
  • 高度为h的满二叉树,每层节点数为2(h-1),总结点数为2h-1
  • 高度为h的完全二叉树,第h层节点范围是[2(h-1),2h-1]

三、堆

  • 堆是一种完全二叉树的数据结构,分为最大堆和最小堆
  • 最大堆:每个节点的值都大于或等于其子节点的值,根节点是堆中的最大值。
  • 最小堆:每个节点的值都小于或等于其子节点的值,根节点是堆中的最小值。
    在这里插入图片描述
    完全二叉树在数组中父子位置的下标关系
  • parent=(child-1)/2
  • leftchild=parent*2+1
  • rightchild=parent*2+2
    如何创建最大堆
typedef int HPDataType;
typedef struct Heap
{
	HPDataType* a;
	int size;
	int capacity;
}HP;
void Swap(HPDataType* p1, HPDataType* p2)
{
	HPDataType tmp = *p1;
	*p1 = *p2;
	*p2 = tmp;
}

、堆的初始化

void HeapInit(HP* php)
{
	assert(php);
	php->a = (HPDataType*)malloc(sizeof(HPDataType) * 4);
	if (php->a == NULL)
	{
		perror("malloc fail");
		return;
	}
	php->size = 0;
	php->capacity = 4;
}

、堆的销毁

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

、判断堆是否为空

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

、获取堆的大小

int HeapSize(HP* php)
{
	assert(php);
	return php->size;
}

、获取堆顶数据

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

、向堆中添加数据

void AdjustUp(HPDataType* a, int child)
{
	int parent = (child - 1) / 2;
	while (child>0)
	{
		if (a[parent] < a[child])
		{
			Swap(&a[parent], &a[child]);
			child = parent;
			parent = (child - 1) / 2;
		}
		else
		{
			break;
		}
	}
}
void HeapPush(HP* php, HPDataType x)
{
	assert(php);
	if (php->size == php->capacity)
	{
		HPDataType* tmp = (HPDataType*)realloc(php->a, sizeof(HPDataType) * php->capacity * 2);
		if (tmp == NULL);
		{
			perror("realloc fail");
			return;
		}
		php->a = tmp;
		php->capacity *= 2;
	}
	php->a[php->size] = x;
	php->size++;
	AdjustUp(php->a, php->size-1);//尾插之后,从下向上调整,才能符合二叉树的规则
}

、从堆中删除数据

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;
		}
		if (a[child] > a[parent])
		{
			Swap(&a[parent], &a[child]);
			parent = child;//向下走
			child = parent*2+1;
		}
		else
		{
			break;
		}
	}

}
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);//从上向下调整
}

代码测试


int main()
{
	HP hp;
	HeapInit(&hp);
	HeapPush(&hp, 4);
	HeapPush(&hp, 18);
	HeapPush(&hp, 42);
	HeapPush(&hp, 12);
	

	int k = 0;
	scanf("%d", &k);
	while (!HeapEmpty(&hp) && k--)
	{
		printf("%d ", HeapTop(&hp));
		HeapPop(&hp);
	}
	printf("\n");

	return 0;
}

堆的应用

1堆排序

  • 将数组构建成一个最大堆(升序排序)或最小堆(降序排序)。
  • 重复删除堆顶元素,并将其放入已排序序列的末尾。
  • 重新调整堆,直到排序完成。
void HeapSort(int* a, int n)
{

	// 建堆 -向下调整建堆(升序)
	for (int i = (n - 1 - 1) / 2; i >= 0; --i)
	//n-1是最后一个数据的下标,找到父亲节点,(child-1)/2
	{
		AdjustDown(a, n, i);
	}

	// 自己先实现 -- O(N*logN)
	int end = n - 1;
	while (end > 0)
	{
		Swap(&a[end], &a[0]);
		AdjustDown(a, end, 0);

		--end;
	}
}

在这里插入图片描述

TOP——K问题

  • 一般求数组中前k个最大或最小的元素
  • 求前k个最大元素,建小堆
  • 求前k个最小元素,建大堆
  • 用数据剩余n-k个元素依次与堆顶比较,不满足条件则与堆顶替换
  • 比较完后,堆中剩余的的k个元素就是所求
void PrintTopK(const char* file, int k)
{
	// 1. 建堆--用a中前k个元素建小堆
	int* topk = (int*)malloc(sizeof(int) * k);
	assert(topk);

	FILE* fout = fopen(file, "r");
	if (fout == NULL)
	{
		perror("fopen error");
		return;
	}

	// 读出前k个数据建小堆
	for(int i = 0; i < k; ++i)
	{
		fscanf(fout, "%d", &topk[i]);
	}

	for (int i = (k-2)/2; i >= 0; --i)
	{
		AdjustDown(topk, k, i);
	}

	// 2. 将剩余n-k个元素依次与堆顶元素交换,不满则则替换
	int val = 0;
	int ret = fscanf(fout, "%d", &val);
	while (ret != EOF)
	{
		if (val > topk[0])
		{
			topk[0] = val;
			AdjustDown(topk, k, 0);
		}

		ret = fscanf(fout, "%d", &val);
	}

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

	free(topk);
	fclose(fout);
}

void CreateNDate()
{
	// 造数据
	int n = 10000000;
	srand(time(0));
	const char* file = "data.txt";
	FILE* fin = fopen(file, "w");
	if (fin == NULL)
	{
		perror("fopen error");
		return;
	}

	for (size_t i = 0; i < n; ++i)
	{
		int x = rand() % 10000;
		fprintf(fin, "%d\n", x);
	}

	fclose(fin);
}

int main()
{
	//int a[10] = { 2, 1, 5, 7, 6, 8, 0, 9, 4, 3}; // 对数组排序
	//HeapSort(a, 10);

	CreateNDate();
	//PrintTopK("data.txt", 10);

	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值