1.堆的概念与结构
如果有⼀个关键码的集合 ,把它的所有元素按完全⼆叉树的顺序存储⽅ 式存储,在⼀个⼀维数组中,并满⾜: ( 且 ), i = 0、1、2... ,则称为⼩堆(或⼤堆)。将根结点最⼤的堆叫做最⼤堆或⼤根堆,根结点最⼩的堆 叫做最⼩堆或⼩根堆。
2.堆的性质
• 堆中某个结点的值总是不⼤于或不⼩于其⽗结点的值;
• 堆总是⼀棵完全⼆叉树。
3.堆的实现
3.1初始化及基础功能
typedef struct heap
{
int* a;
int size;
int capacity;
}HP;
void heapinit(HP* php)
{
assert(php);
php->a = NULL;
php->size = 0;
php->capacity = 0;
} //初始化
void swap(int* p1, int* p2)
{
int t = 0;
t = *p1;
*p1 = *p2;
*p2 = t;
} //交换函数
void heappush(HP* php, int data)
{
if (php->size == php->capacity) //如果内存空间满了
{
int newcapacity = php->capacity == 0 ? 4 : php->capacity * 2; //内存空间和元素数量一样》》》》》1.内存空间为0,令内存大小为4, 2.内存空间满了,扩容至两倍大
int* tmp = (int*)realloc(php->a, newcapacity * sizeof(int)); //开辟内存空间记得加sizeof(sldatatype)
php->a = tmp; //开辟内存空间赋给a指针
php->capacity = newcapacity; //设置新的容量
}
php->a[php->size] = data;
php->size++;
adjustup(php->a, php->size - 1);
}
void heappop(HP* php)
{
swap(&php->a[0],&php->a[php->size - 1]);
php->size--;
adjustdown(php->a,php->size-1,0);//首尾互换!!!!!!,可以保证根节点以下的二叉树均不被改变(即排好序),只需由新的根节点从上往下排序交换一轮
}
bool heapempty(HP* php)
{
return php->size == 0;
} //判断是否为空
int heaptop(HP* php)
{
assert(php);
return php->a[0];
} //取根节点
3.2 向上调整算法
向上调整算法
• 先将元素插⼊到堆的末尾,即最后⼀个孩⼦之后
• 插⼊之后如果堆的性质遭到破坏,将新插⼊结点顺着其双双亲往上调整到合适位置即可
(要求插入前必须是堆)!!!!!!
void adjustup(int* a, int child) //向上调整法 //上方必须是堆
{
int parent = (child-1)/2;
while (child>0)
{
if (a[parent]<=a[child]) //小堆为>=,大堆为<=
{
swap(&a[parent],&a[child]);
child = parent;
parent = (child - 1)/2;
}
else
{
break;
}
}
}
3.3 向下调整算法
向下调整算法
• 将堆顶元素与堆中最后⼀个元素进⾏交换
• 删除堆中最后⼀个元素
• 将堆顶元素向下调整到满⾜堆特性为⽌
!!!!!!向下调整算法有⼀个前提:左右⼦树必须是⼀个堆,才能调整。
void adjustdown(int* a,int n, int parent) //向下调整法,!!!!!!(仅适用于根结点两端都是大堆或小堆)
{
int child = 2 * parent + 1;
while (child<=n) //
{
if (child + 1<=n && a[child + 1] > a[child]) // child+1>n可以推出child==n,所以只有左孩子
{
child++; //选出左右孩子中最大的一个‘>’,最小的为'<'
}
if (a[parent]<=a[child]) //大堆为“<=”,小堆为“>=”
{
swap(&a[parent], &a[child]);
parent = child;
child = 2 * parent + 1; //每次都先找左孩子
}
else
{
break;
}
}
}
4.堆排序
int main()
{
int a[] = { 65,100,70,32,50,35 };
int i;
int n = sizeof(a) / sizeof(a[0]);
int flag = n;
for (i = 1; i < sizeof(a) / sizeof(a[0]); i++) //i从1开始,因为只有一个元素时可以视作堆
{
adjustup(a, i);
} //升序排序前的第一步,建立大堆,i从1开始,因为i=0时可视作堆,从1开始向上调整
//记住,只是建好了堆,堆不代表直接打印出来是有序的!!!!!!!!!还需要调整
while (n>0) //上文所说的调整,因为是大堆,所以root一定是最大的
{
swap(&a[0],&a[n-1]);//将最大的换到tail
n--; //不再关注最大的tail
adjustdown(a,n-1, 0);//排除tail后重新建立大堆,选择从根开始的向下调整最合适(交换后,根的左右两边都是堆,只是根变了),让第二大的变为root,循环
}
for (i = 0; i <= flag - 1; i++)
printf("%d ",a[i]);
return 0;
}
5.实际问题解决 ——topK问题
TOP-K问题:即求数据结合中前K个最⼤的元素或者最⼩的元素,⼀般情况下数据量都⽐较⼤。 ⽐如:专业前10名、世界500强、富豪榜、游戏中前100的活跃玩家等。 对于Top-K问题,能想到的最简单直接的⽅式就是排序,但是:如果数据量⾮常⼤,排序就不太可取了 (可能数据都不能⼀下⼦全部加载到内存中)。最佳的⽅式就是⽤堆来解决,基本思路如下:
1)⽤数据集合中前K个元素来建堆
前k个最⼤的元素,则建⼩堆
前k个最⼩的元素,则建⼤堆
2)⽤剩余的N-K个元素依次与堆顶元素来⽐较,不满⾜则替换堆顶元素
将剩余N-K个元素依次与堆顶元素⽐完之后,堆中剩余的K个元素就是所求的前K个最⼩或者最⼤的元素
void CreateNDate()
{
// 造数据
int n = 100000;
srand(time(0));
const char* file = "data.txt";
FILE* fin = fopen(file, "w");
if (fin == NULL)
{
perror("fopen error");
return;
}
for (int i = 0; i < n; ++i)
{
int x = (rand()+i) % 1000000;
fprintf(fin, "%d\n", x);
}
fclose(fin);
}
void topk()
{
printf("请输⼊k:>");
int k = 0;
scanf("%d", &k);
const char* file = "data.txt";
FILE* fout = fopen(file, "r");
if (fout == NULL)
{
perror("fopen error");
return;
}
int val = 0;
int* minheap = (int*)malloc(sizeof(int) * k);
if (minheap == NULL)
{
perror("malloc error");
return;
}
for (int i = 0; i < k; i++)
{
fscanf(fout, "%d", &minheap[i]);
}
// 建k个数据的⼩堆
for (int i = (k - 1 - 1) / 2; i >= 0; i--)
{
AdjustDown(minheap, k, i);
}
int x = 0;
while (fscanf(fout, "%d", &x) != EOF)
{
// 读取剩余数据,⽐堆顶的值⼤,就替换他进堆
if (x > minheap[0])
{
minheap[0] = x;
AdjustDown(minheap, k, 0);
}
}
for (int i = 0; i < k; i++)
{
printf("%d ", minheap[i]);
}
fclose(fout);
}