八大排序算法

前言

算法名称 分类 时间复杂度(最好情况) 时间复杂度(平均情况) 时间复杂度(最坏情况) 空间复杂度 是否稳定
直接插入排序 插入排序 O(n) O(n2) O(n2) O(1)
冒泡排序 交换排序 O(n) O(n2) O(n2) O(1)
简单选择排序 选择排序 O(n2) O(n2) O(n2) O(1)
希尔插入排序 插入排序 O(1)
快速排序 交换排序 O(nlog2n) O(nlog2n) O(n2) O(log2n)
堆排序 选择排序 O(nlog2n) O(nlog2n) O(nlog2n) O(1)
2-路归并排序 归并排序 O(nlog2n) O(nlog2n) O(nlog2n) O(n)
基数排序 基于关键字排序 O(d(n+r)) O(d(n+r)) O(d(n+r)) O( r )

1. 插入排序

1.1 直接插入排序
    //直接插入排序,从小到大 ,平均性能O(n2)
    public static void InsertSort(int[] list) {
        int key = 0;  //哨兵位
        int i, j;
        //第0位已经是完成排序,从第1位开始遍历
        for (i = 1; i < list.length; i++) {
            if (list[i] < list[i - 1]) {
                key = list[i];
                for (j = i - 1; j >= 0 && list[j] > key; j--) {
                    list[j + 1] = list[j];   //往后移动位置,直到找到i该放置的地方
                }
                list[j + 1] = key;
            }
        }
    }

1.2 折半插入排序(直接插入的改进)
 //在直接插入的基础上改进,折半插入排序,平均性能O(n2),只是减少了比较的次数
    public static void BinaryInsertSort(int[] list) {
        int key, i, j, low, high, mid = 0;
        for (i = 1; i < list.length; i++) {
            if (list[i] < list[i - 1]) {
                low = 0;
                high = i - 1;
                key = list[i];
                while (low <= high) {  //折半查找。默认左侧已经递增有序
                    mid = (low + high) / 2;  //取中间点
                    if (list[mid] < key)
                        low = mid + 1;     //查找右半边
                    else
                        high = mid - 1;    //查找左半边

                }
                for (j = i - 1; j >= high + 1; j--)
                    list[j + 1] = list[j];
                list[high + 1] = key;
            }
        }
    }
1.3 希尔插入排序(shell排序/缩小增量排序)
 //希尔插入排序,根据步长而定,约等于O(n2)
    public static void ShellInsertSort(int[] list) {
        int dk, i, j, key;
        for (dk = list.length / 2; dk > 0; dk /= 2) {
            for (i = dk; i < list.length; i++) {
                if (list[i] < list[i - dk]) {
                    key = list[i];
                    for (j = i - dk; j >= 0 && list[j] > key; j -= dk)
                        list[j + dk] = list[j];
                    list[j + dk] = key;
                }
            }
        }
    }

2. 交换排序

2.1 冒泡排序
//冒泡排序|起泡排序,选择一个方向两两比较相邻元素的值,每趟起泡下来可以确定一个元素的最终位置
    public static void BubbleSort(int[] list) {
        int i, j, flag, temp;
        for (i = 0; i < list.length; i++) {
            flag = 0;     //标记位
            for (j = list.length - 1; j > i; j--) {
                if (list[j] < list[j - 1]) {   //进行交换操作
                    temp = list[j];
                    list[j] = list[j - 1];
                    list[j - 1] = temp;
                    flag = 1;
                }
            }
            if (flag == 0) return;     //表示这一趟没有交换,即已经有序,可以结束排序
        }
    }

2.2 快速排序

(对冒泡排序的一种改进,等会的测试中会看到提升了很大性能)

//快速排序,基于分治的思想,是对冒泡排序的一种改进
//每趟排序下来使基准左边的值都小于他,右边的值都大于他
    public static void QuickSort(int[] list, int low, int high) {
        int pivot;   //划分点
        if (low < high) {   //跳出递归的条件
            pivot = Partition(list, low, high);  //获得划分点
            QuickSort(list, low, pivot - 1);   //左侧子表划分
            QuickSort(list, pivot + 1, high);   //右侧子表划分
        }
    }

    //一趟排序过程,划分操作其实有很多版本,我这里取我学习时候的严版教材的
    public static int Partition(int[] list, int low, int high) {
        int key, i, j;
        key = list[low];  //以第一个为基准点,以此为划分点
        while (low < high) {
            while (low < high && list[high] >= key) high--;
            list[low] = list[high];          //将比基准点小的值移到左边
            while (low < high && list[low] <= key) low++;
            list[high] = list[low];          //将比基准点大的值移到右边
        }
        list[low] = key;        //基准点最终被存放的位置
        return low;             //返回区分的坐标点,左侧都比他小,右侧都比他大
    }

3. 选择排序

