数据结构与算法之经典排序

1 经典算法对比

数据结构与算法之经典排序_第1张图片

2 衡量算法的优劣指标

2.1 三大指标

(1)时间复杂度:主要是分析关键字的比较次数和记录的移动次数。
(2)空间复杂度:分析排序算法中需要多少辅助内存
(3)稳定性:假定在待排序的记录序列中,存在多个具有相同的关键字的记录,若经过排序,这些记录的相对次序保持不变,即在原序列中,ri=rj,且ri在rj之前,而在排序后的序列中,ri仍在rj之前,则称这种排序算法是稳定的;否则称为不稳定的。

2.2 指标的意义

(1)时间复杂度

  • 找出算法中的基本语句:算法中执行次数最多的那条语句就是基本语句,通常是最内层循环的循环体。
  • 计算基本语句的执行次数的数量级:只需计算基本语句执行次数的数量级,这就意味着只要保证基本语句执行次数的函数中的最高次幂正确即可,可以忽略所有低次幂和最高次幂的系数。这样能够简化算法分析,并且使注意力集中在最重要的一点上:增长率。
  • 用大O记号表示算法的时间性能。
    三步计算时间复杂度
    关于计算时间复杂度和空间复杂度
    算法(一)时间复杂度

(2)空间复杂度

(3)稳定性的意义

  • 如果只是简单的进行数字的排序,那么稳定性将毫无意义。
  • 如果排序的内容仅仅是一个复杂对象的某一个数字属性,那么稳定性依旧将毫无意义
  • 如果要排序的内容是一个复杂对象的多个数字属性,但是其原本的初始顺序毫无意义,那么稳定性依旧将毫无意义。
  • 除非要排序的内容是一个复杂对象的多个数字属性,且其原本的初始顺序存在意义,那么我们需要在二次排序的基础上保持原有排序的意义,才需要使用到稳定性的算法,例如要排序的内容是一组原本按照价格高低排序的对象,如今需要按照销量高低排序,使用稳定性算法,可以使得想同销量的对象依旧保持着价格高低的排序展现,只有销量不同的才会重新排序。(当然,如果需求不需要保持初始的排序意义,那么使用稳定性算法依旧将毫无意义)。

2 参考链接

在线动画演示各种排序算法过程 - aTool在线工具

java的几种经典排序算法

Java程序员必须掌握的8大排序算法

3 算法分类解析

3.1 直接插入排序

3.1.1 核心思想

  核心思想:对n个数据要进行 n-1 趟比较,每趟比较 a[i] 与 a[i-1]~~a[0],即是a[i] 与 i 前面的数据逐个比较 ,目的就是将一个待排序的数据,插入到前面已经排好序的有序序列中去,直到插完所有元素为止。

3.1.2 演示图

数据结构与算法之经典排序_第2张图片
这里写图片描述

3.1.3 源码

 /**
     * 直接插入排序
     */
    private static void insertSort(int[] arr) {
        if (arr == null || arr.length < 2) {
            return;
        }

        for (int i = 1; i < arr.length; i++) {
            for (int j = i - 1; j >= 0; j--) {
                //j=i-1开始,j--,直至j=-1 或 arr[j + 1] >= arr[j]
                if (arr[j + 1] <= arr[j]) {
                    //arr[j+1] 与 j 以及前面的数据逐个比较, 将小的数插入到前一位
                    swap(arr, j, j + 1);
                }
            }
        }
    }

    public static void swap(int[] arr, int i, int j) {
        int temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }

    public static void main0() {
        int[] a = {49, 38, 65, 97, 76, 13, 27, 49, 78, 34, 12, 64, 1};
        Log.i("TAG", "排序之前:");
        for (int anA : a) {
            Log.i("TAG", anA + " ");
        }

        Log.i("TAG", "排序之后:");
        insertSort(a);

        for (int anA : a) {
            Log.i("TAG", anA + " ");
        }
    }

3.1.4 时间复杂度计算

1)最坏情况
9, 8, 7, 6, 5, 4, 3, 2, 1(逆序)
//所以时间复杂度是:O(N^2)

(2)最好情况
1, 2, 3, 4, 5, 6, 7, 8, 9(正序)
//所以时间复杂度是:O(N)

