排序-插入排序(Insertion Sort)

描述

插入排序是一种简单的排序方法,其基本操作是将后续元素插入到已排好的有序列表中,从而得到一个新的、记录数量增1的有序表。当所有元素都被插入时,整个序列排序完成。

原理

假设有一组无序序列 R1, R2, ... , Rn。

  1. 将第一个元素视为元素已排序的有序序列。
  2. 再依次把 R2, R3, ... , Rn 插入到这个有序序列中。所以,我们需要一个外部循环,从第2个元素扫描到第n个元素。
  3. 接下来进行元素插入过程,假设要将 Ri插入到前面有序的序列中。由前面所述,我们可知,插入Ri时,前 i-1 个元素已经有序。所以需要将Ri 和R1 ~ Ri-1 进行比较,确定要插入的位置。一般从后往前比较,即从第 i-1个元素开始向前进行比较。

排序的过程和打扑克牌一样,按顺序将手牌一一排序。需要注意的是:为了给插入的元素腾出空间,我们需要将其余所有元素向后移动。为了更好理解,假设已经排序到Ri,对于1到i-1的元素是已经有序的了,当Ri从i-1开始逐一比较时,假设需要插入的位置是3,那么从3到i-1的元素都需要后移,为插入的元素Ri腾出空间,i-1处的元素移动到i处。

下面图中展示了插入排序的过程:灰色部分表示没有移动的元素,红色部分表示交换的元素。

插入排序

性质
  1. 平均情况:N^2/4 次比较,N^2/4次交换;
  2. 最坏情况:N^2/2次比较,N^2/2次交换;
  3. 最好情况:N-1次比较,0次交换;
  4. 平均时间复杂度: O(N^2)
  5. 最好时间复杂度: O(N)

排序对初始数据的顺序是敏感的,有序度越高插入排序越有效。当初始数据是已排序序列时,时间复杂度为O(N)。插入排序对小数组非常有效。

插入排序

为了更好的理解插入排序,我们按照上面的思路写出代码,代码如下:

/**
 * Insertion Sort
 */
public class InsertionSorted extends AbstractSorted {

    public <T extends Comparable<T>> void sort(T[] arrays) {

        // 开始一趟有序列扫描,当前i之前的数据视为有序序列
        for (int i = 1; i < arrays.length; i ++) {

            // 当前需要被插入比较的元素
            T inserted = arrays[i];

            // 寻找插入位置,从后向前与i之前的有序序列一一比较,如果比插入元素大,则向前移动
            int j = i - 1;

            while (j >= 0 && greater(arrays[j], inserted)) {
                // 元素向后移动(j位置的元素移动到j+1)
                arrays[j + 1] = arrays[j];
                j --;
            }

            // 找到插入位置(如果j没有变化, 其实inserted还是插入在原来位置,所以这里加j+1!=i限制)
            if (j + 1 != i) {
                arrays[j + 1] = inserted;
            }

            String msg = String.format("第%2d次 ->: %s", i, Arrays.toString(arrays));
            System.out.println(msg);
        }

    }
}

上面的方式能更清晰了解排序过程中的比较和交换,但是代码还不够简洁,下面提供更简洁的写法。

/**
 * Insertion Sort
 */
public class InsertionSorted extends AbstractSorted {

    public <T extends Comparable<T>> void sort(T[] arrays) {

        // 开始一趟有序列扫描,当前i之前的数据视为有序序列
        for (int i = 1; i < arrays.length; i ++) {

            // 寻找位置,从后向前与i之前的有序序列一一比较,找到比当前元素小的位置
            for (int j = i; j >= 0 && less(arrays[j], arrays[j - 1]; j --)) {
                exch(arrays, j, j - 1);
            }

            String msg = String.format("第%2d次 ->: %s", i, Arrays.toString(arrays));
            System.out.println(msg);
        }

    }
}
测试代码
/**
 * 排序测试
 */
@RunWith(JUnit4.class)
public class SortedTest {

    private Integer[] data;

    @Before
    public void setUp() {
        Random random = new Random();
        data = new Integer[10];
        for (int i = 0; i < data.length; i ++) {
            data[i] = random.nextInt(10000);
        }
    }

