重生之“我打数据结构,真的假的?”--5.堆(无习题)

avatar
作者
猴君
阅读量:0

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); }        

 

 

    广告一刻

    为您即时展示最新活动产品广告消息,让您随时掌握产品活动新动态!