3.2 二分法插入排序(折半插入排序,是对直接插入排序的改进)

3.2.1 核心思想

  具体操作为:在将一个新元素temp插入已排好序的数组的过程中,寻找插入点时, 将待插入区域的首元素设置为a[low],末元素设置为a[high],将temp与a[mid]比较,其中mid=(low+high)/2相比较;
  (1)如果比参考元素小,则选择a[low]到a[m-1]为新的插入区域(即high=m-1);否则选择a[m+1]到a[high]为新的插入区域(即low=m+1);
  (2)如此直至low<=high不成立,即将此位置之后所有元素后移一位,并将新元素插入a[low]。

3.2.2 演示图

这里写图片描述

3.2.3 源码

package com.dn.sort;
public class BinaryInsertSort {
    private static int[] binaryInsertSort(int[] a) {
        for (int i = 0; i < a.length; i++) {
            int temp = a[i];//待插入元素
            int low = 0;
            int high = i - 1;
            int mid = 0;
            // 确定要插入的位置
            while (low <= high) {
                // 找出low和high中间的索引  
                mid = (low + high) / 2;
                if (temp < a[mid]) {
                    // 如果值比中间值小,限制在小于mid的那一半搜索,让high左移到"中间下标-1"
                    high = mid - 1;
                } else {
                    // 如果值比中间值大,限制在大于mid的那一半搜索,让low右移到"中间下标+1"
                    low = mid + 1;
                }
            }

            //将low到i处的所有元素后移一位  
            for (int j = i - 1; j >= low; j--) {
                a[j + 1] = a[j];
            }
            //元素位置变化时
            if (low != i) {
                // 元素位置变化时插入新元素temp(因为插入都在mid的右边mid+1,即是low)
                a[low] = temp;
            }
        }
        return a;
    }

    public static void main(String[] args) {
        int[] a = { 49, 38, 65, 97, 176, 213, 227, 49, 78, 34, 12, 164, 11, 18,
                1 };
        System.out.println("排序之前:");
        for (int i = 0; i < a.length; i++) {
            System.out.print(a[i] + " ");
        }

        // 二分插入排序
        a = binaryInsertSort(a);
        System.out.println();
        System.out.println("排序之后:");
        for (int i = 0; i < a.length; i++) {
            System.out.print(a[i] + " ");
        }
    }
}

3.3 希尔排序(缩小增量排序)

3.3.1 核心思想

  核心思想:算法先将要排序的一组数按某个增量d(n/2,n为要排序数的个数)分成若干组,每组中记录的下标相差d.对每组中全部元素进行直接插入排序,然后再用一个较小的增量(d/2)对它进行分组,在每组中再进行直接插入排序。当增量减到1时,进行直接插入排序后,排序完成。

3.3.2 演示图

这里写图片描述

3.3.3 源码

package com.dn.sort;
//不稳定
public class HeerSort {
    private static int[] heerSort(int[] a) {
        System.out.println();
        int d = a.length / 2;//默认增量
        while (true) {
            for (int i = 0; i < d; i++) {
                for (int j = i; j + d < a.length; j += d) {//i开始,d为增量分组比较(eg:d=3; i=0; j=0、3,3、6,6、9,9、12,12、15)
                    int temp;
                    if (a[j] > a[j + d]) {//互换位置
                        temp = a[j];
                        a[j] = a[j + d];
                        a[j + d] = temp;
                    }
                }
            }
            if (d == 1) {
                break;
            }
            d--;//增量-1继续比较
        }
        return a;
    }

    /**
     * 增强希尔排序&冒泡排序
     */
    private static int[] heerBubbleSort(int[] a) {
        System.out.println();
        int d = a.length / 2;
        while (true) {
            d = d/2;
            for (int i = 0; i < d; i++) {
                for (int j = i; j + d < a.length; j += d) {//i开始,d为增量分组比较并组内排序(eg:d=3; i=0; j=0、3、6、9、12、15)
                    for (int n = i; n + d < a.length; n += d) {//冒泡排序(组内排序)
                        int temp;
                        if (a[n] > a[n + d]) {//互换位置
                            temp = a[n];
                            a[n] = a[n + d];
                            a[n + d] = temp;
                        }
                    }
                }
            }
            if (d == 1) {
                break;
            }
            d--;//增量-1继续比较
        }
        return a;
    }

