插入排序说明(结合DualPivotQuicksort类)

1、描述
待排序的数组分为已排序、未排序两部分;
初始状态时,仅有第一个元素为已排序序列,第一个以外的元素为未排序序列;
此后遍历未排序序列, 将元素逐一插入到已排序的序列中:
  即把该为排序元素与原有一排序序列当做一个新序列,
  通过一次冒泡排序整合成已排序序列(从右侧开始,两个相邻元素进行比较,
  匹配成功则换位置,不成功就不做变动)

例:

源数据321
步骤1 (3为已排序,2、1 为未排序;3 和 2 比较)231
步骤2.1 (2、3为已排序,1为未排序;3 和 1 比较)213
步骤2.2 (2 和 1 比较)123
2、实现
/* 
java.util.DualPivotQuicksort,这个是jdk中的一个类(双轴快速查询类),常见的 Arrays.sort 方法调用的就是这个类的 sort 静态方法;以 int[] 为例,sort 方法主要是用了插入排序

// 排序算法代码均从以下方法中摘取
// private static void sort(int[] a, int left, int right, boolean leftmost)
*/
2.1 基本实现
/**
 * 插入排序基本实现
 * @param a     需要排序的数组
 * @param left  最小下标
 * @param right 最大下标
 */
public static void sort(int[] a, int left, int right) {
    for (int i = left, j = i; i < right; j = ++i) {
        int ai = a[i + 1];		// 记录下标+1的值
        // 该循环相当于冒泡排序(局部)
        while (ai < a[j]) {		// 比较两个下标对应的值,为false时表示0-j下标的值都要比 ai 小
            a[j + 1] = a[j];	// 把更大的值赋值给更高下标
            if (j-- == left) {	// 下标向左移,并做判断避免出现非法下标
                break;
            }
        }
        a[j + 1] = ai;			
        // 注意:无论i、j为何值,0~i / 0~j 区间的数据一定是已排序的
    }
}
public class SimpleInsertSort {
    // 数组长度
    public final static int MAX_SIZE = 10;
    
    public static void main(String[] args) {
        int[] arr = new int[MAX_SIZE];
		// 数组填充数据
        for (int i = 0; i < arr.length; i++) {
            arr[i] = Integer.valueOf(Math.round(Math.random() * 100) + "");
        }
        
        System.out.println("数据:" + Arrays.toString(arr));
        sort(arr,0,arr.length-1);
        System.out.println("结果:" + Arrays.toString(arr));
    }
}
2.2 优化
  1. 如果起始数据是已排序的,那么可以忽略这一部分;从而减少循环的次数
  2. 插入排序每次只判断两个值,是否可以扩展为多个值一起比较
/**
 * 插入排序优化
 * @param a     需要排序的数组
 * @param left  最小下标
 * @param right 最大下标
 */
public static void sort2(int[] a, int left, int right) {
    // 1. 从下标0开始,判断是否存在已排序数据,存在则更新下标(需要进行排序的最小下标)
    do {
        if (left >= right) {			// 避免下标异常
            return;
        }
    } while (a[++left] >= a[left - 1]); // 首次循环:a[1] >= a[0] ...
    // 2. 成对插入排序;每执行一次循环,left会对应加2;即每次会对两个值分别进行一次插入排序
    // 注:由于一次对两个值排序,需要注意下标的处理,避免越界或者存在遗漏
    for (int k = left; ++left <= right; k = right > left+2?++left:left) {
        // 每次进入循环体时,会先执行判断语句,所以第一次:k=left,left=left+1
        // a1表示较小值,a2表示较大值
        int a1 = a[k], a2 = a[left];
	    
        if (a1 < a2) {				// 比较两下标对应的值,true则交换
            a2 = a1; a1 = a[left];
        }
        // 取值:a[left-2],a[left-1],a[left];后两个是未排序的值,前一个是之前排序好的
        while (--k >= 0 && a1 < a[k]) { // 取最大值 赋值到 a[left] 中
            a[k + 2] = a[k];
        }
        a[++k + 1] = a1;    // 取中间的值赋值到 a[left-1]

        while (--k >= 0 && a2 < a[k]) {
            a[k + 1] = a[k];
        }
        a[k + 1] = a2; // 取最小值赋值到 a[left-2]
    }
}

PS. 个人猜测源码中是分这两种情况:1. 起始数据不存在已排序的,直接使用 2.1 的常规插入排序;2. 起始数据存在已排序的,找出已排序的最大下标,以改下标为起始,对数据成对插入排序,对应 2.2

3、结果
数据:[12, 18, 75, 25, 71, 59, 84, 42, 87, 13]
结果:[12, 13, 18, 25, 42, 59, 71, 75, 84, 87]
4、复杂度
最好情况,第二个循环都不需要执行,O(N)
最坏情况,第一个以外的元素都需要和之前的数据做一次交换 O(N*N)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值