IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 数据结构与算法 -> [数据结构]~堆 -> 正文阅读

[数据结构与算法][数据结构]~堆

前言

作者小蜗牛向前冲

名言我可以接收失败,但我不能接收放弃

?如果觉的博主的文章还不错的话,还请点赞,收藏,关注👀支持博主。如果发现有问题的地方欢迎?大家在评论区指正。

目录

?一 堆

?二 堆的实现

1 堆实现的功能

2 向上调整算法和向下调整算法

3 实现堆

三 堆的应用

1 堆排序?

2 TOP-K问题



?一 堆

1 堆的概念

? 有一组数据,我们将他们用完全二叉树的方式储存在一个一维数组中,并满足一定规律。 则称为小堆(或大堆)。将根节点最大的堆叫做最大堆或大根堆根节点最小的堆叫做最小堆或小根堆

?2 堆的性质

堆中某个节点的值总是不大于或不小于其父节点的值;
堆总是一棵完全二叉树。

这里我们要区分堆在逻辑上是个树的形状,但是在物理层面上是挨着存储的。?

?二 堆的实现

1 堆实现的功能

对于堆来是一般要实现入堆出堆的功能,这里我们对于堆的建立很像顺序表,但他们仍然是有很大区别的,下面大家可以在堆的实现过程中细细体会。

#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 HeapPrint(HP* php);
//初始化
void HeapInit(HP* php);
//销毁堆
void HeapDestroy(HP* php);
// 入堆
void HeapPush(HP* php, HPDataType x);
// 出堆顶元素
void HeapPop(HP* php);
// 返回堆顶元素
HPDataType HeapTop(HP* php);
//判断堆是否为空
bool HeapEmpty(HP* php);
//求堆的大小
int HeapSize(HP* php);
//向上调整
void ADjustDown(HPDataType* a, int n, int parant);
//向下调整
void ADjustUp(HPDataType* a, int child);
//交换
void swop(int* p1, int* p2);

2 向上调整算法和向下调整算法

在这里我们先来了解堆调整的二种算法:

向上调整算法

我们先说一个结论:

数组小标计算父子关系公式:

parant = (child-1)/2;

LeftChild = parant*2+1;? ? ? 奇数

RightChild = parant*2+2;? ??偶数? ? ??

好我们知道了,这个公式我们就可以通过数组建树?,至于如何建堆,下面在说,我们继续可看到向上调整算法。

我们知道是通过数组来建堆,我们要插入一个数据,直接插入到数组的后一个元素这肯定是不符合堆的规则的,那么我们可以用向上调整算法来进行调整,调整的方式见上图(小堆)。?

下面我们用代码来实现一下:

/向上调整
void ADjustUp(HPDataType* a, int child)
{
	int parent = (child - 1) / 2;
	while (child > 0)
	{
		//小堆
		if (a[parent] > a[child])
		{
			//交换
			swop(&a[parent], &a[child]);
		}
		else
		{
			break;
		}
		//向上调整
		child = parent;
		parent = (child - 1) / 2;
	}
}

那么我们在来思考一下,该算法的时间复杂度为都少呢?

假设我们认为数有N层,我们入堆一个数,最坏的结果是N-1次也就是,该算法的时间复杂度为O(N)。

向下调整算法

这个算法我们可用来解决出堆的问题,为什么这么说呢?因为每次我们出堆,我们都要保持堆的结构的完整性,那么我们就要选出最小或者最大的数做堆顶。

当我们要出堆时,只要让数组的首元素和最后的元素交换,在size--,在用向下调整算法就可以保持堆的结构的父子关系

该算法的核心就是当在交换后,让首元素(父节点)和最小的子节点交换,以次类推得到小堆。

要想得到大堆只要改变:

?把a[minChild]<a[parant]变为a[minChild]>a[parant]即可。

代码实现如下:

