排序算法笔记 - 高级排序

1. 希尔排序

希尔排序是插入排序的一种, 是插入排序的一种更高效的排序算法.

1.1 排序原理

1. 选定一个增长量h, 按照增长量h作为数据分组的依据, 对数据进行分组
2. 对分好组的每一组数据完成插入排序
3. 减小增长量, 最小减为1, 重复第二步的操作

 增长量h的确定: 增长量h的值没有固定的规则, 这里采用如下规则:

int h = 1;
while(h < N){ // 此处N为数组的长度
    h = 2 * h + 1;
}
// 循环结束后, 就可以确定h的最大值
// h的减小规则为:
    h = h / 2;

1.2 API设计

类名Shell
构造方法Shell(): 创建Shell对象
成员方法

1. public static void sort(Comparable[] a): 对数组内的元素进行排序
2. private static greater(Comparable v, Comparable w): 判断元素v是否大于w

3. private static exch(Comparable[] a, int i, int j): 交换数组a中索引i和j处的值

1.3 代码实现

package sort;

public class Shell {
    public static void sort(Comparable<Integer>[] a) {
        // 1. 根据数组a的长度, 确定增长量h的初始值
        int h = 1;
        while (h < a.length / 2) {
            h = 2 * h + 1;
        }

        // 2. 希尔排序
        while (h >= 1) {
            // 排序
            // 1. 找到待插入的元素
            for (int i = h; i < a.length; i++) {
                // 2. 把待插入的元素插入到有序数列中
                for (int j = i; j >= h; j -= h) {
                    // 待插入的元素是a[j], 比较a[j]和a[j-h]
                    if (greater(a[j - h], a[j])) {
                        exch(a, j - h, j);
                    } else {
                        break;
                    }
                }
            }

            // 减小h的值
            h = h / 2;
        }
    }

    private static boolean greater(Comparable<Integer> v, Comparable<Integer> w) {
        // 比较元素v是否大于元素w
        return v.compareTo((Integer)w) > 0;
    }

    private static void exch(Comparable<Integer>[] a, int i, int j) {
        //交换数组a中索引i, j处的值
        Comparable<Integer> temp;
        temp = a[i];
        a[i] = a[j];
        a[j] = temp;
    }
}

指针i永远指向下一个待排序的元素, 但由于在同一个队列中, 同时会有2两个或以上的组在进行插入排序, 因此i指针指向的下一个待插入元素总是在不同组之间交替进行的. 而j指针初始时会指向一个组之中的待插入元素, 其后和插入排序一样, 会跟随待插入的元素直至找到合适的位置.

1.4 时间复杂度分析

 希尔排序中的增长量h没有固定的规则, 也超出了本文的讨论范围. 但是, 仍可以使用事后分析法对希尔排序和插入排序做性能的比较. 一般来说, 希尔排序的性能是要优于插入排序的.

2. 归并排序

归并排序体现的是分而治之(Divide and Conquer)的思想

2.1 排序原理

1. 尽可能地将一组数据拆分成两个元素相等的子组, 并对每一个子组继续进行拆分, 直到拆分后的每个子组的元素个数是1为止.
2. 将相邻的两个子组进行合并, 合并的同时进行排序, 成为一个有序的大组
3. 不断重复步骤2, 直到最终只有一个组为止.

 2.2 API设计

类名Merge
构造方法Merge(): 创建Merge对象
成员方法

1. public static void sort(Comparable[] a): 对数组a内的元素进行排序
2. private static sort(Comparabale[] a, int lo, int hi): 对数组a中, 从索引lo到hi之间的元素进行排序

3. private static void merge(Comparable[] a, int lo, int mid, int hi): 从索引lo到mid为一个子组, 从索引mid+1到hi为另一个子组, 把数组a中的这两个子组的数据合并并排序, 成为一个有序的大组(从索引lo到hi)

4. private static boolean less(Comparable v, Comparable w): 判断元素v是否小于元素w

