【数据结构】堆

堆的概念

:::block-1

  • 堆是一种完全二叉树,它的所有元素都按照完全二叉树的顺序存储方式存储,一般使用数组来实现。
    :::

堆的分类

:::block-1

  • 小堆
    小堆是指满足k[i]<=k[2i+1] && k[i]<=k[2i+2]的堆,通俗来讲就是其父节点一定要小于任何一个子节点。
  • 大堆
    大堆是指满足k[i]>=k[2i+1] && k[i]>=k[2i+2]的堆,通俗来讲就是其父节点一定要大于任何一个子节点。
    :::

通过子节点(父节点)下标寻找父节点(子节点)下标

  • 子节点找父节点
    父 = (子-1)/2
  • 父节点找子节点
    子 = (父*2)+1

堆的实现

:::block-1

  • 堆的实现需要依靠两种算法,一种是向下调整算法、另一种是向上调整算法,这两种算法具体的应用在下文给出。

堆的结构

  • 和顺序表的结构有点像,因为我们使用顺序表去实现堆。
typedef int phpDataType;

typedef struct Heap
{
	phpDataType* a;
	int size;
	int capacity;
}Heap;

向下调整算法

  • 过程为:给定父亲下标,向下寻找子节点,满足条件则交换。
void AdjustDwon(int* a, int n, int parent)
{
	assert(a);
	int child = parent * 2 + 1;

	while (child < n)
	{
		if (child + 1 < n && a[child] > a[child + 1])
		{
			child++;
		}

		if (a[parent] > a[child])
		{
			Swap(&a[parent], &a[child]);
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			break;
		}
	}
}

向上调整算法

  • 过程为:给定孩子下标,向上寻找父节点,满足条件则交换。
void AdjustUp(Heap* php,int child)//堆,要调整的位置
{
	assert(php);
	int parent = (child - 1) / 2;
	while (child > 0)//子节点不等于零代表有父节点,可继续调整
	{
		if (php->a[child] < php->a[parent])//给定条件
		{
			Swap(&(php->a[child]), &(php->a[parent]));//交换子父节点
		}
		else
		{
			break;//如果不满足条件,则跳出进入下一次调整
		}
		child = parent;
		parent = (child - 1) / 2;
	}
}

堆的创建

  • 堆的创建有两种方式,一种是通过向下调整建堆(倒着建堆),另外一种是向上调整建堆(模拟插入的过程)

向下调整建堆

  • 从数组最后开始建堆
//(n - 1 - 1)/2的含义是((n-1)-1)/2,n-1是数组最后一个元素的下标,减一后整体处二是为了找他的父亲节点,因为叶子节点没有必要去向下调整。
for (int i = (n - 1 - 1)/2; i >= 0; i--)
	{
		AdjustDwon(a, n, i);
	}

向上调整建堆

  • 这实际上是一个模拟插入的过程,将数组第一个元素看做是堆,将后面的元素看做要插入的元素,插入一个便向上调整一次
for (int i = 1; i < n; i++)
	{
		AdjustUp(a, i);
	}

堆的初始化

  • php->capacity = 0; 将堆的容量设置为0,表示当前堆中没有元素。
  • php->size = 0; 将堆的大小设置为0,表示当前堆中没有元素。
  • php->a = NULL; 将指向堆元素数组的指针a设置为NULL,表示当前堆中没有任何元素。
  • 通过调用这个函数,可以将一个Heap对象初始化为空的状态,以便后续进行堆操作。
void InitHeap(Heap* php)
{
	php->capacity = 0;
	php->size = 0;
	php->a = NULL;
}

堆的插入的实现

  • assert(php); 使用断言确保传入的指针不为空。
  • phpDataType NewCapacity = php->capacity == 0 ? 4 : 2 * php->capacity; 根据堆的当前容量来计算新的容量,如果当前容量为0,则将新容量设置为4,否则设置为当前容量的两倍。
  • if (php->capacity == php->size) 如果堆的容量等于当前大小,说明堆已满,需要进行扩容操作。
  • phpDataType* tmp = (phpDataType*)realloc(php->a,NewCapacity*sizeof(phpDataType)); 使用realloc函数重新分配堆的元素数组的内存空间,将容量调整为新容量。
  • assert(tmp); 使用断言确保内存分配成功。
  • php->capacity = NewCapacity; 更新堆的容量为新容量。
  • php->a = tmp; 更新堆的元素数组指针为新的内存空间。
  • php->a[php->size] = x; 将需要插入的数据存放在堆的末尾。
  • php->size++; 堆的大小增加1,表示插入了一个新元素。
  • AdjustUp(php,php->size-1); 调用AdjustUp函数对插入的新元素进行向上调整,以满足堆的条件。
  • 通过调用这个函数,可以将数据插入到堆中,并确保堆的性质得到维护。
void HeapPush(Heap* php, phpDataType x)//堆,需要插入的数据
{
	assert(php);
	phpDataType NewCapacity = php->capacity == 0 ? 4 : 2 * php->capacity;
	if (php->capacity == php->size)
	{
		phpDataType* tmp = (phpDataType*)realloc(php->a,NewCapacity*sizeof(phpDataType));
		assert(tmp);
		php->capacity = NewCapacity;
		php->a = tmp;
	}
	php->a[php->size] = x;
	php->size++;

	AdjustUp(php,php->size-1);//要调整的对,需要向上调整的新的位置
}

