堆/堆排序(C/C++)

        本篇文章将会较为全面的介绍堆的概念以及实现堆两个重要算法:向上调整算法和向下调整算法。接着实现了堆排序。

        若想查看对应位置,可直接按照以下目录进行查看:

        目录

1.堆的概念及结构

2.堆的实现

2.1 堆的向上调整算法

2.2 堆的向下调整算法

2.3 堆的插入

2.4 堆的删除 

2.5 堆的实现

3.堆排序

1.堆的概念及结构

        概念:若一个关键码的集合K=\left\{ k_0,k_1,k_2,k_3,...,k_{n-1} \right\},把它的所有元素按照完全二叉树的顺序存储方式存储在一个一维数组中,并满足:K_i\leqslant K_{2i+1}K_i\leqslant K_{2i+2}K_i\geqslant K_{2i+1}K_i\geqslant K_{2i+2}),则称为小堆(大堆)。将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆。

        性质:1.堆中的某个节点的值总是不大于或不小于其父节点的值;

                   2.堆总是一颗完全二叉树;

                   3.堆中父节点的关系与孩子节点的关系为:leftchild_k=parent_{2\times i+1},rightchild_k=parent_{2\times i+2}\,\,

        堆的结构如下:

        堆的存储结构:在数据结构中,对于二叉树的存储结构一般可以分为链式存储、顺序表存储;同样,对于堆的存储我们也可以选择链式存储和顺序表存储,但我们需要从中选出最优的存储结构。对于满二叉树和完全的二叉树的顺序表存储结构对于整个顺序表都可以将其存储满,并且下标的变化便于我们在堆中添加数据调整数据。所以我们使用顺序表来存储堆。如下:

2.堆的实现

        堆的实现主要的两个点为堆的建立和堆的调整,其中的主要思想为堆的向上调整算法向下调整算法,具体的实现将会在下面实现。

2.1 堆的向上调整算法

        堆的向上调整发算法是实现堆的插入核心算法。

        对于堆的建立,假设我们将一组数据一个一个的加入到堆中,那么我们需要在每一个元素入堆时都要进行调整,我们将加入的元素存入到顺序表的最后,然后利用双亲结点和孩子结点之间的关系来判断是否需要调整,知道调整到根节点为止。对于此算法思想既可以使用递归算法,也可以使用循环算法,在这里我们使用循环算法,如下:

        具体的实现算法如下:

void Swap(HPDataType* p1, HPDataType* p2) {
	HPDataType* tmp = *p1;
	*p1 = *p2;
	*p2 = tmp;
}

void AdjustUp(HPDataType* a, int child) {
	int parent = (child - 1) / 2;
	while (child > 0) {
		//建立小堆,若建大堆,将 < 改为 >
		if (a[child] < a[parent]) {
			Swap(&a[child], &a[parent]);
			child = parent;
			parent = (child - 1) / 2;
		}
		else {
			break;
		}
	}
}

2.2 堆的向下调整算法

        向下调整算法是堆删除堆顶元素的核心算法。

        向下调整算法的主要作用为:当我们需要将堆顶的元素弹出时,将整个堆进行维护,建立一个新的堆。其主要思路是将当前元素的孩子结点与双亲结点进行比较,但是此时双亲节点的孩子节点有两个,我们需要找出最合适的那一个,比如:建小堆则找出最小的孩子结点,建大堆则找出最大的孩子结点。然后将孩子结点与双亲结点进行交换,以此迭代,直到迭代到叶子结点。

        图示如下:

        以建小堆的算法为例,算法如下:

//建小堆的向下调整算法
void AdjustDown(HPDataType* a, int size, int parent) {
    //左孩子结点与双亲结点的关系
	int child = parent * 2 + 1;
    //假若右孩子比左孩子更小,则将child变量加一。child + 1 <size 用于保证不会越界
	//若建大堆,改为if ((a[child + 1] > a[child]) && (child + 1) < size)
    if ((a[child + 1] < a[child]) && (child + 1) < size) {
		++child;
	}
    //当child>=size时表示已经到达叶子结点跳出循环
	while (child<size) {
        //双亲结点大于孩子结点交换
        //若建大堆,改为if (a[parent] < a[child])
		if (a[parent] > a[child]) {
			Swap(&a[parent], &a[child]);
            //进行迭代
			parent = child;
			child = parent * 2 + 1;
			if ((a[child + 1] < a[child]) && (child + 1) < size) {
				++child;
			}
		}
        //若双亲结点不在大于孩子结点,说明已经迭代结束,不在需要继续进行迭代
		else {
			break;
		}
	}
}

