初级排序算法

1 游戏规则

1.1 验证

在测试代码中添加一条语句

assert isSorted(arr);

来确认排序后的数组元素都是有序的,若有交换数组的元素,这个测试是足够的

  • 直接将值存入数组中,这条语句无法提供足够的保证(全部相同的值也能通过测试)
1.2 运行时间

需要计算比较交换的数量

对于不交换元素的算法,我们计算访问数组的次数

1.3 额外的内存使用

额外内存开销和运行时间同等重要

分为两类

  1. 除了函数调用所需的栈和固定数目的实例变量之外,无需额外内存的原地排序算法
  2. 需要额外内存空间来存储另一份数组副本的其他排序算法
1.4 数据类型

Java中封装数字的类型Integer和Double,以及String和其他许多高级数据类型(File和URL)都实现了Comparable接口。因此可以直接使用这些类型的数组作为参数来调用排序方法。

2 选择排序

一种最简单的排序算法:首先,找到数组中最小的那个元素,其次,将它和数组的第一个元素交换位置(如果第一个元素就是最小元素,那么它就和自己交换)。再次,剩下的元素中找到最小的元素,将它与数组中的第二个元素交换位置。如此反复。这种排序就叫做选择排序,因为它不断地选择剩余元素之中的最小者。

/**
 * 
 * 选择排序
 * @作者 yrucrew
 * @日期 2018年5月3日 下午8:39:16
 */
public class Selection {

    @SuppressWarnings("all")
    public static void sort(Comparable[] a) {
        //将a[]升序排序
        int N = a.length;
        for(int i = 0; i < N; i++) {
            int min = i;
            for(int j = i+1;j<N;j++) {
                if(SortUtils.less(a[j], a[min])) { // less(a,b) 若a<b返回true
                    min = j; // 令最小值索引指向j
                }
            }
            SortUtils.swap(a, i, min);
        }
    }

    public static void main(String[] args) {
        Integer[] a = {1,3,2,4,7,3,4,2,7,2,3};
        sort(a);
        System.out.println(Arrays.toString(a));
    }
}

选择排序两个鲜明的特点:

  • 运行时间和输入无关
  • 数据移动是最少的

3 插入排序

通常人们整理桥牌的方法是一张一张的来,将每一张牌插入到其他已经有序的牌中的适当位置。

在计算机的实现中,为了给要插入的元素腾出空间,我们需要将其余所有元素在插入之前都向右移动一位。

这种算法叫做插入排序

/**
 * 插入排序
 * 
 * @作者 yrucrew
 * @日期 2018年5月3日 下午8:39:57
 */
public class Insertion {

    public static void sort(Comparable[] a) {

        // 将a[]升序排序
        int N = a.length;
        for (int i = 1; i < N; i++) {
            for (int j = i; j > 0; j--) {
                if(SortUtils.less(a[j], a[j - 1])){
                    SortUtils.swap(a, j, j-1);     
                 }
            }
        }
    }

    public static void main(String[] args) {
        Integer[] a = {1,3,2,4,7,3,4,2,7,2,3};
        sort(a);
        System.out.println("插入排序:" + Arrays.toString(a));
    }
}

在索引 i 由左向右变化的过程中,它左侧的元素总是有序的,所以当 i 到达数组的右端时排序就完成了。

插入排序对部分有序的数组很有效

倒置指的是数组中的两个顺序颠倒的元素。比如 EXAMPLE 中就有11对倒置:E-A、X-A、X-M、X-P、X-L、X-E、M-L、M-E、P-L、P-E 以及 L-E。

如果数组中倒置的数量大于数组大小的某个倍数,那么我们说这个数组是部分有序的。

说到底,排序最终目的就是为了消除逆序。

4 希尔排序

对于大规模的乱序数组,插入排序很慢,因为每次它只会交换相邻的元素,元素只能一点一点地从数组的一端移动到另一端。

希尔排序也称之为递减增量排序,他是对插入排序的改进。

希尔排序通过将待比较的元素划分为几个区域来提升插入排序的效率。这样可以让元素可以一次性的朝最终位置迈进一大步,然后算法再取越来越小的步长进行排序, 最后一步就是步长为 1 的普通的插入排序的,但是这个时候,整个序列已经是近似排好序的,所以效率高。

希尔排序的思想是使数组中任意间隔为h的元素都是有序的。这样的数组被称为h有序数组。换句话说,一个h有序数组就是h个相互独立的有序数组编织在一起组成一个数组。

希尔排序的增量(h)递减算法可以随意指定,可以以N/2递减,只要保证最后的步长为1即可。

/**
 * 希尔排序
 * 
 * @作者 yrucrew
 * @日期 2018年5月3日 下午9:25:31
 */
public class Shell {

    @SuppressWarnings("all")
    public static void sort(Comparable[] a) {
        // 升序排列
        int N = a.length;
        int h = 1;
        // 初始最大步长
        while (h < N / 3)
            h = h * 3 + 1; // 1, 4, 13, 40, 121, 364, 1093, ......
        while (h >= 1) {
            for (int i = h; i < N; i++) {
                for (int j = i; j >= h && SortUtils.less(a[j], a[j - h]); j = j - h) {
                    SortUtils.swap(a, j, j - h);
                }
            }
            h = h / 3;
        }
    }

    public static void main(String[] args) {
        Integer[] a = { 1, 3, 2, 4, 7, 3, 4, 2, 7, 2, 3 };
        sort(a);
        System.out.println("希尔排序:" + Arrays.toString(a));
    }
}
  • Shell’s 序列: N/2 , N/4 , …, 1 (重复除以2);
  • Hibbard’s 序列: 1, 3, 7, …, 2k - 1 ;
  • Knuth’s 序列: 1, 4, 13, …, (3k - 1) / 2 ;该序列是本文代码中使用的序列。
  • 已知最好的序列是 Sedgewick’s (Knuth的学生,Algorithems的作者)的序列: 1, 5, 19, 41, 109, ….
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值