【数据结构】堆

什么是堆?

按照完全二叉树的层序遍历顺序存储的元素集合,当每一个根结点大于左右孩子结点时为大堆,小于左右孩子结点时为小堆

小堆:
在这里插入图片描述
大堆:
在这里插入图片描述
以下举例以大根堆为例

堆的向下调整

调整前提:逻辑上表示的完全二叉树,其左右子树已经满足堆的性质

  • 先判断需要调整的结点有无左右孩子,由于是完全二叉树,如果不存在左孩子,右孩子一定不存在。假设需要调整的结点存放在数组中的下标为root,则可以根据二叉树的特点计算出左右孩子存储在数组中的下标

    • 左孩子下标:root * 2 + 1
    • 右孩子下标:root * 2 + 2
  • 左右孩子是否存在的问题可以转换为,左右孩子的数组下标是否越界,一旦越界表明孩子不存在

  • 如果左右孩子都不存在,说明为叶子结点,调整结束;当左右孩子都存在时,找出左右孩子里面的较大者与根进行比较,若较大者比根大,进行数组元素交换;若只有左孩子,将左孩子与根比较,若比根大做交换

void Swap(int *a, int *b)
{
	int tmp = *a;
	*a = *b;
	*b = tmp;
}
void AdjuestDown(int array[], int size, int root)
{
	int left, right, max, i;
	while (1)
	{
		left = root * 2 + 1;
		right = root * 2 + 2;

		if (left >= size)
		{
			break;
		}
		if (right < size && array[right] > array[left])
		{
			max = right;
		}
		else
		{
			max = left;
		}

		if (array[root] < array[max])
		{
			Swap(array + root, array + max);
			root = max;
		}
		else
		{
			break;
		}
	}
}

堆的向上调整

根据该结点找到该结点的双亲,假设该结点下标为child,则其双亲结点下标为(child - 1) / 2,比较二者大小,若该结点大交换两者元素,若该结点满足大堆的性质,调整结束;交换过后则继续比较交换后的结点与其双亲,直到根结点调整结束

void AdjustUp(int array[], int child)
{
	int parent = 0;
	while (child >= 0)
	{
		parent = (child - 1) / 2;
		if (array[parent] < array[child])
		{
			Swap(array+parent, array+child);
		}
		else
		{
			break;
		}
		child = parent;

	}
}

堆的创建

借助向下调整,为了满足向下调整的前提条件,从最后一个非叶子节点开始向下调整,一直调整到根结点结束
而最后一个非叶子结点的下标可以通过数组元素个数计算的到,size-1为最后一个元素的下标,根据左/右孩子找到他的双亲即为最后一个非叶子结点,下标为:(size - 2)/ 2

void BuildHeap(Heap *h)
{
	int i = 0;
	int start = (h->size - 2) / 2;
	for (i=start; i>=0; i--)
	{
		AdjuestDown(h->array, h->size, i);
	}
}

堆的插入

将需要插入的元素插入到堆的尾部,即数组的最后一个元素后面,然后对该元素做一次向上调整即可

void PushBack(Heap *h, int d)
{
    assert(h);
    h->array[h->size] = d;
    h->size++;
}

void HeapPush(Heap *h, int data)
{
	PushBack(h, data);
	AdjustUp(h->array, h->size - 1);
}

弹出堆顶元素

让堆尾的元素覆盖掉堆顶的元素,堆的个数减一后,将堆顶的元素做一次向下调整即可

void HeapPop(Heap *h)
{
	h->array[0] = h->array[h->size - 1];
	h->size--;
	AdjuestDown(h->array, h->size, 0);
}

堆的应用-海量数据TopK问题

首先将海量数据的前K个建立成一个小堆,然后依次从K+1个开始用剩下的数据与创建的堆顶元素作比较,如果比堆顶元素大,就直接替换掉堆顶元素,然后堆顶元素做一次向下调整,循环比较剩下的所有元素后,堆中所保存的元素即为海量数据索要查找的TopK

void AdjuestDownSmall(int array[], int size, int root)
{
	int left, right, min, i;
	while (1)
	{
		left = root * 2 + 1;
		right = root * 2 + 2;

		if (left >= size)
		{
			break;
		}
		if (right < size && array[right] < array[left])
		{
			min = right;
		}
		else
		{
			min = left;
		}

		if (array[root] > array[min])
		{
			Swap(array + root, array + min);
			root = min;
		}
		else
		{
			break;
		}
	}
}
void BuildHeapSmall(Heap *h)
{
	int i = 0;
	int start = (h->size - 2) / 2;
	for (i=start; i>=0; i--)
	{
		AdjuestDownSmall(h->array, h->size, i);
	}
}
void TopK(int array[], int k, int size)
{
	int i = 0;
	Heap heap;
	HeapInit(&heap);

	for (i=0; i<k; i++)
	{
		PushBack(&heap, array[i]);
	}
	BuildHeapSmall(&heap);
	for (i=k; i<size; i++)
	{
		if (array[i] > HeapTop(&heap))
		{
			heap.array[0] = array[i];
			AdjuestDownSmall(heap.array, heap.size, 0);
		}
	}
	for (i=0; i<k; i++)
	{
		printf("%d ", heap.array[i]);
	}
	printf("\n");
}

在普通数组多次找最值,时间复杂度一直都保持在O(n),但是在堆里找最值,第一次只需弹出堆顶元素即可,其后将剩余元素继续调整成堆,仅需要做一次向下调整即可,时间复杂度为O(logn),这也就是堆真正存在的原因

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值