堆
16 | 14 | 10 | 8 | 7 | 9 | 3 | 2 | 4 | 1 |
这样看是不是就是一个数组?是的,(二叉)堆就是一个数组,可以被看作近似的
完全二叉树。
对于表示堆的数组array[0…n-1],我们以array[0]为根,给定某个节点下标 i,其父节点和左右后代节点的下标分别为:
parent(i) = (i-1)/2;
left(i) = 2*i+1;
right(i) = 2*i+2;
实际操作是可以根据您的设置而改变。所以看作完全二叉树:
根据它的特点,堆分为最大堆和最小堆。最大堆就是:除根节点以外的每个节点 i,都有arr[ parent(i) ] >= arr[i]。
最小堆的特点则是:除根节点以外的每个节点i,都有arr[ parent(i) ] <= arr[i]。在做排序时,一般使用最大堆。
故而这个排序大致可分为以下的步骤:
一、维护最大堆性质
此时,我们假定left(i), right(i)为根节点的二叉树均满足条件,可是i对应的值可能小于其孩子啊,所以就让i对应的这个值逐级下降,从而满足最大堆的条件。
所以需要做的是:
从元素arr[i], arr[left(i)], arr[right(i)]中找出最大的元素,将下标存在largest中;如果arr[i]是最大的,说明以节点i为根的二叉树是最大堆,无须调整;否则,交换arr[i]和arr[largest],于是arr[i], arr[left(i)], arr[right(i)]三者满足了最大堆的性质,但是交换后,下标为largest的节点存放arr[i]的值,以该节点为根的子树又可能违反最大堆的性质,因此需要对该子树递归调用本调整过程。
参考代码:
private static void MAX_heapify(int[] a, int i, int size) {//维护最大堆性质 -- 前提是i的左右子树均是最大堆
//parent(i) = (i - 1) / 2
int left_child = 2 * i + 1;
int right_child = 2 * (i + 1);
int largest;
if(left_child < size && a[left_child] > a[i]){
largest = left_child;
}
else{
largest = i;
}
if(right_child < size && a[right_child] > a[largest]){
largest = right_child;
}
if(largest != i){
int temp = a[i];
a[i] = a[largest];
a[largest] = temp;
MAX_heapify(a, largest, size);
}
二、建最大堆
利用(一)建堆。
对于每一片树叶,我们都可以看作是一个只含一个元素的堆。于是对于叶子结点的父亲结点(左右子树都是最大堆),我们可以调用maxHeapify()来进行调整。调整之后,我们得到更大的堆,对于这些堆的父节点,我们又可以调用maxHeapify()来进行调整。
所以保证前提的情况下,只需要从他的最下面的非叶子节点开始,一直到根节点结束即可。(
叶子或终端结点:度为0的结点。
度:结点拥有的子树数称为结点的度。)
参考代码:
private static void Build_MAXheap(int[] a, int size) {
//当数组表示存储n个元素的堆时,叶子结点的下标:n/2, n/2+1...n-1(n/2表示向下取整)
int i;
for(i = size / 2 - 1; i >=0 ; i --){
MAX_heapify(a, i, size);
}
}
三、堆排序算法
引入另一个变量:heap_size,它用来表示堆的大小,而用size来表示数组的大小。
根据最大堆的性质,知道第一个值最大,所以将他和最后一个值进行位置交换,移除出它,但是需要保证剩余的部分仍然满足最大堆的性质,故而调用(一)。重复过程。
private static void Heap_sort(int[] a, int size) {
if(a == null || size <= 0){
return;
}
int heap_size = size;
Build_MAXheap(a, heap_size);//建堆
for(int i= size - 1; i >= 1; i --){
int temp = a[0];
a[0] = a[i];
a[i] = temp;
heap_size --;//将最大的值给数组 的第一个数,然后将他移除 堆的长度减 1
MAX_heapify(a, 0, heap_size);
//System.out.println(Arrays.toString(a)); 仍然使用原数组 来装数字, 相当于把最大的数字调到了数组的最后一位,在返回做堆里的活动
}
}
测试代码和结果:
public static void main(String[] args) {
int[] A={1, 3, 2, 5, 7, 6, 9, 4, 10};
int size = A.length;
Heap_sort(A, size);
System.out.println(Arrays.toString(A));
}
堆排序的时间复杂度为O(n logn),堆排序具有空间原址性:任何时候都只需常数个额外的元素空间来存储临时数据。
引入堆这种数据结构进行数据管理,不仅可应用于堆排序中,还可用于一种有效的优先队列(参看:点击打开链接)。
参考:
《算法导论》
以上就是这篇文章的内容,如果存在错误或者有什么可以改进的地方,请您指出,谢谢!