排序算法总结

讲一下什么情况该使用什么排序算法?

在这里插入图片描述

  • 当 n 较大,则应采用时间复杂度为 O(nlog2n)的排序方法:快速排序、堆排序或归并排序

    快速排序:是目前基于比较的内部排序中被认为是最好的方法,当待排序的关键字是随机分布时,快速排序的平均时间最短;

  • 当 n 较大,内存空间允许,且要求稳定性 => 归并排序

  • 当 n 较小,可采用直接插入或直接选择排序。

  • 一般不使用或不直接使用传统的冒泡排序。

  • 基数排序:它是一种稳定的排序算法

冒泡排序

/**
     * 冒泡排序:
     * 1、一共进行数组的大小减一次大的循环,
     * 2、每一趟排序的次数在逐渐的减少,
     * 3、如果我们发现在某趟排序中,没有发生一次交换,可以提前结束冒泡排序。
     * <p>
     * 时间复杂度:O(n^2)
     */
    public static void bubbleSort(int[] arr) {
        int temp = 0;
        boolean flag = false;//表示是否进行过排序
        for (int i = 0; i < arr.length - 1; i++) {
            for (int j = 0; j < arr.length - 1 - i; j++) {
                if (arr[j] > arr[j + 1]) {//如果前面的数比后面的数大,则交换
                    flag = true;
                    temp = arr[j];
                    arr[j] = arr[j + 1];
                    arr[j + 1] = temp;
                }
            }
            if (!flag) {//在一趟排序中,一次交换都没有发生过
                break;
            } else {
                flag = false;//重置flag,进行下次判断
            }
        }
    }


选择排序

/**
 * 选择排序
 * 1、选择排序共有n-1轮排序
 * 2、每一轮排序,又是一个循环
 * <p>
 * 2.1先假定当前这个数是最小数
 * 2.2然后和后面的每个数进行比较,如果发现有比当前数更小的数,就重新确定最小数,得到下标
 * 2.3当遍历到数组的最后时,就得到本轮最小数和下标
 * 2.4交换
 */
public class SelectSort {
    public static void main(String[] args) {
        //1、测试排序
        System.out.println("测试一:");
        int[] arr2 = {101, 34, 119, 1, 22, 10};
        System.out.println("排序前的数组" +Arrays.toString(arr2));
        selectSort(arr2);
        System.out.println("排序后的数组" + Arrays.toString(arr2));     
    }

    /**
     * 选择排序
     * 时间复杂度:O(n^2)
     *
     * @param arr
     */
    public static void selectSort(int[] arr) {
        for (int i = 0; i < arr.length - 1; i++) {
            int minIndex = i;
            int min = arr[i];
            for (int j = i + 1; j < arr.length; j++) {
                if (min > arr[j]) {//说明假定的最小值,并不是最小
                    min = arr[j];//重置最小值
                    minIndex = j;//重置最小索引
                }
            }
            //交换
            if (minIndex != i) {
                arr[minIndex] = arr[i];//把当前值(第i轮选择的值)复制到最小索引位置
                arr[i] = min;//第i轮最小值放到第i个位置上
            }
        }
    }
}

快速排序

/**
 * 快速排序
 * 时间复杂度:O(nlog2^n)
 * <p>
 * 快速排序基本思想是:
 * 通过一趟排序将要排序的数据分割成独立的两部分,
 * 其中一部分的所有数据都比另一部分的所有数据要小,
 * 然后再按此方法对这两部分数据分别进行快速排序,
 * 整个排序过程可以递归进行,以此达到整个数据变成有序序列
 */
class Solution {
    public static int[] quickSort(int[] arr, int left, int right) {
        int start = left;//从前向后比较的索引
        int end = right;//从后向前比较的索引
        int key = arr[left];//基准值
        while (end > start) {
            //从后向前比较
            while (end > start && arr[end] >= key) {
                end--;
            }
            //此时找到比基准值小的,交换位置
            if (arr[end] <= key) {
                int temp = arr[start];
                arr[start] = arr[end];
                arr[end] = temp;
            }
            //从前向后比较
            while (end > start && arr[start] <= key) {
                start++;
            }
            //此时找到比基准值大的,交换位置
            if (arr[start] >= key) {
                int temp = arr[start];
                arr[start] = arr[end];
                arr[end] = temp;
            }
        }
        //相遇
        //此时第一次循环比较结束,基准值左面的值都比它小,右面的值都比它小
        //递归左边序列,从第一个索引位置到“基准值索引-1”
        if (start > left) {
            quickSort(arr, left, start - 1);
        }
        //递归右边序列,从“基准值索引+1”到最后一个位置
        if (end < right) {
            quickSort(arr, end + 1, right);
        }
        return arr;
    }
}