2.3 堆的插入

        堆的插入就是在堆中插入元素,直接在堆的顺序存储的最后一个位置插入元素,然后调用向上调整算法,对堆进行维护。具体算法如下:

void HeapPush(Heap* php, HPDataType x) {
	assert(php);
    //判断顺序表的容量,是否需要扩容
	if (php->capacity == php->size) {
		int newCapacity = php->capacity == 0 ? 4 : 2 * (php->capacity);
		HPDataType* tmp = (HPDataType*)realloc(php->a,newCapacity * sizeof(HPDataType));
		if (tmp == NULL) {
			perror("realloc failed:");
			exit(1);
		}
		php->a = tmp;
		php->capacity = newCapacity;
	}
	php->a[php->size] = x;
    //调用向上调整算法
	AdjustUp(php->a, php->size);
	php->size++;
}

2.4 堆的删除 

        堆的删除就是将堆顶的元素弹出,然后使用向下调整算法对堆进行维护。具体算法如下:

void HeapPop(Heap* php) {
	assert(php);
	assert(php->a > 0);
    //将最后一个元素调换在堆顶元素位置,便于进行向下调整算法
	php->a[0] = php->a[php->size - 1];
	php->size--;
    //调用向下调整算法
	AdjustDown(php->a, php->size, 0);
}

2.5 堆的实现

        以下为堆的所有代码:

        Heap.h

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <assert.h>

typedef int HPDataType;

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

void HeapInit(Heap* php);
void HeapDestroy(Heap* php);
void HeapPush(Heap* php, HPDataType x);
void HeapPop(Heap* php);
HPDataType HeapTop(Heap* php);
int HeapSize(Heap* php);
bool HeapEmpty(Heap* php);

void AdjustUp(HPDataType* a, int child);
void AdjustDown(HPDataType* a, int size, int parent);
void Swap(HPDataType* p1, HPDataType* p2);

        Heap.c

#define _CRT_SECURE_NO_WARNINGS 1
#include "Heap.h"

void HeapInit(Heap* php) {
	assert(php);
	php->a = NULL;
	php->capacity = 0;
	php->size = 0;
}

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

void Swap(HPDataType* p1, HPDataType* p2) {
	HPDataType* tmp = *p1;
	*p1 = *p2;
	*p2 = tmp;
}
 
void AdjustUp(HPDataType* a, int child) {
	int parent = (child - 1) / 2;
	while (child > 0) {
		//建立小堆
		if (a[child] < a[parent]) {
			Swap(&a[child], &a[parent]);
			child = parent;
			parent = (child - 1) / 2;
		}
		else {
			break;
		}
	}
}

void HeapPush(Heap* php, HPDataType x) {
	assert(php);
	if (php->capacity == php->size) {
		int newCapacity = php->capacity == 0 ? 4 : 2 * (php->capacity);
		HPDataType* tmp = (HPDataType*)realloc(php->a,newCapacity * sizeof(HPDataType));
		if (tmp == NULL) {
			perror("realloc failed:");
			exit(1);
		}
		php->a = tmp;
		php->capacity = newCapacity;
	}
	php->a[php->size] = x;
	AdjustUp(php->a, php->size);
	php->size++;
}

//建小堆的向下调整算法
void AdjustDown(HPDataType* a, int size, int parent) {
	//左孩子结点与双亲结点的关系
	int child = parent * 2 + 1;
	//假若右孩子比左孩子更小,则将child变量加一。
	if ((a[child + 1] < a[child]) && (child + 1) < size) {
		++child;
	}
	//当child>=size时表示已经到达叶子结点跳出循环
	while (child < size) {
		//双亲结点大于孩子结点交换
		if (a[parent] > a[child]) {
			Swap(&a[parent], &a[child]);
			//进行迭代
			parent = child;
			child = parent * 2 + 1;
			if ((a[child + 1] < a[child]) && (child + 1) < size) {
				++child;
			}
		}
		//若双亲结点不在大于孩子结点,说明已经迭代结束,不在需要继续进行迭代
		else {
			break;
		}
	}
}

void HeapPop(Heap* php) {
	assert(php);
	assert(php->a > 0);
	php->a[0] = php->a[php->size - 1];
	php->size--;
	AdjustDown(php->a, php->size, 0);
}

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

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

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

        test.c

