一头扎进算法导论-堆排序

定义:堆排序(Heapsort)是指利用堆积树(堆)这种数据结构所设计的一种排序算法,它是选择排序的一种。可以利用数组的特点快速定位指定索引的元素。堆分为大根堆和小根堆,是完全二叉树。大根堆的要求是每个节点的值都不大于其父节点的值,即A[PARENT[i]] >= A[i]。在数组的非降序排序中,需要使用的就是大根堆,因为根据大根堆的要求可知,最大的值一定在堆顶。

过程:
一头扎进算法导论-堆排序_第1张图片

用自己的话说:
分成两个步骤:
1.构造最大堆(最小堆),即把原来数组的值假想放到一个二叉堆里面去,每次放置值的时都要和自己结点的父亲进行比较,把最大(最小)的值往上排,最后构造成一个最大(最小堆)。特别要注意的一点是在二叉堆中,假设当前结点指针为i,那么父节点的指针计算公式为i/2,但是要注意一点是数组是从0开始计算的,那么就会导致当指针为2的时候计算不正确,所以一定要认识这一个点,下面是我根据概念实现的一段代码。在这段代码里面我为了分辨坐标为0的问题,使用了一个临时数组进行最大堆(最小堆的构建)

public static int[] buildMaxHeap(int[] a) {
        int[] temp = new int[a.length + 1];// 新建一个临时数组
        for (int i = 0; i < a.length; i++) {// 对每一个数进行判断
            int tid = i + 1;// 当前数对应临时数组的id
            int value = a[i];// 当前数值
            int pid = tid / 2;// 父结点在临时数组中的值
            while (temp[pid] < value && pid > 0) {// 父结点小于子结点,父结点下滑,且父结点比较大于0
                temp[tid] = temp[pid];// 父结点下滑
                tid = pid;// 当前插入位置改为原来的父节点位置
                pid = pid / 2;// 查现在父结点的父结点
            }
            temp[tid] = value;
        }
        return temp;
    }

按照上述代码可以更快理解最大堆(小堆)的构建方法,不过这里面产生出了一个临时数组,会导致了性能问题,那么接下来我将对它进行优化

public static int[] goodBuildMaxHeap(int[] a){
        for(int i=0;iint value = a[i];//当前值
            int pid = (i-1)/2;//求父节点指针,原公式:(i+1)/2-1
            while(a[pid]<value&&pid>-1){//注意这里的父id是可以等于0的
                a[i] = a[pid];//父节点下滑
                i=pid;//当前插入结点更改为父节点
                if(i==0)//插入值为0那么就不用判断了,要加这一句,否则-1/2java会提升为0,造成死循环
                    break;
                pid=(i-1)/2;//再求上一个父结点
            }
            a[i]=value;//插入值
        }
        return a;
    }

2.进行对最大堆(小堆)排序,是这样的,因为构建最大堆(小堆)完了之后呢,最大的值会在堆顶,也就是数组的第一个元素,那么把他摘除了之后,与数组的尾部的元素进行交换位置,然后记录当前排序个数+1,最后让堆重新进行一次排序,如此循环,就能把排序好的数组整理出来。交换位置完了之后的排序是这样的,每一次把第一个元素与自己的最大子结点数交换位置,直到把最大的数冒出去。同样的要注意java的数组指针问题,计算公式是这样的,左结点l = i*2,右结点r=i*2+1,首先为了方便理解,我假设传进来的数组坐标是从1开始的,a[0]=0

// 清除排序
    public static int[] percDown(int[] maxa) {
        int l = 0;// 当前排序个数
        while (true) {
            // 把最大的摘除
            int c = 1;
            int li = maxa.length - 1 - l;// 最后一个数坐标
            int ti = maxa[li];// 最后一个数
            if (ti < maxa[c]) {//如果最后一个数小于第一个数,那么就交换
                maxa[li] = maxa[c];// 最后一个数等于最大数
                maxa[c] = ti;// 最大数等于最小数
            }
            l++;// 成功排序一个
            li--;// 最后一个数左边右移
            int lid = 1 * 2;// 左结点id
            int rid = 1 * 2 + 1;// 右结点id
            System.out.println("swap before:" + Arrays.toString(maxa));
            System.out.println("sort:" + l);
            while (lid <= li || rid <= li) {// 存在子结点,子节点等于最后一个结点位置
                int rv = rid <= li ? maxa[rid] : 0;// 右结点的值
                int lv = lid <= li ? maxa[lid] : 0;// 左结点的值
                if (rv > lv) {// 右边大,往右边下移
                    int temp = maxa[c];
                    maxa[c] = maxa[rid];
                    maxa[rid] = temp;
                    c = rid;
                    rid = rid * 2 + 1;
                    lid = rid * 2;
                } else {// 左边大,往左边下移
                    int temp = maxa[c];
                    maxa[c] = maxa[lid];
                    maxa[lid] = temp;
                    c = lid;
                    rid = lid * 2 + 1;
                    lid = lid * 2;
                }
            }
            System.out.println("swap end:" + Arrays.toString(maxa));
            if (li == 1)
                break;
        }
        return maxa;
    }

通过上面代码是我按照对堆排序的思想写出来的,比较方便理解,但是传入的数组毕竟不是从1开始的,所以,同一个道理,对方法进行优化。

// 清除排序
    public static int[] goodPercDown(int a[]) {
        int l = 0;// 当前排序个数
        for (int i = 0; i < a.length; i++) {// 对每个数进行判断
            int fid = 0;// 第一个数的指针
            int eid = a.length - 1 - l;// 最后一个数的指针
            int fv = a[0];// 第一个数的值
            int ev = a[eid];// 最后一个数的值
            // 如果第一个数比最后一个数大那么置换
            if (fv > ev) {
                int t = ev;
                a[eid] = a[fid];
                a[fid] = t;
            }
            l++;// 排序个数+1
            eid--;// 指针-1
            int rid = 2 * fid + 2;// 原公式 (fid+1)*2+1-1
            int lid = 2 * fid + 1;// 原公式 (fid+1)*2-1
            System.out.println("swap before:" + Arrays.toString(a));
            System.out.println("sort:" + l);
            while (rid <= eid || lid <= eid) {// 只要子节点指针不要越界就行
                    int rv = rid<=eid?a[rid]:0;
                    int lv = lid<=eid?a[lid]:0;
                    if(rv>lv){
                        int t = a[fid];
                        a[fid] = a[rid];
                        a[rid] = t;
                        fid = rid;
                        rid = 2 * fid +2;
                        lid = 2 * fid +1;
                    }else{
                        int t = a[fid];
                        a[fid] = a[lid];
                        a[lid] = t;
                        fid = lid;
                        rid = 2 * fid +2;
                        lid = 2 * fid +1;
                    }
            }
            System.out.println("swap end:" + Arrays.toString(a));
        }
        return a;
    }

以上实现均是根据图和算法思想自己写的,能达到排序目的,看书里的代码实在太难懂了,还不如根据自己的理解撸一套出来,这样还更不错,有什么不对希望有大神看到可以指点迷津
复杂度同并归排序
一头扎进算法导论-堆排序_第2张图片

你可能感兴趣的:(数据结构与算法)