一篇文章学会Java十大排序算法

简介: 一篇文章学会Java十大排序算法

Java十大排序算法

1.1 选择排序

选择排序算法的运作如下:

①从待排序序列中,找到关键字最小的元素;

②如果最小元素不是待排序序列的第一个元素,将其和第一个元素互换;

③从余下的 N - 1 个元素中,找出关键字最小的元素,重复①、②步,直到排序结束。

/**
 * 选择排序
 *
 * @param array
 * @return
 */
public static int[] selectSort(int[] array) {
    for (int i = 0; i < array.length - 1; i++) {
        //标记第一个为待比较的数
        int index = i;
        //然后从后面遍历与第一个数比较
        for (int j = i + 1; j < array.length; j++) {
            //如果小,就交换最小值
            if (array[j] < array[index]) {
                //保存最小元素的下标
                index = j;
            }
        }
        //找到最小值后,将最小的值放到第一的位置,进行下一遍循环
        int temp = array[index];
        array[index] = array[i];
        array[i] = temp;
    }
    return array;
}

1.2 快速排序

快速排序算法的运作如下:

①从数列中挑出一个元素,称为"基准"(pivot)。

②重新排序数列,所有比基准值小的元素摆放在基准前面,所有比基准值大的元素摆在基准后面(相同的数可以到任一边)。在这个分区结束之后,该基准就处于数列的中间位置。这个称为分区(partition)操作。

③递归地(recursively)把小于基准值元素的子数列和大于基准值元素的子数列排序。

/**
 * 快速排序(递归)
 *
 * @param arr  待排序数组
 * @param low  左边界
 * @param high 右边界
 */
public static int[] quickSort(int[] arr, int low, int high) {
    if (arr.length <= 0) {
        return arr;
    }
    if (low >= high) {
        return arr;
    }
    int left = low;
    int right = high;
    //挖坑1:保存基准的值
    int temp = arr[left];
    while (left < right) {
        //坑2:从后向前找到比基准小的元素,插入到基准位置坑1中
        while (left < right && arr[right] >= temp) {
            right--;
        }
        arr[left] = arr[right];
        //坑3:从前往后找到比基准大的元素,放到刚才挖的坑2中
        while (left < right && arr[left] <= temp) {
            left++;
        }
        arr[right] = arr[left];
    }
    //基准值填补到坑3中,准备分治递归快排
    arr[left] = temp;
    quickSort(arr, low, left - 1);
    quickSort(arr, left + 1, high);
    return arr;
}

1.3 冒泡排序

冒泡排序算法的运作如下:

①比较相邻的元素。如果第一个比第二个大,就交换他们两个。

②对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。这步做完后,最后的元素会是最大的数。

③针对所有的元素重复以上的步骤,除了最后一个。

④持续每次对越来越少的元素重复上面的步骤①~③,直到没有任何一对数字需要比较。

/**
 * 冒泡排序
 *
 * @param array 将要被排序的数组
 * @return 排序完成的数组
 */
public static int[] bubbleSort(int[] array) {
    for (int i = 0; i < array.length - 1; i++) {
        boolean flag = true;
        for (int j = 0; j < array.length - 1 - i; j++) {
            if (array[j] > array[j + 1]) {
                int temp = array[j];
                array[j] = array[j + 1];
                array[j + 1] = temp;
                flag = false;
            }
        }
        if (flag) {
            break;
        }
    }
    return array;
}

1.4 归并排序

归并排序算法的运作如下:

归并排序算法是将两个(或两个以上)有序表合并成一个新的有序表,即把待排序序列分为若干个子序列,每个子序列是有序的。然后再把有序子序列合并为整体有序序列。

归并排序可通过两种方式实现:

  • 自上而下的递归
  • 自下而上的迭代

一、递归法(假设序列共有n个元素):

①. 将序列每相邻两个数字进行归并操作,形成 floor(n/2)个序列,排序后每个序列包含两个元素;

②. 将上述序列再次归并,形成 floor(n/4)个序列,每个序列包含四个元素;

③. 重复步骤②,直到所有元素排序完毕。

二、迭代法

①. 申请空间,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列

②. 设定两个指针,最初位置分别为两个已经排序序列的起始位置

③. 比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下一位置

④. 重复步骤③直到某一指针到达序列尾

⑤. 将另一序列剩下的所有元素直接复制到合并序列尾

代码

/**
 * 归并排序(递归)
 *
 * ①. 将序列每相邻两个数字进行归并操作,形成 floor(n/2)个序列,排序后每个序列包含两个元素;
 * ②. 将上述序列再次归并,形成 floor(n/4)个序列,每个序列包含四个元素;
 * ③. 重复步骤②,直到所有元素排序完毕。
 * @param arr  待排序数组
 */
