【算法】十大排序算法以及具体用例算法题

本文涉及的产品
云原生大数据计算服务 MaxCompute,5000CU*H 100GB 3个月
云原生大数据计算服务MaxCompute,500CU*H 100GB 3个月
简介: 【算法】十大排序算法以及具体用例算法题

源代码下载


1:冒泡排序

/*
 * 冒泡排序是内部排序
 * 冒泡排序将会每一次都从头开始遍历
 * 每一次遍历都会把最大的数据放到最后一个
 * 因此每一次都可以少遍历一个元素
*/
public static void bubbleSort(int[]arr){
        boolean flag = false;
        out:
        for (int i = 0; i < arr.length; i++) {
            flag = false;
            for (int j = 0; j < arr.length - 1 - i; j++) {
                if (arr[j] > arr[j + 1]) { //从小到大 >改为<就是从大到小
                    arr[j] ^= arr[j + 1];
                    arr[j + 1] ^= arr[j];
                    arr[j] ^= arr[j + 1];
                    flag = true;
                }
            }
            if (flag == false) { //如果内层循环一次没修改过位置说明已经有序
                break out;
            }
        }
        System.out.println(Arrays.toString(arr));
    }
    public static void main(String[] args) {
        int[] arr = new int[]{3, 5412, 12, 3, 513, -1, 6, -324};
        bubbleSort(arr);
    }

2:选择排序

/*
 * 选择排序也是内部排序
 * 选择排序的思路是每一次都遍历整个二维数组
 * 然后从中找出最小的数据,与当前外层循环对应的i的数据交换位置(注意是交换位置)
 * 假设i=2 然后要交换的是10这个数据 那么这时arr[2]=10 arr[10原先位置]=arr[2]
 * 每次开头的位置都是+1的也就是一开是0 之后是1 2 3...
*/
 public static void selectionSort(int[]arr){
        for (int i = 0; i < arr.length - 1; i++) {
            int min = arr[i];
            int p = i; //让p先赋值为i 这样可以防止由于没有进入if语句而导致p是之前的值
            for (int j = i + 1; j < arr.length; j++) {
                if (min > arr[j]) { //遍历找到最小值所在位置
                    min = arr[j];
                    p = j;
                }
            }
            if (p != i) { //如果p不是当前位置的数据就进行修改
                arr[p] = arr[i];//这个标志位至关重要哦
                arr[i] = min;
            }
            System.out.println("第"+(i+1)+"轮结果是"+ Arrays.toString(arr));
        }
    }
    public static void main(String[] args) {
        int[] arr = new int[]{3, 5412, 12, 3, 513, -1, 6, 123,123,234,5345,-324};
        selectionSort(arr);
    }

3:插入排序

/*
 * 每次都判断当前数据与前面一个数据是否位置不对
 * 如果不对 那么将当前数据设置为哨兵节点
 * 然后再当前数据下标-1开始遍历 遍历到直到arr[0]>arr[i]为止
 * 如果当前循环arr[0]<arr[i]那么执行arr[i+1]=arr[i]
 * 也就是将这个数据后移 为插入更小的数据arr[0]做准备
 * 最后一次退出之后由于执行了i--操作才退出
 * 所以退出时的前一个位置就是arr[0]应该插入的位置
*/
   private static int[] arr = new int[]{0,3, 5412, 12, 3, 513, -1, 6, -324};
    public static void insertSort(int[]arr){
        int i,j;
        for(i=2;i<arr.length;i++){
            if(arr[i-1]>arr[i]){//如果前面的数据更大说明当前需要排序
                arr[0]=arr[i]; //i是更小的数据  哨兵节点
                for(j=i-1;arr[j]>arr[0];j--){//找到小数据应该插入的位置
                    arr[j+1]=arr[j]; //arr[0]此时就是待插入的数据
                    //此时需要寻找arr[0]应该插入的位置 因此把更大的数据都后移给arr[0]腾位置
                }//退出循环说明j对应的位置比待插入数据还小 此时数据应该插在这个位置之后一个位置
                arr[j+1]=arr[0];
                System.out.println("第"+(i-1)+"次数据位置为"+ Arrays.toString(arr));
            }
        }
    }
    public static void main(String[] args) {
        insertSort(arr);
    }

4:希尔排序