5. private static void exch(Comparable[] a, int i, int j): 交换数组a中, 索引i和j处的值

成员变量1. private static Comparable[] assist: 完成归并操作需要的辅助数组

归并原理:

 2.3 代码实现

package sort;

public class Merge {
    // 归并所需要的辅助数组
    private static Comparable<Integer>[] assist;

    public static void sort(Comparable<Integer>[] a){
        // 初始化辅助数组
        assist = new Comparable[a.length];
        // 定义lo和hi变量, 分别记录数组中最小的索引和最大的索引
        int lo = 0;
        int hi = a.length - 1;
        // 调用sort重载方法完成数组a中, 从索引lo到hi处的元素的排序
        sort(a, lo, hi);
    }

    private static void sort(Comparable<Integer>[] a, int lo, int hi){
        // 对数组a中从lo到hi的元素进行排序
        // 安全性校验
        if(hi <= lo){
            return;
        }

        // 将lo到hi之间的数据分为两个组
        int mid = lo + (hi - lo) / 2;

        // 分别对每一组数据进行排序
        sort(a, lo, mid);
        sort(a, mid + 1, hi);

        // 把两个数组中的数据进行归并
        merge(a, lo, mid, hi);
    }

    private static boolean less(Comparable<Integer> v, Comparable<Integer> w){
        // 比较元素v是否比元素w小
        return v.compareTo((Integer)w)<0;
    }

    private static void merge(Comparable<Integer>[] a, int lo ,int mid, int hi){
        // 对数组a中, 从lo到mid为一组, 从mid+1到hi为一组, 对这两组数据进行归并
        // 定义三个指针
        int i = lo;
        int p1 = lo;
        int p2 = mid + 1;

        /*
        遍历, 移动p1和p2指针, 比较对应索引处的值,
        找出较小的值放入辅助数组中对应的索引处
        */
        while (p1 <= mid && p2 <= hi){
            // 比较索引处的值
            /*
            注意, 此处的if判断条件会使得这个归并算法变得不稳定, 
            只有将条件改为<=时, 此归并算法才会变为稳定的
            */
            if(less(a[p1], a[p2])){
                assist[i++] = a[p1++];
            } else {
                assist[i++] = a[p2++];
            }
        }

        // 遍历, 如果p1指针没有走完, 那么顺序移动另p1指针, 将对应的元素一一放入辅助数组中
        while(p1 <= mid){
            assist[i++] = a[p1++];
        }

        // 遍历, 如果p2指针没有走完, 那么顺序移动另p2指针, 将对应的元素一一放入辅助数组中
        while(p2 <= hi){
            assist[i++] = a[p2++];
        }

        // 把辅助数组中的元素拷贝到原数组中
        for (int index = lo; index <= hi; index++){
            a[index] = assist[index];
        }
    }
}

2.4 时间复杂度分析

归并排序的时间复杂度为O(nlogn).
;) 

3. 快速排序

快速排序是对冒泡排序的一种改进. 它的基本思想是: 通过一趟排序将要排序的数据分割成独立的两部分, 其中一部分的所有数据都比另外一部分要小, 然后再按此方法对这两部分数据分别进行快速排序, 整个排序过程可以递归进行, 以此达到整个数据变成有序数列.

3.1 排序原理

1. 首先设定一个分界值, 通过该分界值将数组分成左右两部分. 一般情况下, 分界值可以取数组中第一个数据.
2. 将大于或等于分界值的数据放到数组右边, 小于分界值的数据放到数组的左边. 此时左边部分中各元素都小于或等于分界值, 而右边部分中各元素都大于或等于分界值;
3. 然后, 左边和右边的数据可以独立排序, 对于左侧的数据, 又可以取一个分界值, 重复第二步的分类. 右侧的数组也做同样的处理.
4. 以此往复, 通过递归将左右两侧的数据排号, 也就完成了整个数组的排序.

 3.2 API设计