public static int[] mergingSort(int[] arr){
    if(arr.length <= 1) return arr;
    int num = arr.length >> 1;
    int[] leftArr = Arrays.copyOfRange(arr, 0, num);
    int[] rightArr = Arrays.copyOfRange(arr, num, arr.length);
    System.out.println("split two array: " + Arrays.toString(leftArr) + " And " + Arrays.toString(rightArr));
    return mergeTwoArray(mergingSort(leftArr), mergingSort(rightArr));      //不断拆分为最小单元,再排序合并
}
private static int[] mergeTwoArray(int[] arr1, int[] arr2){
    int i = 0, j = 0, k = 0;
    int[] result = new int[arr1.length + arr2.length];  //申请额外的空间存储合并之后的数组
    while(i < arr1.length && j < arr2.length){      //选取两个序列中的较小值放入新数组
        if(arr1[i] <= arr2[j]){
            result[k++] = arr1[i++];
        }else{
            result[k++] = arr2[j++];
        }
    }
    while(i < arr1.length){     //序列1中多余的元素移入新数组
        result[k++] = arr1[i++];
    }
    while(j < arr2.length){     //序列2中多余的元素移入新数组
        result[k++] = arr2[j++];
    }
    System.out.println("Merging: " + Arrays.toString(result));
    return result;
}

1.5 基数排序

基数排序算法的运作如下:

①. 取得数组中的最大数,并取得位数;

②. arr为原始数组,从最低位开始取每个位组成radix数组;

③. 对radix进行计数排序(利用计数排序适用于小范围数的特点)

/**
 * 基数排序(LSD 从低位开始)
 *
 * 基数排序适用于:
 *  (1)数据范围较小,建议在小于1000
 *  (2)每个数值都要大于等于0
 *
 * ①. 取得数组中的最大数,并取得位数;
 * ②. arr为原始数组,从最低位开始取每个位组成radix数组;
 * ③. 对radix进行计数排序(利用计数排序适用于小范围数的特点);
 * @param arr    待排序数组
 */
public static void radixSort(int[] arr){
    if(arr.length <= 1) return;
    //取得数组中的最大数,并取得位数
    int max = 0;
    for(int i = 0; i < arr.length; i++){
        if(max < arr[i]){
            max = arr[i];
        }
    }
    int maxDigit = 1;
    while(max / 10 > 0){
        maxDigit++;
        max = max / 10;
    }
    System.out.println("maxDigit: " + maxDigit);
    //申请一个桶空间
    int[][] buckets = new int[10][arr.length-1];
    int base = 10;
    //从低位到高位,对每一位遍历,将所有元素分配到桶中
    for(int i = 0; i < maxDigit; i++){
        int[] bktLen = new int[10];        //存储各个桶中存储元素的数量
        //分配:将所有元素分配到桶中
        for(int j = 0; j < arr.length; j++){
            int whichBucket = (arr[j] % base) / (base / 10);
            buckets[whichBucket][bktLen[whichBucket]] = arr[j];
            bktLen[whichBucket]++;
        }
        //收集:将不同桶里数据挨个捞出来,为下一轮高位排序做准备,由于靠近桶底的元素排名靠前,因此从桶底先捞
        int k = 0;
        for(int b = 0; b < buckets.length; b++){
            for(int p = 0; p < bktLen[b]; p++){
                arr[k++] = buckets[b][p];
            }
        }
        System.out.println("Sorting: " + Arrays.toString(arr));
        base *= 10;
    }
}

1.6 堆排序

此处以大顶堆为例,堆排序的过程就是将待排序的序列构造成一个堆,选出堆中最大的移走,再把剩余的元素调整成堆,找出最大的再移走,重复直至有序。

堆排序算法的运作如下:

①. 先将初始序列K[1…n]K[1…n]建成一个大顶堆, 那么此时第一个元素K1K1最大, 此堆为初始的无序区.

②. 再将关键字最大的记录K1K1 (即堆顶, 第一个元素)和无序区的最后一个记录 KnKn 交换, 由此得到新的无序区K[1…n−1]K[1…n−1]和有序区K[n]K[n], 且满足K[1…n−1].keys⩽K[n].keyK[1…n−1].keys⩽K[n].key

③. 交换K1K1 和 KnKn 后, 堆顶可能违反堆性质, 因此需将K[1…n−1]K[1…n−1]调整为堆. 然后重复步骤②, 直到无序区只有一个元素时停止.

代码

