插入排序(InsertionSort)

生活中的“插入排序”

扑克牌我们大部分人都玩过,当然也都知道该怎么把刚抓上来的牌放哪里,最后得到一手排好的牌。但其中所蕴含的算法原理

不知道你有没有想过。计算机科学家把人的这一直观想法翻译为计算机程序于是便有了我们所说的插入排序:

图示

代码(C++)

/*************************************
    函数:插入排序
    说明:对区间[low, high)的数据排序
    时间复杂度:O(n + inverse)
*************************************/
void insertionSort(int* low , int* high)
{
    for(int* cur = low; ++cur < high; )     ///实际是从第二个元素开始插入,因为第一个已经有序了
    {
        int tmp = *cur; ///临时保存要插入的值
        int* destPos = cur;     ///记录当前要插入的元素的正确安放位置,这里初始化为本来的位置
        while(--destPos >= low && *destPos > tmp)      ///测试上一个是否是目标位置
            *(destPos + 1) = *destPos;
        *(destPos + 1) = tmp;   ///最后一次测试失败使得destIndex比实际小1
    }
}

这里使用指针作为输入参数是比输入元素个数是有优势的:常数更小,移植性更强。

inverse是什么

从上面的函数说明中得知算法的时间复杂度 O(n + inverse),O(n)很容易理解就是外层循环遍历n个元素。

内层循环的次数便是inverse,而inverse便是数学上逆序数的概念:

假设A[1...n]是一个有n个不同数的数组。若i < j 且 A[i] > A[j],则对偶(i,j)称为A的一个逆序,逆序数就是逆序之和。

逆序数用来描述一组数据无序的程度。而插入排序过程中每移动一次元素便“修复”一个逆序,所以内层循环次数 = inverse。

1.很容易得知inverse的范围:0 <= inverse <= n(n - 1) / 2

2.inverse的期望为 n(n - 1) / 4,即为最差情况的一半。

关于期望的证明很简洁易懂,是我从数据结构和算法分析那本书上看来的:

假设元素是互异的。

对排列分组:每个排列与其反转后得到的逆排列分为一组,这样一来每对元素(i,j)

会对任一组中的其中一个排列贡献一个逆序,这样每组都有C(n,2) = n(n - 1) / 2个逆序,平均每个排列有n(n - 1) /4

现在来测试一个随机排列inverse的大小:

void insertionSort(int* low , int* high)
{
    int inverse = 0;    ///逆序数
    for(int* cur = low; ++cur < high; )
    {
        int tmp = *cur;
        int* destPos = cur;
        while(--destPos >= low && *destPos > tmp)
        {
            *(destPos + 1) = *destPos;
            inverse++;  ///修复一个逆序
        }
        *(destPos + 1) = tmp;
    }
}

下面是测试数据(假设元素互异,且随机排列):

优化

当数据量很大时利用二分查找插入排序和希尔排序都是很不错的,不过数据量大的时候插入排序常数小这一优势就没什么用了,基本上丧失了战斗力。

所以这里做的优化都是以小数据输入作为基本条件的。

对上面的代码分析发现就算第i个元素本来就在正确位置还是会被自己给替代一次,下面的代码解决了这一浪费:

/*************************************
    函数:优化版插入排序
    说明:对区间[low, high)的数据排序
    时间复杂度:O(n + inverse)
*************************************/
void improvedInsertionSort(int* low , int* high)
{
    for(int* cur = low; ++cur < high; )     ///实际是从第二个元素开始插入,因为第一个已经有序了
    {
        int tmp = *cur; ///临时保存要插入的值
        int* destPos = cur;     ///记录当前要插入的元素的正确安放位置,这里初始化为本来的位置
        ///把第一次测试单独提出来
        if(*(--destPos) > tmp)
        {
            do
            {
                *(destPos + 1) = *destPos;
            }while(--destPos >= low && *destPos > tmp);     ///测试上一个是否是目标位置
            *(destPos + 1) = tmp;   ///最后一次测试失败使得destIndex比实际小1
        }
    }
}

通过把第一次测试单独提出来,使得在正确位置的元素不用执行多余的最后一条赋值语句。

但这一优化是十分十分有限的,因为第i个元素就在第i个位置的条件就是这一元素是前i个元素中最大的,这一概率为 1 / i ,所以n个元素中大约只有ln(n)个元素满足条件

它唯一的优点就是在任何情况都不会使运算更费时。

后记

实在是想不到什么好的优化方案,所以如果你知道其他更好的优化方法或上面内容有什么问题请在下面评论。

时间: 2024-10-01 03:04:16

插入排序(InsertionSort)的相关文章

插入排序(InsertionSort)

