堆排序(Heap Sort)

堆排序是一种高效率的排序方法,它充分的利用了二叉堆的性质,无需借助额外的辅助空间,并且拥有O(n*log(n))的时间复杂度。

首先这里我从二叉堆讲起。二叉堆是一种具有一定逻辑关系的完全二叉树(这里需要和满二叉树做一下区分),它始终满足:任意节点的值均大于(或小于)其子节点,满足该条件的二叉堆又叫大根堆(或小根堆)。

再说堆排序,它即是利用了二叉堆的该性质,在每次调整二叉堆结构后取堆顶元素,即该二叉堆内最大(或最小)的元素,取n次后得到的序列即为一个有序序列。

接下来来看一下队的存储结构:

int heap[HEAPLENGTH];
int *anotherHeap = new int[HEAPLENGTH];

由于二叉堆满足“完全二叉树”的性质,因此二叉堆可以很轻松的由一个一维数组来表示,而祖先节点和儿子节点可以由索引很方便的计算出,来看代码:

int getLeftChild(const int &index) {
    if (index < 0) return (0);
    return (2 * index + 1);
}

int getRightChild(const int &index) {
    if (index < 0) return (0);
    return (2 * index + 2);
}

需要注意的是这里计算索引的前提是使用了“0~n - 1”下标的数组,这样可以不造成内存浪费。

通过这两个方法可以快速的得到与传入参数对应的该完全二叉树的左(右)儿子,方便后续的算法操作。

接下来就看一下构建堆的过程,首先笔者总结了一个规律:在一个拥有n个节点的完全二叉树中,最后一个拥有孩子的节点在层次遍历序列中的位置为(n / 2) - 1。因此,为了节省资源,可以从该位置进行递归向下调整,直至整个二叉完全树满足堆的条件。下面给出代码:

void adjustHeap(int *arr, const int &index, const int &n) {
	int child = getLeftChild(index);
	int now = index;
	while (child < n) {
		if (getRightChild(now) < n && arr[child] /**/ < /**/ arr[getRightChild(now)]) {
			child = getRightChild(now);
		}
		if (arr[now] /**/ < /**/ arr[child]) {
			dataSwap(arr[now], arr[child]);
		}
		else break;
		now = child;
		child = getLeftChild(now);
	}
}

当然,你可能注意到该段代码里的getRightChild完全可以被child + 1替代(因为右儿子肯定紧挨着左儿子),这里只是为了易读。

在调整过后,我们可以由堆的性质得知堆顶(最前面那个元素)一定是大根堆(可以通过替换代码中有注释部分的“<”为“>”来将调整过程改为小根堆调整),然后再将第(0)的元素和第(n - 1 - 调整次数)的元素交换,来保证第(n - 1 - 调整次数)的元素到第(n - 1)的元素的序列为有序序列。此时堆顶元素变化,因此可能破坏了堆的性质,则再从堆顶进行递归向下调整,最终经过n - 2次调整,即从第(n - 1 - (n - 2) = 1)的位置到第(n - 1)的位置为有序序列,第0的位置为堆,自然整个序列满足了排序的最终要求,堆排序完成。下面给出堆排序代码:

void dataSwap(int &a, int &b) {
	if (&a == &b || a == b) return;
	a = a + b;
	b = a + b;
	a = b - a;
	b = b - 2 * a;
}

void heapSort(int *arr, const int &n) {
	if (arr == NULL || n <= 0) {
		return;
	}
	for (int i = (n / 2) - 1; i >= 0; i--) {
		adjustHeap(arr, i, n);
	}
	for (int i = n - 1; i > 0; i--) {
		dataSwap(arr[0], arr[i]);
		adjustHeap(arr, 0, i);
	}
}

有一个有趣的函数,即“dataSwap”,本质上是一个交换函数,但是不需要借助中间量。

如有不对敬请指出,感谢阅读!

参考资料
完全二叉树
满二叉树
二叉堆

posted @ 2021-09-19 20:51  Yeehok  阅读(150)  评论(0编辑  收藏  举报