【数据结构】二叉树_堆

目录

1、堆的概念及结构

2、堆的实现

2.1堆向下调整算法

2.2堆的创建

2.3建堆的时间复杂度

2.4堆的插入

2.5堆的删除

2.6堆的具体实现

3、堆的应用

3.1 堆排序

3.2 TOP-K问题


一、堆的概念及结构

1.1 堆的概念

1. 堆在逻辑上是一棵 完全二叉树(如下图,类比满二叉树缺了右下角)
2. 堆的实现利用是通过数组,我们通常会利用 动态数组来存放元素,这样可以快速拓容也不      会很浪费空间,我们是将这颗 完全二叉树用层序遍历的方式储存在数组里的。
3.堆有两种,分为大根堆和小根堆。

 注意:这里的堆指的是数据结构中的堆结构,用来存储和用来查找操作;要与操作系统中的堆区区分开

 

 1.2 堆的分类

1.2.1 小根堆

小根堆:其双亲节点数值的大小永远比左右子树节点的数值要小,以此类推到整个堆,可以得知根节点的数值是堆中最小的。如图所示

1.2.2大根堆

大根堆:其双亲节点数值的大小永远比左右子树节点的数值要大,以此类推到整个堆,可以得知根节点的数值是堆中最小的。如图所示

1.3堆的结构

堆的逻辑结构就是完全二叉树。

其物理结构(如何存储)为数组。

左孩子 leftchild = parent * 2 + 1;

右孩子 rightchild = parent * 2 + 2;

双亲节点 parent = (child - 1) / 2;  (这里child可以是左孩子也可以是右孩子,因为C语言的整除规则,无论哪个节点,整除都会是双亲的下标)    

在数组中的表示如下图:

堆的结构:

  • void HPInit(HP* php);  初始化堆
  • void HPDestroy(HP* php);  销毁堆
  • void HPPush(HP* php, HPDataType x);  插入数据
  • void HPPop(HP* php);   删除数据
  • HPDataType HPTop(HP* php);   获取堆顶元素
  • bool HPEmpty(HP* php);   判空操作
  • void AdjustUp(HP* php);   自上而下调整
  • void AdjustDown(HP* php);   自下而上调整

二、堆的实现 

2.1 交换函数

//在调整过程中实现交换
void Swap(HPDataType* px, HPDataType* py)
{
	HPDataType tmp = *px;
	*px = *py;
	*py = tmp;
}

 注意:交换的目的在于使(小)大堆保持原来的规则形态,为了避免在插入和删除之后,兄弟变成父子,父子变成叔侄这样的事情发生,这样就破坏了原有的(小)大堆规则,因此做出调整

2.2 向上调整算法

//向上调整(此处皆以实现大根堆作为示范)
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 = (parent - 1) / 2; //将父亲节点同样向上调整
		}
		else
		{
			break;
		}
	}
}

 时间复杂度:O(logN)

2.3 向下调整算法

//向下调整(此处皆以实现大根堆作为示范)
void AdjustDown(HPDataType* a, int n, int parent)
{
	//使用假设法
	//选出左右孩子中较大的一个
	int child = parent * 2 + 1;
	while (child<n)
	{
		if (child + 1 < n && a[child + 1] > a[child]) //若实现小根堆,则a[child + 1] < a[child]
		{
			child++;
		}
		if (a[child] > a[parent]) //如果孩子大于父亲,则交换
    //同理,若实现小根堆,则a[child] < a[parent]
		{
			Swap(&a[child], &a[parent]);
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			break;
		}
	}

}

时间复杂度:O(logN)

2.4 堆的创建

//结构体成员初始化可以置空,也可以赋值
void HPInit(HP* php)
{
	assert(php);
	php->a = NULL;
	php->capacity = 0;
	php->size = 0;
}

2.5 建堆的时间复杂度

