Basic sorting (ii) insertion sort (direct insertion, Shell, binary)

  Insertion sort is one of the common internal order. Common including direct insertion sort insertion sort, Shell sort, binary sort. This part describes the three sort.

  Please indicate the source --http: //www.cnblogs.com/zrtqsk/p/3807611.html, thank you!

 

A direct insertion sort

  Direct insertion sort is probably a kind of sort of our most easily understood.

  1, the principle of

  For recording n elements.

  The first trip: the second element out to compare with the first element, in front of a small, large later.

  Second pass: the third element is inserted into the front out of the two elements to make them orderly.

  Third trip: the first four elements out to be inserted into the first three elements to make them orderly.

  ......

  The n-1 times: the n-th element is inserted into the front out n-1 elements, the sorting is completed.

 

  2, the Java realization

Package Penalty for the Sort; 

/ ** 
 * idea: If you want to post a collective shift array, it must traverse forward from the rear. 
 * 
 * @ClassName: InserSort 
 * @Description: insertion sort 
 * @author QSK 
 * @date 4:06:04 2014 Nian 6 21 afternoon 
 * / 
public  class InsertSort { 

    public  static  void the Sort ( int [] Source) { 
        SortUtil. the outputArray (Source); 
        int size = source.length;
         // second begins, through each array element 
        for ( int I =. 1; I <size; I ++ ) {
             // taken out 
            int TEMP =Source [I];
             // with good compared before sorting, inserting 
            for ( int J = 0; J <I; J ++ ) {
                 // if more than a small, 
                IF (TEMP < Source [J]) {
                     // then the original sorted, the collective shift 
                    for ( int K = I; K> J; K-- ) { 
                        Source [K] = Source [K -. 1 ]; 
                    } 
                    Source [J] = TEMP;
                     // output 
                    SortUtil. the outputArray (Source);
                     // after collective move out of the loop 
                    break ;
                } 
            } 
        } 
    } 

    // improved 
    public  static  void SORT1 ( int [] Source) { 
        SortUtil.outputArray (Source); 
        int size = source.length;
         // second begins, through each array element 
        for ( int . 1 = I; I <size; I ++ ) {
             // taken out 
            int TEMP = Source [I];
             // forward from the rear traverse, find the insertion position 
            int J;
             for (I = J -. 1; J> = 0 && TEMP <Source [J]; J, ) { 

                Source [J. 1 +] = Source [J]; 
            } 
            // Because of the above cycle is completed after performing the j--, so here a source [j + 1] Assignment 
            source [j + 1] = TEMP;
             // output 
            SortUtil.outputArray ( Source); 
        } 
    } 

    public  static  void main (String [] args) { 
        SORT1 (SortUtil.getRandomArray ()); 
    } 
}

As above, there are two to achieve, sort () I quickly wrote out, it is clear that three nested loops is very troublesome. Here we can see that traverse an array structure when the forward and backward traversal are very particular, to make it clear processing logic then decided to select forward or backward traversal. Comments explaining very clearly, not saying much. The results are as follows:

[6, 22, 71, 64, 33, 57, 38, 30, 42, 14]
[6, 22, 71, 64, 33, 57, 38, 30, 42, 14]
[6, 22, 71, 64, 33, 57, 38, 30, 42, 14]
[6, 22, 64, 71, 33, 57, 38, 30, 42, 14]
[6, 22, 33, 64, 71, 57, 38, 30, 42, 14]
[6, 22, 33, 57, 64, 71, 38, 30, 42, 14]
[6, 22, 33, 38, 57, 64, 71, 30, 42, 14]
[6, 22, 30, 33, 38, 57, 64, 71, 42, 14]
[6, 22, 30, 33, 38, 42, 57, 64, 71, 14]
[6, 14, 22, 30, 33, 38, 42, 57, 64, 71]

 

  3、时间复杂度和稳定性
  直接插入排序的时间复杂度是O(N2)
  假设被排序的数列中有N个数。遍历一趟的时间复杂度是O(N),需要遍历多少次呢?N-1!因此,直接插入排序的时间复杂度是O(N2)。

  直接插入排序是稳定的算法,它满足稳定算法的定义。

 

 