/*
希尔排序是插入排序的升级 其使用了一个增量
 * 由于增量的存在,每次的遍历都是直接跳跃式比对
 * 就能将比较后面的数据直接很快的弄到前面
 * 基本写法与插入排序无二 只是内层for循环判断的时候需要增加一个j>0&&arr[0]<arr[j]*/
private int arr[];
    public void shellSort(int[]arr){
        int i,j;
        int increment=arr.length;//本质与直接插入排序无二 就是增加了一个增量进行跳跃式排序
        do{
            increment=increment/3+1;//加1是为了最后能使得增量取到1进行最后一次排序
            for(i=increment+1;i<=arr.length-1;i++){
                if(arr[i]<arr[i-increment]){
                    arr[0]=arr[i];//小的数据设置为哨兵结点
                    for(j=i-increment;j>0&&arr[j]>arr[0];j-=increment){//为这个数据找到它应该插入的位置 以increment为间隔寻找插入位置
                        arr[j+increment]=arr[j];//将不合理数据后移increment
                    }//退出for循环时已经为这个小数据留好了可插入的位置
                    arr[j+increment]=arr[0];//需要加一个increment是因为最后退出之前-increment了
                    //导致j此时位置向前偏移了increment
                }
            }
        }while(increment>1);//最后的停止条件就是执行increment=1时的排序 这次排序将会交换
        //相邻元素,也就是进行最后一次排序
    }
    public static void main(String[] args) {
        ShellSort sh = new ShellSort();
        sh.arr=new int[]{0,8,87,7,6,54,34,123};
        sh.shellSort(sh.arr);
        System.out.println(Arrays.toString(sh.arr));
    }

5:堆排序

/*
 * 使用堆排序需要了解二叉树
 * 其实堆排序就是大顶堆或者小顶堆 每次都将
 * 最大或者最小的数据先移动到非叶子结点位置
 * 将结点与最后一个结点进行数据交换
 * 多次循环下来就可以得到一个有序的
 * 建议用草纸模拟一下堆排序的过程*/
 public static void main(String[] args) {
        int[] arr = {7, 8, 9, 10, 4, 7, 3, 4};
        heapSort(arr);
    }
    public static void heapSort(int[] arr) {
        //将无序序列构建成一个大顶堆或者小顶堆
        for (int i = arr.length / 2 - 1; i >= 0; i--) {
            adjustHeap(arr, i, arr.length);
        }
        System.out.println("初始构造大顶堆" + Arrays.toString(arr));
        //将对顶元素与末尾元素进行交换 将最大数据沉到末尾
        //重新调整结构 使其满足堆定义 然后继续交换当前与末尾元素
        for (int j = arr.length - 1; j > 0; j--) {
            arr[j] ^= arr[0]; //交换
            arr[0] ^= arr[j];
            arr[j] ^= arr[0];
            adjustHeap(arr, 0, j);
        }
        System.out.println(Arrays.toString(arr));
    }
    /**
     * @param arr    待调整数组
     * @param i      当前非叶子结点再数组中的索引
     * @param length 当前可操作数据的长度 对于大顶堆最大的数据再数组最后之后就会被抛弃不在操作
     *               因此length--
     */
    public static void adjustHeap(int[] arr, int i, int length) {
        int temp = arr[i]; //将当前非叶子结点保存
        for (int k = 2 * i + 1; k < length; k = k * 2 + 1) {//k=2*i+1则k代表的是当前非叶子结点的左孩子
            if (k + 1 < length && arr[k] < arr[k + 1]) {//判断左孩子大还是右孩子大
                k++;//如果是右孩子大就++获得到右孩子的索引
            }
            if (arr[k] > temp) {//判断孩子大还是当前结点大
                arr[i] = arr[k]; //将更大的向上排
                i = k; //大的元素就跑到了非叶子结点的索引去 然后让i去更小数据的索引
            } else {
                break;//由于i是从下往上的 所以如果当前的非叶子结点更大直接退出就好 因为假设是一个
                //4层8结点的完全二叉树 那么i=3一开始 然后arr[7]<arr[3]那么直接break后
                //下一次i=2(i=3那个结点的双亲结点) 然后i=1 i=0
            }
        }
        arr[i] = temp;//大的数据被移上去了 但是其原有位置的数据还没有被修改
    }   //因此这一步操作就是为了修改那个数据

