代码改变世界

堆排序

2012-08-03 17:12  coodoing  阅读(880)  评论(1编辑  收藏  举报

1、堆排序定义

    n个关键字序列Kl,K2,…,Kn称为堆,当且仅当该序列满足如下性质(简称为堆性质):
    (1) ki≤K2i且ki≤K2i+1

    (2)Ki≥K2i且ki≥K2i+1(1≤i≤ )
    若将此序列所存储的向量R[1..n]看做是一棵完全二叉树的存储结构,则堆实质上是满足如下性质的完全二叉树:树中任一非叶结点的关键字均不大于(或不小于)其左右孩子(若存在)结点的关键字。

    大顶堆和小顶堆

    根结点(亦称为堆顶)的关键字是堆里所有结点关键字中最小者的堆称为小根堆。
    根结点(亦称为堆顶)的关键字是堆里所有结点关键字中最大者,称为大根堆。
注意:
    ①堆中任一子树亦是堆。
    ②以上讨论的堆实际上是二叉堆(Binary Heap),类似地可定义k叉堆。

2、堆排序动画演示

3、堆排序算法的基本思想

(1)用大顶堆排序的基本思想
① 先将初始文件R[1..n]建成一个大顶堆,此堆为初始的无序区 。
② 再将关键字最大的记录R[1](即堆顶)和无序区的最后一个记录R[n]交换,由此得到新的无序区R[1..n-1]和有序区R[n],且满足R[1..n-1].keys≤R[n].key 。
③ 由于交换后新的根R[1]可能违反堆性质,故应将当前无序区R[1..n-1]调整为堆。然后再次将R[1..n-1]中关键字最大的记录R[1]和该区间的最后一个记录R[n-1]交换,由此得到新的无序区R[1..n-2]和有序区R[n-1..n],且仍满足关系R[1..n-2].keys≤R[n-1..n].keys,同样要将R[1..n-2]调整为堆。
    ……
直到无序区只有一个元素为止。
(2)大顶堆排序算法的基本操作
建初始堆:将R[1..n]构造为初始堆;
堆调整:将当前无序区的堆顶记录R[1]和该区间的最后一个记录交换,然后将新的无序区调整为堆。
注意:
①只需做n-1趟排序,选出较大的n-1个关键字即可以使得文件递增有序。
②用小顶堆排序与利用大顶堆类似,只不过其排序结果是递减有序的。堆排序和直接选择排序相反:在任何时刻,堆排序中无序区总是在有序区之前,且有序区是在原向量的尾部由后往前逐步扩大至整个向量为止。

4、实现代码  

 

//最大堆排序
public class MaxHeapProblem {

    public static void buildHeap(int a[]) {
        int heapSize = a.length;
        int filter = (int) Math.floor(heapSize / 2);
        // i从第一个非叶子结点开始
        for (int i = filter - 1; i >= 0; i--) {
            heapAdjust(a, i, heapSize);
        }
    }

    // 已知H.r[i...heapSize]中记录的关键字除H.r[i]外,均满足最大堆结构
    public static void heapAdjust(int arr[], int i, int heapSize) {
        // 当前待调整的元素
        int tmp = arr[i];
        // 该元素的左孩子
        int index = 2 * i + 1;
        while (index < heapSize) {
            // 如果右孩子大于左孩子,则index+1,即交换右孩子和双亲节点
            if (index + 1 < heapSize && arr[index] < arr[index + 1]) {
                index = index + 1;
            }
            if (arr[i] < arr[index]) {
                // 交换孩子和双亲节点
                arr[i] = arr[index];
                
                // 重新赋初值
                i = index;
                index = 2 * i + 1;
            } 
            // 已经是最大堆
            else {
                break;
            }
            // 把双亲值赋给孩子节点
            arr[i] = tmp;
        }
    }
    
    private void heapAdjust2(int[] arr, int i, int heapSize) {
        int maxIndex = i;
        if (2 * i + 1 <= heapSize - 1 && arr[2 * i + 1] > arr[i])
            maxIndex = 2 * i + 1;
        if (2 * i + 2 <= heapSize - 1 && arr[i * 2 + 2] > arr[maxIndex])
            maxIndex = 2 * i + 2;
        if (maxIndex != i) {
            int temp = arr[maxIndex];
            arr[maxIndex] = arr[i];
            arr[i] = temp;
            heapAdjust2(arr, maxIndex, heapSize);
        }
        
    }

    public static void heapSort(int a[]) {
        int heapSize = a.length;
        for (int i = heapSize - 1; i > 0; i--) {
            // 交换堆顶和最后一个元素
            int tmp = a[0];
            a[0] = a[i];
            a[i] = tmp;
            // 在heapSize范围内根结点的左右子树都已经是最大堆,所以只需看新交换的堆顶元素是否满足最大堆结构即可。
            // 将H.r[0...i]重新调整为最大堆
            heapAdjust(a, 0, i);
        }
    }

    public static void main(String[] args) {
        int arr[] = new int[] { 6, 5, 3, 1, 8, 7, 2, 4 };
        buildHeap(arr);
        System.out.println("初始建立的最大堆是:");
        for (int data : arr)
            System.out.print(data + " ");
        System.out.println();
        System.out.println("堆经过筛选调整后,排序结果为:");
        heapSort(arr);
        for (int data : arr)
            System.out.print(data + " ");
    }

}

 5、复杂度分析

   堆排序的平均时间复杂度和最坏时间复杂度均为:O(nlgn)

  参考资料:

http://www.cnblogs.com/rollenholt/archive/2012/04/15/2450175.html

http://blog.csdn.net/morewindows/article/details/6709644