C/C++ 入门核心算法大局观:堆排序

观看本系列博文提醒:

  1. 你将学会堆的原理算法实现
  2. 一个企业级应用:堆实现优先队列
  3. 还有堆排序
  4. 最后还有一道检测是否掌握堆算法作业

这已经是本系列博文的第三篇了,还没看过第二篇博文:C/C++ 入门核心算法:堆的企业级应用 之 堆实现优先队列 的朋友可以点击下面链接去了解一下。
https://editor.csdn.net/md/?articleId=105602667

我们下面所讲的案例完全是依照本系列第一篇博文来将的,所以强烈建议大家先去看本系列的第一篇博文 入门核心算法大局观:堆 ,然后再看这篇博文。
https://blog.csdn.net/cpp_learner/article/details/105599877


堆排序(Heapsort)是指利用堆这种数据结构所设计的一种排序算法,它是选择排序的一种。可以利用数组的特点快速定位指定索引的元素.

(选择排序工作原理 - 第一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,然后再从剩余的未排序元素中寻找到最小(大)元素,然后放到已排序的序列的末尾。以此类推,直到全部待排序的数据元素的个数为零) 。
如下图:
在这里插入图片描述
在这里插入图片描述

之前的博文讲的是堆顶出堆后,堆顶元素也就没了,现在呢,我们将他和最后一个元素替换位置,然后堆的长度减一,这样堆就无法访问到它了,最后再进行堆的重新排序,又可以形成最大堆了。

当堆中还剩下一个元素时,元素就已经排好序了,如上图中排好序是这样的:
82, 86, 87, 92, 93, 95, 99
升序排序,如果再代码中将堆的节点判断搞相反的话,就可以弄成降序排序。
也即是下面代码中的这条代码:

#define IsEstimate(a, b) (a < b)	// 判断两数大小

只需要改变一下宏判断的关系元素符就行了。

好了,原理讲到这里了,下面开始码代码。


定义

typedef int DateType;
#define IsEstimate(a, b) (a < b)	// 判断两数大小

typedef struct _heap {
	DateType *arr;
	int size;		// 当前存储个数
	int capacity;	// 当前存储容量
}Heap;

定义一个单纯的堆结构体。
#define IsEstimate(a, b) (a < b) // 判断两数大小 :用于判断两个数据的大小。


定义算法所需要的函数:

bool initHeap(Heap &heap, DateType *arr, int size);		// 初始化堆
static void buildHeap(Heap &heap);				// 建堆
static void heapDown(Heap &heap, int index);	// 堆节点下移

void heapSort(Heap &heap);	// 排序

比之前的少了很多,因为是排序,所以我们不需要插入和删除的操作。

下面是算法的具体实现:

建堆


void heapDown(Heap &heap, int index) {
	int parent, child;
	int cur = heap.arr[index];

	for (parent = index; parent * 2 + 1 < heap.size; parent = child) {
		child = parent * 2 + 1;	// 计算出左子节点的小标

		// 不管有没有右子节点,此条if判断都可以得到最大的子节点下标
		if (child + 1 < heap.size && IsEstimate(heap.arr[child], heap.arr[child + 1])) {
			child++;
		}

		if (IsEstimate(heap.arr[child], cur)) {	// 如果父节点大于子节点,那么无需执行任何操作
			break;
		} else {
			heap.arr[parent] = heap.arr[child];	// 进行头尾交换数据
			heap.arr[child] = cur;
		}
	}
}

void buildHeap(Heap &heap) {	// 从最后一个节点的父节点开始往回遍历
	for (int i = heap.size / 2 - 1; i >= 0; i--) {
		heapDown(heap, i);
	}
}

bool initHeap(Heap &heap, DateType *arr, int size) {
	if (!arr) {
		return false;
	}

	// 堆指向了数组,那么就是堆可以随遍调整数组中的顺序
	/*******************************************/
	heap.arr = arr;	// 将数组指针赋值给堆的指针(无需再分配内存)
	/*******************************************/

	heap.capacity = size;
	heap.size = size;

	if (size > 0) {	// 直到堆中还剩下一个数据为止
		buildHeap(heap);
	}
}