6:计数排序

计数排序是通过对所有的数据集进行大小上下限的划分的方式来对数据进行排序。

其主要方式是得到数据集中的最大和最小值,然后创建一个max-min+1大小的数组,然后再一次遍历所有的数组元素,将对应的下标的元素计数+1,得到计数完毕之后的数组之后,我们再将这个计数的数组遍历一次,只要数组下标数据不为0,那么我们就一直复制这个值到最后的结果数组中,当计数数组中的所有下标对应元素都为0的时候,遍历完成,得到的数组有序。

/*
 * 计数排序是稳定的 ,这个大家应该能很明显的看出来,因为计数排序本身并不是基于比较的算法.
 * 计数排序需要的额外空间比较大,这个大家很明显的看出来,并且空间浪费的情况也会比较严重,
 * 因为一旦序列中MAX与MIN的差距过大,那么需要的内存空间就会非常大.并且假如序列中的元素
 * 都是散布在一个特定的区间内,那么内存空间浪费的情况也会非常明显.
*/
public static void countSort(int[]arr){
        int max=Integer.MIN_VALUE;//数组中数据最大值
        int min=Integer.MAX_VALUE;//数组中数据最小值
//        for(int i=0;i<arr.length;i++){//取得数组中的最大与最小值
//            if(arr[i]<min){
//                min=arr[i];
//            }
//            if(arr[i]>max){
//                max=arr[i];
//            }
//        }//如果是一个整数数组,那么得知了这个数组中的最大与最小数据(通过散列)
        for (int i : arr) {
            min=Math.min(i,min);
            max=Math.max(i,max);
        }
        int index=0;
        int elementCount[]=new int[max-min+1];//我们就可以用他们的差来得到这个数组中最多有对少个数据
        for(int i=0;i<arr.length;i++){//假设最小数据是3 那么数组下标0->3 1->4以此类推
            elementCount[arr[i]-min]++;//对应的数组位置的数据量+1
        }
        for(int i=0;i< elementCount.length;i++){
            if (elementCount[i]!=0) {//如果当前数组位置有数据 那么就说明这个数字存在
                for(int j=0;j<elementCount[i];j++){
                    arr[index++]=i+min;//那么就把这个数据覆盖到原数组中去
                }
            }
        }
        System.out.println(Arrays.toString(arr));
    }
    public static void main(String[] args) {
        countSort(new int[]{0, -1, 9, 3, 2, 1, 8, 6, 5, 10});
    }

计数排序的代码比较简单,使用计数排序的场景比较少,一般用于数据量上下限较小的数据,下面是一道例题。

明明的随机数

import java.util.*;
/**
 * @author: 张锦标
 * @date: 2023/7/6 19:53
 * RandomOfMingMing类
 * 删除重复并且排序
 * 这道题由于数据量较小
 * 所以可以直接使用计数排序
 */
public class Main {
    public static void main(String[] args) {
         Scanner in = new Scanner(System.in);
        // 注意 hasNext 和 hasNextLine 的区别
        int n = in.nextInt();
        int[] arr = new int[1001];
        int count = 0;
        while (in.hasNextInt()) { // 注意 while 处理多个 case
            int i = in.nextInt();
            if (arr[i] != 1) {
                count++;
            }
            arr[i] = 1;
        }
        System.out.println(count);
        for (int i = 1; i < arr.length; i++) {
            if (arr[i] == 1) {
                System.out.print(i+" ");
            }
        }
    }
}

7:基数排序

