堆和堆排序的实现(C语言数据结构)

堆和堆排序的实现(C语言数据结构),第1张

目录

一、需要实现的功能

二、具体功能的实现

1.初始化堆

2.堆的销毁

3.交换函数

4.打印堆中的全部元素

5.向上调整我们的元素

6.堆元素的插入

7.向下调整

8.删除堆顶的元素

9.判断我们当前的堆是否为空

10.输出我们堆中的元素个数

11.查找我们当前堆顶的元素

12.合集

三、堆排序的实现

方法一:

方法二:

方法三:


一、需要实现的功能
#pragma once

// 小堆
#include
#include
#include
#include

//将我们的数据类型定义为int
typedef int HPDataType;
//这里定义我们堆的结构体
//其中a为我们的顺序表,用来实现我们堆的物理上的存储逻辑
//size为我们当前顺序表的最大的空间大小
//capacity为我们当前堆存储的元素个数。
typedef struct Heap
{
    HPDataType* a;
    size_t size;
    size_t capacity;
}HP;

//定义我们的交换函数,用来交换两个数据的具体值,将在下面的程序中调用
void Swap(HPDataType* pa, HPDataType* pb);
//实现堆数据结构的初始化 *** 作。
void HeapInit(HP* php);
//实现堆结构摧毁的 *** 作
void HeapDestroy(HP* php);
//将我们堆中数据打印出来的 *** 作
void HeapPrint(HP* php);

// 插入x以后,保持他依旧是(大/小)堆
void HeapPush(HP* php, HPDataType x);

// 删除堆顶的数据。(最小/最大)
void HeapPop(HP* php);
//判断我们的堆是否为空
bool HeapEmpty(HP* php);
//查看我们的堆的大小
size_t HeapSize(HP* php);
//返回我们堆定的元素
HPDataType HeapTop(HP* php);
二、具体功能的实现 1.初始化堆

将我们的顺序表置空,同时将我们的size和capacity置为0

void HeapInit(HP* php)
{
	assert(php);
	php->a = NULL;
	php->size = php->capacity = 0;
}
2.堆的销毁

分别将我们的堆中的顺序表的空间释放,同时将我们的size和capacity置为0

void HeapDestroy(HP* php)
{
	assert(php);
	free(php->a);
	php->a = NULL;
	php->size = php->capacity = 0;
}
3.交换函数

使用传址的方式来交换我们的数据,如果使用传值的方式,对于形参的拷贝是不会影响实参的。

void Swap(HPDataType* pa, HPDataType* pb)
{
	HPDataType tmp = *pa;
	*pa = *pb;
	*pb = tmp;
}
4.打印堆中的全部元素

从我们的堆的顺序表中顺序将我们全部的元素依次打印出来(注意,我们的size存储的我们当前顺序表中的最大元素,而我们的数组的下标是从0开始的,所以我们的i要小于size)

void HeapPrint(HP* php)
{
	assert(php);
	for (size_t i = 0; i < php->size; ++i)
	{
		printf("%d ", php->a[i]);
	}
	printf("\n");
}
5.向上调整我们的元素
void AdjustUp(HPDataType* a, size_t child)
{
//完全二叉树中寻找我们的父节点
	size_t 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;
		}
	}
}
6.堆元素的插入
void HeapPush(HP* php, HPDataType x)
{
	assert(php);
//判断我们当前的存储空间已经满了的时候,我们开辟一块更大的空间来存储我们的数据
	if (php->size == php->capacity)
	{
		size_t newCapacity = php->capacity == 0 ? 4 : php->capacity * 2;
		HPDataType* tmp = realloc(php->a, sizeof(HPDataType)* newCapacity);
//如果我们的空间开辟失败,我们将返回如下的错误提示
		if (tmp == NULL)
		{
			printf("realloc failed\n");
			exit(-1);
		}
//将我们堆的指针指向我们新开辟的空间,同时将我们的最大存储容量更新

		php->a = tmp;
		php->capacity = newCapacity;
	}
//将我们的要插入的元素放到我们列表的最后的位置
	php->a[php->size] = x;
	++php->size;

	// 向上调整,控制保持是一个大根堆
	AdjustUp(php->a, php->size - 1);
}
7.向下调整