void ADjustDown(HPDataType*a,int n,int parant)
{
	int minChild = parant * 2 + 1;
	//找出最小的孩子
	while (minChild < n)
	{
		if (minChild + 1 < n && a[minChild] > a[minChild + 1])
		{
			minChild++;
		}
		if (a[minChild] < a[parant])
		{
			//交换
			swop(&a[parant], &a[minChild]);
			parant = minChild;
		minChild = parant * 2 + 1;
		}
		else
		{
			break;
		}
	}
}

那该算法的时间复杂度又是多少呢?

我们假设该树有N层,总节点数为n:

第1层:有2^0个节点

第2层:有2^1个节点

第3层:有2^2个节点

…………………………

第N层:有2^(N-1)个节点

从中可以看出这就是个等比数列,所以我们直接通过公式求和:

T(N)=2^N-1,对于该算法最坏的结果就是每层都要调整,则n = 2^N-1,所以时间复杂度为

N = log(n+1),即为O(logn)

综上说:

向上调整算法的时间复杂度为O(n),而向下调整法的时间复杂度为O(logn),也就是向下调整算法的效率是更高的。

3 实现堆

堆的初始化

//初始化堆
void HeapInit(HP* php)
{
	assert(php);//断言
	php->a = NULL;
	php->size = php->capacity = 0;
}

在入堆和出堆的过程中我们都次要用到交换这个功能,所以我们在这里去实现个交换功能

//交换
void swop(int* p1, int* p2)
{
	int tmp = *p1;
	*p1 = *p2;
	*p2 = tmp;
}

堆的销毁

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

?入堆

这里用到了向上调整算法,上面我们知道向下调整算法是比向下调整更优的,所以说我们这里是可以改进的。

void HeapPush(HP* php, HPDataType x)
{
	assert(php);
	//扩容
	if (php->size == php->capacity)
	{
		int newCapacity = php->capacity == 0 ? 4 : php->capacity * 2;
		HPDataType* tmp = (HPDataType*)realloc(php->a, sizeof(HPDataType*) * newCapacity);
		if (tmp == NULL)
		{
			perror("realloc fail");
			exit(-1);
		}
		php->a = tmp;
		php->capacity = newCapacity;
	}
	//插入
	php->a[php->size] = x;
	php->size++;
	//向上调整保持堆的形状
	ADjustUp(php->a, php->size - 1);
}

出堆顶元素

//出堆顶元素
void HeapPop(HP* php)
{
	assert(php);
	assert(!HeapEmpty(php));
	//交换
	swop(&php->a[0], &php->a[php->size - 1]);
	php->size--;
	//向下调整
	ADjustDown(php->a,php->size,0);
}

返回堆顶元素

//返回堆顶元素
HPDataType HeapTop(HP* php)
{
	assert(php);
	assert(!HeapEmpty(php));
	return php->a[0];
}

判断堆是否为空

bool HeapEmpty(HP* php)
{
	assert(判断堆是否为空php);
	return php->size == 0;//为空返回true,不为空返回flase
}

返回堆的长度

//返回堆的长度
int HeapSize(HP* php)
{
	assert(php);
	return php->size - 1;
}

完整实现:

#define  _CRT_SECURE_NO_WARNINGS

#include"heap.h"

//打印堆
void HeapPrint(HP* php)
{
	assert(php);
	int i = 0;
	while (php->size > i)
	{
		printf("%d ", php->a[i]);
		++i;
	}
	printf("\n");
}

//初始化堆
void HeapInit(HP* php)
{
	assert(php);//断言
	php->a = NULL;
	php->size = php->capacity = 0;
}

//交换
void swop(int* p1, int* p2)
{
	int tmp = *p1;
	*p1 = *p2;
	*p2 = tmp;
}
//向上调整
void ADjustUp(HPDataType* a, int child)
{
	int parent = (child - 1) / 2;
	while (child > 0)
	{
		//小堆
		if (a[parent] > a[child])
		{
			//交换
			swop(&a[parent], &a[child]);
		}
		else
		{
			break;
		}
		//向上调整
		child = parent;
		parent = (child - 1) / 2;
	}
}

