数据结构--堆排序(超详细!!!)

文章目录

堆排序

堆排序(Heap Sort)是一种基于二叉堆(Binary Heap)的比较类排序算法,它的时间复杂度为 O(nlogn),并且具有不稳定性。堆排序的核心思想是将待排序的序列构造成一个大顶堆(或小顶堆),此时整个序列的最大值(或最小值)就是堆顶的根节点。接着将其与末尾元素进行交换,此时末尾就为最大值。然后将剩余 n-1 个元素重新构造成一个堆,这样会得到 n 个元素的次小值。如此反复执行,便能得到一个有序序列了。

堆排序的过程可以分为两个主要阶段:

建堆(Build Heap):

这个阶段的目标是将一个无序数组转换为一个堆(通常是大顶堆)。

从最后一个非叶子节点开始(即最后一个节点的父节点),向上遍历每个节点,并对每个节点执行"下沉"操作,以确保它们满足堆的性质。

"下沉"操作是指,如果当前节点的值小于其子节点中的较大值,则将其与较大的子节点交换。交换后,继续对交换后的子节点执行相同的操作,直到当前节点大于或等于其所有子节点,或者已经下沉到叶子节点位置。
排序(Heap Sort):

当堆构建完成后,堆顶元素是整个数组的最大值。

将堆顶元素(即数组的第一个元素)与数组的最后一个元素交换,这样最大值就被放到了数组的最后位置。

由于交换后可能破坏了堆的性质,因此需要对剩下的元素(不包括已经排序好的最后一个元素)重新进行堆的调整。

重复上述过程,直到整个数组都被排序。

堆排序的优点包括其高效的时间复杂度 O(nlogn) 和原地排序的特性(即不需要额外的存储空间)。然而,它的缺点是不稳定性,即相等的元素在排序后可能改变它们的相对顺序。此外,由于堆排序涉及到大量的元素交换,因此在某些情况下,其实际性能可能不如其他 O(nlogn) 的排序算法,如快速排序或归并排序。

总的来说,堆排序是一种非常有效的排序算法,特别适用于需要快速找到最大(或最小)值的场景,以及在外部排序中构建初始归并段的情况。

如果以下代码看不太懂,可以看我上一篇文章

代码实现

1. Swap 函数

这个函数用于交换两个整数的值。它接受两个整数指针作为参数,并通过一个临时变量来交换这两个指针所指向的值。

c 复制代码
// 交换两个整数的值  
void Swap(int* a, int* b) {  
    int tmp = *a;  // 将a指向的值存储到临时变量tmp中  
    *a = *b;       // 将b指向的值赋给a指向的变量  
    *b = tmp;      // 将临时变量tmp中的值(即原来a的值)赋给b指向的变量  
}

2. AdjustDown 函数

这个函数用于调整堆的结构,确保父节点的值大于或等于其子节点的值(大顶堆)。它接受一个整数数组、数组的大小和一个父节点的索引作为参数。注意如果是升序就建大堆,降序就建小堆,以下代码建的是大堆

c 复制代码
// 向下调整堆的结构,确保父节点的值大于或等于其子节点的值(大顶堆)  
void AdjustDown(int* a, int size, int parent) {  
    int child = parent * 2 + 1;  // 计算左子节点的索引  
    while (child < size) {  // 当子节点的索引小于数组大小时,继续调整  
        // 如果右子节点存在且其值大于左子节点的值,则更新child为右子节点的索引  
        if (child + 1 < size && a[child + 1] > a[child]) {  
            child++;  
        }  
        // 如果子节点的值大于父节点的值,则交换它们的位置,并更新parent和child的值  
        if (a[child] > a[parent]) {  
            Swap(&a[child], &a[parent]);  
            parent = child;  
            child = parent * 2 + 1;  
        } else {  
            break;  // 如果子节点的值不大于父节点的值,则退出循环  
        }  
    }  
}

3. HeapSort 函数

这个函数实现了堆排序算法。它首先构建一个初始堆,然后不断地将堆顶元素与堆的最后一个元素交换,并重新调整堆的结构,直到整个数组有序。

c 复制代码
// 堆排序算法实现  
void HeapSort(int* a, int n) {  
    // 构建初始堆,从最后一个非叶子节点开始向上调整堆的结构  
    for (int i = (n - 1 - 1) / 2; i >= 0; i--) {  
        AdjustDown(a, n, i);  
    }  
    int end = n - 1;  // 初始化end为数组的最后一个元素的索引  
    while (end > 0) {  
        // 将堆顶元素(最大值)与堆的最后一个元素交换  
        Swap(&a[0], &a[end]);  
        // 重新调整堆的结构,确保剩下的元素仍然满足堆的性质  
        AdjustDown(a, end, 0);  
 //这里先调整再使end--是因为end在循环里作为下标,到了函数AdjustDown里就作为数据个数使用
 //下标刚好比数据个数小一
        end--;  // 更新end的值,继续处理剩下的元素  
    }  
}

测试用例

c 复制代码
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
void PrintArray(int* a, int n)//打印
{
	for (int i = 0; i < n; i++)
	{
		printf("%d ", a[i]);
	}
	printf("\n");
}

void Swap(int* a, int* b) {
	int tmp = *a;
	*a = *b;
	*b = tmp;
}


//向下调整
void AdjustDown(int* a, int size, int parent) {
	int child = parent * 2 + 1;
	while (child < size) {//注意!!!
		if (child + 1 < size && a[child + 1] > a[child]) {
			child++;
		}

		if (a[child] > a[parent]) {
			Swap(&a[child], &a[parent]);
			parent = child;
			child = parent * 2 + 1;
		}
		else {
			break;
		}
	}
}
//堆排序
void HeapSort(int* a, int n) {
	for (int i = (n - 1 - 1) / 2; i >= 0; i--) {
		AdjustDown(a, n, i);
	}
	int end = n - 1;
	while (end > 0) {
		Swap(&a[0], &a[end]);
		AdjustDown(a, end, 0);
		end--;
	}
}

void TestSort()
{
	int a[] = { 6, 3, 9, 1, 5, 8, 2, 4, 7};
	PrintArray(a, sizeof(a) / sizeof(int));//计算数组元素个数并打印

	HeapSort(a, sizeof(a) / sizeof(int));

	PrintArray(a, sizeof(a) / sizeof(int));
}



int main() {

	TestSort();
	return 0;
}
相关推荐
懒惰才能让科技进步34 分钟前
从零学习大模型(十二)-----基于梯度的重要性剪枝(Gradient-based Pruning)
人工智能·深度学习·学习·算法·chatgpt·transformer·剪枝
DARLING Zero two♡38 分钟前
关于我、重生到500年前凭借C语言改变世界科技vlog.16——万字详解指针概念及技巧
c语言·开发语言·科技
Ni-Guvara1 小时前
函数对象笔记
c++·算法
泉崎1 小时前
11.7比赛总结
数据结构·算法
你好helloworld1 小时前
滑动窗口最大值
数据结构·算法·leetcode
QAQ小菜鸟1 小时前
一、初识C语言(1)
c语言
何曾参静谧2 小时前
「C/C++」C/C++ 之 变量作用域详解
c语言·开发语言·c++
互联网打工人no12 小时前
每日一题——第一百二十一题
c语言
AI街潜水的八角2 小时前
基于C++的决策树C4.5机器学习算法(不调包)
c++·算法·决策树·机器学习
白榆maple2 小时前
(蓝桥杯C/C++)——基础算法(下)
算法