二、折半插入排序

  1、原理

  折半插入排序是对直接插入排序的改进。

  我们看直接插入排序的步骤简单而言其实就2步,第1步是从已经排好序的数组中找到该插入的点,第2步是将数据插入,然后后面的数据整体后移。那么直接插入排序是如何找到该插入的点的呢?是无脑式的从头到尾的遍历。问题是被插入的数组是排好序的,根本没有必要从头到尾遍历。折半插入排序就是改进了第1步——从已经排好序的数组中找到该插入的点

  折半插入排序是怎么做的呢?非常简单。取已经排好序的数组的中间元素,与插入的数据进行比较,如果比插入的数据大,那么插入的数据肯定属于前半部分,否则属于后半部分。这样,不断遍历缩小范围,很快就能确定需要插入的位置。这就是所谓“折半”。

  (Arrays类的binarySearch()方法就是折半查找的实现)

  

  2、Java实现

package sort;

public class HalfInsertSort {

    public static void sort(int[] source) {
        int size = source.length;
        for (int i = 1; i < size; i++) {
            // 拿出来
            int temp = source[i];
            int begin = 0; // 标记排好序的数组的头部
            int end = i - 1; // 标记排好序数组的尾部
            // 只要头部一直小于尾部,说明temp还在2个标记范围内
            while (begin <= end) {
                // 取2个标记的中间数据的值
                int mid = (begin + end) / 2;
                // 比较,若比中间值大,则范围缩小一半
                if (temp > source[mid]) {
                    begin = mid + 1;
                    // 否则,范围也是缩小一半
                } else {
                    end = mid - 1;
                }
                // 循环结束时,end<begin,即i应该插入到begin所在的索引
            }
            // 从begin到i,集体后移
            for (int j = i; j > begin; j--) {
                source[j] = source[j - 1];
            }
            // 插入i
            source[begin] = temp;
            SortUtil.outputArray(source);
        }
    }

    public static void main(String[] args) {
        sort(SortUtil.getRandomArray());
    }
}

 

如上,注释已经非常清楚了。结果如下:

[4, 11, 4, 41, 61, 83, 86, 81, 35, 90]
[4, 4, 11, 41, 61, 83, 86, 81, 35, 90]
[4, 4, 11, 41, 61, 83, 86, 81, 35, 90]
[4, 4, 11, 41, 61, 83, 86, 81, 35, 90]
[4, 4, 11, 41, 61, 83, 86, 81, 35, 90]
[4, 4, 11, 41, 61, 83, 86, 81, 35, 90]
[4, 4, 11, 41, 61, 81, 83, 86, 35, 90]
[4, 4, 11, 35, 41, 61, 81, 83, 86, 90]
[4, 4, 11, 35, 41, 61, 81, 83, 86, 90]

 

  3、时间复杂度和稳定性
  折半插入排序的时间复杂度是O(N2)
  折半插入排序算法是一种稳定的排序算法,比直接插入算法明显减少了关键字之间比较的次数,因此速度比直接插入排序算法快,但记录移动的次数没有变,所以折半插入排序算法的时间复杂度仍然为O(n^2),与直接插入排序算法相同。

  折半插入排序是稳定的算法,它满足稳定算法的定义。

 

 

 

三、Shell排序

  1、原理

  Shell排序也是对直接插入排序的改进。它实质上是一种分组插入方法。可以这么简单理解:

  对于n个元素的数组,假设增量为 h:

  第一趟  :  从第1个元素开始,每隔h取一个元素,那么最后可以得到n/h个元素,一边取,一边通过直接插入将这h个元素排序

  第二趟  :  从第2个元素开始,每隔h取一个元素,跟第一趟一样。  

  ...

  第h趟   :  从第h个元素开始,每隔h取一个元素,跟第一趟一样。

   (此时,整个数组还不是有序的)

  然后,减少h的值,重复上面的操作,直到h减小为1,排序完成。

 

  2、Java实现