向下调整主要用于我们堆元素的删除

void AdjustDown(HPDataType* a, size_t size, size_t root)
{
//向下调整就意味着我们之前插入元素之后,我们的新插入的元素可能随着向上调整会被调整到根节点,我们需要使用向下调整的办法来讲我们的元素插入到正确的位置
	size_t parent = root;
//找到我们当前父节点的左孩子结点
	size_t child = parent * 2 + 1;
//如果我们的左孩子结点存在的话,进入循环
	while (child < size)
	{
		// 1、选出左右孩子中大的那个
		if (child + 1 < size && a[child+1] > a[child])
		{
			++child;
		}

		// 2、如果孩子大于父亲,则交换,并继续往下调整
		if (a[child] > a[parent])
		{
			Swap(&a[child], &a[parent]);
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			break;
		}
	}
}
8.删除堆顶的元素
// 删除堆顶的数据。(最小/最大)
void HeapPop(HP* php)
{
	assert(php);
	assert(php->size > 0);
//将我们堆定的元素交换到我们的堆顺序表的末尾位置,然后将我们的当前容量--,来表示我们最大元素的删除。
//这样 *** 作不会更改我们堆中其他元素的关系,只需要将我们堆顶的元素重新向下调整到合适的位置就可以了
	Swap(&php->a[0], &php->a[php->size - 1]);
	--php->size;

	AdjustDown(php->a, php->size, 0);
}
9.判断我们当前的堆是否为空
bool HeapEmpty(HP* php)
{
	assert(php);
//如果我们当前的元素个数为0,也就是空就返回TRUE
	return php->size == 0;
}
10.输出我们堆中的元素个数
size_t HeapSize(HP* php)
{
	assert(php);
//直接返回我们的堆中当前元素大小的参数就可以了
	return php->size;
}
11.查找我们当前堆顶的元素
HPDataType HeapTop(HP* php)
{
	assert(php);
	assert(php->size > 0);
//返回我们顺序表中的第0号元素,也就是我们的堆顶元素
	return php->a[0];
}
12.合集
#include "Heap.h"

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

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

void Swap(HPDataType* pa, HPDataType* pb)
{
	HPDataType tmp = *pa;
	*pa = *pb;
	*pb = tmp;
}

void HeapPrint(HP* php)
{
	assert(php);
	for (size_t i = 0; i < php->size; ++i)
	{
		printf("%d ", php->a[i]);
	}
	printf("\n");
}

void AdjustUp(HPDataType* a, size_t child)
{
	size_t parent = (child - 1) / 2;
	while (child > 0)
	{
		if (a[child] > a[parent])
		//if (a[child] > a[parent])
		{
			Swap(&a[child], &a[parent]);
			child = parent;
			parent = (child - 1) / 2;
		}
		else
		{
			break;
		}
	}
}

void HeapPush(HP* php, HPDataType x)
{
	assert(php);

	if (php->size == php->capacity)
	{
		size_t newCapacity = php->capacity == 0 ? 4 : php->capacity * 2;
		HPDataType* tmp = realloc(php->a, sizeof(HPDataType)* newCapacity);
		if (tmp == NULL)
		{
			printf("realloc failed\n");
			exit(-1);
		}

		php->a = tmp;
		php->capacity = newCapacity;
	}

	php->a[php->size] = x;
	++php->size;

	AdjustUp(php->a, php->size - 1);
}

