首先来描述一下堆这种数据结构,我们现在所说的堆一般就是指二叉堆,所以下面我就统一把堆默认为二叉堆。堆是一棵被完全填满的二叉树,设树的高度为h,即从第1层到第h-1层,树的节点都是被填满的,唯一有例外的就是在底层,若果底层是被从左到右被填满的,那这就是一棵完全二叉树。我们也可以用满树的概念来定义完全二叉树:完全二叉树是一棵与满树从左到右一一对应的树。堆排序是不稳定的排序算法,空间复杂度为O(1),时间复杂度为O(n*log2n)。堆分为大根堆和小根堆,大根堆的意思就是其父节点的值总是大于其子节点的值,小根堆同理。由于堆是一个完全二叉树,完全二叉树是非常有规律的,所以我们可以采用一个数组来存储完全二叉树,而不用采用链表来存储。而在数组中存储是一系列的数字,而真正的结构在我们心中,我们给与这个数组几个性质,则此数组就变成了堆。对于堆中的任意节点i,我们可以获得其左孩子的节点为2*i,右孩子的节点为2*i+1,即为左孩子后的那个节点,而其父节点为i/2的下届。这样我们就要先写几个功能函数,一个是取得左孩子节点,一个是取得右孩子节点,一个是取得父节点,还有我们需要一个空穴的位置,这个空穴为最后一个可用节点后的一个位置,所以我们会用到一个交换空间函数。对于取得节点的函数我们要用移位操作,这样比用纯粹的算术运算有更高的效率。至于第三个函数swap因为java里的数都是值传递,所以采用数组的引用传递。
此函数表示从第i个元素开始构建堆,直到从第i个后的左右孩子节点及其子节点后的节点都满足大根堆的定义,循环终止。
然后现在进入到我们堆排序的思想:大顶堆的堆顶元素即为最大,我们每次将堆顶元素和最后一个元素进行交换,这时默认下一次循环的最后一个元素就为当前最后一个元素的上一个元素,然后让除最后一个元素以外的剩下的元素再构成堆,依次类推,这样最后整个数组就是一个已排好序的数组。
因为每次都要交换第一个元素和最后一个元素,为了保持堆的特性,就要重新排堆,而除了根元素意外其他元素都保持了堆的特性,中间控制好确定最后一个元素就行。
public static int getParent(int i) {
return (i - 1) >> 1;
}
public static int GetLeftChild(int i) {
return ((i + 1) <<1) - 1;
}
public static int GetRightChild(int i) {
return (i + 1) <<1;
}
private static void swap(Object[] arr, int i, int j) {
Object tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
}
现在基本的需要的功能函数都已经写好了,我们现在来构建一个函数用来保持堆特性的存在,这个函数的意思就是从第几个节点开始及其子节点都要保持堆的特性,而我写的这个保持的堆的特性的堆是一个大根堆(即堆顶的元素)。
public static<T> void keepheap(T[] a, int i, Comparator <? super T> c, int size) {
int l = getLeftChild(i);
int r = getRightChild(i);
int next = i;
if (l < size && c.compare(a[l], a[i]) > 0)
next = l;
if (r < size && c.compare(a[r], a[next]) > 0)
next = r;
if (i == next)
return;
swap(a, i, next);
keepheap(a, next, c, size);
}
此函数表示从第i个元素开始构建堆,直到从第i个后的左右孩子节点及其子节点后的节点都满足大根堆的定义,循环终止。
下面要写的一个函数为构建堆的函数,初始的时候我们给出任意数组,我们需要首先把这个数组构建成堆,然后再进行元素的筛选。构建堆的过程是一个自下往上的一个过程。从最后一个有叶子节点的父节点开始,我们可以用列举的方法看到最后一个有叶子的节点的父节点为数组的长度(length)/2,即为最后一个有叶子节点的父节点的位置。
public static <T> void buildHeap(T[] a, Comparator <? super T> c) {
for (int i = (a.length + 1) / 2 - 1; i >= 0; i--) {
keepheap(a, i, c,a.length);
}
}
然后现在进入到我们堆排序的思想:大顶堆的堆顶元素即为最大,我们每次将堆顶元素和最后一个元素进行交换,这时默认下一次循环的最后一个元素就为当前最后一个元素的上一个元素,然后让除最后一个元素以外的剩下的元素再构成堆,依次类推,这样最后整个数组就是一个已排好序的数组。
public static <T> void heapSort(T[] a, Comparator <? super T> c) {
buildHeap(a, c);
for (int i = a.length - 1; i > 1; i--) {
swap(a, 0, i);
keepheap(a, 0, c, i - 1);
}
}
因为每次都要交换第一个元素和最后一个元素,为了保持堆的特性,就要重新排堆,而除了根元素意外其他元素都保持了堆的特性,中间控制好确定最后一个元素就行。