package sort;

/**
 * @ClassName: ShellSort
 * @Description: 折半排序
 * @author qsk
 * @date 2014年6月22日 下午3:48:01
 */
public class ShellSort {

    public static void sort(int[] source) {
        // 排序前先输出
        SortUtil.outputArray(source);
        int size = source.length;
        // 增量
        int h = 1;
        // 得到增量的最大值
        while (h <= size / 3) {
            h = h * 3 + 1;
        }
        while (h > 0) {
            System.out.println("h的值为" + h);
            // 因为每个i都要跟i-h比较,所以从h到size遍历了每个数组元素
            for (int i = h; i < size; i++) {
                // 取值
                int temp = source[i];
                // 取i之前h距离的索引为j
                int j = i - h;
                // 如果temp比j对应的值小
                if (temp < source[j]) {
                    // 从j开始往前每隔h取一个值,如果这个值比temp要大,那么把这个值后移h个单位。
                    for (; j >= 0 && source[j] > temp; j -= h) {
                        source[j + h] = source[j];
                    }
                    // 最后将temp的值插入合适位置
                    source[j + h] = temp;
                    SortUtil.outputArray(source);
                }

            }
            h = (h - 1) / 3;
        }
    }

    public static void sort1(int[] source) {
        // 排序前先输出
        SortUtil.outputArray(source);
        int size = source.length;
        // 增量
        int h = 1;
        // 得到增量的最大值
        while (h <= size / 3) {
            h = h * 3 + 1;
        }
        while (h > 0) {
            System.out.println("h的值是" + h);
            // 0到h的遍历
            for (int x = 0; x < h; x++) {
                // i每次递增h,这两个for循环,遍历了所有数组元素
                for (int i = x + h; i < source.length; i = i + h) {
                    // 用temp记录i的值
                    int temp = source[i];
                    int j;
                    // 从j开始往前,每隔h取一个值与temp进行比较,若比temp大则向后移动h个单位
                    for (j = i - h; j >= 0 && source[j] > temp; j = j - h) {
                        source[j + h] = source[j];
                    }
                    source[j + h] = temp;
                }
                // 每一趟排序后输出
                SortUtil.outputArray(source);
            }
            h = (h - 1) / 3;
        }
    }

    public static void main(String[] args) {
        sort1(SortUtil.getRandomArray());
    }
}

这里有2个算法实现,第二个sort1()方法,用了3个for循环嵌套,比较容易理解,不过实在不够优雅。而sort1()将其进行了改进,使用2个for循环实现。

我们知道,Shell排序的关键是确定增量 h 的值,以及 h 如何减少。上文的 h 值算法由Knuth提出,是比较常用的取h值的算法。经常可以看到许多人实现shell排序,取h的时候,直接减半,这样,数组项移动的距离很长,不过移动元素的个数较少,相对而言没有Knuth的算法有效率。

上面的结果如下:

h的值是4
[4, 9, 89, 85, 36, 5, 85, 44, 96, 96]
[4, 5, 89, 85, 36, 9, 85, 44, 96, 96]
[4, 5, 85, 85, 36, 9, 89, 44, 96, 96]
[4, 5, 85, 44, 36, 9, 89, 85, 96, 96]
h的值是1
[4, 5, 9, 36, 44, 85, 85, 89, 96, 96]

 

  3、时间复杂度和稳定性
  Shell排序的时间复杂度是根据增量h的不同而不同,当增量为1时,希尔排序退化成了直接插入排序,此时的时间复杂度为O(N²)。Shell排序的时间复杂度在O(n3/2)-O(n7/6)之间。
  Shell排序算法是一种不稳定的排序算法。

 

 

参考:《Java程序员的基本修养》

  http://www.cnblogs.com/skywang12345/p/3597597.html

 

转载于:https://www.cnblogs.com/zrtqsk/p/3807611.html

Guess you like

Origin blog.csdn.net/weixin_34192816/article/details/93248538