这里有一个重点:heap.arr = arr; // 将数组指针赋值给堆的指针(无需再分配内存)
这步操作,使得堆可以直接操作数组中的元素,实现更加高效的排序。

排序:

void heapSort(Heap &heap) {
	if (heap.size < 1) {
		return;
	}

	while (heap.size > 0) {			// 直到堆中还剩下一个数据为止
		int value = heap.arr[0];	// 保存头部的值
		heap.arr[0] = heap.arr[heap.size - 1];	// 将尾部的值赋值给头部
		heap.arr[heap.size - 1] = value;		// 将头部的值赋值给尾部(实现两数交换)
		heap.size--;	// 长度减一,将原先头部的值排除在外(即堆无法访问到它)
		heapDown(heap, 0);	// 将头节点的值排序一遍
	}
}

函数执行完后,数组中的值也就已经排好序了。


下面我们来测试一下:

#include <iostream>
#include <Windows.h>

using namespace std;

typedef int DateType;
#define IsEstimate(a, b) (a < b)	// 判断两数大小

typedef struct _heap {
	DateType *arr;
	int size;		// 当前存储个数
	int capacity;	// 当前存储容量
}Heap;

bool initHeap(Heap &heap, DateType *arr, int size);		// 初始化堆
static void buildHeap(Heap &heap);				// 建堆
static void heapDown(Heap &heap, int index);	// 堆节点下移

void heapSort(Heap &heap);	// 排序



void heapDown(Heap &heap, int index) {
	int parent, child;
	int cur = heap.arr[index];

	for (parent = index; parent * 2 + 1 < heap.size; parent = child) {
		child = parent * 2 + 1;	// 计算出左子节点的小标

		// 不管有没有右子节点,此条if判断都可以得到最大的子节点下标
		if (child + 1 < heap.size && IsEstimate(heap.arr[child], heap.arr[child + 1])) {
			child++;
		}

		if (IsEstimate(heap.arr[child], cur)) {	// 如果父节点大于子节点,那么无需执行任何操作
			break;
		} else {
			heap.arr[parent] = heap.arr[child];	// 进行头尾交换数据
			heap.arr[child] = cur;
		}
	}
}

void buildHeap(Heap &heap) {	// 从最后一个节点的父节点开始往回遍历
	for (int i = heap.size / 2 - 1; i >= 0; i--) {
		heapDown(heap, i);
	}
}

bool initHeap(Heap &heap, DateType *arr, int size) {
	if (!arr) {
		return false;
	}

	// 堆指向了数组,那么就是堆可以随遍调整数组中的顺序
	/*******************************************/
	heap.arr = arr;	// 将数组指针赋值给堆的指针(无需再分配内存)
	/*******************************************/

	heap.capacity = size;
	heap.size = size;

	if (size > 0) {	// 当数组中有数据时
		buildHeap(heap);
	}
}


void heapSort(Heap &heap) {
	if (heap.size < 1) {
		return;
	}

	while (heap.size > 0) {			// 直到堆中还剩下一个数据为止
		int value = heap.arr[0];	// 保存头部的值
		heap.arr[0] = heap.arr[heap.size - 1];	// 将尾部的值赋值给头部
		heap.arr[heap.size - 1] = value;		// 将头部的值赋值给尾部(实现两数交换)
		heap.size--;	// 长度减一,将原先头部的值排除在外(即堆无法访问到它)
		heapDown(heap, 0);	// 将头节点的值排序一遍
	}
}

int main(void) {
	DateType arr[] = { 23, 54, 24, 88, 45, 8, 1, 9, 55 };
	Heap heap;

	cout << "堆排序前:" << endl;
	for (int i = 0; i < sizeof(arr) / sizeof(arr[0]); i++) {
		cout << arr[i] << " ";
	}
	cout << endl;

	// 初始化堆
	initHeap(heap, arr, sizeof(arr) / sizeof(arr[0]));

	// 排序(升序)
	heapSort(heap);

	cout << endl << "堆排序后:" << endl;
	for (int i = 0; i < sizeof(arr) / sizeof(arr[0]); i++) {
		cout << arr[i] << " ";
	}
	cout << endl;

	system("pause");
	return 0;
}

