堆和堆排序

目录

1.二叉树的顺序存储

顺序存储就是数组存储 ,一般使用数组只适合完全二叉树 ,因为不是完全二叉树会有空间上的浪费。在现实生活中,只有堆才会使用数组来存储。二叉树顺序存储在物理上是一个数组,在逻辑上是一颗二叉树。

2.堆的性质

小堆

堆是一棵完全二叉树

堆的父亲节点总是小于孩子节点

根节点最小

大堆

堆是一棵完全二叉树

堆的父亲节点总是大于孩子节点

根节点最大

3.堆的实现

3.1 堆的插入(向上调整算法)

方法:

1.先将元素插入到堆的末尾,即最后一个孩子处

2.插入后如果堆的性质遭到破坏,将新插入节点顺着双亲往上调整

时间复杂度:O(NlogN)

++举例:先插入一个20到堆的末尾,再往上进行调整算法,直到满足堆++

++举例:先插入一个10到堆的末尾,再往上进行调整算法,直到满足堆++

c 复制代码
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 = (child - 1) / 2;
		}
		else
		{
			break;
		}
	}
}

3.2 堆向下调整算法

现在我们给出一个数组,逻辑上看作一棵完全二叉树。我们通过从根节点开始的向下调整算法可以把它调整成一个小堆。

前提:左右子树必须是一个堆,才能调整

时间复杂度:O(N)
int a[]={27,15,19,18,28,34,65,49,25,37};


c 复制代码
void AdjustDown(HPDataType* a, int n, int parent)
{
	//假设法:假设左孩子比右孩子小
	//求左孩子的下标
	int child = parent * 2 + 1;
	while (child<n)
	{
		if (child + 1 < n && a[child] > a[child + 1])//左孩子比右孩子大
		{
			++child;
		}
		
		if (a[child] < a[parent])
		{
			swap(&a[child], &a[parent]);
			parent = child;
			child = 2 * parent + 1;
		}
		else
			break;
	}
}

3.3 堆的创建

下面我们给一个数组,这个数组逻辑上可以看成一棵完全二叉树,但是还不是一个堆,现在我们通过算法,把它构建成一个堆。

根节点左右子树不是堆,我们怎么调整呢?

方法一:向下调整法

我们从倒数的第一个非叶子节点的子树开始调整,一直调整到根节点的数,就可以调整成堆

方法二:向上调整算法

我们把数组第一个元素看成是堆,然后利用循环构建堆
int a[]={ 4,2,8,1,5,6,9,7,2,7,9 };

向下调整算法

c 复制代码
for (int i = (n - 1 - 1) / 2; i >= 0; i--)
{
	AdjustDown(a, n, i);
}

向上调整算法

c 复制代码
for (int i = 1; i < n; i++)
{
	Adjustup(a, i);
}

3.4 堆的删除

删除堆是删除堆顶的数据,将堆顶的数据跟最后一个数据交换,然后删除数组最后一个数据,再进行向下调整算法。

3.5 全套代码

c 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <stdbool.h>

typedef int HPDataType;

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

void HPInint(Heap* hph);//初始化
void HPDestroy(Heap* hph);//销毁
void HPPush(Heap* hph, HPDataType x);//插入
void HPPop(Heap* hph);//删除
HPDataType HPTop(Heap* hph);//取堆顶元素
bool HPEmpty(Heap* hph);//判空
int HeapSize(Heap* hp);//求个数

void Adjustup(HPDataType* a, int child);//向上调整算法
void swap(HPDataType* p1, HPDataType* p2);//交换
void AdjustDown(HPDataType* a, int n, int parent);//向下调整算法

#include "Heap.h"

void HPInint(Heap* hph)
{
	assert(hph);
	hph->a = NULL;
	hph->capacity = hph->size = 0;
}

void HPDestroy(Heap* hph)
{
	assert(hph);
	free(hph->a);
	hph->a = NULL;
	hph->capacity = hph->size = 0;
}

void swap(HPDataType* p1, HPDataType* p2)
{
	HPDataType tmp = *p1;
	*p1 = *p2;
	*p2 = tmp;
}

//时间复杂度:NlogN
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 = (child - 1) / 2;
		}
		else
		{
			break;
		}
	}
}


