【数据结构】——堆及其应用
一、堆
先说说堆概念:如果有一个关键码的集合K = {k0,k1, k2,…,kn-1},把它的所有元素按完全二叉树的顺序存储方式存储在一个一维数组中,并满足:Ki <= K2i+1 且 Ki<= K2i+2 (Ki >=K2i+1 且 Ki >= K2i+2) i =0,1,2…,则称为小堆(或大堆)。
小堆(大堆)中:任一结点的关键码均小于(大于)等于它的左右孩子的关键码,位于堆顶结点的关键码最小(最大),从根节点到每个结点的路径上数组元素组成的序列都是递增(递减)的堆存储在下标为0开始的数组中,因此在堆中给定下标为i的结点时:如果i=0,结点i是根节点,没有双亲节点;否则结点i的双亲结点为结点(i-1)/2如果2 * i + 1 <= n - 1,则结点i的左孩子为结点2 * i + 1,否则结点i无左孩子如果2 * i + 2 <= n - 1,则结点i的右孩子为结点2 * i + 2,否则结点i
①大小堆的构建
将二叉树调整为最小堆的原理:
从最后一个非叶子结点开始调整,一直到根节点为止,将每个结点及其子树调整到满足小堆的性质即可。
代码如下:
void AdjustDown(DataType* a, size_t n, int root) //向下调整 { int parent = root; int child = parent*2 + 1; while (child<(int)n) { if(a[child]>a[child+1] && child+1 <(int)n) ++child; if (a[child]<a[parent]) Swap(&a[child],&a[parent]); else break; parent = child; child = parent*2 + 1; } } void MakeSmallHeap(DataType* a, size_t n) //构建小堆 { int i = (n-2)>>1; for (; i >= 0; --i) { AdjustDown(a,n,i); } }
大堆与小堆原理相同,代码相似,此处不再赘述。
②堆的插入和删除
插入
其实在一个堆中是可以在任意位置插入和删除结点的,为了高效起见我们在插入一个结点时我们将该结点尾插到存储堆结构的顺序表中,如果我们插入的结点比原来的大堆中的所有数据都大的话我们就破坏了原来的大顶堆的结构了,此时我们就需要调整新堆的,在这里用的是向上调整的算法.
插入数据的时间复杂度为O(lgn).
向上调整代码:
void AdjustUp(DataType* a,int child) //向上调整 { int parent = (child-1)>>1; while (child >0) { if (a[parent] > a[child] && parent >= 0) Swap(&a[child],&a[parent]); else break; child = parent; parent = (child-1)>>1; } }
删除
1).将最后一个结点的数据域与堆顶的元素交换.
2).删除最后一个结点,此时删除的就是原来的堆顶元素
3).向下调整删除之后的堆,使其继续满足大顶堆的定义.
删除数据的时间复杂度为O(lgn).
插入和删除的算法会在堆的应用中写道,此处不再赘述。
堆的应用
①优先级队列
我们知道队列的特性是先进先出,那什么是优先级队列呢?在某一情况下队列的先进先出并不能满足我们的需求,我们需要优先级高的先出队列,这就类似VIP之类的.
下面给出实现优先级队列的两种思路:
想法一:
Push:在需求的优先级的位置插入数据,时间复杂度为O(n).
Pop:直接从队头删除数据,时间复杂度为O(1).
想法二:
Push:直接插在队尾,时间复杂度为O(1).
Pop:找到优先级最高的元素删除,时间复杂度为O(n).
在实际应用中第一种想法是优于第二种想法的,但是其实还有一种更加高效的方法,那就是用堆实现优先级队列
函数代码:
void PriorityQueuePush(PriorityQueue* q, DataType x) { assert(q); if (q->_size == N) return; q->_a[q->_size] = x; q->_size++; AdjustUp(q->_a,q->_size-1); } void PriorityQueuePop(PriorityQueue* q) { assert(q); if (q->_size == 0) return; q->_a[0] = q->_a[q->_size-1]; q->_size--; AdjustDown(q->_a,q->_size,0); } DataType PriorityQueueTop(PriorityQueue* q) { if (PriorityQueueEmpty(q)) return q->_a[0]; } size_t PriorityQueueSize(PriorityQueue* q) { assert(q); return q->_size; } size_t PriorityQueueEmpty(PriorityQueue* q) { assert(q); if (q->_size > 0) return 1; else return 0; }
头文件和测试代码在结尾给出。
②topk问题(构建相反堆找出前k个数) 在大规模数据处理中,经常会遇到的一类问题:在海量数据中找出出现频率最好的前k个数,或者从海量数据中找出最大的前k个数,这类问题通常被称为top K问题。例如,在搜索引擎中,统计搜索最热门的10个查询词;在歌曲库中统计下载最高的前10首歌等。
维护一个K个数据的小顶堆,遍历元素,若元素大于堆顶元素,则将堆顶元素移除,当前元素插入堆顶,并进行调整。
代码实现
void TopK(DataType* a, size_t n, size_t k) //topk问题 { size_t i = k; MakeSmallHeap(a,k); //构建小堆 for (i=k; i<n; i++) //遍历剩下的数 { if (a[i]>a[0]) { a[0] = a[i]; AdjustDown(a,k,0);//向下调整 } } for (i=0; i<k; i++) { printf("%d ",a[i]); } printf("\n"); }
头文件和测试代码在结尾给出。
③堆排序(升序 — 构建大堆 降序 — 构建小堆)
堆排序:先建立一个最大堆。然后将最大堆的a[0]与a[n]交换,然后从堆中去掉这个节点n,通过减少n的值来实现。剩余的节点中,新的根节点可能违背了最大堆的性质,因此需要调用向下调整函数来维护最大堆。
函数代码:
void HeapSort(DataType* a, size_t n) //堆排序 { MakeBigHeap(a,n); //构建大堆 while (n>0) { Swap(&a[0],&a[n-1]); n--; AdjustDown(a,n,0); } }
头文件和测试代码在结尾给出。
Head.h
#ifndef __HEAD_H__ #define __HEAD_H__ #include <stdio.h> #include <stdlib.h> #include <assert.h> #include <time.h> #include<string.h> typedef int DataType; //构建大小堆 void AdjustDown(DataType* a, size_t n, int root); void MakeBigHeap(DataType* a, size_t n); void MakeSmallHeap(DataType* a, size_t n); void AdjustUp(DataType* a,int child); // topk 最大的前K void TopK(DataType* a, size_t n, size_t k); //优先级队列问题 #define N 1000 typedef struct PriorityQueue { DataType _a[N]; size_t _size; }PriorityQueue; void PriorityQueueInit(PriorityQueue* q); //初始化 void PriorityQueuePush(PriorityQueue* q, DataType x); //入队 void PriorityQueuePop(PriorityQueue* q); //出队 DataType PriorityQueueTop(PriorityQueue* q); size_t PriorityQueueSize(PriorityQueue* q); size_t PriorityQueueEmpty(PriorityQueue* q); void HeapSort(DataType* a, size_t n); //堆排序 #endif //__HEAD_H__
Head.c
#include "Heap.h" static void Swap(int *child,int *parent) //交换函数 { int tmp = *child; *child = *parent; *parent = tmp; } void AdjustDown(DataType* a, size_t n, int root) //向下调整 { int parent = root; int child = parent*2 + 1; while (child<(int)n) { if(a[child]<a[child+1] && child+1 <(int)n) ++child; if (a[child]>a[parent]) Swap(&a[child],&a[parent]); else break; parent = child; child = parent*2 + 1; } } void MakeBigHeap(DataType* a, size_t n) //构建大堆 { int i = (n-2)>>1; for (; i >= 0; --i) { AdjustDown(a,n,i); } } void MakeSmallHeap(DataType* a, size_t n) //构建小堆 { int i = (n-2)>>1; for (; i >= 0; --i) { AdjustDown(a,n,i); } } void AdjustUp(DataType* a,int child) //向上调整 { int parent = (child-1)>>1; while (child >0) { if (a[parent] > a[child] && parent >= 0) Swap(&a[child],&a[parent]); else break; child = parent; parent = (child-1)>>1; } } void TopK(DataType* a, size_t n, size_t k) //topk问题 { size_t i = k; MakeSmallHeap(a,k); for (i=k; i<n; i++) { if (a[i]>a[0]) { a[0] = a[i]; AdjustDown(a,k,0); } } for (i=0; i<k; i++) { printf("%d ",a[i]); } printf("\n"); } void PriorityQueueInit(PriorityQueue* q) { assert(q); memset(q->_a,0,sizeof(DataType)*N); q->_size = 0; } void PriorityQueuePush(PriorityQueue* q, DataType x) { assert(q); if (q->_size == N) return; q->_a[q->_size] = x; q->_size++; AdjustUp(q->_a,q->_size-1); } void PriorityQueuePop(PriorityQueue* q) { assert(q); if (q->_size == 0) return; q->_a[0] = q->_a[q->_size-1]; q->_size--; AdjustDown(q->_a,q->_size,0); } DataType PriorityQueueTop(PriorityQueue* q) { if (PriorityQueueEmpty(q)) return q->_a[0]; } size_t PriorityQueueSize(PriorityQueue* q) { assert(q); return q->_size; } size_t PriorityQueueEmpty(PriorityQueue* q) { assert(q); if (q->_size > 0) return 1; else return 0; } void HeapSort(DataType* a, size_t n) //堆排序 { MakeBigHeap(a,n); while (n>0) { Swap(&a[0],&a[n-1]); n--; AdjustDown(a,n,0); } }
Test.c
#include "Heap.h" void Test1() { int i = 0; DataType a[] = {16, 18, 15, 17, 14, 19,10,11, 13, 12}; MakeSmallHeap(a, sizeof(a)/sizeof(DataType)); MakeBigHeap(a, sizeof(a)/sizeof(DataType)); DataType NArray[1000]; srand((int)time(0)); for (i = 0; i < 1000; ++i) { NArray[i] = rand()%10000; } NArray[30] = 10001; NArray[350] = 10002; NArray[999] = 10003; NArray[158] = 10004; NArray[334] = 10005; TopK(NArray, 1000, 5); HeapSort(a,sizeof(a)/sizeof(DataType)); } void TestPriorityQueue() { PriorityQueue q; PriorityQueueInit(&q); PriorityQueuePush(&q, 5); PriorityQueuePush(&q, 2); PriorityQueuePush(&q, 3); PriorityQueuePush(&q, 7); PriorityQueuePush(&q, 6); PriorityQueuePush(&q, 1); PriorityQueuePush(&q, 4); while (PriorityQueueEmpty(&q) != 0) { printf("%d ", PriorityQueueTop(&q)); PriorityQueuePop(&q); } printf("\n"); } int main() { Test1(); TestPriorityQueue(); return 0; }
topk问题测试时要巧妙构建测试案例。