    public static void main(String[] args) {
        int[] a = { 49, 38, 65, 97, 76, 13, 27, 49, 78, 34, 12, 64, 1, 33, 85,29 };
        System.out.println("排序之前:");
        for (int i = 0; i < a.length; i++) {
            System.out.print(a[i] + " ");
        }

        a = heerBubbleSort(a);
        System.out.println();
        System.out.println("排序之后:");
        for (int i = 0; i < a.length; i++) {
            System.out.print(a[i] + " ");
        }
    }
}

3.4 直接选择排序

3.4.1 核心思想

  核心思想:在待排序的数据元素中,选出最小(或最大)的一个数与第一个位置的数交换;然后在剩下的数当中再找最小(或最大)的与第二个位置的数交换,直到所有元素排完为止,简单选择排序是不稳定排序。

3.4.2 演示图

数据结构与算法之经典排序_第3张图片
这里写图片描述

3.4.3 源码

/**
     * 选择排序
     * 
     * @param arr
     */
    public static void selectSort(int[] arr) {
        if (arr == null || arr.length < 2) {
            return;
        }

        for (int i = 0; i < arr.length; i++) {
            int minIndex = i;
            for (int j = i + 1; j < arr.length; j++) {
                //找到最小数的下标
                minIndex = arr[j] < arr[minIndex] ? j : minIndex;
            }
            swap(arr, i, minIndex);
        }
    }

    public static void swap(int[] arr, int i, int j) {
        int temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }

    public static void main2() {
        int[] array = new int[]{57,68,59,52};

        selectSort(array);

        Log.i("TAG", "选择排序:");
        for (int num : array) {
            Log.i("TAG", num + " ");
        }
    }

3.5 堆排序

3.5.1 定义

  堆是具有以下性质的完全二叉树:每个结点的值都大于或等于其左右孩子结点的值,称为大顶堆;或者每个结点的值都小于或等于其左右孩子结点的值,称为小顶堆。如下图:
这里写图片描述
这里写图片描述

大顶堆:arr[i] >= arr[2i+1] && arr[i] >= arr[2i+2]  

小顶堆:arr[i] <= arr[2i+1] && arr[i] <= arr[2i+2]  

3.5.2 核心思想

  堆排序是一种树形选择排序,是对直接选择排序的有效改进。堆排序的关键在于:①建堆(大顶堆或小顶堆);②拿堆的根节点和最后一个节点交换。堆排序的核心思想:
  (1)将无需序列构建成一个堆,根据升序降序需求选择大顶堆或小顶堆;
  (2)将堆顶元素与末尾元素交换,将最大元素”沉”到数组末端;
  (3)重新调整结构,使其满足堆定义,然后继续交换堆顶元素与当前末尾元素,反复执行调整+交换步骤,直到整个序列有序。

3.5.3 存储模式:

数据结构与算法之经典排序_第4张图片

3.5.4 演示图

(1)构造初始堆。将给定无序序列构造成一个大顶堆(一般升序采用大顶堆,降序采用小顶堆)
这里写图片描述
(2)index = 2;值为8,不符合情况
(3)index = 1;值为6,符合情况 —>largest=index=4
  从最后一个非叶子结点开始(叶结点自然不用调整,第一个非叶子结点 arr.length/2-1=5/2-1=1,也就是下面的6结点),从左至右,从下至上进行调整。
这里写图片描述
(4)index = 4;值为6,不符合情况
(5)index = 0;值为4,符合情况 —>largest=index=1
  找到第二个非叶节点4,由于[4,9,8]中9元素最大,4和9交换:
这里写图片描述
数据结构与算法之经典排序_第5张图片
数据结构与算法之经典排序_第6张图片
(6)index = 1;值为4,符合情况
  交换导致了子根[4,5,6]结构混乱,继续调整,[4,5,6]中6最大,交换4和6:
