堆排序实现(C语言)

   

目录

堆排序的实现:

建堆:

建队代码:

 堆排序的第一种实现:时间复杂度为O(N*logN)

堆排序的第二种实现:时间复杂度为O(N*logN)

堆排序的第三种实现:

解析:


    堆排序是一种时间复杂度较低的排序算法,相较于冒泡排序的时间复杂度O(n^2),堆排序的时间复杂度只有O(n*logN),和快速排序的时间复杂度相同。

堆排序的实现:

建堆:

要实现堆排序首先要建堆,堆分为两种类型,一种是大根堆,一种是小根堆;

可以辅助理解为:大根堆的根节点的值为数值序列中最大值,小根堆的根节点的值为数值序列中最小值。

建队代码:

下面是大根堆的函数实现代码,如果要建小根堆,只要将AdjustUp函数中的if语句从if (a[child] > a[parent])改为if (a[child] < a[parent]);

#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>

#define HeapType int

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

void HeapInit(Heap* hp)//堆的初始化
{
	assert(hp);
	hp->a = NULL;
	hp->size = 0;
	hp->capacity = 0;
}

void HeapDestory(Heap* hp)//堆的销毁
{
	assert(hp);
	hp->size = 0;
	hp->capacity = 0;
	free(hp->a);
	hp->a = NULL;
}

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

void AdjustUp(HeapType* a, int child)
//判断函数,如果子节点的值大于父节点的值
//交换两个节点的值
{
	assert(a);
	while (child > 0)
	{
		int parent = (child - 1) / 2;
		if (a[child] > a[parent])
		{
			Swap(&a[child], &a[parent]);
			child = parent;
			parent = (child - 1) / 2;
		}
		else
		{
			break;
		}
	}
}

void HeapPush(Heap* hp, HeapType x)
//插入节点函数,插入前判断空间是否足够
//足够则插入,不够则扩容
{
	assert(hp);
	if (hp->size == hp->capacity)
	{
		int newcapacity = hp->capacity == 0 ? 4 : hp->capacity * 2;
		HeapType* tmp = (HeapType*)realloc(hp->a, sizeof(hp->a) * newcapacity);
		if (tmp == NULL)
		{
			perror("realloc fail");
			return;
		}
		hp->a = tmp;
		hp->capacity = newcapacity;
	}
	hp->a[hp->size] = x;
	hp->size++;
	AdjustUp(hp->a, hp->size - 1);
}

int HeapEmpty(Heap* hp)
{
	assert(hp);
	return hp->size == 0;
}

void AdjustDown(HeapType* 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 HeapPop(Heap* hp)
{
	assert(hp);
	assert(!HeapEmpty(hp));
	Swap(&hp->a[0], &hp->a[hp->size - 1]);
	hp->size--;
	AdjustDown(hp->a, hp->size, 0);
}

HeapType HeapTop(Heap* hp)
{
	assert(hp);
	assert(!(HeapEmpty(hp)));
	return hp->a[0];
}

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

 建堆时只要在主函数中调用以下函数建堆即可。这边给出一个简单的范例:

int main()
{
	Heap hp;
	HeapInit(&hp);
	HeapType arr[] = { 23,53,143,42,4,445,322 };
	for (int i = 0; i < sizeof(arr) / sizeof(HeapType); i++)
	{
		HeapPush(&hp, arr[i]);
	}
	return 0;
}

具体函数的解析可以看我上一篇文章:C语言基础数据结构——树,二叉树,堆的实现_S+叮当猫的博客-CSDN博客

 堆排序的第一种实现:时间复杂度为O(N*logN)

解析:

①创建结构体,存放数组的信息,并初始化(结构体中存储元素的空间和原数组的空间不为同一块空间,是动态开辟的)

②循环使用HeapPush函数插入数组中的数据,使得数组变为堆

③之后使用HeapTop函数取得堆首元素,并将堆首元素(当前为大根堆,所以堆首元素为数组中最大值)复制到原数组,然后执行HeapPop函数删除堆首元素,并重新成堆(HeapPop函数中调用了向下调整函数)。

④循环获取堆首元素,完成排序。

void HeapSort(HeapType* a, int size)
{
	Heap hp;
	HeapInit(&hp);
	int i = 0;
	for (i = 0; i < size; i++)
	{
		HeapPush(&hp, a[i]);
	}
	i = 0;
	while (!HeapEmpty(&hp))
	{
		int top = HeapTop(&hp);
		a[i++] = top;
		HeapPop(&hp);
	}
	HeapDestory(&hp);
}

缺点:

        这种方法在排序前,首先要动态开辟空间,不是在原数组的操作,就会造成空间的浪费;

其次,要在结构体的空间和原数组之间来回拷贝数据,比较麻烦;

堆排序的第二种实现:时间复杂度为O(N*logN)

第二种方法的实现有一点局限,但是会在原空间操作数组使之直接成堆,减少了空间的消耗。

降序——建小堆

升序——建大堆

void HeapSort2(HeapType* a, int size)
{
	assert(a);
	int i = 0;
	for (i = 1; i < size; i++)
	{
		AdjustUp(a, i);
	}
	i = 0;
	int end = size - 1;
	while (end > 0)
	{
		Swap(&a[0], &a[end]);//交换堆首元素和堆尾元素,建立大堆
		AdjustDown(a, end, 0);//向下调整,重新成堆
		end--;
	}
}

解析(升序——建大堆的情况):

①因为一个元素也可以看作是一个堆,所以我们将数组的第一个元素当作一个堆,向其中循环插入元素,使之成为一个大堆。

②因为建堆完成之后,数组为大堆,所以数组第一个元素为整个数组中最大值;

这时我们将第一个元素与最后一个元素互换,完成交换后当前数组的最后一个元素不在参与调整;

然后调用向下调整函数,调整数组首元素;

完成后数组首元素再次变为数组中除最后一个元素外最大的元素。

依次循环,直到当前数组只剩一个值时结束循环;

此时数组变为升序数组。

降序——建小堆方法实现:

只需将AdjustUp函数的 “>” 改为 “<”先建出小堆;

然后将AdjustDown函数中 “if (child + 1 < size && a[child + 1] > a[child])” 改为 “if (child + 1 > size && a[child + 1] < a[child])”;

最后将AdjustDown函数中 “if (a[child] > a[parent])” 改为 “if (a[child] < a[parent])”即可。

堆排序的第三种实现:

在第二种方法的基础上优化建堆。

从原来的向上调整建堆改变为向下调整建堆,使得原来建堆的时间复杂度从O(N*logN)变为O(N);

解析:

 向下调整建堆不需要对8~15节点调整,因为单个节点可以看作是一个堆,所以只需要从第一个非叶子节点开始向下调整,也就是最后一个节点的父亲节点开始向下调整(7号节点开始)。

我们从7号节点开始向下调整,这样7,14,15这三个节点就成堆;

然后开始调整6号节点,使得6,12,13这三个节点成堆,

再调整5号节点,使得5,10,11这三个节点成堆。

依此类推,直到1号节点向下调整完成,就完成了建堆。

这种建堆方式优于向上调整建堆。

void HeapSort3(HeapType* a, int size)
{
	assert(a);
	int i = 0;
	for (i = (size - 2) / 2; i >= 0; i--)
	{
		AdjustDown(a, size, i);//向下调整建堆
	}
	i = 0;
	int end = size - 1;
	while (end > 0)
	{
		Swap(&a[0], &a[end]);
		AdjustDown(a, end, 0);
		end--;
	}
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

S+叮当猫

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

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

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

打赏作者

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

抵扣说明:

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

余额充值