/**
 * 堆排序
 *
 * 1. 先将初始序列K[1..n]建成一个大顶堆, 那么此时第一个元素K1最大, 此堆为初始的无序区.
 * 2. 再将关键字最大的记录K1 (即堆顶, 第一个元素)和无序区的最后一个记录 Kn 交换, 由此得到新的无序区K[1..n−1]和有序区K[n], 且满足K[1..n−1].keys⩽K[n].key
 * 3. 交换K1 和 Kn 后, 堆顶可能违反堆性质, 因此需将K[1..n−1]调整为堆. 然后重复步骤②, 直到无序区只有一个元素时停止.
 * @param arr  待排序数组
 */
public static void heapSort(int[] arr){
    for(int i = arr.length; i > 0; i--){
        max_heapify(arr, i);
        int temp = arr[0];      //堆顶元素(第一个元素)与Kn交换
        arr[0] = arr[i-1];
        arr[i-1] = temp;
    }
}
private static void max_heapify(int[] arr, int limit){
    if(arr.length <= 0 || arr.length < limit) return;
    int parentIdx = limit / 2;
    for(; parentIdx >= 0; parentIdx--){
        if(parentIdx * 2 >= limit){
            continue;
        }
        int left = parentIdx * 2;       //左子节点位置
        int right = (left + 1) >= limit ? left : (left + 1);    //右子节点位置,如果没有右节点,默认为左节点位置
        int maxChildId = arr[left] >= arr[right] ? left : right;
        if(arr[maxChildId] > arr[parentIdx]){   //交换父节点与左右子节点中的最大值
            int temp = arr[parentIdx];
            arr[parentIdx] = arr[maxChildId];
            arr[maxChildId] = temp;
        }
    }
    System.out.println("Max_Heapify: " + Arrays.toString(arr));
}

1.7 桶排序

桶排序的基本思想是:把数组 arr 划分为n个大小相同子区间(桶),每个子区间各自排序,最后合并

计数排序是桶排序的一种特殊情况,可以把计数排序当成每个桶里只有一个元素的情况。

桶排序算法的运作如下:

1.找出待排序数组中的最大值max、最小值min

2.我们使用 动态数组ArrayList 作为桶,桶里放的元素也用 ArrayList 存储。桶的数量为(max-min)/arr.length+1

3.遍历数组 arr,计算每个元素 arr[i] 放的桶

4.每个桶各自排序

5.遍历桶数组,把排序好的元素放进输出数组

代码:

public static void bucketSort(int[] arr){ 
    int max = Integer.MIN_VALUE;
    int min = Integer.MAX_VALUE;
    for(int i = 0; i < arr.length; i++){
        max = Math.max(max, arr[i]);
        min = Math.min(min, arr[i]);
    } 
    //桶数
    int bucketNum = (max - min) / arr.length + 1;
    ArrayList<ArrayList<Integer>> bucketArr = new ArrayList<>(bucketNum);
    for(int i = 0; i < bucketNum; i++){
        bucketArr.add(new ArrayList<Integer>());
    } 
    //将每个元素放入桶
    for(int i = 0; i < arr.length; i++){
        int num = (arr[i] - min) / (arr.length);
        bucketArr.get(num).add(arr[i]);
    } 
    //对每个桶进行排序
    for(int i = 0; i < bucketArr.size(); i++){
        Collections.sort(bucketArr.get(i));
    } 
    System.out.println(bucketArr.toString()); 
}

1.8 希尔排序

希尔排序算法的运作如下:

①选择一个增量序列t1,t2,…,tk,其中ti>tj,tk=1;(一般初次取数组半长,之后每次再减半,直到增量为1)

②按增量序列个数k,对序列进行k 趟排序;

③每趟排序,根据对应的增量ti,将待排序列分割成若干长度为m 的子序列,分别对各子表进行直接插入排序。仅增量因子为1 时,整个序列作为一个表来处理,表长度即为整个序列的长度。

/**
 * 希尔排序
 *
 * @param array
 * @return
 */
public static int[] shellSort(int[] array) {
    for (int gap = array.length / 2; gap > 0; gap = gap / 2) {
        //将整个数组分为若干个子数组
        for (int i = gap; i < array.length; i++) {
            //遍历各组的元素
            for (int j = i - gap; j >= 0; j = j - gap) {
                //交换元素
                if (array[j] > array[j + gap]) {
                    int temp = array[j];
                    array[j] = array[j + gap];
                    array[j + gap] = temp;
                }
            }
        }
    }
    return array;
}

1.9 插入排序

插入排序算法的运作如下:

①从第一个元素开始,该元素可以认为已经被排序

②取出下一个元素,在已经排序的元素序列中从后向前扫描

③如果该元素(已排序)大于新元素,将该元素移到下一位置

④重复步骤3,直到找到已排序的元素小于或者等于新元素的位置

⑤将新元素插入到该位置后

⑥重复步骤②~⑤

/**
 * 插入排序
 *
 * @param array
 * @return
 */