/*
 * 基数排序是效率高的稳定性排序法
 * 基数排序是桶排序的升级 其实现思路如下:
 * 跳过构造每个位数可能出现的数字的十个二维数组桶0-9
 * 用这些桶构成存放数字的容器
 * 第一次将取得数字的个位,第二次十位,以此类推,每次获取都将
 * 直接覆盖桶中原有数据而不是直接擦除桶中数据(没有必要)
*/
  public static void radixSort(int[]arr){
        //1:得到数组中最大数据的位数
        int max=arr[0];//最大数据
        int maxLength=0;//最大数据长度
        for(int i=1;i<arr.length;i++){
            if(arr[i]>max){
                max=arr[i];
            }
        }
        maxLength=(max+"").length();
        //2:创建桶和创建记录桶中数据的数组
        int[][]bucket=new int[10][arr.length];//存放数据的桶
        int[]bucketCount=new int[arr.length];//记录每个桶中数据的桶
        int digitOfElement=0;//当前位数对应的值0-9
        for(int i=0,n=1;i<maxLength;n*=10,i++){//需要 遍历最长的数字的长度
            for(int j=0;j<arr.length;j++){//对每一个数据进行遍历
                digitOfElement=arr[j]/n%10;//取得这个数据当前的个位/十位/百位的值
                bucket[digitOfElement][bucketCount[digitOfElement]]=arr[j];//将当前数据放入对应桶中
                bucketCount[digitOfElement]++;//对应的桶中的数据量增加
            }
            int index=0;//用于对原数组进行覆盖
            for(int k=0;k<bucketCount.length;k++){//对十个桶进行遍历
                if(bucketCount[k]!=0){//判断当前桶中是否有数据 当前数据值是新一轮存放进去的次数而不是原先没有被覆盖的数据个数
                    for(int l=0;l<bucketCount[k];l++){//有数据则将数组直接覆盖到原数组
                        arr[index++]=bucket[k][l];
                    }
                }
                bucketCount[k]=0;//不直接擦除桶中数据而是覆盖 因为没有必要
            }//因为每次从计数桶中取得的数据个数通过bucketCount来获得的 而每次取得完毕数据后
        }//都会将bucketCount变为0
        System.out.println("排序后:"+ Arrays.toString(arr));
    }
    public static void main(String[] args) {
        int[] arr = new int[]{3, 5412, 12, 3, 513, 0, 6, 324};
        radixSort(arr);
    }

8:快速排序

/*
* 快速排序 找到一个中轴 让中轴左边小于中轴 右边大于中轴
 * 然后把左边的再次按上面的方法进行
*/
 public static void quickSort(int[]arr,int left,int right){
        int l=left;//左下标
        int r=right;//右下标
        //pivot中轴值
        int pivot=arr[(left+right)/2];
        //while循环是为了让左边小于pivot 右边大于pivot
        while(l<r){//左下标还小于右下标
            while(arr[l]<pivot){//在pivot左边一直找 直到找到的数据大于pivot退出
                l++;
            }//退出时arr[l]>=pivot
            while(arr[r]>pivot){//在pivot右边一直找 直到找到比pivot小的退出
                r--;
            }//退出时arr[r]>pivot
            if(l>=r){
                break;//如果按照上面的寻找方法使得l>=r说明左边的已经都小于pivot
                //右边的都大于pivot了
            }
            //如果没有成立 那么就交换此时的左值和右值 因为他们不合理
            arr[l]^=arr[r];
            arr[r]^=arr[l];
            arr[l]^=arr[r];
            //交换后l指向arr[r] r指向arr[l]
            //如果交换后arr[l]==pivot 那么r--
            if(pivot==arr[l]){//arr[l]==pivot说明之前是r指向了pivot 那么之后r继续左移
                r--;
            }
            if(pivot==arr[r]){//反之右移
                l++;
            }
        }
        //如果l==r 必须l++和r-- 否则栈溢出
        if(l==r){
            l++;
            r--;
        }
        //向左递归
        if(left<r){
            quickSort(arr,left,r);
        }
        //向右递归
        if(right>l){
            quickSort(arr,l,right);
        }
    }
    public static void main(String[] args) {
        int[]arr={1,123,5,3425,2130,-4365,-123};
        quickSort(arr,0, arr.length-1);
        System.out.println(Arrays.toString(arr));
    }

9:归并排序