void AdjustDown(HPDataType* a, size_t size, size_t root)
{
	size_t parent = root;
	size_t 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(HP* php)
{
	assert(php);
	assert(php->size > 0);

	Swap(&php->a[0], &php->a[php->size - 1]);
	--php->size;

	AdjustDown(php->a, php->size, 0);
}

bool HeapEmpty(HP* php)
{
	assert(php);

	return php->size == 0;
}

size_t HeapSize(HP* php)
{
	assert(php);

	return php->size;
}

HPDataType HeapTop(HP* php)
{
	assert(php);
	assert(php->size > 0);

	return php->a[0];
}
三、堆排序的实现 方法一:

因为我们的堆顶元素一定为最大或最小,所以我们每次都将我们堆定的元素取出,然后把我们的剩下的元素重新调整为一个大根堆或一个小根堆,进行迭代,就可以实现我们的排序思想。

void HeapSort(int* a, int size)
{
//创建我们的队列
    HP hp;
//初始化我们的队列
    HeapInit(&hp);
//创建堆,将我们的要排序的数列中的元素一个个传入,搭建我们的堆
    for (int i = 0; i < size; ++i)
    {
        HeapPush(&hp, a[i]);
    }
//设置一个j来作为我们数组的伪指针
    size_t j = 0;
//当我们的堆不为空的时候
    while (!HeapEmpty(&hp))
    {
//因为我们排序所生成的是一个大根堆,所以我们堆顶的元素就是我们最大的元素
//将我们最大的元素赋值给我们的数组的第一个元素
        a[j] = HeapTop(&hp);
//将我们的伪指针后移
        j++;
//将我们的堆顶元素弹出,调用我们之前写的堆的代码
//我们剩下的元素又会重新生成一个大根堆,第二大的元素会被调换到我们的根节点的位置,然后我们就通过迭代来实现我们的堆排序
        HeapPop(&hp);
    }

    HeapDestroy(&hp);
}
方法二:

在上面的方法中,由于我们有对数组元素的拷贝,堆的生成需要开辟的空间比较大,所以我们不妨直接对我们需要排序的数组直接进行 *** 作。

//从前往后依,采用向上调节的方式调整我们的原数组,从而生成一个堆。
 for (int i = 1; i < n; ++i)
    {
    	AdjustUp(a, i);
    }

但是,这种生成方法的时间复杂度为:O(N*logN)

就如我们下面这张图中所展示的,计算出我们最坏的情况一共需要调整的结点,再利用差比数列计算出我们的复杂度,为我们的 O(N*logN)

方法三:
//在这种方法中,我们是找到我们所有叶子结点的父节点来进行向下调整,
//而在我们的堆为完全二叉树,我们只有n0与n2的结点,并且n0=n2+1
//在n-1找到的是我们数组的最后一个元素的位置(我们数组的下标是从0开始的),
//由于我们总的结点数为n0+n2,而n0=n2+1
//所以将我们的n-1再减去1除以2,就得到我们所有叶子结点的父节点。
    for (int i = (n-1-1)/2; i >= 0; --i)
    {
        AdjustDown(a, n, i);
    }

 这种方法的时间复杂度为O(N)

在这种建堆的方法中,我们第一层的2^0个元素最大需要调整h-1次

第二层2^1个元素最大需要调整h-2次

……

第h-1层有2^h-2个元素最大需要调整的次数为1次

T(N)=2^1(h-1)+2^2(h-2)+……2^(h-2)1

然后我们采用错位相减的方式可以算出我们的T(N)=2^h-1-h

h=log2(n+1)

带入T(N)就可以得到我们的T(N)为n-log2(n+1),约为N

可以看出我们这种算法更优

以下是我们的堆排序的算法。

void HeapSort(int* a, int n)
{
//完成我们大根堆的搭建

    for (int i = (n-2)/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;
    }
}
int main()
{
    int a[] = { 4, 2, 7, 8, 5, 1, 0, 6 };
    HeapSort(a, sizeof(a) / sizeof(int));

    for (int i = 0; i < sizeof(a) / sizeof(int); ++i)
    {
        printf("%d ", a[i]);
    }
    printf("\n");
    return 0;
}

 注意:按照我们上面的算法,我们想要升序排序的话,我们需要创建大根堆,如果想要实现降序排序的话,我们需要创建小根堆。

注:为什么我们不采用小根堆的方式来实现升序序列?

因为我们如果采用小根堆的方式搭建完小根堆,我们的第一个元素确实是我们最小的元素,但是我们剩余的元素需要全部重新排序成一个新的小根堆,时间复杂度太大。不像我们之前的大根堆排序一样,只需要进行向下调整 *** 作就可以重新将我们剩下的元素排列成一个大根堆。

欢迎分享,转载请注明来源:内存溢出

原文地址:https://54852.com/langs/1295914.html

(0)
打赏 微信扫一扫微信扫一扫 支付宝扫一扫支付宝扫一扫
上一篇 2022-06-10
下一篇2022-06-10

发表评论

登录后才能评论

评论列表(0条)

    保存