数据结构0基础学习堆

文章目录

简介

堆是一种重要的数据结构,是一种完全二叉树,(二叉树的内容后面会出),

堆分为大小堆,大堆,左右结点都小于根节点,(又称子节点和父节点),

小堆则反过来,可以用静态数组/顺序表实现

公式

已知某节点下标 i ,(根节点下标为0),

  • 左孩子节点为 2*i+1
  • 右孩子节点为 2*i+2
  • 父节点 ( i - 1 )/ 2

建立堆

c 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
typedef int HPTypeDate;
typedef struct {
	HPTypeDate* a;
	HPTypeDate size;
	HPTypeDate capacity;
}HP;

将元素插入a里面,size表示堆里面的元素个数,避免浪费内存,满了就开辟

c 复制代码
void HeapInit(HP* php);
void HeapPush(HP* php, HPTypeDate x);
void Adjustup(HPTypeDate* a, int child);
void swap(HPTypeDate* a, HPTypeDate* b);
void HeapPrint(HP* php, HPTypeDate n);
HPTypeDate HPTop(HP* php);
void HPPop(HP* php);
bool HPEmpty(HP* php);
void Adjustdown(HPTypeDate* a, HPTypeDate n,HPTypeDate parent);

堆常用的函数,初始化,插入,上下调整,取堆顶元素,消堆顶元素

函数解释

c 复制代码
void HeapInit(HP* php)
{
    assert(php);
    php->a = (HPTypeDate*)malloc(sizeof(HPTypeDate) * 4);
    if (php->a == NULL)
    {
        perror("malloc fail");
        return;
    }
    php->size = 0;
    php->capacity = 4;
}

初始化,为数组a开辟空间,size赋值,capacity先赋值4,

c 复制代码
void HeapPrint(HP* php, HPTypeDate n)
{
    assert(php);
    for (int i = 0;i < n;i++)
    {
        printf("%d ", php->a[i]);
    }
    printf("\n");
}
void swap(HPTypeDate* a, HPTypeDate* b)
{
    int p = *b;
    *b = *a;
    *a = p;
}

打印和交换函数

c 复制代码
void Adjustup(HPTypeDate* 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;
    }
}

向上调整函数,非常重要,传入child,是某个节点的下标。再找出父亲节点,判断孩子和父亲节点的大小,根据建大小堆的不同 if中判断符号作改变,我这里是小堆,再递归,父亲的位置给孩子,父亲根据公式 再重新向上找点,一直往复,直到不满足堆的规定时间复杂度为O(log n);

c 复制代码
void Adjustdown(HPTypeDate* a,HPTypeDate n, HPTypeDate parent)
{
    HPTypeDate child = parent * 2+1;
    while (child < n)
    {
        if (child+1<n&&a[child] < a[child + 1])
        {
            child = child + 1;
        }
        if (a[parent] > a[child])
        {
            swap(&a[parent], &a[child]);
            parent = child;
            child = parent * 2 + 1;
        }
        else 
            break;
    }
}

向下调整,传入父节点下标,求出孩子结点,找出那个大孩子,再根据大小堆是否与孩子交换,递归,与向上类似时间复杂度与向上一致

c 复制代码
void HeapPush(HP* php, HPTypeDate x)
{
    assert(php);
    if (php->capacity == php->size)
    {
        HPTypeDate* tmp = (HPTypeDate*)realloc(php->a, sizeof(HPTypeDate) * (php->capacity) * 2);
        if (tmp == NULL)
        {
            perror("realloc");
            return;
        }
        php->a = tmp;
        php->capacity *= 2;
    }

    php->a[php->size++] = x;
    Adjustup(php->a, php->size - 1);
}

插入函数,判断空间够不够,不够realloc再开辟capacity2的,把开辟的空间给a,capacity =2;
将新来的数插入数组末尾,再向上调整一遍。时间复杂度最坏O(n),均摊O(1)

c 复制代码
bool HPEmpty(HP* php)
{
    return php->size == 0;
}
HPTypeDate HPTop(HP* php)
{
    assert(php);
    return php->a[0];
}
void HPPop(HP* php)
{
    assert(php);
    if (HPEmpty(php)) return;
    swap(&php->a[0], &php->a[php->size - 1]);
    php->size--;
    Adjustdown(php->a, php->size,0);
}

