排序算法--插入排序(直接插入排序、折半插入、shell排序)的java实现

来源:互联网 发布:男士单肩包推荐 知乎 编辑:程序博客网 时间:2024/03/29 20:40

插入排序是很多排序算法的一个总称,这里主要学习一下直接插入排序、折半插入排序和shell排序
约定:文中提到的序列代表要排序的数据项的线性集合(如数组等),记录代表序列中的一个数据项(如数组中的一个元素)

(一)直接插入排序(Straight Insertion Sort)是一种非常简单的排序方法,它的基本操作是将一个记录插入到已排好序的有序表中。
算法的基本思想和自然语言描述:
**首先:将待排序的无序序列分割成为两部分,一部分是已经排好序的,一部分是待排序的。这里一开始的划分一般默认序列中的第一个记录为初始有序部分,剩余的组成待排序部分。
之后:每次从无序序列中取出第一个元素,把它插入到有序序列的合适位置,使有序序列仍然有序。
依次进行下去,进行了(n-1)趟扫描以后就完成了整个排序过程。**

直接插入排序属于稳定的排序,时间复杂性为o(n^2),空间复杂度为O(1)。
直接插入排序是由两层嵌套循环组成的。外层循环标识并决定待比较的数值。内层循环为待比较数值确定其最终位置。直接插入排序是将待比较的数值与它的前一个数值进行比较,所以外层循环是从第二个数值开始的。当前一数值比待比较数值大的情况下继续循环比较,直到找到比待比较数值小的并将待比较数值置入其后一位置,结束该次循环。

//定义一个数据包装类,方便我们定义自己的数据比较逻辑public class DataWrap implements Comparable<DataWrap>  {    int data;    String flag;    public DataWrap(int data, String flag){        this.data = data;        this.flag = flag;    }    public String toString(){        return data + flag;    }    //定义我们自己的数据比较逻辑    public int compareTo (DataWrap dw){        return this.data > dw.data ? 1             : (this.data == dw.data ? 0 : -1);    }}

直接插入排序的java代码实现:

public static void insertSort(DataWrap[] data) {        // 实现代码优化,这样每次比较时少一个计算指令        int arrayLength = data.length;        for (int i = 1 ; i < arrayLength ; ++i ){            //当整体后移时,保证data[i]的值不会丢失            DataWrap tmp = data[i];            //i索引处的值已经比前面所有值都大,表明已经有序,无需插入            //(i-1索引之前的数据已经有序的,i-1索引处元素的值就是最大值)            if (data[i].compareTo(data[i - 1]) < 0){                int j = i - 1;                //整体后移一格                for ( ; j >= 0 && data[j].compareTo(tmp) > 0 ; j--){                    data[j + 1] = data[j];                }                //最后将tmp的值插入合适位置                data[j + 1] = tmp;            }            // 用于观察每一步排序后的表            //  System.out.println(java.util.Arrays.toString(data));        }

当我们学习完直接插入排序之后我们可能想到去优化直接插入排序,这时为了达到一定的目的,我们能够从减少“比较”次数和减少“移动”次数两方面着手。进一步演化出下面两种排序:
折半插入排序:
由前面的直接插入排序的自然语言表述可知:我们在排序的过程中将表分为了有序和无序两个部分,这时为了减少记录在查找自己位置的过程中的比较次数,我们可以采用折半查找算法来代替之前的顺序查找算法。这时进行的插入排序称为折半插入排序(Binary Insertion Sort),由于折半插入排序仅仅是减少了关键字间的比较次数,而记录的移动次数不变,所有,折半插入排序的事件复杂度仍然为:O(n^2)
2-路插入排序:目的在于减少排序过程中的移动记录的次数,但为此我们需要n个记录的辅助空间来实现我们的算法。这一排序需要新开辟一个和待排序序列一样长度的新序列用于辅助。(可参见《计算机程序设计技巧》(第三卷,排序与查找))

以上两种排序不提供代码实现了。

(二)Shell 排序
Shell排序又称为“缩小增量排序”,由对直接插入排序的分析可以知道,直接插入排序的时间复杂度为O(n^2),但是若待排序的表是有序的,其时间复杂度可以提高至O(n)。
shell排序的基本思想是:
先将整个待排序的记录序列分割成为若干个子序列分别进行直接插入排序,待整个序列中的记录“基本有序”时,在对全体记录进行一个直接插入排序。
Shell排序的一个特点是:子序列的构成并不是简单的“逐段分割”的,而是将相隔某个“增量”的记录组成一个子序列。这个分割的增量我们称之为每次间隔的“步长”。随着间隔“步长”的减小,序列也被分割为更多的子序列,直到“步长”为1。
被推荐的希尔步长为: h = h * 3 + 1;
代码实现:

public class ShellSort{    public static void shellSort(DataWrap[] data) {        int arrayLength = data.length;        //h为希尔步长        int h = 1;        //按h * 3 + 1得到增量序列的最大值        while(h <= arrayLength / 3){            h = h * 3 + 1;        }        while(h > 0){            System.out.println("debug >>>> : h === " + h + "===");            for (int i = h ; i < arrayLength ; i++ ){                //当整体后移时,保证data[i]的值不会丢失                DataWrap tmp = data[i];                //i索引处的值已经比前面所有值都大,表明已经有序,无需插入                //(i-1索引之前的数据已经有序的,i-1索引处元素的值就是最大值)                if (data[i].compareTo(data[i - h]) < 0){                    int j = i - h;                    //整体后移h格                    for ( ; j >= 0 && data[j].compareTo(tmp) > 0 ; j-=h){                        data[j + h] = data[j];                    }                    //最后将tmp的值插入合适位置                    data[j + h] = tmp;                }                System.out.println(java.util.Arrays.toString(data));            }            h = (h - 1) / 3;        }    }}

Shell 排序是不稳定排序,取决于步长的值,Shell排序的时间复杂度介于:O(n^2)~~O (log n)^2

0 0
原创粉丝点击