3.1 简单选择排序
    //简单选择排序,每次把最大/最小的元素选出来,置于一侧
    public static void SelectSort(int[] list) {
        int min, i, j, temp;
        for (i = 0; i < list.length; i++) {
            min = i;   //设置第一个元素为最小值
            for (j = i + 1; j < list.length; j++)
                if (list[j] < list[min]) min = j;  //遍历找到最小的那个值
            if (min != i) {            //进行交换
                temp = list[min];
                list[min] = list[i];
                list[i] = temp;
            }
        }
    }
3.2 堆排序
    //堆排序,比较复杂的一个排序,用于选出前a个最大/小的值比较合适
    //刚开始建立大顶堆或者小顶堆,然后一次次的将堆顶元素和最后一个元素交换
    //破坏堆结构再重新调整建堆,从第一个元素到第n-1个元素
    //最后就得到了一个有序数列。即达到了排序的目的
    //实际是一颗完全二叉树,一般都用数组来表示堆
    //若从1开始 ,则i结点的父结点下标就为i/2,左右子结点下标分别为2*i和2*i+1
    //若从0开始 ,则i结点的父结点下标就为(i–1)/2,左右子结点下标分别为2*i+1和2*i+2
    //此处以从0开始为例,创造一个大顶堆(大顶堆得到升序,小顶堆得到逆序)
    public static void HeapSort(int[] list) {
        int i, j, key, temp;
        //以大顶堆为例,初始化堆
        //从i=0~[n/2],反复调整堆,不必从最后一个节点开始
        // 因为完全二叉树,后半子表没有子节点,所以从中间开始遍历即可
        for (i = list.length / 2 - 1; i >= 0; i--) {
            AdjustDown(list, i, list.length);
        }
        //从后往前遍历,堆顶元素为最值元素
        // 依次将堆顶元素和最后一个元素交换,达到排序目的
        for (i = list.length -1; i > 0; i--) {
            temp = list[i];       //堆顶元素和最后一个元素进行交换
            list[i] = list[0];
            list[0] = temp;
            AdjustDown(list, 0, i );  //遍历调整
        }
    }

    //将itr位置的元素向下调整
    //itr是位置坐标,len是数组长度
    public static void AdjustDown(int[] list, int itr, int len) {
        int i, j, temp, key;
        key = list[itr];
        for (i = 2 * itr + 1; i < len; i = i * 2 + 1) {
            if (i+1 < len && list[i] < list[i + 1]) i++;  //取较大的子节点
            if (key >= list[i]) break;     //比子节点要大,遍历结束
            else {
                list[itr] = list[i];   //将值往下走
                itr = i;
            }
        }
        list[itr]=key;    //赋值回去
    }

4. 归并排序和基数排序

4.1 2路-归并排序
    //归并排序,将数组分成好几列,分别排序再整合
    // 这里以2路为例,每两个为一组进行排序
    public static void MergeSort(int[] list, int low, int high) {
        if (low < high) {
            int mid = (low + high) / 2;         //取中间点
            MergeSort(list, low, mid);      //左侧遍历排序
            MergeSort(list, mid + 1, high);  //右侧遍历排序
            Merge(list, low, mid, high);     //左右有序列表合并
        }
    }

    static int[] tempList = new int[10000000];   //辅助数组

    //合并方法,这里要用到辅助数组,所以空间复杂度O(n)
    //现在左右两侧都已经是有序数组,需要合并
    public static void Merge(int[] list, int low, int mid, int high) {
        int i, j, k, key;
        for (i = low; i <= high; i++) tempList[i] = list[i];    //辅助数组赋值
        for (i = low, j = mid + 1, k = i; i <= mid && j <= high; k++) {
            if (tempList[i] < tempList[j]) {     //将较小值复制回去
                list[k] = tempList[i++];
            } else {
                list[k] = tempList[j++];
            }
        }
        while (i <= mid) list[k++] = tempList[i++];   //若左表没比较完,复制左表内容
        while (j <= high) list[k++] = tempList[j++];  //若右表没比较完,复制右表内容
    }
4.2 基数排序
    // 基数排序,这个脑回路有点强。。不是基于比较进行排序,而是采用关键字排序思想
    // 借助“分配”和“收集”两种操作对单逻辑关键字进行排序
    public static void RadixSort(int[] list) {
        int i, j, k, temp, max, exp;
        for (i = 0, max = list[0]; i < list.length; i++)
            if (list[i] > max) max = list[i];    //先获得数组的最大值,根据他来获得位数
        for (exp = 1; max / exp > 0; exp *= 10)
            RadixCount(list, exp);          //从各位开始,对数组按exp进行排序
    }

     public static void RadixCount(int[] list, int exp) {
        int i, j, k, temp, max;
        int[][] outputs = new int[10][list.length];            //辅助数组, 十进制,0~9共10位
        int[] buckets = new int[10];
        //按第exp位划分,将对应的数据放进对应的桶
        for (i = 0; i < list.length; i++) {
            temp = (list[i] / exp) % 10;
            outputs[temp][buckets[temp]++] = list[i];
        }

        //排序好的值赋值回去
        for (i = 0, k = 0; i < buckets.length; i++)
            for (j = 0; j < buckets[i]; j++, k++)
                list[k] = outputs[i][j];

        outputs = null;  //赋值为null,更快地回收 变量的内存空间
        buckets = null;
    }