    /**
     * 测试插入排序
     */
    @Test
    public void testInsertionSorted() {
        long start = System.currentTimeMillis();
        Sorted sorted = new InsertionSorted();
        System.out.println("排序前: " + Arrays.toString(data));
        sorted.sort(data);
        System.out.println("排序后: " + Arrays.toString(data));
        System.out.println("执行时间: " + (System.currentTimeMillis() - start) + " ms");
    }

}
测试结果
排序前: [4075, 3969, 6077, 3345, 6942, 8945, 8489, 2890, 482, 2619]
第 1次 ->: [3969, 4075, 6077, 3345, 6942, 8945, 8489, 2890, 482, 2619]
第 2次 ->: [3969, 4075, 6077, 3345, 6942, 8945, 8489, 2890, 482, 2619]
第 3次 ->: [3345, 3969, 4075, 6077, 6942, 8945, 8489, 2890, 482, 2619]
第 4次 ->: [3345, 3969, 4075, 6077, 6942, 8945, 8489, 2890, 482, 2619]
第 5次 ->: [3345, 3969, 4075, 6077, 6942, 8945, 8489, 2890, 482, 2619]
第 6次 ->: [3345, 3969, 4075, 6077, 6942, 8489, 8945, 2890, 482, 2619]
第 7次 ->: [2890, 3345, 3969, 4075, 6077, 6942, 8489, 8945, 482, 2619]
第 8次 ->: [482, 2890, 3345, 3969, 4075, 6077, 6942, 8489, 8945, 2619]
第 9次 ->: [482, 2619, 2890, 3345, 3969, 4075, 6077, 6942, 8489, 8945]
排序后: [482, 2619, 2890, 3345, 3969, 4075, 6077, 6942, 8489, 8945]
执行时间: 6 ms
注意

可能会有小伙伴会有这样的疑问,在上面的代码中明明有两层for循环,时间复杂度应该是O(N^2)才对。为什么排序对于初始数据敏感了?为什么对于已经排序好的数据时间复杂度是O(N)?程序的运行时间是以执行循环体内部而言的,对于已经排序好但数据,exch()方法从来不会执行,内循环并不执行。

参考资料

《Algorithms, 4th Edition》https://algs4.cs.princeton.edu/21elementary/

转载于:https://my.oschina.net/xiaoqiyiye/blog/2242774

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
插入排序一种简单且直观的排序算法。它的基本思想是将一个待排序的数组分成两个部分,一部分是已经排好序的,另一部分是未排序的。在每一次排序过程,从未排序的部分取出一个元素插入到已排序的部分的适当位置,直到所有元素都被插入到已排序的部分为止。 以下是一个使用微机汇编语言实现的插入排序算法: ```assembly ; 插入排序 ; 参数:edi - 待排序的数组首地址,ecx - 数组元素个数 InsertionSort: mov ebx, 1 ; ebx = 1,从第二个元素开始插入排序 OuterLoop: cmp ebx, ecx ; 检查是否已经排完序 jge Exit ; 如果已经排完序,跳转到 Exit mov eax, [edi + ebx * 4] ; 取出当前待排序元素 mov edx, ebx ; 将 edx 指向当前待排序元素的前一个元素 InnerLoop: cmp edx, 0 ; 如果已经到了数组的起始位置,跳转到 Insert jl Insert mov esi, [edi + edx * 4] ; 取出当前已排序元素 cmp esi, eax ; 比较当前已排序元素和待排序元素大小 jle Insert ; 如果当前已排序元素小于等于待排序元素,跳转到 Insert mov [edi + (edx + 1) * 4], esi ; 将当前已排序元素后移一位 dec edx ; 将 edx 指向下一个已排序元素 jmp InnerLoop Insert: mov [edi + (edx + 1) * 4], eax ; 将待排序元素插入到已排序的部分 inc ebx ; 将 ebx 指向下一个待排序元素 jmp OuterLoop Exit: ret ``` 在该算法,我们使用了寄存器 ebx,eax,edx 和 esi 来存储一些临时变量。其,ebx 存储当前待排序元素的下标,eax 存储当前待排序元素的值,edx 存储当前待排序元素前面一个已排序元素的下标,esi 存储当前已排序元素的值。 该算法的时间复杂度为 O(n^2),其 n 是数组的元素个数。虽然该算法的效率不高,但是它非常容易实现,并且可以在实际应用发挥一些作用。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值