这里写图片描述
(7)就将一个无需序列构造成了一个大顶堆
(8)将堆顶元素与末尾元素进行交换,使末尾元素最大。然后继续调整堆,再将堆顶元素与末尾元素交换,得到第二大元素,如此反复进行交换、重建、交换。
(9)将堆顶元素9和末尾元素4进行交换
这里写图片描述
(10)重新调整结构,使其继续满足堆定义
这里写图片描述
(11)再将堆顶元素8与末尾元素5进行交换,得到第二大元素8
这里写图片描述
(12)后续过程,继续进行调整,交换,如此反复进行,最终使得整个序列有序
这里写图片描述

3.5.5 参考链接

图解排序算法(三)之堆排序

3.5.5 源码

package com.dn.sort;
public class HeapSort { 
    public void heapSort(int[] array) {
        if (array == null || array.length <= 1) {
            return;
        }
        // 建立最大堆
        buildMaxHeap(array);
        // 排序
        for (int i = array.length - 1; i >= 1; i--) {
            // 最大的在0位置,那么开始沉降,这样每交换一次最大的值就丢到最后了
            exchangeElements(array, 0, i);
            // 调整大堆
            maxHeap(array, i, 0);//i--,不遍历最后的值
        }
    }

    /**
     * 建立最大堆
     * @param array
     */
    private void buildMaxHeap(int[] array) {
        if (array == null || array.length <= 1) {
            return;
        }
        int half = (array.length - 1) / 2;//遍历一半(即可得到另一半,最深一层),假设有5个
        for (int i = half; i >= 0; i--) {//从中间序号8开始
            //调整大堆,只需遍历i=2、1、0
            maxHeap(array, array.length, i);
        }
    }

    /**
     * 调整大堆️
     * @param array 堆数组
     * @param length 表示用于构造大堆的数组长度元素数量
     * @param index 从哪位置开始
     */
    private void maxHeap(int[] array, int length, int index) {
        int left = index * 2 + 1;//左节点
        int right = index * 2 + 2;//右节点
        int largest = index;//目标序号
        //left < length安全范围内
        if (left < length && array[left] > array[index]) {
            largest = left;//左节点大于根节点,将左序号需要赋值为目标序号
        }
        if (right < length && array[right] > array[largest]) {
            largest = right;//右节点大于根节点或左节点中一个,将右序号需要赋值为目标序号
        }
        if (index != largest) {//largest位置元素不是最大值
            //数据交换
            exchangeElements(array, index, largest);
            //继续调整大堆
            maxHeap(array, length, largest);//largest==变化的值
        }
    }

    /**
     * 数据交换
     */
    public void exchangeElements(int[] array, int index1, int index2) {
        int temp = array[index1];
        array[index1] = array[index2];
        array[index2] = temp;
    }

    /**
     * 打印
     */
    public void printArray(int[] array) {
        System.out.print("{");
        for (int i = 0; i < array.length; i++) {
            System.out.print(array[i]);
            if (i < array.length - 1) {
                System.out.print(", ");
            }
        }
        System.out.println("}");
    }

    public static void main(String[] args) {
        HeapSort heapSort = new HeapSort();
        int[] array = { 19, 8, 27, 6, 35, 14, 3, 12, 1, 0, 9, 10, 7 };

        System.out.println("Before heap:");
        heapSort.printArray(array);

        heapSort.heapSort(array);

        System.out.println("After heap sort:");
        heapSort.printArray(array);
    }
}

3.6 冒泡排序

3.6.1 核心思想

  基本思想:进行 n-1 趟比较并交换,对相邻的元素进行两两比较,大小不相等则进行交换,每一趟会将最小或最大的元素“冒”到末端,最终达到完全有序。

3.6.2 演示图

这里写图片描述

3.6.3 源码

 /**
     * 冒泡排序
     *
     * @param arr
     */
    public static void bubbleSort(int[] arr) {
        if (arr == null || arr.length < 2) {
            return;
        }

        for (int end = arr.length - 1; end > 0; end--) {//N-1趟排序
            for (int i = 0; i < end; i++) {//N-1次关键字的比较
                if (arr[i] > arr[i + 1]) {
                    swap(arr, i, i + 1);
                }
            }
        }
    }

    public static void swap(int[] arr, int i, int j) {
        int temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }

    public static void main3() {
        int arr[] = {9, 3, 1, 4, 2, 7, 8, 6, 5};
        Log.i("TAG", "排序前:");
        for (int data : arr) {
            Log.i("TAG", data + " ");
        }

        bubbleSort(arr);

        Log.i("TAG", "排序后:");
        for (int data : arr) {
            Log.i("TAG", data + " ");
        }
    }

