堆以及堆排序
堆的概念
堆是用数组实现的二叉树,它没有使用父指针或者子指针。利用数组的下标,就可以得到节点的父节点与子节点在数组中的位置。下标为i的节点,在不越界的情况下,它的左孩子坐标为2i+1,右孩子坐标为2i+2,父节点为(i-1)/2。
堆分为两种,大根堆与小根堆。在大根堆中,父节点的值比每一个子节点的值都要大,任何一棵子树的最大值都是这棵子树的头部。在小根堆中,父节点的值比每一个父节点的值都要大,任何一棵子树的最小值都是这棵子树的头部。
堆的操作
-
插入过程:
以大根堆为例,在已有的堆中插入一个新的元素,需要经过如下的调整:将插入的节点与父节点的值进行比对,如果父节点的值小于插入值,两者进行交换,直到新插入的值小于它的父节点的值。
void heapinsert(vector<int> &arr, int index) { while (arr[index] > arr[(index - 1) / 2]) //与父节点的值进行对比 { swap(arr[index], arr[(index - 1) / 2]); index = (index - 1) / 2; } }
-
调整过程:
在堆中某个元素发生改变,使数组不再形成一个大根堆,需要将该节点向下调整,与子节点中的最大值进行交换。
void heapify(vector<int> &arr, int index, int heapsize) //heapsize为堆的大小 { int left = index * 2 + 1; while (left < heapsize) { int largest; if (left + 1 < heapsize) largest = arr[left] > arr[left + 1] ? left : left + 1; else largest = left; largest = arr[index] > arr[largest] ? index : largest; //节点与其子节点中的最大值 if (largest == index) //最大值为该节点,不调整 break; swap(arr[index], arr[largest]); index = largest; left = index * 2 + 1; } }
-
堆的弹出:
与栈一样,堆也有弹出操作,具体的做法如下:
- 将堆顶元素与堆中的最后一个元素进行交换。
- 对堆顶位置进行heapify操作。
- 堆的大小减少1。
堆排序
堆排序是利用堆这种数据结构而设计的一种排序算法,它的时间复杂度为O(NlogN)。它的算法步骤如下:
- 利用heapinsert操作将一个无序数组调整为一个大根堆。
- 将堆顶元素与末尾元素进行交换,使末尾元素最大,heapsize减1。利用heapify对堆进行调整,使其重新满足堆的定义。然后继续调整堆,再将堆顶元素与末尾元素交换,不断进行重建。
void heapsort(vector<int> &arr)
{
if (arr.size() < 2)
return;
for (int i = 0; i < arr.size(); i++) //构建大根堆
heapinsert(arr, i);
int size = arr.size();
while (size > 0) //将最大值调整到末尾
{
swap(arr[0], arr[--size]);
heapify(arr, 0, size);
}
}