位置p上的元素存储于tmp(第一趟p通常取1),而(位置p之前)所有更大的元素都向右移动一个位置. 然后tmp被放在正确的位置上. 代码: public class InsertionSort { private int q; int[] insertionSort(int[] a) { for (int p = 1; p < a.length; p++) { int temp = a[p]; for (q = p; q > 0 && (temp - a[q - 1]) <

插入排序 Insertion-sort

---恢复内容开始--- 插入排序 经典显示(参照算法导论) 一副扑克牌放在桌面上 花色朝下,每次从桌面上拿去最上面的一张 ,放入自己手中的牌中的正确位置,(每次都是对手中的牌排序,并且每次拿去桌面上的牌时,手中的牌已经排序好,当桌上的牌没有时,所有的排序都已经排序好.   其中注意点:将后面一张牌与前一张比如果小于就交换位置. 办法二 [最佳方法]将最后一张牌与 for循环下面的每个元素比A[i],若小于就向后移一位A[i+1] = A[i], 但是刚拿过的牌 j 必须要付给一个临时变量来保存

插入排序InsertionSort

/** * * @author Administrator * 功能:插入排序法 */ package com.test1; import java.util.Calendar; public class InsertionSort { public static void main(String[] args) { // TODO Auto-generated method stub int[] arr = new int[50000]; for (int i = 0; i < arr.len

排序(一):插入排序

前言 快被数据结构与算法碾成渣渣了,决定开始补补 参考资料:算法导论(有MIT公开课-算法导论) .Data Structures and Algorithms in Python.数据结构与算法 插入排序(insertion-sort) 插入排序的基本思想:每次将一个带排序的记录,按其关键字大小插入到前面已经排好序的子表中的适当位置,直到 全部记录插完为止. eg.对8个字母使用插入排序进行排序,每一行中对应的是外层迭代循环,每一行中的各个副本对应的是内层迭代循环, 当前被插入数组中的元素以高

排序算法 基于Javascript

写在前面 个人感觉:javascript对类似排序查找这样的功能已经有了很好的封装,以致于当我们想对数组排序的时候只需要调用arr.sort()方法,而查找数组元素也只需要调用indexOf()方法或lastIndexOf()方法,我们忽略了其内部的实现.而今,js能开发的项目越来越庞大,对性能和效率要求也越来越高,虽然众多的库和框架也可以帮我们应付这些问题,但小编觉得框架过眼云烟,把握程序开发的基础,才能在飞速的更新换代中应对自如.因此我们不妨也研究一下这些算法,其中的思路有助于我们自身的提高

Python全栈开发之5、几种常见的排序算法以及collections模块提供的数据结构

在面试中,经常会遇到一些考排序算法的题,在这里,我就简单了列举了几种最常见的排序算法供大家学习,说不定以后哪天面试正好用上,文章后半段则介绍一下collections模块,因为这个模块相对于python提供的基本数据结构(list,tuple,dict)不被人们所熟悉,但是如果你对他们了解的话,用起来也是非常方便高效的. 排序算法 一.冒泡排序(BubbleSort) 步骤: 比较相邻的元素,如果第一个比第二个大,就交换他们两个. 循环一遍后,最大的数就“浮”到了列表最后的位置. 将剩下的数再次

七种经典排序算法最全攻略

经典排序算法在面试中占有很大的比重,也是基础.包括冒泡排序,插入排序,选择排序,希尔排序,归并排序,快速排序,堆排序.希望能帮助到有需要的同学.全部程序采用JAVA实现. 本篇博客所有排序实现均默认从小到大. 一.冒泡排序 BubbleSort 介绍: 冒泡排序的原理非常简单,它重复地走访过要排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来. 步骤: 比较相邻的元素.如果第一个比第二个大,就交换他们两个. 对第0个到第n-1个数据做同样的工作.这时,最大的数就"浮"到了

面试常考各类排序算法总结.(c#)

一. 面试以及考试过程中必会出现一道排序算法面试题,为了加深对排序算法的理解,在此我对各种排序算法做个总结归纳. 1.冒泡排序算法(BubbleSort) 1 public Class SortDemo 2 { 3 public void BubbleSort(int arr) 4 { 5 int temp=0; 6 //需要走arr.Length-1 趟 7 for(int i=0;i<arr.Length-1;i++) 8 { 9 //每一趟需要比较次数 10 for(int j=0,j<

排序算法学习程序

这个程序是我刚刚学OI的时候写的程序.因为那时候刚刚开学,又面临着开学考试,用了好几个晚上赶了出来,写的有些粗糙,请见谅. 那时候总是写程序写到晚上12点,这个程序也获得了2014年日照市中小学电脑制作活动二等奖……虽然不理想,但也是个奖…… 既然活动结束了,我将公开源代码,请在FreePascal2.0.4下编译,然后再Windows下执行(不要直接Run,否则中文将显示乱码) 程序很简单,就是简单的输入输出操作,以及把各个简单排序算法整合在一起. 下面是源代码(转载或上传到网络时请注明作者版

经典排序算法总结与实现 ---python

原文:http://wuchong.me/blog/2014/02/09/algorithm-sort-summary/ 经典排序算法在面试中占有很大的比重,也是基础,为了未雨绸缪,在寒假里整理并用Python实现了七大经典排序算法,包括冒泡排序,插入排序,选择排序,希尔排序,归并排序,快速排序,堆排序.希望能帮助到有需要的同学.之所以用Python实现,主要是因为它更接近伪代码,能用更少的代码实现算法,更利于理解. 本篇博客所有排序实现均默认从小到大. 一.冒泡排序 BubbleSort 介绍