void AdjustDown(HPDataType* a, int n, int parent)
{
	//求左孩子的下标
	int child = parent * 2 + 1;
	//假设法:
	while (child<n)
	{
		if (child + 1 < n && a[child] > a[child + 1])
		{
			++child;
		}
		if (a[child] < a[parent])
		{
			swap(&a[child], &a[parent]);
			parent = child;
			child = 2 * parent + 1;
		}
		else
			break;
	}
}

void HPPush(Heap* hph, HPDataType x)
{
	assert(hph);
	//扩容
	if (hph->capacity == hph->size)
	{
		int newcapacity = hph->capacity == 0 ? 4 : hph->capacity * 2;
		HPDataType* tmp = (HPDataType*)realloc(hph->a, sizeof(HPDataType) * newcapacity);
		if (tmp == NULL)
		{
			perror("realloc fail");
			return;
		}
		//扩容成功
		hph->a = tmp;
		hph->capacity = newcapacity;
	}
	hph->a[hph->size] = x;
	hph->size++;

	//向上调整
	Adjustup(hph->a, hph->size - 1);
}

void HPPop(Heap* hph)
{
	assert(hph);
	assert(hph->size > 0);
	swap(&hph->a[0], &hph->a[hph->size - 1]);
	hph->size--;

	//向下调整
	AdjustDown(hph->a, hph->size, 0);
}

HPDataType HPTop(Heap* hph)
{
	assert(hph);
	assert(hph->size > 0);
	return hph->a[0];
}
bool HPEmpty(Heap* hph)
{
	assert(hph);
	return hph->size == 0;
}

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

4.堆排序

堆排序是利用堆积树这种结构所设计的一种排序的算法,它是选择排序的一种。

排升序建大堆

排降序建小堆

时间复杂度:O(Nlog N)

c 复制代码
void HeapSort(int* a, int n)
{
	//建堆
	//升序,建大堆
	降序,建小堆
	
	for (int 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--;
	}
}

5.Top-K问题

求数据结合中前k个最大的元素或者最小的元素,一般情况下数据量都比较大。

eg:专业前10名,世界500强等

对于top-k问题,能想到最简单的问题就是排序,但是:如果数据量非常大,排序就不可取了(可能数据都不能一下子全部加载到内存中)。最佳的方式就是用堆来解决

  • 用数据集合中前k个元素建堆
    • 前k个最大元素,建小堆
    • 前k个最小元素,建大堆
  • 用剩余的N-K个元素一次与堆顶元素相比较,不满足则替换堆顶元素
c 复制代码
void PrintTopK(int* a, int n, int k)
{
	//升序,建大堆
	//降序,建小堆
	
	// 1. 建堆--用a中前k个元素建堆
	for (int i = (k - 1 - 1) / 2; i >= 0; i--)
	{
		AdjustDown(a, k, i);
	}

	// 2. 将剩余n-k个元素依次与堆顶元素交换,不满则替换
	for (int i = k ; i < n; i++)
	{
		if (a[i] > a[0])
		{
			a[0] = a[i];
			AdjustDown(a, k, 0);
		}
	}

	int end = k - 1;
	while (end > 0)
	{
		swap(&a[0], &a[end]);
		AdjustDown(a, end, 0);
		end--;
	}
	
	//打印前k个最大的数
	for (int i = 0; i < 5; i++)
		printf("%d ", a[i]);
}
相关推荐
Captain823Jack28 分钟前
nlp新词发现——浅析 TF·IDF
人工智能·python·深度学习·神经网络·算法·自然语言处理
Captain823Jack1 小时前
w04_nlp大模型训练·中文分词
人工智能·python·深度学习·神经网络·算法·自然语言处理·中文分词
Aileen_0v02 小时前
【AI驱动的数据结构:包装类的艺术与科学】
linux·数据结构·人工智能·笔记·网络协议·tcp/ip·whisper
是小胡嘛2 小时前
数据结构之旅:红黑树如何驱动 Set 和 Map
数据结构·算法
m0_748255022 小时前
前端常用算法集合
前端·算法
呆呆的猫2 小时前
【LeetCode】227、基本计算器 II
算法·leetcode·职场和发展
Tisfy2 小时前
LeetCode 1705.吃苹果的最大数目:贪心(优先队列) - 清晰题解
算法·leetcode·优先队列·贪心·
余额不足121382 小时前
C语言基础十六:枚举、c语言中文件的读写操作
linux·c语言·算法
yuanManGan4 小时前
数据结构漫游记:静态链表的实现(CPP)
数据结构·链表
火星机器人life5 小时前
基于ceres优化的3d激光雷达开源算法
算法·3d