/*
 * 归并排序的思路是分治,也就是将数组分为左右两个数组
 * 对左右两个数组递归排序
*/
 public static void mergeSort(int[] arr, int left, int right, int[] temp) {
        if (left < right) {
            int mid = (left + right) / 2;
            mergeSort(arr, left, mid, temp);//对左半部分进行排序
            mergeSort(arr, mid + 1, right, temp);//对右半部分进行排序
            merge(arr, left, mid, right, temp);//对左右两个数组进行融合
        }
    }
    public static void merge(int[] arr, int left, int mid, int right, int[] temp) {
        int i = left;
        int j = mid + 1;
        int t = 0;
        //(一)
        //先把左右两边的有序数据按照规则填充到temp数组
        //直到左右两边有一边处理完毕
        while (i <= mid && j <= right) {
            if (arr[i] <= arr[j]) { //比较当前arr数组中的数据 小的先插入到temp中去
                temp[t++] = arr[i++];
            } else {
                temp[t++] = arr[j++];
            }
        }
        //(二)
        //进行到这里之后将剩下的元素直接赋值到temp中去
        while (i <= mid) {
            temp[t++] = arr[i++];
        }
        while (j <= right) {
            temp[t++] = arr[j++];
        }
        //(三)
        //重点部分 从left开始把到right的数据覆盖到原数组
        int tempLeft=left;
        t=0;
        while(tempLeft<=right){
            arr[tempLeft++]=temp[t++];
        }
    }
    public static void main(String[] args) {
        int[] arr = {1, 123, 53, 123, 65};
        int[] temp = new int[arr.length];
        mergeSort(arr, 0, arr.length - 1, temp);
        System.out.println(Arrays.toString(arr));
    }

10:桶排序

//我们一般不使用桶排序而是使用桶排序的升级
    //例如基数排序和计数排序 因为桶排序需要再对每个桶中的数据进行一次排序
    //并且如果使用二维数组去做桶 那么由于我们不确定每个桶中的数据会有多少
    //会造成空间上的浪费 并且数组开辟的空间是确定的 那么就导致开辟数组时候默认
    //位置的元素是0 null 0.0 这些默认值 那么这些默认值也会参与排序
    //因此我们还需要对桶中每个数组中需要排序的元素进行计数
    //也就是还需要另一个计数桶中数据的桶
    //因此如果这样时间效率反而越来越低
    //除非直接使用ArrayList 但是arraylist再扩容的时候也是需要而外的时间的
    //因此综上 我们一般不使用桶排序
    //在链表中添加元素的同时需要进行元素的排序
    public static void sort(ArrayList<Integer> list, int i) {
        if (list == null)
            list.add(i);
            //这里采用的排序方式为插入排序
        else {
            int flag = list.size() - 1;
            while (flag >= 0 && list.get(flag) > i) {
                if (flag + 1 >= list.size())
                    list.add(list.get(flag));
                else
                    list.set(flag + 1, list.get(flag));
                flag--;
            }
            if (flag != (list.size() - 1))
                //注意这里是flag+1,自己可以尝试将这里换成flag看看,会出现数组越界的情况
                list.set(flag + 1, i);
            else
                list.add(i);
        }
    }
    public static void bucketSort(int[] num, int bucketNum) {
        //遍历得到数组中的最大值与最小值
        int min = Integer.MAX_VALUE;
        int max = Integer.MIN_VALUE;
        for (int i = 0; i < num.length; i++) {
            min = min <= num[i] ? min : num[i];
            max = max >= num[i] ? max : num[i];
        }
        //求出每个桶的长度,这里必须使用Double
        double size = (double) (max - min + 1) / bucketNum;
        ArrayList<Integer>[] list = new ArrayList[bucketNum];
        for (int i = 0; i < bucketNum; i++) { //由于是对象数组 因此需要对数组每个索引先初始化
            list[i] = new ArrayList<Integer>();
        }
        //将每个元素放入对应的桶之中同时进行桶内元素的排序
        for (int i = 0; i < num.length; i++) {
            System.out.println("元素:" + String.format("%-2s", num[i]) + ", 被分配到" + (int) ((num[i] - min) / size) + "号桶");
//            sort(list[(int) ((num[i] - min) / size)], num[i]);
            list[(int) ((num[i] - min) / size)].add(num[i]);
        }
        for(int i=0;i<list.length;i++){
            list[i].sort(Integer::compare);
        }
        System.out.println();
        for (int i = 0; i < bucketNum; i++) {
            System.out.println(String.format("%-1s", i) + "号桶内排序:" + list[i]);
        }
        System.out.println();
        //顺序遍历各个桶,得出我们 已经排序号的序列
        for (int i = 0; i < list.length; i++) {
            if (list[i] != null) {
                for (int j = 0; j < list[i].size(); j++) {
                    System.out.print(list[i].get(j) + " ");
                }
            }
        }
        System.out.println();
        System.out.println();
    }
    public static void main(String[] args) {
        int[] num = {7, 4, 9, 3, 2, 1, 8, 6, 5, 10};
        long startTime = System.currentTimeMillis();
        //这里桶的数量可以你自己定义,这里我就定义成了3
        bucketSort(num, 3);
        long endTime = System.currentTimeMillis();
        System.out.println("程序运行时间: " + (endTime - startTime) + "ms");
    }