3.6.4 时间复杂度计算

1)最坏情况
//第1趟,将9交换到arr.length-1的位置                  --->N-1次比较 (0~~arr.length-1)
//第2趟,将3交换到arr.length-2的位置                  --->N-1
//第3趟,将9交换到arr.length-3的位置                  --->N-2
......
//第N-1趟,将a交换到arr.length-N的位置                --->1次比较 (0~~1)

//计算公式是:(N-1+1)*(N-1)/2=N*(N-1)/2=(N^2+N)/2 
//所以时间复杂度是:O(N^2)

(2)最好情况
1, 2, 3, 4, 5, 6, 7, 8, 9(已经排好序)
//所以时间复杂度是:O(N)

3.7 快速排序

3.7.1 核心思想

  快速排序死对冒泡排序的一种改进。
  基本思想是:选择一个基准元素,通常选择第一个元素或者最后一个元素,通过一趟扫描,将待排序列分成两部分,一部分比基准元素小,一部分大于等于基准元素。此时基准元素在其排好序后的正确位置,然后再用同样的方法递归地排序划分的两部分。

3.7.2 演示图

数据结构与算法之经典排序_第7张图片=

3.7.3 源码

package com.dn.sort;

public class quickSort {
    public static int getMiddle(int[] list, int low, int high) {
        int temp = list[low]; // 数组的第一个作为中轴
        while (low < high) {
            while (low < high && list[high] >= temp) {
                high--;
            }
            list[low] = list[high]; // 比中轴小的记录移到低端

            while (low < high && list[low] <= temp) {
                low++;
            }
            list[high] = list[low]; // 比中轴大的记录移到高端
        }
        list[low] = temp; // 中轴记录到
        return low; // 返回中轴的位置
    }

    public static void quickSort(int[] list, int low, int high) {
        if (low < high) {
            int middle = getMiddle(list, low, high); // 将list数组进行一分为二
            quickSort(list, low, middle - 1); // 对低字段进行递归排序
            quickSort(list, middle + 1, high); // 对高字段进行递归排序
        }
    }

    public static void main(String[] args) {
        int a[] = { 49, 99, 56, 67, 18, 23, 34, 15, 35, 25, 73, 51 };
        if (a.length > 0) { // 查看数组是否为空
            quickSort(a, 0, a.length - 1);
        }
        for (int i = 0; i < a.length; i++)
            System.out.print(a[i] + "  ");
    }
}

3.8 归并排序

3.8.1 核心思想

  基本思想是:归并排序(MERGE-SORT)是利用归并的思想实现的排序方法,该算法采用经典的分治(divide-and-conquer)策略(分治法将问题分(divide)成一些小的问题然后递归求解,而治(conquer)的阶段则将分的阶段得到的各答案”修补”在一起,即分而治之)。
  细化来说,归并排序现将长度为n的无序序列看成是n个长度为1的有序子序列,首先做两两合并,得到n/2个长度为2的有序子序列,再做两两合并…不断重复这个过程,最终可以得到一个长度为n的有序序列。

3.8.2 演示图

这里写图片描述
这里写图片描述

3.8.3 演示步骤

//第1轮:[10,4] --> [4,10] ; [6,3] --> [3,6]
//得到:4      10       3       6
//位置:0      1        2       3
//标注:left middle rightStart right
//第2轮
//left=0,rightStart=2,4>3   --->a,temp=0,tempArray[0]=3
//left=0,rightStart=3,4<6   --->b,temp=1,tempArray[1]=4
//left=1,rightStart=3,10>6  --->a,temp=2,tempArray[2]=6
//left=1,rightStart=4,      --->c,temp=3,tempArray[3]=10

3.8.4 源码

package com.dn.sort;
public class MergeSort {
    public void mergeSort(int[] a, int left, int right) {
        if (left < right) {
            int middle = (left + right) / 2; //找出中间索引
            mergeSort(a, left, middle); //对左边数组进行递归
            mergeSort(a, middle + 1, right); //对右边数组进行递归
            merge(a, left, middle, right); //合并
        }
    }