//堆的销毁
void HeapDestroy(HP* php)
{
	assert(php);
	free(php->a);
	php->a = NULL;
	php->capacity = php->size = 0;
}
// 入堆
void HeapPush(HP* php, HPDataType x)
{
	assert(php);
	//扩容
	if (php->size == php->capacity)
	{
		int newCapacity = php->capacity == 0 ? 4 : php->capacity * 2;
		HPDataType* tmp = (HPDataType*)realloc(php->a, sizeof(HPDataType*) * newCapacity);
		if (tmp == NULL)
		{
			perror("realloc fail");
			exit(-1);
		}
		php->a = tmp;
		php->capacity = newCapacity;
	}
	//插入
	php->a[php->size] = x;
	php->size++;
	//向上(或者向下)调整保持堆的形状
	ADjustDown(php->a, php->size, 0);
}
void ADjustDown(HPDataType*a,int n,int parant)
{
	int minChild = parant * 2 + 1;
	//找出最小的孩子
	while (minChild < n)
	{
		if (minChild + 1 < n && a[minChild] > a[minChild + 1])
		{
			minChild++;
		}
		if (a[minChild] < a[parant])
		{
			//交换
			swop(&a[parant], &a[minChild]);
			parant = minChild;
		minChild = parant * 2 + 1;
		}
		else
		{
			break;
		}
	}
}
//出堆顶元素
void HeapPop(HP* php)
{
	assert(php);
	assert(!HeapEmpty(php));
	//交换
	swop(&php->a[0], &php->a[php->size - 1]);
	php->size--;
	//向下调整
	ADjustDown(php->a,php->size,0);
}

//返回堆顶元素
HPDataType HeapTop(HP* php)
{
	assert(php);
	assert(!HeapEmpty(php));
	return php->a[0];
}
//判断堆是否为空
bool HeapEmpty(HP* php)
{
	assert(php);
	return php->size == 0;//为空返回true,不为空返回flase
}
//返回堆的长度
int HeapSize(HP* php)
{
	assert(php);
	return php->size - 1;
}

三 堆的应用

对于堆来是主要用途:

堆排序
解决TOP-K问题

1 堆排序?

为什么说堆可用来排序呢?我们知道堆顶一定是这堆中最大数或者最小数,所以我们可以利用这一特性进行排序。

堆排序即利用堆的思想来进行排序,总共分为两个步骤:

建堆

升序:建大堆

降序:建小堆

?对于建堆来说,我们可以去写一个堆,但这样太麻烦了,我们可以直接通过向下调整算法建堆。

我们假设树的高度为h

第1层,2^0个节点,需要向下移动h-1层

第2层,2^1个节点,需要向下移动h-2层

第3层,2^2个节点,需要向下移动h-3层

第4层,2^3个节点,需要向下移动h-4层

………………………………………………
第h-1层,2h-2个节点,需要向下移动1层

调整次数 = 每一次层节点个数*这层最坏向下调整的次数

我们建堆的时间复杂度我们可以通过计算得:

?所以说向下调整建堆的时间复杂度为O(N)

?利用堆删除思想来进行排序

其实就是建堆尾和堆头交换,后通过向下调整算法,将最大数或者最小数倒序排出。

?完整代码:

//思路:依次选择数,从后往前排
// 升序------大堆
// 降序------小堆
//堆排
void HeapSort(int* a, int n)
{
	//从下调整建堆
	for (int i = (n - 2) / 2;i >= 0;--i)
	{
		ADjustDown(a, n, i);
	}
	//交换,选数
	int i = 1;
	while (i < n)
	{
		swop(&a[0], &a[n - i]);
		ADjustDown(a, n - i,0);
		++i;
	}
}

2 TOP-K问题

TOP-K问题:即求数据结合中前K个最大的元素或者最小的元素,一般情况下数据量都比较大。

如求世界最高10人的身高,我们第1个想法是世界上所以的人的身高都排序个序,但这样是不是代价太大了,那我们有没有更加简单的方法呢?这将可以用到我们的堆了。

用数据集合的前K个元素来建堆:

前K个最大元素,建小堆

前K个最小元素,建大堆

堆中选数