堆的销毁

  • assert(php); 使用断言确保传入的指针不为空。
  • php->size = 0; 将堆的大小设置为0,表示当前堆中没有元素。
  • php->capacity = 0; 将堆的容量设置为0,表示当前堆中没有元素。
  • free(php->a); 调用free函数释放堆元素数组的内存空间。
  • 通过调用这个函数,可以销毁堆并释放相关的内存空间。
void HeapDestory(Heap* php)
{
	assert(php);
	php->size = 0;
	php->capacity = 0;
	free(php->a);
}

堆的删除(删除堆顶元素)

  • assert(php); 使用断言确保传入的指针不为空。
  • assert(php->size); 使用断言确保堆的大小大于0,即堆中至少有一个元素。
  • int top = 0; 将top变量设置为0,表示堆顶的位置。
  • int end = php->size - 1; 将end变量设置为堆的最后一个元素的位置。
  • Swap(&(php->a[top]), &(php->a[end])); 调用Swap函数交换堆顶元素和最后一个元素的位置,将堆顶元素移到堆的末尾。
  • php->size–; 堆的大小减少1,表示弹出了一个元素。
  • AdjustDown(php); 调用AdjustDown函数对堆进行向下调整,以满足堆的条件。
    通过调用这个函数,可以弹出堆顶元素并保持堆的性质。
void HeapPop(Heap* php)
{
	assert(php);
	assert(php->size);
	int top = 0;
	int end = php->size - 1;
	Swap(&(php->a[top]), &(php->a[end]));
	php->size--;

	AdjustDown(php);
}

取堆顶的数据

  • assert(php); 使用断言确保传入的指针不为空。
  • int top = 0; 将top变量设置为0,表示堆顶的位置。
  • return php->a[top]; 返回堆中位置为top的元素的值。
  • 通过调用这个函数,可以获取堆顶元素的值而不对堆进行修改。
phpDataType HeapTop(Heap* php)
{
	assert(php);
	int top = 0;
	return php->a[top];
}

堆的数据个数

  • assert(php); 使用断言确保传入的指针不为空。
  • return php->size; 返回堆的大小,即堆中元素的个数。
  • 通过调用这个函数,可以获取堆的大小而不对堆进行修改。
int HeapSize(Heap* php)
{
	assert(php);
	return php->size;
}

堆的判空

  • assert(php); 使用断言确保传入的指针不为空。
  • return php->size == 0; 返回一个表达式的结果,该表达式判断堆的大小是否为0。如果堆的大小为0,则返回1代表堆为空;否则,返回0代表堆非空。
  • 通过调用这个函数,可以判断堆是否为空。若返回值为1,则表示堆为空;若返回值为0,则表示堆非空。
int HeapEmpty(Heap* php)//返回1代表该堆为空,返回零代表非空
{
	assert(php);
	return php->size == 0;
}

堆排序(升序建大堆,降序建小堆)

  • 向下调整建堆后,将堆顶元素不断换向堆底
  • assert(a); 使用断言确保传入的数组指针不为空。
  • 首先进行建堆操作。通过循环从堆的倒数第二层开始,依次对每个非叶子节点调用AdjustDown函数,将数组a调整为一个合法的堆。
  • 接下来是排序阶段。使用一个while循环,不断地将堆顶元素(数组首元素a[0])与当前待排序范围内的最后一个元素交换,并将待排序范围缩小,再对堆顶元素进行一次AdjustDown操作,以满足堆的性质。
  • 循环执行上述步骤,直到待排序范围的大小减为0,即完成了堆排序。
  • 通过调用这个函数,可以对指定的整型数组进行堆排序,使其按照升序排列。
void HeapSort(int* a, int n)
{
	assert(a);
	//建堆
	for (int i = (n - 1 - 1)/2; i >= 0; i--)
	{
		AdjustDwon(a, n, i);
	}
	//排序
	while (n)
	{
		Swap(&a[0], &a[n - 1]);
		n--;
		AdjustDwon(a, n, 0);
	}
}

top-k问题

  • 首先,定义一个字符串变量file表示文件名,并使用 fopen 函数以只读方式打开文件。使用条件判断语句检查文件是否成功打开,如果打开失败,则输出错误信息并返回。
  • 动态分配一个大小为k的整型数组a,用于存储前k个最大数。
  • 使用循环从文件中读取k个整数并依次存入数组a中。
  • 接下来进行建堆操作。通过循环从堆的倒数第二层开始,依次对每个非叶子节点调用AdjustDown函数,将数组a调整为一个合法的堆。
  • 然后,使用一个while循环,在文件没有读取完的情况下不断读取下一个整数,并与数组a的堆顶元素比较。
  • 如果新读取的整数大于数组a的堆顶元素,则将该整数替换堆顶元素,并调用AdjustDown函数重新调整堆。
  • 最后,使用循环遍历数组a,并逐个打印出前k个最大数。
void PrintTopK(int k)
{
	const char* file = "data.txt";
	FILE* fin = fopen(file, "r");
	if (fin == NULL)
	{
		perror("fopen error");
		return;
	}

	int* a = (int*)malloc(k * sizeof(int));

	for (int i = 0; i < k; i++)
	{
		fscanf(fin, "%d", &a[i]);
	}
	//建堆
	for (int i = (k - 1 - 1) / 2; i >= 0; i--)
	{
		AdjustDown(a,k,i);
	}

	int val = 0;
	while (!feof(fin))
	{
		fscanf(fin, "%d", &val);

		if (val > a[0])
		{
			a[0] = val;
			AdjustDown(a, k, 0);
		}
	}

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

:::

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值