数据结构:堆

一.堆的概念及结构

1.堆的概念

一种有特殊用途的数据结构——用来在一组变化频繁(发生增删查改的频率较高)的数据集中查找最值。

2.堆的结构

堆在物理层面上,表现为一组连续的数组区间,将整个数组看作是堆。

堆在逻辑结构上,一般被视为是一颗完全二叉树。满足任意结点的值都大于其子树中结点的值,叫做大堆,或者大根堆,或者最大堆;反之,则是小堆,或者小根堆,或者最小堆。当一个堆为大堆时,它的每一棵子树都是大堆。

3.堆的性质
1.堆中某个节点的值总是不大于或不小于其父节点的值;
2.堆总是一棵完全二叉树

这里有一点需要注意,堆上的节点的值只需要和其父亲节点和孩子节点上的值进行比较,对于邻居的值是不做要求的。

4.堆的相关练习题
1. 下列关键字序列为堆的是:(A)
A 100 , 60 , 70 , 50 , 32 , 65
B 60 , 70 , 65 , 50 , 32 , 100
C 65 , 100 , 70 , 32 , 50 , 60
D 70 , 65 , 100 , 32 , 50 , 60
E 32 , 50 , 100 , 70 , 65 , 60
F 50 , 100 , 70 , 65 , 60 , 32
解析:就以A为例,把其写成堆的形式:100  |  60   70  |  50  32  65,这是一颗二叉树形式。
2. 已知小根堆为 8 , 15 , 10 , 21 , 34 , 16 , 12 ,删除关键字 8 之后需重建堆,在此过程中,关键字之间的比较次数是(C)。
A 1
B 2
C 3
D 4
解析:对于该问题,先将8和12交换位置,然后删掉8,这样就是12  |  15  10  |  21  34  16,这样我们adjustdown(向下调整建堆),15先和10比较,然后12和10比较,然后12和16比较,一共三次
3. 一组记录排序码为 ( 5 11 7 2 3 17 ), 则利用堆排序方法建立的初始堆为
A ( 11 5 7 2 3 17 )
B ( 11 5 7 2 17 3 )
C ( 17 11 7 2 3 5 )
D ( 17 11 7 5 3 2 )
E ( 17 7 11 3 5 2 )
F ( 17 7 11 3 2 5 )
4. 最小堆 [ 0 , 3 , 2 , 5 , 7 , 4 , 6 , 8 ], 在删除堆顶元素 0 之后,其结果是()
A [ 3 2 5 7 4 6 8 ]
B [ 2 3 5 7 4 6 8 ]
C [ 2 3 4 5 7 8 6 ]
D [ 2 3 4 5 6 7 8 ]

二.堆的实现

1.头文件
#pragma once
#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
#include<stdbool.h>

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

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 AdjustUp(HPDataType* a, int child);
void AdjustDown(HPDataType* a, int n, int parent);
2.初始化

因为堆在物理结构上其实就是一个数组,所以我们初始化的时候按照顺序表那样初始化就可以

void HeapInit(HP* php)
{
	assert(php);

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

	php->size = 0;
	php->capacity = 4;
}
3.建堆方法

(都是以大根堆为例)

(1).向上调整建堆
void AdjustUp(HPDataType* a, int child)
{
	int parent = (child - 1) / 2;

	while(parent > 0)
	{
		if (a[child] > a[parent])
		{
			Swap(&a[child], &a[parent]);
			child = parent;
			parent = (child - 1) / 2;
		}
		else
		{
			break;
		}
	}
}

向上调整建堆是相对简单的,因为一个节点只有一个父节点,所以就一直往上走就行。

(2).向下调整建堆

向下调整建堆是有条件的,那就是左右必须都是大堆/小堆。

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;
		}
	}
}
4.插入
void HeapPush(HP* php, HPDataType x)
{
	assert(php);
    //先把数据插入数组内部
	if (php->size == php->capacity)
	{
		HPDataType* tmp = (HPDataType*)realloc(php->a, sizeof(HPDataType) * php->capacity*2);
		if (tmp == NULL)
		{
			perror("realloc fail");
			return;
		}
		php->a = tmp;
		php->capacity *= 2;
	}

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

    //把新插入的数据排好
	AdjustUp(php->a, php->size - 1);
}
5.删除
void HeapPop(HP* php)
{
	assert(php);
	assert(!HeapEmpty(php));
    
    // 因为堆顶数据一定是最大的(最小的),所以删除数据的话一定是把堆顶数据删除
	// 删除数据
	Swap(&php->a[0], &php->a[php->size - 1]);
	php->size--;

	AdjustDown(php->a, php->size, 0);
}
6.杂项
HPDataType HeapTop(HP* php)
{
	assert(php);
	return php->a[0];
}

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

int HeapSize(HP* php)
{
	assert(php);
	return php->size;
}

三.堆排序

堆排序是几大排序中的重要一环,我们就在本篇中说一下堆排序

1.基本思想
堆排序 (Heapsort) 是指利用堆积树(堆)这种数据结构所设计的一种排序算法,它是选择排序的一种。它是通过堆来进行选择数据。需要注意的是排升序要建大堆,排降序建小堆。

2.代码实现
// 左右子树都是大堆/小堆
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;
		}
	}
}

void HeapSort(int* a, int n)
{
	// 建堆 -- 向下调整建堆 -- O(N)
    //这里这样写可以不用写adjustup来建堆,堆已经被自动建好了
	for (int i = (n - 1 - 1) / 2; i >= 0; --i)
	{
		AdjustDown(a, n, i);
	}

	// 自己先实现 -- O(N*logN)
	int end = n - 1;
	while (end > 0)
	{
		Swap(&a[end], &a[0]);
		AdjustDown(a, end, 0);

		--end;
	}
}
3.向上调整,向下调整对比

对于堆排序,我们选择向上排序还是向下排序是有说法的,请看下面:

向上调整建堆:

向下调整建堆:

通过对比,我们发现当N个数相对大一点的时候,向上调整建堆比向下调整建堆的建堆次数要多的多,从效率上讲,向下调整建堆绝对是更好的选择。

从时间复杂度上讲,向下是更佳的,但是从最后结果上看,向上和向下对堆排序整体的时间复杂度是没有影响的。

4.堆排序特点
1. 堆排序使用堆来选数,效率就高了很多。
2. 时间复杂度: O(N*logN)
3. 空间复杂度: O(1)
4. 稳定性:不稳定

四.TOP-K问题

  • 25
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值