完全二叉树的实现(堆的实现)<堆排序>

目录

1.完全二叉树实现的基本原理

2.理解和认识堆

2.1大堆

2.2小堆

3.堆的实现

 3.1结构的定义

3.2初始化销毁

 3.3插入数据

3.4取堆顶元素

3.5删除堆顶元素

3.6判断堆是否为空

4.堆排序以及时间复杂度

4.1堆排序代码讲解

 4.2堆排序时间复杂度计算

5.所有代码展示


1.完全二叉树实现的基本原理

我们来实现完全二叉树我们用到的物理结构就是顺序表,我们使用数组下标来访问亲结点和子结点。(前面一篇博客有下图所示的结论)。下面我们会用这个方式来实现堆,我们通常就会用顺序表的方式来实现堆,堆是一种数据结构也是操作中管理内存的一块区域分段。

2.理解和认识堆

堆的本质实际上也是一个完全二叉树,我们在堆中又分为大堆小堆。

堆的性质:

1.堆的任意一个结点的值一定大于或小于等于两个子结点。

2.堆总是一个完全二叉树。

2.1大堆

大堆首先要满足堆的条件---总是一个完全二叉树,同时还需要满足每个结点的值都大于两个子结点。

图中画了两个结构一个是在内存中的存储结构,另一个是逻辑结构。

2.2小堆

 小堆首先要满足堆的条件和大堆一样,总是一个完全二叉树,另外还需满足每个结点的值都小于或等于两个子结点。

3.堆的实现

 3.1结构的定义

我们从起那面所讲我们实际上实现堆,控制的确是一个顺序表,写堆不见堆(就像鱼香肉丝没有鱼,老婆饼比一定是老婆做的,钱包里面没有钱)。我们定义堆的结构就是在定义一个顺序表。

typedef int HPDataType;

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

3.2初始化销毁

 这里我们就不做讲解如果对顺序表的初始化销毁不清楚的话可以看这篇我写的博客顺序表的实现。直接上代码:

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

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

 3.3插入数据

我们想要插入数据我们插入数据一定要满足堆的性质,这里我们举例小堆。

由于我们这里会出现多次交换我们就就把交换写一个函数代码如下 :

void swap(HPDataType* wa, HPDataType* wb)
{
	HPDataType c = *wa;
	*wa = *wb;
	*wb = c;
}

我们如何更好的插入一个数据呢?

我们需要用到一个向上调整的算法,我们先把我们的数据放在顺序表的最后,我们再与它的双亲结点一个一个的比较,如果双亲结点大于它我们就交换。这样最多的话调整到根节点就结束或者双亲结点小于它,也是结束的条件。(子结点的下标是N的话它的双亲结点j就是\frac{\left ( N-1 \right )}{2}

如下图:

 

向上调整的代码如下:

void HPJustUp(HPDataType* arr, int child)
{
	int parent = (child - 1) / 2;
	while (child > 0)
	{
		if (arr[child] < arr[parent])
		{
			swap(&arr[child], &arr[parent]);
			child = parent;
			parent = (child - 1) / 2;
		}
		else
		{
			break;
		}
	}

}

我们分析一下这个函数需要做什么?

1.我们需要先判断是否需要开辟空间(我们实现的是一个可以扩容的堆)。

2.我们需要把数据放在顺序表的尾部。

3.利用向上调整算法把数据向上调整到合适的位置。 

如下图:

代码如下: 

void DataPushHP(HP* php, HPDataType x)
{
	assert(php);
	if (php->capacity == php->size)
	{
		int newcapacity = php->capacity == 0 ? 4 : php->capacity * 2;
		HPDataType* temp = (HPDataType*)realloc(php->a, sizeof(HPDataType) * newcapacity);
		if (temp == NULL)
		{
			perror("realloc fail!");
			return;
		}
		else
		{
			php->a = temp;
			php->capacity = newcapacity;
		}
	}
	php->a[php->size] = x;
	php->size++;
	HPJustUp(php->a, php->size - 1);

}

3.4取堆顶元素

我们的堆顶元素就是顺序表的第一个元素我们就取第一个元素作为返回值就可以了。

十分简单代码如下:

HPDataType HPTop(HP* php)
{
	assert(php);
	
	return php->a[0];
}

3.5删除堆顶元素

我们的数据在堆中只能从堆顶出。

可不可以直接删除第一个数据并把数据一次向前移动呢?

是不可以的,这么做会导致兄弟变父子,父子变兄弟。 

我们如何做呢?

这里我们使用向上调整算法:我们先将顺序表的头尾进行交换并size--,我们再把新的堆顶进行向下调整 ,和下面的两个子结点进行比较,我们选较小的那一个进行交换我们的根结点序号为N,它的左孩子的下标是N*2+1,右孩子的下标就是N*2+ 2),这样比较到最后一层的时候就停止调整或者当两个子结点都大于它的时候也是停止调整的条件。

代码如下:

向下调整算法:

void HPJustDown(HPDataType*arr,int n,int parent)
{
	int child = parent * 2;
	while (child < n)
	{
		if (child + 1 < n && arr[child] > arr[child + 1])
		{
			child++;
		}
		if (arr[child] < arr[parent])
		{
			swap(&arr[child], &arr[parent]);
			parent = child;
			child = child * 2;
		}
		else
		{
			break;
		}
	}
}

 删除堆顶元素:

void HPPop(HP* php)
{
	assert(php);
	assert(php->capacity);
	assert(php->size);
	swap(&php->a[php->size - 1], &php->a[0]);
	php->size--;
	HPJustDown(php->a,php->size - 1,0);
}

3.6判断堆是否为空