    private void merge(int[] a, int left, int middle, int right) {
        //缓存数组
        int[] tempArray = new int[a.length];
        //右起第一个索引
        int rightStart = middle + 1;
        //记录左边第0个索引
        int leftPos = left;
        //temp记录缓存数组的索引
        int temp = left;
                                                           //0      1        2       3
        //比较两个小数组相应下标位置的数组大小,小的先放进缓存数组//left middle rightStart right
        while (left <= middle && rightStart <= right) {    //4      10       3       6
            if (a[left] <= a[rightStart]) {                //4>3,a ---> 4<6,b ---> 10>6,a ---> c
                tempArray[temp++] = a[left++];//b          //3      4      6      10
            } else {
                tempArray[temp++] = a[rightStart++];//a
            }
        }
        //如果左边还有数据需要拷贝,把左边数组剩下的拷贝到缓存数组
        while (left <= middle) {
            tempArray[temp++] = a[left++];//c
        }
        //如果右边还有数据......
        while (rightStart <= right) {
            tempArray[temp++] = a[rightStart++];
        }
        //将缓存数组中的内容复制回原数组
        while (leftPos <= right) {
            a[leftPos] = tempArray[leftPos++];
        }
    }

    public static void main(String[] args){
        MergeSort mergeSort = new MergeSort();
        int [] a = new int[]{10,4,6,3,8,2,5,7};      
        mergeSort.mergeSort(a, 0, a.length-1);
        for(int n:a){
            System.out.print(" "+n);
        }
    }
}

3.9 基数排序

3.9.1 核心思想

  基本思想是:将所有待比较数值(正整数)统一为同样的数位长度,数位较短的数前面补零。然后,从最低位开始,依次进行一次排序。这样从最低位排序一直到最高位排序完成以后,数列就变成一个有序序列。

3.9.2 演示图

数据结构与算法之经典排序_第8张图片
(1)个位—把个位的数值排序好:
数据结构与算法之经典排序_第9张图片
(2)十位—把十位的数值排序好:
数据结构与算法之经典排序_第10张图片
(3)百位—把百位的数值排序好:
数据结构与算法之经典排序_第11张图片

3.9.3 源码

package com.dn.sort;
public class BasicSort {
    public void basicSort(int[] array) {
        int max = 0;// 获取最大值
        int digit = 10;//0-9
        int times = 0;// 获取最大值位数

        for (int num : array) {
            if (max < num) {
                max = num;
            }
        }
        while (max > 0) {
            max = max / 10;
            times++;
        }

        //建立10个集合(0-9)
        List baseList = new ArrayList();// 多维数组
        for (int i = 0; i < digit; i++) {
            ArrayList list1 = new ArrayList<>();
            baseList.add(list1);
        }

        //进行times次分配和收集;
        for (int i = 0; i < times; i++) {
            //分配数组元素
            for (int j = 0; j < array.length; j++) {
                // 获取对应的位的值(pow是平方,i为0是个位,i为1是10位,i为2是百位)
                int x = array[j] % (int) Math.pow(10, i + 1) / (int) Math.pow(10, i);//取余、取整
                ArrayList list2 = baseList.get(x);
                list2.add(array[j]);// 把元素添加进对应下标数组
            }

            int count = 0;//元素计数器;
            //收集队列元素;
            for (int j = 0; j < digit; j++) {//0-9
                while (baseList.get(j).size() > 0) {
                    ArrayList list3 = baseList.get(j);// 拿到每一个集合
                    array[count] = list3.get(0);//获取集合第一个,每次都是新的数据
                    list3.remove(0);//删除分配给集合的数据
                    count++;
                }
            }
        }
    }

    public static void main(String[] args) {
        BasicSort basicSort = new BasicSort();
        int[] a = { 136, 2, 6, 8, 9, 2, 8, 11, 23, 56, 34, 90, 89, 29, 145,209, 320, 78, 3 };
        basicSort.basicSort(a);
        for (int n : a) {
            System.out.print(" " + n);
        }
    }
}

3.10 桶排序

3.10.1 背景

  给阿里 2 万多名员工按年龄排序应该选择哪个算法?阿里员工特征,2万人,规模较小;处于18-99,尤其是23-40占了大部分,使用桶排序适合。