插入排序

插入排序的基本思路是将一个数据插入到已经排好序的序列中,从而得到一个新的有序数据,该算法适用于少量数据的排序,是稳定的排序算法。

插入排序的基本思想:

  • 把n个待排序的元素看成为一个有序和一个无序表,
  • 开始时有序表中只包含一个元素,
  • 无序表中包含n-1个元素,
  • 排序过程中每次从无序表中取出第一个元素,把它的排序码依次与有序表元素的排序码进行比较,将它插入到有序表中的适当位置,使之成为新的有序表

public static void insertSort(int[] arr) {
        for (int i = 1; i < arr.length; i++) {
            //定义待插入的数
            int insertVal = arr[i];
            int insertIndex = i - 1;//即arr[1]的前面这个数的下标
            //给insertVal找到插入的位置
            //1、insertIndex>=0保证在给insertVal找插入位置,不越界
            //2、insertVal<arr[insertIndex]待插入的数,还没有找到插入位置
            //3、就需要将arr[insertIndex]后移
            while (insertIndex >= 0 && insertVal < arr[insertIndex]) {
                arr[insertIndex + 1] = arr[insertIndex];
                insertIndex--;
            }
            //当退出循环时,说明插入的位置找到,insertIndex+1
            //判断是否需要赋值,如果insertIndex + 1 = i,说明位置正确,不需要移动
            if (insertIndex + 1 != i) {
                arr[insertIndex + 1] = insertVal;
            }
        }
    }

希尔排序

把记录按下标的一定增量分组,对每组使用直接插入排序算法排序,随着增量逐渐减小,每组包含的关键词越来越多,当增量减至1时,整个文件恰被分成一组,算法终止。

 /**
   * 移位式
   *
   * @param arr
   */
    public static void shellSort2(int[] arr) {
        //增量gap,并逐步缩小增量
        for (int gap = arr.length / 2; gap > 0; gap /= 2) {
            //从gap个元素,逐个对其所在的组进行直接插入排序
            for (int i = gap; i < arr.length; i++) {
                int j = i;
                int temp = arr[j];
                if (arr[j] < arr[j - gap]) {
                    while (j - gap >= 0 && temp < arr[j - gap]) {
                        //移动
                        arr[j] = arr[j - gap];
                        j -= gap;
                    }
                    //当退出while后,说明给temp找到了插入的位置
                    arr[j] = temp;
                }
            }
        }
    }

归并排序

归并排序的原理是将原始数组分解为多个子序列,然后对每个子序列进行排序,最后将排好序的子序列合并起来。


	public static int[] mergeSort(int[] data) {
        sort(data, 0, data.length - 1);
        return data;
    }

    public static void sort(int[] data, int left, int right) {
        if (left >= right) {
            return;
        }
        //中间索引
        int center = (left + right) / 2;
        //左右递归
        sort(data, left, center);
        sort(data, center + 1, right);
        //将两个数组进行归并
        merge(data, left, center, right);
    }

    public static void merge(int[] data, int left, int center, int right) {
        //创建一个临时数组
        int[] tempArr = new int[data.length];
        //右边数组的第一个索引
        int mid = center + 1;
        //临时数组的索引
        int tempIndex = left;
        //缓存左边数组第一个元素的索引
        int i = left;
        while (left <= center && mid <= right) {
            //从两个数组中取最小值放入临时数组中
            if (data[left] <= data[mid]) {
                tempArr[tempIndex++] = data[left++];
            } else {
                tempArr[tempIndex++] = data[mid++];
            }
        }
        //将剩余部分依次放入临时数组中(实际上两个whil只会执行其中一个)
        while (left <= center) {
            tempArr[tempIndex++] = data[left++];
        }
        while (mid <= right) {
            tempArr[tempIndex++] = data[mid++];
        }
        //将临时数组中的内容复制到原数组中
        while (i <= right) {
            data[i] = tempArr[i++];
        }
    }

堆排序

  • 1、将待排序的序列构造成一个大顶堆,根据大顶堆的性质,当前堆的根节点(堆顶)就是序列中最大的元素;
  • 2、将堆顶元素和最后一个元素交换,然后将剩下的节点重新构造成一个大顶堆;
  • 3、重复步骤2,如此反复,从第一次构建大顶堆开始,每一次构建,我们都能获得一个序列的最大值,然后把它放到大顶堆的尾部。最后,就得到一个有序的序列了。