void HPInitArray(HP* php, HPDataType* a, int n)
{
	assert(php);

	php->a = malloc(sizeof(HPDataType) * n);
	if (php->a == NULL)
	{
		perror("malloc fail");
		return;
	}

	memcpy(php->a, a, sizeof(HPDataType) * n);
	php->capacity = php->size = n;

	//建堆
	//分为向上和向下
	//自顶向下(向上),时间复杂度为O(n*logN)
	/*for (int i = 1; i > php->size - 1; i++)
	{
		AdjustUp(php->a, i);
	}*/

	//向下调整时间复杂度为O(N)
	for (int i = (php->size-1  -  1)/2; i >= 0; --i)  // 这里size-1  -1 是因为双亲节点等于(孩子节点-1)/ 2 ,
	{
		AdjustDown(php->a, php->size, i);
	}
}

2.6 堆的插入

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

	//初始化数组操作,为数组分配空间
	if (php->capacity == php->size)
	{
		int newcapacity = php->capacity == 0 ? 4 : 2 * php->capacity;
		HPDataType* tmp = realloc(php->a, sizeof(HPDataType) * newcapacity);

		if (tmp == NULL)
		{
			perror("realloc fail");
			return;
		}
		php->a = tmp;
		php->capacity = newcapacity;

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

	//向上调整
	AdjustUp(php->a, php->size-1);
}

 这张图帮助大家理解:若新插入元素大于其双亲节点,则向上调整

2.7 堆的删除

//删除堆顶元素
void HPPop(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);

}

 注意:此处删除元素不能像顺序表一样所有元素向前挪动一位,原因有二:

1. 挪动覆盖的时间复杂度是O(N)

2. 堆结构被破坏,兄弟变父子,父子变叔侄

 2.8 堆的销毁

void HPDestroy(HP* php)
{
	free(php->a);
	php->a = NULL;

	php->capacity = 0;
	php->size = 0;
}

 2.9 取堆顶元素

//取出堆顶元素
HPDataType HPTop(HP* php)
{
	assert(php);

	return php->a[0];
}

2.10 堆的具体实现

Heap.h

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

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

void HPInit(HP* php);
void HPDestroy(HP* php);

void HPInitArray(HP* php, HPDataType* a, int n);
void HPPush(HP* php, HPDataType x);
void HPPop(HP* php);
void AdjustDown(HPDataType* a, int n, int parent);

HPDataType HPTop(HP* php);

bool HPEmpty(HP* php);

heap.c

#include"heap.h"

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

void HPInitArray(HP* php, HPDataType* a, int n)
{
	assert(php);

	php->a = malloc(sizeof(HPDataType) * n);
	if (php->a == NULL)
	{
		perror("malloc fail");
		return;
	}

	memcpy(php->a, a, sizeof(HPDataType) * n);
	php->capacity = php->size = n;

	//建堆
	//分为向上和向下
	//自上而下,时间复杂度为O(n*logN)
	/*for (int i = 1; i > php->size - 1; i++)
	{
		AdjustUp(php->a, i);
	}*/

	//向下调整时间复杂度为O(N)
	for (int i = (php->size-1  -  1)/2; i >= 0; --i)
	{
		AdjustDown(php->a, php->size, i);
	}
}

void HPDestroy(HP* php)
{
	free(php->a);
	php->a = NULL;
	php->capacity = 0;
	php->size = 0;
}
void Swap(HPDataType* px, HPDataType* py)
{
	HPDataType tmp = *px;
	*px = *py;
	*py = 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 = (parent - 1) / 2;
		}
		else
		{
			break;
		}
	}
}

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

	//初始化数组操作,为数组分配空间
	if (php->capacity == php->size)
	{
		int newcapacity = php->capacity == 0 ? 4 : 2 * php->capacity;
		HPDataType* tmp = realloc(php->a, sizeof(HPDataType) * newcapacity);

		if (tmp == NULL)
		{
			perror("realloc fail");
			return;
		}
		php->a = tmp;
		php->capacity = newcapacity;

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

	//向上调整
	AdjustUp(php->a, php->size-1);
}