消去堆顶函数,将堆顶和堆尾交换,size-- 就行,再将堆顶向下调整

堆排序O(n logn)

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

void swap(int* a, int* b)
{
	int p = *a;
	*a = *b;
	*b = p;
}
void Adjustdown(int* a, int parent, int n)
{
	int child = parent * 2 + 1;
	while (child < n)
	{
		if (child + 1 < n && a[child] < a[child + 1])
			child = child + 1;
		if (a[child] > a[parent])
		{
			swap(&a[child], &a[parent]);
			parent = child;
			child = parent * 2 + 1;
		}
		else 
			break;
	}

}
void heap_sort(int* a, int n)
{
	for (int i = (n - 2) / 2;i >= 0;i--)
	{
		Adjustdown(a, i, n);
	}
	int end = n - 1;
	while (end > 0)
	{
		swap(&a[0], &a[end]);
		Adjustdown(a, 0, end);
		end--;
	}
}
int main()
{
	int a[] = { 1,9,8,5,6,4,7,4 };
	heap_sort(a, 8);
	for (int i = 0;i < 8;i++)
	{
		printf("%d ", a[i]);
	}
	return 0;
}

先前向上调整函数参数为指针,可以直接用来排序,
先将数组建成一个大堆,从中间开始往下调整O(n),但从下向上调整O(nlogn)

默认升序,若想降序

  • 方法 1:改用 小根堆(最小堆),这样堆顶是最小值,交换到末尾后自然形成降序。

  • 方法 2:仍然用 大根堆,但 不交换堆顶到末尾,而是 直接输出堆顶(每次取最大值),但这样会破坏原数组

topk问题

给出一堆数让求最大的前k个,若给几十亿个数,开辟不了这么大的内存,所以要取巧,

建k个大小的小堆,遍历一遍数,若比堆顶大就代替堆顶,进堆,最后几个就是最大的

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

void swap(int* a, int* b)
{
	int p = *a;
	*a = *b;
	*b = p;
}
void Adjustdown(int* a, int parent, int n)
{
	int child = parent * 2 + 1;
	while (child < n)
	{
		if (child + 1 < n && a[child] < a[child + 1])
			child = child + 1;
		if (a[child] < a[parent])
		{
			swap(&a[child], &a[parent]);
			parent = child;
			child = parent * 2 + 1;
		}
		else 
			break;
	}

}
void heap_sort(int* a, int k)
{
	//建堆
	for (int i = (k - 2) / 2;i >= 0;i--)
	{
		Adjustdown(a, i, k);
	}
	//n-k比较
	for(int i=k;i<8;i++)
	{
		int val = a[i];
		if (val > a[0])
		{
			swap(&val, &a[0]);
			Adjustdown(a, 0, k);
		}
	}
}
int main()
{
	int a[] = { 1,9,8,5,6,4,7,4 };
	int k = 2;
	heap_sort(a, k);
	for (int i = 0;i < k;i++)
	{
		printf("%d ", a[i]);
	}
	return 0;
}

代码稍微一改就行,要求前k小的就建大堆,若比堆顶小,代替进堆

相关推荐
宇希啊2 分钟前
C语言多进程素数计算
linux·c语言
开开心心就好3 分钟前
实用电脑工具,轻松实现定时操作
python·学习·pdf·电脑·word·excel·生活
SuperCandyXu9 分钟前
leetcode0113. 路径总和 II - medium
数据结构·c++·算法·leetcode
可乐拌面19 分钟前
string的模拟实现 (6)
c++·stl
李boyang25 分钟前
C++ IO流
c++·io
罚酒饮得27 分钟前
C++的QT实现接入DeepSeek,实现与DeepSeek对话功能
开发语言·c++·qt·ai·ai编程
硬匠的博客30 分钟前
C++继承与派生
数据结构·算法
小龙在山东30 分钟前
利用 Deepseek 和 Mermaid 画流程图
android·流程图
面包圈蘸可乐34 分钟前
论文学习:《创新编码策略的多类LncRNA亚细胞定位的集成深度学习框架》
人工智能·深度学习·学习
大风起兮云飞扬丶43 分钟前
Android——动画
android