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

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







 

 

  • 16
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 8
    评论
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值