堆排序是一种选择排序,它的最坏,最好,平均时间复杂度均为O(nlogn),它也是不稳定排序

public static void heapSort(int[] arr) {
       if (arr == null || arr.length == 0) {
           return;
       }
       int len = arr.length;
       // 第一次构建大顶堆,将待排序序列变成一个大顶堆结构的数组
       // 从最后一个非叶子结点开始向前遍历,调整节点性质,使之成为大顶堆
       for (int i = (len / 2 - 1); i >= 0; i--) {
           heapify(arr, i, len);
       }

       // 第一次构造大顶堆完毕,交换堆顶和末尾节点的值,并重新构造大顶堆(注意,重新构造的时候,需要将 len 做减一操作)
       for (int i = len - 1; i > 0; i--) {
           swap(arr, 0, i);
           len--;
           // 直接从根节点开始,向下调整
           heapify(arr, 0, len);
       }
   }


   // 将以 arr[i] 为头节点的子树调整为大顶堆
   public static void heapify(int[] arr, int i, int len) {
       int left = 2 * i + 1;  // 左子节点索引
       int right = 2 * i + 2; // 右子节点索引
       // 默认当前节点(父节点)是最大值。
       int maxIndex = i;
       if (left < len && arr[left] > arr[maxIndex]) {
           // 如果有左节点,并且左节点的值更大,更新最大值的索引
           maxIndex = left;
       }
       if (right < len && arr[right] > arr[maxIndex]) {
           // 如果有右节点,并且右节点的值更大,更新最大值的索引
           maxIndex = right;
       }

       if (maxIndex != i) {
           // 如果最大值不是当前非叶子节点的值,那么就把当前节点和最大值的子节点值互换
           swap(arr, i, maxIndex);
           // 因为互换之后,子节点的值变了,如果该子节点也有自己的子节点,仍需要再次调整。
           heapify(arr, maxIndex, len);
       }
   }

   // 数组两两交换顺序
   public static void swap(int[] arr, int i, int j) {
       int temp = arr[i];
       arr[i] = arr[j];
       arr[j] = temp;
   }

基数排序

/**
 * 基数排序(radix sort)  O(nk)
 * 1、将所有待比较数值统一为同样的数位长度,数位较短的数前面补零,
 * 然后,从最低位开始,依次进行一次排序,这样从最低位排序一直到
 * 最高位排序完成以后,数列就变成一个有序序列
 *
 * 稳定性的排序
 */
public class RadixSort{

    public static void main(String[] args) {
        int[] arr = {53, 3, 542, 748, 14, 214};
        radixSort(arr);
    }

    public static void radixSort(int[] arr) {
        //1、得到数组中最大的数的位数
        int max = 0;
        for (int i = 0; i < arr.length; i++) {
            if (arr[i] > max) {
                max = arr[i];
            }
        }
        //2、得到最大数是几位数
        int maxLength = (max + "").length();
        //定义一个二维数组,表示10个桶,每个桶就是一个一维数组
        //1、二维数组包含10个一维数组
        //2、为了防止在放入数的时候,数据溢出,则每一个一维数组(桶),大小定义为arr.length
        //3、基数排序是使用空间换时间的经典算法
        int[][] bucket = new int[10][arr.length];
        //为了记录每个桶中,实际存放了多少个数据,我们定义一个一维数组来记录各个桶的每次放入的数据的个数
        //bucketElementCounts[0]记录的就是bucket[0]桶的放入数据个数
        int[] bucketElementCounts = new int[10];
        for (int i = 0, n = 1; i < maxLength; i++, n *= 10) {
            //针对每个元素的个位进行排序处理,第一次数个位,第二次十位,第三次是百位
            for (int j = 0; j < arr.length; j++) {
                //取出每个元素对应个位的值
                int digitOfElement = arr[j] / n % 10;
                bucket[digitOfElement][bucketElementCounts[digitOfElement]] = arr[j];
                bucketElementCounts[digitOfElement]++;
            }
            //按照这个桶的顺序(一维数组的下标依次取出数据,放入原来数组)
            int index = 0;
            //遍历每一个桶,并将桶中的数据,放入到原数组
            for (int k = 0; k < bucketElementCounts.length; k++) {
                //如果桶中有数据,我们才放入到原数组
                if (bucketElementCounts[k] != 0) {
                    //循环该桶即第k个一维数组,放入
                    for (int l = 0; l < bucketElementCounts[k]; l++) {
                        //取出元素放入到arr
                        arr[index++] = bucket[k][l];
                    }
                }
                bucketElementCounts[k] = 0;
            }
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

德玛西亚!!

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值