插入排序算法

来源:互联网 发布:信息比率 知乎 编辑:程序博客网 时间:2024/06/08 04:35

插入排序有:直接插入排序、二分法插入排序、希尔排序。

排序是一种很直观的算法,用一种可视化的方法来观察算法的执行过程是很有用的,各位可以点击此链接https://visualgo.net/en/sorting来更加透彻得领悟各类排序算法。


下面是直接插入排序

 基本思想就是,p从1开始一直往后移动,每次移动(即指外层循环的每次循环)前从索引0到p-1位置都是已经排好序的,每次移动后保证从索引0到p位置都是已经排序了的,看起来好像在内层循环要不停的交换元素,实际上不需要,只需要先把元素存起来,元素一直往后复制以覆盖,最后循环结束后再赋值。这和堆的插入删除算法(即上滤下滤)是类似的


因为有两个循环,所以算法的时间复杂度:

最坏情况:O(n2)。

最好情况:O(n)。最好的情况就是数据已经预先排过序了。

平均情况:也是O(n2)。

算法是稳定的。

    public static <AnyType extends Comparable<? super AnyType>>    void insertionSort( AnyType [ ] a )    {        int j;        for( int p = 1; p < a.length; p++ )        {            AnyType tmp = a[ p ];//先将要插入的元素存起来            for( j = p; j > 0 && tmp.compareTo( a[ j - 1 ] ) < 0; j-- )//如果是<=0那么算法不稳定                a[ j ] = a[ j - 1 ];//比较后如果小,那么元素就往后移动            if(j!=p) //如果j=p,那么说明p元素就应该在p位置,不需要多余的赋值操作               a[ j ] = tmp;//出了循环,代表j已经到了该到的位置,把tmp赋值给j位置        }    }



下面是二分插入排序

基本思想就是,p从1开始一直往后移动,每次移动(即指外层循环的每次循环)前从索引0到p-1位置都是已经排好序的,每次移动后保证从索引0到p位置都是已经排序了的。但是在寻找插入位置有点不一样了,是通过二分查找的思想来找。二分插入排序的主要操作为比较+后移赋值。


最坏情况:每次都在有序序列的起始位置插入,则整个有序序列的元素需要后移,时间复杂度为O(n2)

最好情况:待排序数组本身就是正序的,每个元素所在位置即为它的插入位置,此时时间复杂度仅为比较时的时间复杂度,为O(log2n)。

平均情况O(n2)

算法是稳定的。

    public static void sort(int[] a)    {        for (int i = 1; i < a.length; i++)//从一开始即可,因为只有一个元素便不用比较        {            int temp = a[i];//前把i元素存起来            int left = 0;//都是从0到i-1的位置中找到i元素的插入位置            int right = i - 1;            int mid;            while (left <= right)            {                mid = (left + right) / 2;                if (temp < a[mid])                {                    right = mid - 1;                }                else                {                    left = mid + 1;                }            }//退出whihe循环时,left肯定等于right+1,,而这个位置就是插入位置            for (int j = i - 1; j >= left; j--)                a[j + 1] = a[j];//元素往后移动            if(left!=i)//当i元素就应该放在i位置时,此时left就会等于i位置,但此时不需要进行插入                       a[left] = temp;                             }    }


下面是希尔排序

基本思想是:先取一个小于数组长度的整数d1作为第一个增量,把文件的全部记录分组,一共分为d1组。所有距离为d1的倍数的记录放在同一个组中。先在各组内进行直接插入排序;然后,取第二个增量d2<d1重复上述的分组和排序,直至所取的增量

  
=1(
  
<
  
…<d2<d1),即所有记录放在同一组中进行直接插入排序为止。


希尔排序的时间复杂度与增量序列的选取有关:

如果是选取的希尔增量

最坏情况O(n2)。

最好情况O(n)。

平均情况

算法是不稳定的。

其余的增量序列还有Hibbard:{1, 3, 7 ..., 2^k-1},Sedgewick:{1, 5, 19, 41, 109...}该序列中的项或者是9*4^i - 9*2^i + 1或者是4^i - 3*2^i + 1。

    public static <AnyType extends Comparable<? super AnyType>>    void shellsort( AnyType [ ] a )     {        int j;        for( int gap = a.length / 2; gap > 0; gap /= 2 )//增量gap最开始为长度一半,一直到gap为1        //进入最外层循环后就可以把gap当成常数        //以下两个循环其实就是插入排序        //把数组分成gap个子数组,每个子数组的插入排序,是依次一部分一部分进行的            for( int i = gap; i < a.length; i++ )//不用i=0开始,因为只有一个元素的时候不用排序            {                AnyType tmp = a[ i ];//将要插入的元素存起来                for( j = i; j >= gap && tmp.compareTo( a[ j - gap ] ) < 0; j -= gap )                //判断条件也可以写成j>0,一样效果,保证gap个数前的那个元素不是null                    a[ j ] = a[ j - gap ];//元素后移                if(j!=i)//如果j=i,那么说明i位置的元素就应该在i位置,不需要多余的赋值操作                a[ j ] = tmp;            }    }


原创粉丝点击