public static int[] insertSort(int[] array) {
    //长度不减1,是因为要留多一个位置方便插入数
    for (int i = 0; i < array.length; i++) {
        //定义待插入的数
        int insertValue = array[i];
        //找到待插入数的前一个数的下标
        int insertIndex = i - 1;
        //拿a[i]与a[i-1]的前面数组比较
        while (insertIndex >= 0 && insertValue < array[insertIndex]) {
            array[insertIndex + 1] = array[insertIndex];
            insertIndex--;
        }
        array[insertIndex + 1] = insertValue;
    }
    return array;
}

1.10 计数排序

需要三个数组:

待排序数组 int[] arr = new int[]{4,3,6,3,5,1};

辅助计数数组 int[] help = new int[max - min + 1]; //该数组大小为待排序数组中的最大值减最小值+1

输出数组 int[] res = new int[arr.length];

计数排序算法的运作如下:

1.求出待排序数组的最大值max=6, 最小值min=1

2.实例化辅助计数数组help,help用来记录每个元素之前出现的元素个数

3.计算 arr 每个数字应该在排序后数组中应该处于的位置,此时 help = [1,1,4,5,6,7];

4.根据 help 数组求得排序后的数组,此时 res = [1,3,3,4,5,6]

代码

public static int[] countSort(int[] arr){
    int max = Integer.MIN_VALUE;
    int min = Integer.MAX_VALUE;
    //找出数组中的最大最小值
    for(int i = 0; i < arr.length; i++){
        max = Math.max(max, arr[i]);
        min = Math.min(min, arr[i]);
    }
    int[] help = new int[max - min + 1];
    //找出每个数字出现的次数
    for(int i = 0; i < arr.length; i++){
        int mapPos = arr[i] - min;
        help[mapPos]++;
    }
    //计算每个数字应该在排序后数组中应该处于的位置
    for(int i = 1; i < help.length; i++){
        help[i] = help[i-1] + help[i];
    }
    //根据help数组进行排序
    int res[] = new int[arr.length];
    for(int i = 0; i < arr.length; i++){
        int post = --help[arr[i] - min];
        res[post] = arr[i];
    }
    return res;
}

总结

排序算法的稳定性定义

假定在待排序的记录序列中,存在多个具有相同的关键字的记录,若经过排序,这些记录的相对次序保持不变,即在原序列中,r[i]=r[j],且r[i]在r[j]之前,而在排序后的序列中,r[i]仍在r[j]之前,则称这种排序算法是稳定的;否则称为不稳定的。

口诀

冒泡、插入、希尔、桶拍最好都是n

选择最好n方

堆、归并、快速都是nlog2n

基数n*k计数n+k

冒泡、插入、归并、桶、计数、基数稳,其他都不稳



相关文章
|
5天前
|
JavaScript Java 测试技术
Java项目基于ssm+vue.js的网上手机销售系统附带文章和源代码设计说明文档ppt
Java项目基于ssm+vue.js的网上手机销售系统附带文章和源代码设计说明文档ppt
14 0
|
1天前
|
JavaScript Java 测试技术
基于springboot+vue.js+uniapp小程序的java语言的考试信息报名系统附带文章源码部署视频讲解等
基于springboot+vue.js+uniapp小程序的java语言的考试信息报名系统附带文章源码部署视频讲解等
7 0
|
2天前
|
搜索推荐 算法 Java
【Java基础】 几种简单的算法排序
几种简单的JAVA算法排序
20 4
|
4天前
|
搜索推荐 算法 Java
JAVA中的交换类排序算法详解
JAVA中的交换类排序算法详解
11 1
|
4天前
|
搜索推荐 算法 Java
JAVA中的排序算法详解与实战
JAVA中的排序算法详解与实战
6 1
|
4天前
|
算法 Java
JAVA中实现最短距离算法——Dijkstra算法详解
JAVA中实现最短距离算法——Dijkstra算法详解
9 1
|
4天前
|
算法 Java
JAVA中的递推算法及其应用
JAVA中的递推算法及其应用
11 1
|
5天前
|
JavaScript Java 测试技术
Java项目基于ssm+vue.js的生鲜在线销售系统附带文章和源代码设计说明文档ppt
Java项目基于ssm+vue.js的生鲜在线销售系统附带文章和源代码设计说明文档ppt
12 0
|
5天前
|
JavaScript Java 测试技术
Java项目基于ssm+vue.js的经典电影推荐网站附带文章和源代码设计说明文档ppt
Java项目基于ssm+vue.js的经典电影推荐网站附带文章和源代码设计说明文档ppt
11 0
|
5天前
|
JavaScript Java 测试技术
Java项目基于ssm+vue.js的餐馆点餐系统附带文章和源代码设计说明文档ppt
Java项目基于ssm+vue.js的餐馆点餐系统附带文章和源代码设计说明文档ppt
12 0