【数据结构】_堆排序问题

目录

[1. 基于建堆的堆排序(需调HPPush)](#1. 基于建堆的堆排序(需调HPPush))

[2. 基于原数组的堆排序(仅调AdjustUp)](#2. 基于原数组的堆排序(仅调AdjustUp))

[3. 堆排序建堆与升降序的总结](#3. 堆排序建堆与升降序的总结)


在专栏前文中,已经实现了堆的基本结构与相关方法;(小根堆)

详见下文:

【数据结构】_堆的实现-CSDN博客文章浏览阅读429次,点赞19次,收藏8次。专栏前文中,已经介绍了入堆及向上调整算法,出堆及向下调整算法,详情见下文:实际对于整除运算parent = (child - 1) / 2,parent并不会小于0,但当parent=0时,若a[parent] > a[child],则再次进行child与parent的更新,使得parent与child均为0。当parent=0时,若若a[parent] > a[child],则再次进行child与parent的更新,使得child更新为0,下次则无法进入while循环;https://blog.csdn.net/m0_63299495/article/details/145521676https://blog.csdn.net/m0_63299495/article/details/145521676修改向上调整与向下调整的比较逻辑,将堆修改为大根堆

1. 基于建堆的堆排序(需调HPPush)

当前堆为大根堆,通过对数组元素逐个push进行建堆,再使用HPPop依次出堆顶元素,最终实现对原数组元素的降序排序:

cpp 复制代码
#define _CRT_SECURE_NO_WARNINGS
#include"Heap.h"
void TestHeap1() {
	int a[] = { 4,2,8,1,5,6,9,7 };
	HP hp;
	HPInit(&hp);
	// 输出原始数组
	printf("orignal array: \n");
	for (size_t i = 0; i < sizeof(a) / sizeof(a[0]); i++) {
		printf("%d ", a[i]);
	}
	printf("\n");
	// 建立小根堆,输出其顺序存储
	for (size_t i = 0; i < sizeof(a) / sizeof(a[0]); i++) {
		HPPush(&hp, a[i]);
	}
	printf("minHeap array: \n");
	HPPrint(&hp);
	// 对原始数组使用堆排序,进行升序输出
	int i = 0;
	while (!HPEmpty(&hp)) {
		//printf("%d ", HPTop(&hp));
		a[i++] = HPTop(&hp);
		HPPop(&hp);
	}
	printf("Ascending sort: \n");
	for (size_t i = 0; i < sizeof(a) / sizeof(a[0]); i++) {
		printf("%d ", a[i]);
	}
	printf("\n");
	HPDestory(&hp);
}
int main() {
	TestHeap1();
	return 0;
}

输出结果如下:

注:1、这种排序思路为实现排序需要额外开辟空间建堆,因为其调用了HPPush方法,空间复杂度为O(N);

2、实现打印升序与降序只需修改向上调整和向下调整方法的对比大小关系,即可实现大根堆的降序和小根堆的升序排序:

2. 基于原数组的堆排序(仅调AdjustUp)

1、回顾已实现的Adjust方法:

cpp 复制代码
void AdjustUp(HPDataType* a, int child);

其参数为数组与新插元素的下标,直接将数组视为一个完全二叉树,并不需要依赖于HPPush方法,故并不额外开辟空间,空间复杂度为O(1);

现不调用HPPush方法,即对于已知数组不建堆,基于原数组仅调用向上调整算法进行堆排序

2、以大根堆为例,调用AdjustUp方法后,在原数组上已经实现了大根堆的顺序存储(但并未申请额外空间),考虑其输出升降序问题:

(1)若想要实现降序排序:

调用AdjustUp方法后,当前数组已满足首元素为最大元素,则输出并删除堆顶的最大元素后,接下来需在剩下的元素中建新大根堆并再删除堆顶最大元素,这与初始考虑的直接删除堆顶元素的HPPop方法思路一致,会导致节点关系的错位与混乱,如此循环往复建堆与调整,虽可以实现最终的排序效果,但显然非常麻烦;

(2)若想要实现升序排序:

调用AdjustUp方法后,当前数组已满足首元素为最大元素,将堆顶元素与最末结点元素交换,再将其位置确定在数组尾,即现确定了最大的元素且保持原堆的大根堆基本关系与结构不变,再进行向下调整即可。如此循环,每次都确定未排序元素中最大的结点,并将其依次从后向前定位在数组中,从而实现了升序排序;

cpp 复制代码
#define _CRT_SECURE_NO_WARNINGS
#include"Heap.h"
// 测试不建堆的排序
void HeapSort(int* a, int n) {
	// 建堆
	for (int i = 1; i < n; i++) {
		AdjustUp(a, i);
	}
	// 打印大根堆的顺序存储序列
	printf("maxHeap array: \n");
	for (size_t i = 0; i < n; i++) {
		printf("%d ", a[i]);
	}
	printf("\n");
	// 交换首尾并删尾,再进行向下调整
	int end = n - 1;
	while (end>0) {
		Swap(&a[0], &a[end]);
		AdjustDown(a, end,0);
		end--;
	}
	// 打印升序排序结果
	printf("Ascending sort: \n");
	for (int i = 0; i < n; i++) {
		printf("%d ", a[i]);
	}
	printf("\n");
}
int main() {
	int a[] = { 4,2,8,1,5,6,9,7 };
	int sz = sizeof(a) / sizeof(a[0]);
	HeapSort(a,sz);
	return 0;
}

运行程序,结果为:

3. 堆排序建堆与升降序的总结

对于实现堆排序:

1、若采用先建堆再排序的方法,则建大根堆实现降序,建小根堆实现升序;(通常不用)

(1)这种方式需要调用HPPush方法,HPPush方法内部自行调用AdjustUp方法,并使用HPTop方法获取堆顶元素并逐个pop以实现降序或升序输出;

(2)这种方式的空间复杂度为O(N);(较高)

2、若采用不建堆直接排序的方法,则建大根堆实现升序,小根堆实现降序;(常用)

(1)这种方式无需调用HPPush方法,直接调用AdjustUp方法,将原数组处理为小根堆或大根堆的顺序存储序列,交换首尾元素并确定尾元素位置后,对其余元素调用AdjustDown方法再确认倒数第二个元素的位置,循环进行直至形成降序或升序排列;

(2)这种方式的时间复杂度为O(N*log N);(较低)

相关推荐
chao_7897 分钟前
链表题解——反转链表【LeetCode】
开发语言·python·算法
Code_流苏37 分钟前
Python趣学篇:从零打造智能AI井字棋游戏(Python + Tkinter + Minimax算法)
python·算法·游戏·tkinter·智能井字棋·minimax算法
Lu Yao_43 分钟前
【数据结构 -- B树】
数据结构·b树
理智的灰太狼1 小时前
题目 3230: 蓝桥杯2024年第十五届省赛真题-星际旅行
算法·职场和发展·蓝桥杯
wcjwdq1 小时前
“顶点着色器”和“片元着色器”是先处理完所有顶点再统一进入片元阶段,还是一个顶点处理完就去跑它的片元?
算法·着色器
技术帮扶户1 小时前
Leetcode-7 寻找用户推荐人
算法·leetcode·职场和发展
VU-zFaith8701 小时前
C++概率论算法详解:理论基础与实践应用
c++·算法·概率论
全栈凯哥1 小时前
Java详解LeetCode 热题 100(23):LeetCode 206. 反转链表(Reverse Linked List)详解
java·算法·leetcode·链表
kingmax542120081 小时前
动态规划十大经典题型状态转移、模版等整理(包括leetcode、洛谷题号)
算法·leetcode·动态规划
闪电麦坤952 小时前
数据结构:递归:自然数之和
数据结构·算法