用剩余的N-K个元素依次与堆顶元素来比较,不满足则替换堆顶元素

完整代码:

#define  _CRT_SECURE_NO_WARNINGS

#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
#include<time.h>

typedef int HPDataType;

//定义堆
typedef struct heap
{
	HPDataType* a;
	int size;
	int capacity;
}HP;

//交换
void swop(int* p1, int* p2)
{
	int tmp = *p1;
	*p1 = *p2;
	*p2 = tmp;
}

//向下调整算法
void ADjustDown(HPDataType* a, int n, int parant)
{
	int minChild = parant * 2 + 1;//默认左孩子是最小
	//找出最小的孩子
	while (minChild < n)
	{
		if (minChild + 1 < n && a[minChild] > a[minChild + 1])
		{
			minChild++;
		}
		if (a[minChild] < a[parant])
		{
			//交换
			swop(&a[parant], &a[minChild]);
			parant = minChild;
			minChild = parant * 2 + 1;
		}
		else
		{
			break;
		}
	}
}

//创建数据
void CreateDataFile(const char* filename, int N)
{
	//以写入的方式打开文件
	FILE* fin = fopen(filename, "w");
	if (NULL==fin)
	{
		perror("fopen fail");
		return;
	}
	srand(time(0));
	//写入数据
	for (int i = 0;i < N;++i)
	{
		fprintf(fin, "%d\n", rand() % 1000000);
	}
	fclose(fin);
}
//TOP前10位数
void PrintTopK(const char* filename, int k)
{
	assert(filename);
	//打开文件
	FILE* fout = fopen(filename, "r");
	if (NULL==fout)
	{
		perror("fopen fail");
		return;
	}
	//为堆分配空间
	int* minHeap = (int*)malloc(sizeof(int) * k);
	if (NULL == minHeap)
	{
		perror("malloc fail");
		return;
	}
	//读取前K个元素
	for (int i = 0;i < k;++i)
	{
		fscanf(fout, "%d", &minHeap[i]);
	}
	//建出小堆
	for (int i = (k - 2) / 2; i >= k;--i)
	{
		ADjustDown(minHeap, k, i);
	}
	//比较后N-K个元素比堆顶元素大的就入堆
	int val = 0;
	while (fscanf(fout, "%d", &val) != EOF)
	{
		if (val > minHeap[0])
		{
			minHeap[0] = val;
			ADjustDown(minHeap, k, 0);
		}
	}
	//打印排序结果
	for (int i = 0;i < k;++i)
	{
		printf("%d ", minHeap[i]);
	}
	//释放空间
	free(minHeap);
	//关闭文件
	fclose(fout);

}
int main()
{
	const char* filename = "Date.txt";
	int N = 1000000;
	int k = 10;
	//CreateDataFile(filename, N);
	PrintTopK(filename, k);
	return 0;
}

?在这里博主遇到了一个BUG想了很久,想和大家分享一下;

报了个断错误,我调试到90行代码时报错,我想这里这么会错误,断错误一般是传了个空参数

,我一想我就往上看,看一眼(我没错啊,我这么会错呢),就这样倒腾了半天。

?突然发现,自己将fout置空了。

?这样的问题大家都有遇到到吧!那我们有上面方式避免吗?

其实有的,我们可以这样写。

?如果我们仍然把等于(==)写成了赋值(=)会这么样呢?

这样编译就不会通过,这样我就能及时是发现问题并解决。

  数据结构与算法 最新文章
【力扣106】 从中序与后续遍历序列构造二叉
leetcode 322 零钱兑换
哈希的应用:海量数据处理
动态规划|最短Hamilton路径
华为机试_HJ41 称砝码【中等】【menset】【
【C与数据结构】——寒假提高每日练习Day1
基础算法——堆排序
2023王道数据结构线性表--单链表课后习题部
LeetCode 之 反转链表的一部分
【题解】lintcode必刷50题<有效的括号序列
上一篇文章      下一篇文章      查看所有文章
加:2022-09-15 02:14:52  更:2022-09-15 02:15:46 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/19 18:11:19-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码