类名Quick
构造方法Quick(): 创建Quick对象
成员方法

1. public static void sort(Comparable[] a): 对数组内的元素进行排序

2. private static void sort (Comparable[] a, int lo, int hi): 对数组a中从索引lo到hi之间的元素进行排序

3. private static int partition(Comparable[] a, int lo, int hi): 对数组a中, 从索引lo到hi之间的元素进行分组, 并返回分界值对应的索引

4. private static void exch(Comparable[] a, int i, int j): 交换数组a中, 索引i和j处的值

 3.3 切分原理

1. 找一个基准值, 用两个指针分别指向数组的头和尾
2. 先从尾部向头部开始搜索一个比基准值小的元素, 搜索到即停止, 并记录指针的位置;
3. 再从头部向尾部搜索一个比基准值大的元素, 搜索到即停止,  并记录指针的位置;
4. 交换当前左边指针位置和右边指针位置的元素;
5. 重复2, 3, 4步骤, 直到左边指针的值大于右边指针的值时停止.

3.4 代码实现

package sort;

public class QuickSort {
    /*
    对数组内的元素进行排序
     */
    public static void sort(Comparable[] a){
         int lo = 0;
         int hi = a.length - 1;
         sort(a, lo, hi);
    }

    /*
    对数组a中索引lo到索引hi之间的元素进行排序
     */
    private static void sort(Comparable[] a, int lo, int hi){
        // 安全性校验, 同时也是迭代的基本结束条件
        if (hi <= lo){
            return;
        }

        // 对数组中lo到hi处的数组进行分组
        int partition = partition(a, lo, hi);

        // 继续对左子组进行迭代排序
        sort(a, lo, partition - 1);

        // 继续对右子组进行迭代排序
        sort(a, partition + 1, hi);
    }

    /*
    对数组a中, 从索引lo到hi之间的元素进行分组, 返回分组界限对应的索引
     */
    private static int partition(Comparable[] a, int lo, int hi){
        // 创建两个扫描指针和一个key值
        Comparable key = a[lo];
        int left = lo; // 扫描比key值大的元素
        int right = hi + 1; // 扫描比key值小的元素

        // 切分操作
        while (true){
            // 先移动右指针, 如果找到比key值小的元素, 则停止
            while(less(key, a[--right])){ // 如果key值比right指针指向的元素小, 则继续
                if(right == lo){ // 如果right指针走到左边, 则结束从右往左的扫描
                    break;
                }
            }

            // 再移动左指针, 如果找到比key值大的元素, 则停止
            while(less(a[++left], key)){ // 如果left指针指向的元素比key值小, 则继续
                if (left == hi){ // 如果left指针走到右边, 则技术从左往右的扫描
                    break;
                }
            }

            // 如果两个指针是否相遇或交错, 则说明已经扫描完毕
            if (left >= right){
                break;
            }else { //如果没有相遇, 则交换两个指针位置的元素
                exch(a, right, left);
            }
        }

        // 交换分界值和key值
        // 注意, 此处必须使用right指针进行交换
        exch(a, lo, right);

        // 返回分界值索引
        return right;
    }

    /*
    判断v是否小于w
     */
    private static boolean less(Comparable v, Comparable w){
        return v.compareTo(w) < 0;
    }

    /*
    交换a数组中索引i和索引j处的值
     */
    private static void exch(Comparable[] a, int i, int j){
        Comparable temp = a[i];
        a[i] = a[j];
        a[j] = temp;
    }
}

3.5 时间复杂度分析

快速排序的时间复杂度和分界值的选择有关
最优情况: 每一次切分选择的分界值刚好将房前序列等分 -- O(nlogn)
最差情况: 每一次切分选择的分界值是当前序列中最大或最小的数字, 这使得每一次切分都只有一个子组, 那么总共就得切分n次, 所以此时的时间复杂度为O(n^2).
平均情况: O(nlogn)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值