运行截图:
在这里插入图片描述

当我们将宏比较修改为:
#define IsEstimate(a, b) (a > b) // 判断两数大小小于号比较的话
在这里插入图片描述

数组中的值已经变成降序排序了。


总结:
至此,堆排序已经讲完了,不知道大家学会没有呢?没学会的话,推荐先去看本系列第一篇博文,然后再来看本篇博文,相信你们一定可以理解的。


下面给出本系列博文最后一道作业:检测是否掌握堆算法作业

快速查找无序集合中前 N 大(小)的记录。

如果能独立做出来的话,说明你已经掌握堆算法啦!

下面是作业答案:

#include <iostream>
#include <Windows.h>

// 快速查找无序组合中的前N大(小)的记录
using namespace std;

typedef int DateType;
#define IsEstimate(a, b) (a < b)	// 判断两数大小

typedef struct _heap {
	DateType* arr;
	int size;		// 当前存储个数
	int capacity;	// 当前存储容量
}Heap;

bool initHeap(Heap& heap, DateType* arr, int size);
static void buildHeap(Heap& heap);
static void heapDown(Heap& heap, int index);

void heapSort(Heap& heap, int n);	// 出队前n个元素



void heapDown(Heap& heap, int index) {
	int parent, child;
	int cur = heap.arr[index];

	for (parent = index; parent * 2 + 1 < heap.size; parent = child) {
		child = parent * 2 + 1;	// 计算出左子节点的小标

		// 不管有没有右子节点,此条if判断都可以得到最大的子节点下标
		if (child + 1 < heap.size && IsEstimate(heap.arr[child], heap.arr[child + 1])) {
			child++;
		}

		if (IsEstimate(heap.arr[child], cur)) {
			break;
		}
		else {
			heap.arr[parent] = heap.arr[child];
			heap.arr[child] = cur;
		}
	}
}

void buildHeap(Heap& heap) {
	for (int i = heap.size / 2 - 1; i >= 0; i--) {
		heapDown(heap, i);
	}
}

bool initHeap(Heap& heap, DateType* arr, int size) {
	if (!arr) {
		return false;
	}

	heap.arr = arr;
	heap.capacity = size;
	heap.size = size;

	if (size > 0) {
		buildHeap(heap);
	}
}


void heapSort(Heap& heap, int n) {
	if (heap.size < 1) {
		return;
	}

	if (n > heap.size) {
		return;
	}

	while (n > 0) {		// 判断前n个数值
		int value = heap.arr[0];	// 保存头部的值
		heap.arr[0] = heap.arr[heap.size - 1];	// 将尾部的值赋值给头部
		heap.arr[heap.size - 1] = value;		// 将头部的值赋值给尾部(实现两数交换)
		heap.size--;	// 长度减一,将原先头部的值排除在外(即堆无法访问到它)
		
		cout << "值:" << value << endl;
		n--;


		heapDown(heap, 0);	// 将头节点的值排序一遍
	}
}

int main(void) {
	DateType arr[] = { 23, 54, 24, 88, 45, 8, 1, 9, 55 };
	Heap heap;

	cout << "堆初始化前数组中的值:" << endl;
	for (int i = 0; i < sizeof(arr) / sizeof(arr[0]); i++) {
		cout << arr[i] << " ";
	}
	cout << endl << endl;

	// 初始化堆
	initHeap(heap, arr, sizeof(arr) / sizeof(arr[0]));
	cout << "堆初始化后数组中的值:" << endl;
	for (int i = 0; i < sizeof(arr) / sizeof(arr[0]); i++) {
		cout << arr[i] << " ";
	}
	cout << endl << endl;

	cout << "堆排序后出堆前三个大的元素:" << endl;
	// 查找前n个元素
	heapSort(heap, 3);
	initHeap(heap, arr, sizeof(arr) / sizeof(arr[0]));
	cout << endl << endl;

	system("pause");
	return 0;
}

运行截图:
在这里插入图片描述

至此本系列入门核心算法大局观:堆 到此完结!感谢观看!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

cpp_learners

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

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

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

打赏作者

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

抵扣说明:

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

余额充值