5. 时间比较

打完收工,运行下比较时间,这里取了几组小数据,一组是1000个数据,一组是10000个数据,卒子后一个500000。。其实后面几个排序就算是一亿个数据,也能在一分钟内解决,嗯,但是运行冒泡这些排序。。太耗时了,就不搞这么大了,读者可以自行尝试:

//测试程序
public static void main(String[] args)  {
        int[] list = new int[100000];
        int[] list2;
        long t1, t2;
        for (int i = 0; i < list.length; i++)
            list[i] = (int) (Math.random() * 1000000);
        System.out.println("原始数据,共有" + list.length + "个数据:");
        System.out.print("[");
//        Arrays.stream(list).forEach(o -> System.out.print(o + " "));
        System.out.println("]");


        list2 = null;
        list2 = list.clone();
        t1 = System.currentTimeMillis();
        InsertSort(list2);
        t2 = System.currentTimeMillis();
        System.out.println("直接插入时间耗时:" + (t2 - t1) + "ms:");
        System.out.print("[");
//        Arrays.stream(list2).forEach(o -> System.out.print(o + " "));
        System.out.println("]");


        list2 = null;
        list2 = list.clone();
        t1 = System.currentTimeMillis();
        BinaryInsertSort(list2);
        t2 = System.currentTimeMillis();
        System.out.println("折半插入时间耗时:" + (t2 - t1) + "ms:");
        System.out.print("[");
//        Arrays.stream(list2).forEach(o -> System.out.print(o + " "));
        System.out.println("]");


        list2 = null;
        list2 = list.clone();
        t1 = System.currentTimeMillis();
        ShellInsertSort(list2);
        t2 = System.currentTimeMillis();
        System.out.println("希尔插入时间耗时:" + (t2 - t1) + "ms:");
        System.out.print("[");
//        Arrays.stream(list2).forEach(o -> System.out.print(o + " "));
        System.out.println("]");


        list2 = null;
        list2 = list.clone();
        t1 = System.currentTimeMillis();
        BubbleSort(list2);
        t2 = System.currentTimeMillis();
        System.out.println("冒泡排序时间耗时:" + (t2 - t1) + "ms:");
        System.out.print("[");
//        Arrays.stream(list2).forEach(o -> System.out.print(o + " "));
        System.out.println("]");

        list2 = null;
        list2 = list.clone();
        t1 = System.currentTimeMillis();
        QuickSort(list2, 0, list.length - 1);
        t2 = System.currentTimeMillis();
        System.out.println("快速排序时间耗时:" + (t2 - t1) + "ms:");
        System.out.print("[");
//        Arrays.stream(list2).forEach(o -> System.out.print(o + " "));
        System.out.println("]");

        list2 = null;
        list2 = list.clone();
        t1 = System.currentTimeMillis();
        SelectSort(list2);
        t2 = System.currentTimeMillis();
        System.out.println("简单选择" +
                "时间耗时:" + (t2 - t1) + "ms:");
        System.out.print("[");
//        Arrays.stream(list2).forEach(o -> System.out.print(o + " "));
        System.out.println("]");


        list2 = null;
        list2 = list.clone();
        t1 = System.currentTimeMillis();
        HeapSort(list2);
        t2 = System.currentTimeMillis();
        System.out.println("大顶堆排序" +
                "时间耗时:" + (t2 - t1) + "ms:");
        System.out.print("[");
//        Arrays.stream(list2).forEach(o -> System.out.print(o + " "));
        System.out.println("]");


        list2 = null;
        list2 = list.clone();
        t1 = System.currentTimeMillis();
        MergeSort(list2, 0, list.length - 1);
        t2 = System.currentTimeMillis();
        System.out.println("2路归并排序" +
                "时间耗时:" + (t2 - t1) + "ms:");
        System.out.print("[");
//        Arrays.stream(list2).forEach(o -> System.out.print(o + " "));
        System.out.println("]");


        list2 = null;
        list2 = list.clone();
        t1 = System.currentTimeMillis();
        RadixSort(list2);
        t2 = System.currentTimeMillis();
        System.out.println("基数排序" +
                "时间耗时:" + (t2 - t1) + "ms:");
        System.out.print("[");
//        Arrays.stream(list2).forEach(o -> System.out.print(o + " "));
        System.out.println("]");
    }

1000个数据:
八大排序算法
10000个数据:
八大排序算法
500000个数据:
八大排序算法