算法 —— 排序 —— 初级排序

前言

算法,在计算机领域都是一个永恒的话题。

尤其是当今这个信息科技高速飞跃的时代,对算法的深入学习和挖掘可以说是炽手可热。

不说宏观的,为了更好的工作和生活,深入浅出学习算法。


选择排序

选择排序是一种最简单的入门排序算法

每次排序都会遍历数组中还未排序的元素,然后不断比较,找出最小的元素。并且重复这个动作至数组排序完毕。

例如有一个数组[0,5,3,4,2,1],选择排序算法即会将第0个元素从左往右一直对比(交换)直到数组的大小。

这里写图片描述


  • 为了找出数组的最小元素而扫描一遍数组,并且不能为下一遍扫描提供信息。

即使是一个已经有序的数组仍然也要遍历的去一次次对比排序,这便是选择排序的最大缺点。所以也称上面这种排序为直接选择排序


public class S_选择排序 {
    public static void main(String[] args) {
        int[] a = new int[] { 0, 5, 3, 4, 2, 1 };
        sort(a);
        System.out.println(Arrays.toString(a));
    }

    public static void sort(int[] a) {
        int LEN = a.length;
        for (int i = 0; i < LEN; i++) {//从第0个元素到数组的长度
            int min = a[i];

            for (int j = i + 1; j < LEN; j++) {
                //从当前元素对比到最后一个元素
                //直到找出最小的数
                if (min > a[j]) {
                    min = a[j];
                    exch(a, i, j);
                }
            }
        }
    }

    private static void exch(int a[], int i, int j) {
        int temp = a[i];
        a[i] = a[j];
        a[j] = temp;
    }

}



插入排序

插入排序一样,当前交换排序的索引(下标)左边所有的元素都是有序的。

但是!插入排序的优化点在于: 插入排序是将索引右边 (LEN - i )个 元素,和已经有序的左边(i )个元素进行对比。

通俗的概括就是 :

  • 选择排序 : 每个元素都要和无序的数组比较。
  • 插入排序 : 每个无序的元素和已经有序的数组比较。(类似于整理桥牌)

这里写图片描述

public class S_插入排序 {
    public static void main(String[] args) {

        int[] a = new int[] { 0, 5, 3, 4, 2, 1 };
        sort(a);
        System.out.println(Arrays.toString(a));
    }

    private static void sort(int a[]) {
        int LEN = a.length;
        for (int i = 1; i < LEN; i++) {//从第i个元素开始向左插入排序

            for (int j = i; j > 0 && a[j] < a[j - 1]; j--) {
                exch(a, j, j - 1);
            }
        }
    }

    private static void exch(int a[], int i, int j) {
        int temp = a[i];
        a[i] = a[j];
        a[j] = temp;
    }
}



和选择排序不同的是,插入排序所需的时间取决于数组的初始顺序。
 
例如对一个已经有序(或者接近有序)的数组(正序),对比(a[j] < a[j-1])执行的次数会非常的小,整体的排序速度将会非常快。

但是例如 :

【1,2 ,….. ,100, 2 , 1 , 0】

此时后面的3个元素(2,1,0)便需要多次对比,这便是插入排序的缺点了(直接插入排序)

那么下面我们来看一下,直接插入排序的升级版 希尔(shell)排序



选择排序和插入排序对比

初级排序算法的可视轨迹图(摘自算法4)

这里写图片描述



希尔排序

上面说道,直接插入排序的一大弊端是 :

如果主键最小的元素正好在数组的尽头,要将它挪到正确的位置就需要 N - 1 次移动。

希尔排序将会弥补该缺陷,它的思想是假设任意间隔h的元素都是有序的

通俗的来说 : 我们可以直接比较交换 (n ,n + h)这两个元素。(h可变)

例如有100个元素

  1. 当 h = 90 ,直接比较交换(1,91)(2,92)(3,93)…(10,100)
  2. 当 h = 50 ,直接比较交换(1,51)(2,52)(3,53)…(50,100)

如果 h 数值比较大,我们就能将元素移动到很远的位置,为实现更小的 h 有序创造方便。


当数组大小=16,h=4情况。(摘自算法4)
这里写图片描述

比较交换间隔 h = 4 的元素



引入 h

因为希尔排序是直接插入排序的升级版,关键在于引入 h 这个思想。
所以在代码上的升级是

这里写图片描述


h 的取值

那么 h 的取值有哪些呢,在希尔排序中,h 的取值为从 1 每次递增至 (3 * h + 1 ),直至小于 LEN 的最大值。

也就是 1 , 4 , 13 , 40 , 121 , 364 , 1093 … …
 
当 LEN = 15时, h 分别为 13 , 4 , 1;
当 LEN = 150时,h 分别为 121 , 40 , 13 , 4 , 1;

    private static void sort(int a[]) {
        int LEN = a.length;
        int h = 1;

        while (h < LEN / 3) {
            h = h * 3 + 1;//1,4,13,40,121...
        }

        while (h >= 1) {
            for (int i = h; i < LEN; i++) {
                for (int j = i; j > 0 && a[j] < a[j - h]; j--) {
                    exch(a, j, j - h);
                }
            }
            h /= 3;//...121,40,13,4,1
        }
    }

这里写图片描述

希尔排序也就是在直接插入排序的基础上,加入一个外循环将 h 按照递增序列递减。当 h = 1 时,也就是直接插入排序。



希尔排序的性能

希尔排序的可视轨迹(摘自算法4)
这里写图片描述

有经验的程序员有时会选择希尔排序,因为对中等大小的数组它的运行时间是可以接受的。
 
并且它的代码量很小,且不需要额外的内存空间(新建数组)。
 
在后面会看到更高效的算法,但除了对于很大的数组,它们可能只会比希尔排序快接近2倍。

猜你喜欢

转载自blog.csdn.net/qian520ao/article/details/79769140