判断方法和顺序表一样因为我们控制的就是顺序表。

代码如下:

bool HPEmpty(HP* php)
{
	assert(php);
	return php->size;
}

4.堆排序以及时间复杂度

4.1堆排序代码讲解

我们在完成上面的代码之后我们会发现我们如果每次都可以取到堆中的最小值我们把最小值都取出来按顺序放置我们就会发现出来的数据会是升序。这就是堆排序,我们利用堆这个数据结构完成排序。

我们在进行堆排序之前的时候我们会进行建堆加上建堆这一步我们的代码才是一个完整的堆排序。

我们来梳理一下步骤:

1.建堆(把数据进行建一个小堆(我们需要完成升序就建小堆))。

2.建好堆后第一个堆顶数据一定是我们所有数据里面最小的那一个,我们先出堆顶数据(前面的HPTop函数)存进一个数组再删除堆顶数据(前面的HPPop函数)以此为循环,堆当为空(HPEmpty)的时候我们就停止。

代码如下:

void HPShort(HPDataType* arr, size_t n)
{
	assert(arr);
	//建立堆
	HP php;
	InitHP(&php);
	for(int i = 0; i < n; i++)
	{
		DataPushHP(&php, arr[i]);
	}
	//出数据进行排序
	for (int i = 0; i < n; i++)
	{
		HPDataType temp = HPTop(&php);
		arr[i] = temp;
		HPPop(&php);
	}
	DestoryHP(&php);
}

这样我们就可以做到排序了:

 4.2堆排序时间复杂度计算

我们有许多排序的方法为什么我们要大费周章的来利用数据结构堆来排序怎么做有什么好处?
为了探求这一点我们从时间复杂的角度来讲解一下。

因为堆是完全二叉树,而满二叉树也是完全二叉树,此处为了简化使用满二叉树来证明(时间复杂度本来看的就是近似值,多几个节点不影响最终结果)。 

我们在进行数据的出堆是层次的深度为O\left (\log _{2}N \right ),我们在对每个数据出的时候会重复N遍。所以储数据的时间复杂度为 O\left (N\log _{2}N \right )。加上建堆的时间复杂度就是O\left (N\log _{2}N \right )+O\left (N \right )根据大O阶的表示法就是O\left (N\log _{2}N \right )

5.所有代码展示

//heap.h

#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>
typedef int HPDataType;

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

void InitHP(HP* php);


void DestoryHP(HP* php);


void swap(HPDataType* wa, HPDataType* wb);


void HPJustUp(HPDataType*arr,int child);

void DataPushHP(HP* php, HPDataType x);

void HPPop(HP* php);

HPDataType HPTop(HP* php);

void HPJustDown(HPDataType* arr, int n, int parent);


void HPShort(HPDataType* arr,size_t n);

//test.c

#include"Heap.h"

int main()
{
	int arr1[] = { 10,15,30,18,20,33,35 };
	HPShort(&arr1, sizeof(arr1) / sizeof(int));
	for (int i = 0; i < sizeof(arr1) / sizeof(int); i++)
	{
		printf("%d ",arr1[i]);
	}
	printf("\n");
	return 0;
}
//heap.c

#include"Heap.h"

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

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


void swap(HPDataType* wa, HPDataType* wb)
{
	HPDataType c = *wa;
	*wa = *wb;
	*wb = c;
}

void HPJustUp(HPDataType* arr, int child)
{
	int parent = (child - 1) / 2;
	while (child > 0)
	{
		if (arr[child] < arr[parent])
		{
			swap(&arr[child], &arr[parent]);
			child = parent;
			parent = (child - 1) / 2;
		}
		else
		{
			break;
		}
	}

}

void DataPushHP(HP* php, HPDataType x)
{
	assert(php);
	if (php->capacity == php->size)
	{
		int newcapacity = php->capacity == 0 ? 4 : php->capacity * 2;
		HPDataType* temp = (HPDataType*)realloc(php->a, sizeof(HPDataType) * newcapacity);
		if (temp == NULL)
		{
			perror("realloc fail!");
			return;
		}
		else
		{
			php->a = temp;
			php->capacity = newcapacity;
		}
	}
	php->a[php->size] = x;
	php->size++;
	HPJustUp(php->a, php->size - 1);

}


void HPJustDown(HPDataType*arr,int n,int parent)
{
	int child = parent * 2;
	while (child < n)
	{
		if (child + 1 < n && arr[child] > arr[child + 1])
		{
			child++;
		}
		if (arr[child] < arr[parent])
		{
			swap(&arr[child], &arr[parent]);
			parent = child;
			child = child * 2;
		}
		else
		{
			break;
		}
	}
}


void HPPop(HP* php)
{
	assert(php);
	assert(php->capacity);
	assert(php->size);
	swap(&php->a[php->size - 1], &php->a[0]);
	php->size--;
	HPJustDown(php->a,php->size - 1,0);
}


HPDataType HPTop(HP* php)
{
	assert(php);
	
	return php->a[0];
}

bool HPEmpty(HP* php)
{
	assert(php);
	return php->size;
}



void HPShort(HPDataType* arr, size_t n)
{
	assert(arr);
	//建立堆
	HP php;
	InitHP(&php);
	for(int i = 0; i < n; i++)
	{
		DataPushHP(&php, arr[i]);
	}
	//出数据进行排序
	for (int i = 0; i < n; i++)
	{
		HPDataType temp = HPTop(&php);
		arr[i] = temp;
		HPPop(&php);
	}
	DestoryHP(&php);
}
  • 6
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

脆脆鲨<码>

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

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

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

打赏作者

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

抵扣说明:

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

余额充值