数据结构初阶 —— 堆

目录

一,堆的概念

二,向下调整法(数组)

三,向上调整法(数组)

四,堆的创建(建堆)

建堆时间复杂度

五,堆排序  

堆排序时间复杂度推算

六,堆的实现 

七,TOP-K问题


堆(heap),二叉树的顺序存储结构;

向下/上调整时间复杂度:O(logN);

建堆时间复杂度:O(N);

堆排序时间复杂度:O(NlogN);

一,堆的概念

  • 如有个集合K={ k_{0}k_{1}k_{2},...,k_{n-1}},把它的所有元素按完全二叉树顺序存储方式存储在一个一维数组中;
  • 并满足 k_{i}<=k_{2*i+1} 且 k_{i}<=k_{2*i+2} (k_{i}>=k_{2*i+1} 且 k_{i}>=k_{2*i+2}),i=0、1、...,则称为小堆(大堆);

小堆

  • k_{i}<=k_{2*i+1} 且 k_{i}<=k_{2*i+2} ,即所有节点小于等于孩子;
  • 根节点最小,叫做最小堆或小根堆;

大堆

  • k_{i}>=k_{2*i+1} 且 k_{i}>=k_{2*i+2} ,即所有节点大于等于孩子;
  • 根节点最大,叫做最大堆或大根堆;

堆的性质

  • 堆中某个节点的值,总是不大于或不小于其父节点的值;
  • 根一定是最值(最大值或最小值);
  • 堆总是一颗完全二叉树,适合顺序结构存储;

二,向下调整法(数组)

  • 前提条件为,此二叉树的左右子树需是一个堆,只有根节点不满足堆要求;
  • 从根开始,与左右子节点中的最小值比较并交换,依次类推,直到比子节点小或到叶子节点终止(假设为小堆);
  • 时间复杂度,O(logN);
int array[] = {27,15,19,18,28,34,65,49,25,37};

//向下调整法,小堆
void Swap(int* px, int* py)
{
	int tmp = *px;
	*px = *py;
	*py = tmp;
}
//arr数组,n数组大小,parent元素序号
void AdjustDown(int* arr, int n, int parent)
{
	int child = 2 * parent + 1;

	//无孩子节点或父亲小于孩子,即终止
	while (child < n)
	{
		if (child + 1 < n && arr[child + 1] < arr[child])
			child++;
		
		if (arr[parent] > arr[child])
		{
			Swap(arr + parent, arr + child);
			parent = child;
			child = 2 * parent + 1;
		}
		else
			break;
	}
}

三,向上调整法(数组)

  • 前提条件为,此二叉树的左右子树需是一个堆,只有最后节点不满足堆要求;
  • 从最后开始,与父节点比较并交换,依次类推,直到比父亲小或到根节点终止(假设为大堆);
  • 时间复杂度,O(logN);
//向上调整法
//arr数组,child元素序号
void AdjustUp(int* arr, int child)
{
	int parent = (child - 1) / 2;

	//大堆
	//根节点或父亲大于孩子,即终止
	while (child > 0)
	{
		if (arr[parent] < arr[child])
		{
			Swap(arr + parent, arr + child);
			child = parent;
			parent = (child - 1) / 2;
		}
		else
			break;
	}
}

四,堆的创建(建堆)

  • 即对数据如数组(可看为完全二叉树),将其构建为堆;
  • 方法为,从倒数第一个非叶子节点的子树开始调整,直到根节点为止;
  • 即每次调整时,使用向下调整法;
  • 建堆时间复杂度,O(N),下面有证明;
  • 注,也可使用向上调整法建堆,但初始化堆的个别元素位置可能不一样;
int arr[] = {1,5,3,8,7,6}; 
//建堆-向下调整法
void Heap(int* arr, int n)
{
	int i = (n - 1 - 1) / 2; //最后节点的父节点
	for (i; i >= 0; i--)
	{
		AdjustDown(arr, n, i);
	}
}
//建堆-向上调整法
void Heap(int* arr, int n)
{
	int i = 1; 
	for (i; i < n; i++)
	{
		AdjustUp(arr, i);
	}
}

建堆时间复杂度

五,堆排序  

  • 升序,建大堆(将最大的数换到最后,在将剩下的数向下调整下,选出次大数,依次类推);
  • 降序,建小堆(将最小的数换到最后,在将剩下的数向下调整下,选出次小数,依次类推);
  • 整体的时间复杂度,O(N*logN)

注:

  • 如升序建小堆(或降序建大堆)的话,先建堆O(N)选出最值后,剩余部分只能继续建堆O(N-1),以此类推,时间复杂度=N+(N-1)+...+2+1=O(N^2);
  • 如此这样,效率较低,还不如直接遍历,建堆的价值未体现;
//建堆排序
void HeapSort(int* arr, int n)
{
	//建堆 - O(N)
	int i = (n - 1 - 1) / 2; //最后节点的父节点
	for (i; i >= 0; i--)
	{
		AdjustDown(arr, n, i);
	}

	//排序 - O(N*log(N))
	int end = n - 1;
	while (end)
	{
		Swap(arr, arr + end);
		AdjustDown(arr, end, 0);
		end--;
	}
}

堆排序时间复杂度推算
  • 总时间复杂度 = 建堆时间复杂度O(N) + 调整时间复杂度O(Nlog(N)) = O(Nlog(N))

        假设刚好为满二叉树,建完堆后,随后首尾节点交换获取最值,在调整剩下节点,获取剩下节点最值,依次类推;

六,堆的实现 

//堆
typedef int HPDataType;

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

//初始化
void HeapInit(Heap* php, HPDataType* arr, int n);
//释放销毁
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);

注:完整接口实现

七,TOP-K问题

直接建堆排序:

  • O(N*log(N)),数据过大的话,内存可能不足;

建堆并pop出前K个数:

  • O(N+K*log(N)),数据过大的话,内存可能不足;

建K个元素的堆:

  • 前K个最大元素,建小堆(前K个最小元素,建大堆);
  • 将剩下的数依次与堆顶比较,大即入堆,最后此堆即为最大的K个数;
  • O(N*log(K)),通常K远小于N;
//TOP-K,前K个最大值,建小堆
void TOPK(int* arr, int n, int k)
{
	Heap hp;
	//建堆
	HeapInit(&hp, arr, k);

	int i = k;
	for (i; i < n; i++)
	{
		//替换
		if (arr[i] > HeapTop(&hp))
		{
			HeapPop(&hp);
			HeapPush(&hp, arr[i]);
		}
	}

	HeapPrint(&hp);
	HeapDestroy(&hp);
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值