#define _CRT_SECURE_NO_WARNINGS 1
#include "Heap.h"

int main() {
	int a[] = { 4,6,2,1,5,8,2,9 };
	Heap hp;
	HeapInit(&hp);
	for (int i = 0; i < sizeof(a) / sizeof(int); ++i) {
		HeapPush(&hp, a[i]);
	}
	for (int i = 0; i < sizeof(a) / sizeof(int); ++i) {
		printf("%d ", hp.a[i]);
	}
    printf("\n");
	while (!HeapEmpty(&hp)) {
		printf("%d ", HeapTop(&hp));
		HeapPop(&hp);
	}
	return 0;
}

        测试结果:

3.堆排序

        在上文中,已经较为介绍了堆的实现,现在实现堆的应用之一:堆排序。下列以降序排序为例进行介绍。

        对于堆的作用,我们在上文可以很清楚的得知,在小堆中,堆顶元素就是整个数据中最小的数据,但是对于后面的数据,并不一定呈现有序的方式排列,所以只能保证小堆的堆顶元素为最小值。所以若要将一组数据进行排序,其主要思路是先将其建堆,然后将堆顶元素放在数据最后,在将前n - 1个数据进行建堆,然后放到第n - 1个位置,以此循环递归,直至将数据排列完成。

        对于堆排序的排序原则为,降序排序为建大堆,升序排序为建小堆。以降序排序为例:当我们建立出小顶堆时,可以得出整列数据中最小的值,然后将最小值移至顺序表的最后位置,接着对前面的数据建堆,然后置换位置,以此迭代。若使用建大堆进行降序排列呢?每一次可以得到一个最大的值,然后将其放在第一个位置,对2 ~ n个位置的元素进行建堆。理论上可以,但是真正实行起来,对于第一个位置后的元素进行建堆这个步骤就会很麻烦,所以我们使用小堆进行降序排序。

        升序排序思路和上面基本一致,只有建小堆和大堆的区别。具体代码如下:

//降序排序
void HeapSort(HPDataType* a, int length) {
	// 建小堆
	for (int i = 1; i < length; i++) {
		AdjustUp(a, i);
	}
	//排序
	int end = length - 1;
	while (end > 0) {
		Swap(&a[0], &a[end]);
		AdjustDown(a, end, 0);
		end--;
	}
}

int main() {
	int a[] = { 4,6,2,1,5,8,2,9 };
	HeapSort(a, sizeof(a) / sizeof(a[0]));
	for (int i = 0; i < sizeof(a) / sizeof(HPDataType); i++) {
		printf("%d ", a[i]);
	}
	printf("\n");
	return 0;
}

        测试结果:

  • 26
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
引用:● QT工具(qt设计师,qt预言家,qt助手,qt例子); QT跨平台移植 [url=]Android[/url],[url=]IOS[/url],Windows,Linux, Mac 打包部署 移 ●页面基本控件 动 ●内容区域、列表与对话框应用 框 ●表单控件 架 ●主题定制 与 ●网格布局与页面创建、加载、跳转 设 ●动态内容整合 计 ●插件应用 模 ●设计模式与Boost ,ACE ,QT,cocos2dx详解 工厂模式,单例模式等等23种设计模式 式 ●设计模式与Boost ,ACE ,QT,cocos2dx详解 工厂模式,单例模式等等23种设计模式,UML实战 数 ●动态数组模板库 数组栈 数组队列库,字符串库 据 ●链表模板库,单链表,双链表,环链表 结 ●链式栈,链式队列 数组嵌套与链表嵌套 构 ●二叉树,线索二叉树模板库,优先队列库 部 ●哈弗曼树模板库 分 ●Tree B Tree 模板库 ●最大最小库 ●红黑树模板库 ●图模板库 ●排序模板库,数组排序,链表排序 ●贪心算法,背包算法,高级递归,动态规划 项 目 打飞机游戏 实 战 下载地址:关注wx公众号feixueteam。 C / C++ 语言中的 vector 是一个动态数组容器,它可以根据需要动态地增加或减少元素。通过使用 vector,你可以方便地处理变长数组,而无需手动处理内存分配和释放。在 C++ 中,vector 是标准模板库(STL)提供的容器之一,它提供了一系列的方法和操作符,使得对数组的操作更加方便和高效。你可以使用 vector 类型来声明一个变量,并通过调用其方法来添加、删除、访问和修改元素。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值