//向下调整
void AdjustDown(HPDataType* a, int n, int parent)
{
	//使用假设法
	//选出左右孩子中较小的一个
	int child = parent * 2 + 1;
	while (child<n)
	{
		if (child + 1 < n && 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 HPPop(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);

}

HPDataType HPTop(HP* php)
{
	assert(php);

	return php->a[0];
}

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

test.c

#include"heap.h"

int main()
{
	HP hp;
	int a[] = { 50, 100, 70, 65, 60, 32 };

	HPInit(&hp);
	for (int i = 0; i < sizeof(a) / sizeof(int); i++)
	{
		HPPush(&hp, a[i]);
	}
	//HPInitArray(&hp, a, sizeof(a) / sizeof(int));
	while(!HPEmpty(&hp))
	{
		printf("%d\n", HPTop(&hp));
		HPPop(&hp);
	}
	HPDestroy(&hp);

	return 0;
}

三、堆的应用

3.1 堆排序

学习堆并不只是为了学习知识,更在应用。

堆在排序、查找方面对于其他排序方法效率是很高的, 因为我们知道,堆顶元素一定是整个数组的最大(小)值,向下只要根据堆的性质,就可以做到升序或是降序排序。

以实现升序排序为例:

1.将该数组建成一个大堆

2.第一个数和最后一个数交换,然后把交换的那个较大的数不看做堆里面的节点

3.前n-1和数进行向下调整算法,选出大的数放到根节点,再跟倒数第二个交换......

 代码如下:

void HeapSort(int* a,int n)
{
	int i = 0;
	//这里用向下调整算法来建堆,因为时间复杂度只有O(N)
	for (i = (n - 1 - 1) / 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;
	}
}

 时间复杂度:O(N*logN)

3.2 TOP-K问题

我们在做一些编程题会遇到一类问题,就是top-k问题

top-k问题指的有一组很大的数据,我们需要返回它最大(最小)的前K个元素。

这里我们就可以用堆排序很好的解决此类问题。

这里力扣平台有一个练习题,我们一起来看一看

面试题 17.14. 最小K个数 - 力扣(LeetCode)

 思路:我们先建立一个大堆,先把前K个元素建成一个大堆,然后在将剩下的数和堆顶元素进行比较,如过大于堆顶数据,我们就和堆顶元素进行交换,然后将现在的堆顶元素向下调整,前k个数就是这组数据中最小的前K个数。

void Swap(int* p1, int* p2)
{
	int temp = *p1;
	*p1 = *p2;
	*p2 = temp;
}
//向下调整
void AdjustDown(int* a, int n, int parent)
{
	int child = parent * 2 + 1;
	while (child < n)
	{
		//大堆
		if (child + 1 < n && a[child+1] > a[child])   //找到两个孩子节点较小的值
		{
			child++;
		}
		//建大堆
		if (a[child]>a[parent])
		{
			Swap(&a[child], &a[parent]);
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			break;
		}
	}
}
 
int* smallestK(int* arr, int arrSize, int k, int* returnSize)
{
    if(k==0)
    {
        *returnSize=0;
        return NULL;
    }
    int *ret=(int*)malloc(sizeof(int)*k);
    for(int i=0;i<k;i++)
    {
        ret[i]=arr[i];
    }
 
 
    //给前k个元素建大堆
    for(int i=(k-1-1)/2;i>=0;i--)
    {
        AdjustDown(ret, k, i);
    }
 
    for(int i=k;i<arrSize;i++)
    {
        if(ret[0]>arr[i])
        {
            ret[0]=arr[i];
            AdjustDown(ret,k,0);
        }
    }
    *returnSize=k;
    return ret;
}

每文一言:

当你觉得这个事情无法实现的时候,或者你觉得烦闷的时候,每天消灭一点点,可能你就爱上它了

评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值