相关实践学习
基于MaxCompute的热门话题分析
本实验围绕社交用户发布的文章做了详尽的分析,通过分析能得到用户群体年龄分布,性别分布,地理位置分布,以及热门话题的热度。
SaaS 模式云数据仓库必修课
本课程由阿里云开发者社区和阿里云大数据团队共同出品,是SaaS模式云原生数据仓库领导者MaxCompute核心课程。本课程由阿里云资深产品和技术专家们从概念到方法,从场景到实践,体系化的将阿里巴巴飞天大数据平台10多年的经过验证的方法与实践深入浅出的讲给开发者们。帮助大数据开发者快速了解并掌握SaaS模式的云原生的数据仓库,助力开发者学习了解先进的技术栈,并能在实际业务中敏捷的进行大数据分析,赋能企业业务。 通过本课程可以了解SaaS模式云原生数据仓库领导者MaxCompute核心功能及典型适用场景,可应用MaxCompute实现数仓搭建,快速进行大数据分析。适合大数据工程师、大数据分析师 大量数据需要处理、存储和管理,需要搭建数据仓库?学它! 没有足够人员和经验来运维大数据平台,不想自建IDC买机器,需要免运维的大数据平台?会SQL就等于会大数据?学它! 想知道大数据用得对不对,想用更少的钱得到持续演进的数仓能力?获得极致弹性的计算资源和更好的性能,以及持续保护数据安全的生产环境?学它! 想要获得灵活的分析能力,快速洞察数据规律特征?想要兼得数据湖的灵活性与数据仓库的成长性?学它! 出品人:阿里云大数据产品及研发团队专家 产品 MaxCompute 官网 https://www.aliyun.com/product/odps&nbsp;
相关文章
|
2月前
|
机器学习/深度学习 存储 缓存
数据结构与算法学习十:排序算法介绍、时间频度、时间复杂度、常用时间复杂度介绍
文章主要介绍了排序算法的分类、时间复杂度的概念和计算方法,以及常见的时间复杂度级别,并简单提及了空间复杂度。
40 1
数据结构与算法学习十:排序算法介绍、时间频度、时间复杂度、常用时间复杂度介绍
|
6月前
|
算法 搜索推荐 测试技术
【调度算法】快速非支配排序算法
【调度算法】快速非支配排序算法
59 3
|
2月前
|
搜索推荐 算法
数据结构与算法学习十四:常用排序算法总结和对比
关于常用排序算法的总结和对比,包括稳定性、内排序、外排序、时间复杂度和空间复杂度等术语的解释。
29 0
数据结构与算法学习十四:常用排序算法总结和对比
|
2月前
|
机器学习/深度学习 搜索推荐 算法
探索数据结构:初入算法之经典排序算法
探索数据结构:初入算法之经典排序算法
|
4月前
|
机器学习/深度学习 运维 算法
无监督学习的12个最重要的算法介绍及其用例总结
无监督学习的12个最重要的算法介绍及其用例总结
|
6月前
|
搜索推荐 算法 数据挖掘
十大排序算法详解-上篇:比较排序算法【python 动态图解】
十大排序算法详解-上篇:比较排序算法【python 动态图解】
|
6月前
|
存储 算法 搜索推荐
【数据结构和算法】--- 基于c语言排序算法的实现(2)
【数据结构和算法】--- 基于c语言排序算法的实现(2)
39 0
|
6月前
|
搜索推荐 算法 大数据
​【数据结构与算法】冒泡排序:简单易懂的排序算法解析
​【数据结构与算法】冒泡排序:简单易懂的排序算法解析
|
6月前
|
搜索推荐 算法 C语言
【数据结构和算法】--- 基于c语言排序算法的实现(1)
【数据结构和算法】--- 基于c语言排序算法的实现(1)
50 0
|
7月前
|
存储 算法 搜索推荐
黑马c++ STL常用算法 笔记(3) 排序算法
黑马c++ STL常用算法 笔记(3) 排序算法