3.10.2 核心思想

  基本思想是:桶排序(Bucket Sort)的原理很简单,它是将数组分到有限数量的桶子里。
  过程:假设待排序的数组a中共有N个整数,并且已知数组a中数据的范围[0, MAX)。在桶排序时,创建容量为MAX的桶数组r,并将桶数组元素都初始化为0;将容量为MAX的桶数组中的每一个单元都看作一个”桶”。在排序时,逐个遍历数组a,将数组a的值,作为”桶数组r”的下标。当a中数据被读取时,就将桶的值加1。例如,读取到数组a[3]=5,则将r[5]的值+1。
  特征:(1)桶排序是稳定的; (2)桶排序是常见排序算法中最快的一种,大多数情况下比快排和归并排序还要快 (3)桶排序非常快但是也非常消耗空间,典型的以空间换时间,基本上是最耗内存的一种排序算法。

3.10.3 演示图

  bucketSort(a, n, max)是作用是对数组a进行桶排序,n是数组a的长度,max是数组中最大元素所属的范围[0,max)。假设a={8,2,3,4,3,6,6,3,9}, max=10。此时,将数组a的所有数据都放到需要为0-9的桶中。如下图:
这里写图片描述

3.10.4 源码

public class BucketSort {
    /*
     * 桶排序
     * 参数说明:
     *     a -- 待排序数组
     *     max -- 数组a中最大值的范围
     */
    public static void bucketSort(int[] a, int max) {
        int[] buckets;

        if (a==null || max<1)
            return ;

        // 创建一个容量为max的数组buckets,并且将buckets中的所有数据都初始化为0。
        buckets = new int[max];

        // 1. 计数
        for(int i = 0; i < a.length; i++) 
            buckets[a[i]]++; 

        // 2. 排序
        for (int i = 0, j = 0; i < max; i++) {
            while( (buckets[i]--) >0 ) {
                a[j++] = i;
            }
        }

        buckets = null;
    }

    public static void main(String[] args) {
        int i;
        int a[] = {8,2,3,4,3,6,6,3,9};

        System.out.printf("before sort:");
        for (i=0; iout.printf("%d ", a[i]);

        bucketSort(a, 10); // 桶排序

        System.out.printf("after  sort:");
        for (i=0; iout.printf("%d ", a[i]);
    }
}
before sort:8 2 3 4 3 6 6 3 9 
after  sort:2 3 3 3 4 6 6 8 9 

3.10.5 参考链接

桶排序

[排序算法]–桶排序的Java实现

Java实现桶排序

4 总结

数据结构与算法之经典排序_第12张图片
数据结构与算法之经典排序_第13张图片
图片来源于:八大经典排序算法

4.1 稳定性

(1)稳定:冒泡排序、插入排序、归并排序和基数排序。
(2)不稳定:选择排序、快速排序、希尔排序、堆排序。
(3)参考链接:排序算法的稳定性及其意义

4.2 平均时间复杂度

(1)O(n^2):直接插入排序,简单选择排序,冒泡排序。
(2)在数据规模较小时(9W内):直接插入排序,简单选择排序差不多。当数据较大时:冒泡排序算法的时间代价最高。性能为O(n^2)的算法基本上是相邻元素进行比较,基本上都是稳定的。
(3)O(nlogn):快速排序,归并排序,希尔排序,堆排序。快排是最好的, 其次是归并和希尔,堆排序在数据量很大时效果明显。

4.3 排序算法的选择

4.3.1 数据规模较小(9W内)

(1)直接插入排序、冒泡排序:待排序列基本有序的情况下,对稳定性有要求;
(2)直接选择排序:待排序列无序,对稳定性不作要求;

4.3.2 数据规模不是很大

(1)归并排序:序列本身基本有序,对稳定性有要求,空间允许下。
(2)快速排序:序列本身无序。完全可以用内存空间,对稳定性没有要求,此时要付出log(n)的额外空间。

4.3.3 数据规模很大

(1)归并排序、堆排序。

4.3.4 序列初始基本有序(正序)

(1)直接插入排序、冒泡排序。

你可能感兴趣的:(数据结构与算法)