首页 > 编程语言 >常见的排序算法

常见的排序算法

时间:2024-08-16 19:57:56浏览次数:10  
标签:arr right temp int 常见 算法 排序 left

插入排序

时间复杂度 O(n^2) 空间复杂度 O(1)

选择排序的工作原理是在0~i的索引范围内去进行排序,将新增的数arr[ i ]一个个放进来排序,个人感觉有点类似于冒泡排序,但是和冒泡排序不同是选择排序是在小范围内找出最大最小完成一个小范围的有序,而冒泡排序是在整个数组中每次找到最大或者最小值

int[] arr = {7, 5, 1, 9, 3, 41, 5, -1};
public static void insertSort(int[] arr) {
    for (int i = 1; i < arr.length; i++) {
        int temp = arr[i];//记录当前值
        int index = i - 1; //当前数组内移动指针
        //判断从索引0开始到现在的范围内比temp大的 相当于比temp大的数向前移动腾一个位置出来
        while (index >= 0 && arr[index] > temp) arr[index + 1] = arr[index--];
        arr[index + 1] = temp;//在空出来的位置赋值temp
    }
}
//每次的数组变化
[5, 7, 1, 9, 3, 41, 5, -1]
[1, 5, 7, 9, 3, 41, 5, -1]
[1, 5, 7, 9, 3, 41, 5, -1]
[1, 3, 5, 7, 9, 41, 5, -1]
[1, 3, 5, 7, 9, 41, 5, -1]
[1, 3, 5, 5, 7, 9, 41, -1]
[-1, 1, 3, 5, 5, 7, 9, 41]

希尔排序

时间复杂度 O(nlog n) 空间复杂度 O(1)

希尔排序是在插入排序上的升级版,做到的是一个跳跃式的插入排序,将数组拆分成多个小数组进行插入排序,比如下面的案例就是将arr拆分成了 5 7 4 0 和 1 3 -2 来进行插入排序 之后变为 0, 1, -2, 3, 4, 7, 5 再整体进行一次插入排序即可

    int[] arr = {5, 1, 7, 3, 4, -2, 0};
    int gap = arr.length / 2;
    while (gap > 0) {
        for (int i = gap; i < arr.length; i++) {
            int index = i;
            int temp = arr[index];
            while (index >= gap && temp < arr[index - gap]) {//跳跃式的插入排序
                arr[index] = arr[index - gap];
                index = index - gap;
            }
            arr[index] = temp;
        }
        gap = gap / 2;
    }
}
//每次变化的arr
[0, 1, -2, 3, 4, 7, 5]
[-2, 0, 1, 3, 4, 5, 7]

冒泡排序

时间复杂度 O(n^2) 空间复杂度 O(1)

冒泡排序是在每一次循环都找出最大值并放到最后面

public static void bubbleSort(int[] arr) {
    boolean flag; // 标记当前循环是否发生了元素交换 用于剪枝
    // 外层循环控制趟数,每一趟将一个最大值移到数组末尾
    for (int i = 0; i < arr.length - 1; i++) {
        flag = false; // 每次循环开始时将标记设置为false
        // 内层循环用于对未排序部分进行冒泡操作
        for (int j = 0; j < arr.length - 1 - i; j++) {
            // 如果前一个元素大于后一个元素,则交换它们
            if (arr[j] > arr[j + 1]) {
                int temp = arr[j];
                arr[j] = arr[j + 1];
                arr[j + 1] = temp;
                flag = true; // 发生了交换,更新标记
            }
        }
        // 如果一趟下来没有发生交换,说明数组已经有序,直接退出循环
        if (!flag) break;
    }
}

选择排序

时间复杂度 O(n^2) 空间复杂度 O(1)

和冒泡排序一个样,找到最小的数和索引在不停的放在为排序的数组的最前面

int[] arr = {12, 2, -1, 9, 2, 4, 6, 0};
for (int i = 0; i < arr.length; i++) {
    int temp = arr[i], index = i;
    for (int j = i + 1; j < arr.length; j++) {//找到最小的数和最小数的索引
        if (arr[j] < temp) {
            temp = arr[j];
            index = j;
        }
    }
    if (index != i) {
        arr[index] = arr[i];//让最小的数和当前未确定的数字进行交换
        arr[i] = temp;
    }
}
//每次排序完的arr
[-1, 2, 12, 9, 2, 4, 6, 0]
[-1, 0, 12, 9, 2, 4, 6, 2]
[-1, 0, 2, 9, 12, 4, 6, 2]
[-1, 0, 2, 2, 12, 4, 6, 9]
[-1, 0, 2, 2, 4, 12, 6, 9]
[-1, 0, 2, 2, 4, 6, 12, 9]
[-1, 0, 2, 2, 4, 6, 9, 12]
[-1, 0, 2, 2, 4, 6, 9, 12]

基数排序

时间复杂度 O(n+z * n) 空间复杂度 O(m*n) m代表桶的个数,一般是10,n表述数组长度 z表示最大数的位数

基数排序的原理是通过比较位数来进行排序 因此基数排序只能适用于正数 可以想从个位数开始进行位数的排序每一次都是对当前位数的数完成了范围内的排序 比如 8 12 3 6 在各位排序完就是 3 12 6 8 可以看出来个位数已经被完全排序好了(12忽略掉,他属于两位数),再比较十位数的大小,个数可以补零进行比较,于是就得到了 3 6 8 12 这个懂了的话桶排序就很好理解了

int[] arr = {53, 3, 542, 748, 14, 214};
public static void radixSort(int[] arr) {
    //找到最大数 用来判断需要循环几次
    int max = Integer.MIN_VALUE;
    for (int num : arr) {
        if (num > max) max = num;
    }
    //创建二维数组桶,因为每个位有0~9共十个数 所以需要创建十个桶,最极限的情况是一个桶装arr里面所有的元素
    int[][] bucket = new int[10][arr.length];
    //还需要定义一个一维数组用来表示桶中有多少个数据
    int[] bucketElementCounts = new int[10];
    for (int i = 1, z = 1; i <=(max + "").length(); i++, z = z * 10) {
        for (int num : arr) {//将当前的数按照 位数放到对应的桶中 i=1表示比较个位 i=2表示十位.....
            int digit = num / z % 10;
            bucket[digit][bucketElementCounts[digit]] = num;
            bucketElementCounts[digit]++;
        }
        //开始排序,直接按顺序放入数据即可
        int index = 0;//插入数据的索引
        for (int j = 0; j < 10; j++) {
            int num = bucketElementCounts[j];
            //说明桶中没有数据
            if (num == 0) continue;
            for (int k = 0; k < num; k++) {//将桶中的数据放进去 并归零记录数据量的数组
                arr[index++] = bucket[j][k];
            }
            bucketElementCounts[j] = 0;
        }
    }
}
//每次排序完的arr
[542, 53, 3, 14, 214, 748] //个位
[3, 14, 214, 542, 748, 53] //十位
[3, 14, 53, 214, 542, 748] //百位

快速排序

时间复杂度 O(nlog n) 空间复杂度 O(n)

快速排序的算法是一种分治的体现,将大问题拆分成若干个小问题去解决

在快排中我们首先需要选择一个基准数,用于区分左右两边(任意选择都可以,但是修改了基准之后代码也会有相应的小改动),这里以选择最左边为例,在我们选择好了基准之后就可以进行数字的交换了,要做到的是循环完之后基准数左边的数字都是小于等于基准数的,右边都是大于等于 顺序为先从右边开始递减寻找比基准小的,当找到这个数(a)的时候就停止循环,此时的left索引记录的数就可以修改成a,再开始从左边找起,当找到这个比基准数大的数(b)的时候就停止循环,此时right索引记录的数就修改为b,一直如此循环,直到right==left,此时结束循环.且right索引下的数字修改为基准数,这样第一次分左右就完成了,接下来就是重复的事情就可以递归完成

 public static void main(String[] args) {
        int[] arr = {3, 24, 16, 1, 32, 4, 7, 4, 98, 2, 0};
        int left = 0, right = arr.length - 1;
        quickSort(arr, left, right);
        System.out.println(Arrays.toString(arr));
    }
​
    public static void quickSort(int[] arr, int start, int end) {
        //结束条件
        if (start >= end) {
            return;
        }
        int right = end, left = start;
        int base = arr[left];
        while (right > left) {
            //找到比基准要小的数字 从右向左
            while (right > left && arr[right] >= base) right--;
            //交换
            arr[left] = arr[right];
            //找到比基准要大的数字 从左向右
            while (right > left && arr[left] <= base) left++;
            //交换
            arr[right] = arr[left];
        }
        //因为循环条件是right > left 所以结束的时候一定right = left
        arr[left] = base;
        //递归解决左半边
        quickSort(arr, 0, left - 1);
        //递归解决右半边
        quickSort(arr, right + 1, end);
    }

归并排序

时间复杂度 O(nlog n) 空间复杂度 O(n)

归并排序是一种基于分治法的经典排序算法。它的基本思想是将数组分成较小的子数组,分别对这些子数组进行排序,然后再将它们合并成一个有序的数组,首先设定两个指针,最初位置分别为两个已经排序序列的起始位置,之后比较两个指针的大小并赋值给临时数组,在将剩下的需要排序的数组放入到临时数组中,这样当前范围内的排序就完成了,再将临时数组赋值给原本的数组,一直重复这样的步骤就可以了

    public static void main(String[] args) {
        int[] arr = {8, 4, 5, 7, 1, 3, 6, 2};
        mergeSort(arr, 0, arr.length - 1);
    }
​
    //合并
    public static void mergeSort(int[] arr, int left, int right) {
        if (left < right) {
            int mid = (left + right) / 2;
            //向左递归分解
            mergeSort(arr, left, mid);
            //向右递归分解
            mergeSort(arr, mid + 1, right);
            //合并
            merge(arr, left, mid, right);
        }
    }
​
    public static void merge(int[] arr, int left, int mid, int right) {
        int[] temp = new int[right - left + 1]; // 只创建需要的临时数组
        int i = left;//左指针
        int j = mid + 1;//右指针
        int index = 0;//temp的索引
        while (i <= mid && j <= right) {
            //如果左边的有序序列的元素小于等于右边的有序序列的元素就拷贝左边的元素到temp中 反之则反过来
            if (arr[i] <= arr[j]) temp[index++] = arr[i++];
            else temp[index++] = arr[j++];
        }
        //把有剩余的数据的一遍的数据依次填充到temp中
        while (i <= mid) temp[index++] = arr[i++];//左边的有序的填充
        while (j <= right) temp[index++] = arr[j++];//右边的全部填充
        //将temp拷贝到原本的arr中
        System.arraycopy(temp, 0, arr, left, temp.length);
    }
//每次排序完的arr
[4, 8, 5, 7, 1, 3, 6, 2]
[4, 8, 5, 7, 1, 3, 6, 2]
[4, 5, 7, 8, 1, 3, 6, 2]
[4, 5, 7, 8, 1, 3, 6, 2]
[4, 5, 7, 8, 1, 3, 2, 6]
[4, 5, 7, 8, 1, 2, 3, 6]
[1, 2, 3, 4, 5, 6, 7, 8]

计数排序

时间复杂度 O(n+k) 空间复杂度 O(n+k) 其中 k 是输入数组中的最大值

一种很明显的拿空间换时间的排序方法,不同于其他的排序,计算排序是不需要和其他元素进行比较的,其原理在于创建一个最大数为长度的数组,之后遍历整个数组获取每个元素的数量,在对应的数组索引下+1,最后再将整个最大数为长度的数组遍历找出里面的元素,这个数组中对应的数字为多少就代表当前这个元素有多少个 这个排序在重复元素多并且间隔小的时候是非常快的,但遇到元素间距差很大的时候就不太能考虑了

 public static void countingSort(int[] arr) {
        if (arr.length == 0 || arr.length == 1) return;
        //找到数组中的最大值
        int max = Arrays.stream(arr).max().getAsInt();
        //创建对应的数组
        int[] count = new int[max + 1];
        // 统计每个元素的出现次数
        for (int num : arr) count[num]++;
        //赋值
        for (int i = 0, index = 0; i < count.length; i++) {
            if (count[i] == 0) continue;
            for (int j = 0; j < count[i]; j++) arr[index++] = i;
        }
    }

堆排序

时间复杂度 O(nlog n) 空间复杂度 O(1)

堆是具有以下性质的完全二叉树:每个结点的值都大于或等于其左右孩子结点的值,称为大顶堆或者每个结点的值都小于或等于其左右 孩子结点的值,称为小顶堆 大顶堆:arr[i] >= arr[2i+1] && arr[i] >= arr[2i+2] 小顶堆:arr[i] <= arr[2i+1] && arr[i] <= arr[2i+2]

堆排序的步骤分为三步,第一步,构造大(小)顶堆,第二步,将数组未排序的最后一位元素和第一位元素进行交换,最后一步,重复前两步直到最后未排序的最后一位元素的索引-1和第一位元素重合 即下面的 i >1

    public static void main(String[] args) {
        int[] arr = {4, 7, 1, 8, 543, 43, 65, 3};
        HeapSort(arr);
    }

    
    public static void HeapSort(int[] arr) {
        //构建大顶堆
        for (int i = arr.length / 2 - 1; i >= 0; i--) {
            bigHeap(arr, arr.length, i);
        }
        //构建完之后开始交换
        for (int i = arr.length - 1; i > 0; i--) {
            int temp = arr[i];
            arr[i] = arr[0];
            arr[0] = temp;
            //调整大顶堆
            bigHeap(arr, i, 0);//因为每次删除的都是堆顶(根节点),所以需要从根节点 0 开始重新堆化
            // 下面的其他节点在之前都已经被堆化好了
        }
        //小顶堆
        //构建小顶堆
        for (int i = arr.length / 2 - 1; i >= 0; i--) {
            smallHeap(arr, arr.length, i);
        }
        //开始交换
        for (int i = arr.length - 1; i > 0; i--) {
            int temp = arr[i];
            arr[i] = arr[0];
            arr[0] = temp;
            //调整小顶堆
            smallHeap(arr, i, 0);
        }

    }

    /**
     * @Param arr: 需要调整的数组
     * @Param n: 需要调整的节点的数量
     * @Param i: 调整的非子叶节点
     */
    public static void bigHeap(int[] arr, int n, int i) {
        int max = i;
        int left = 2 * i + 1;//左子节点
        int right = 2 * i + 2;//右子节点
        if (left < n && arr[left] > arr[max]) max = left;
        if (right < n && arr[right] > arr[max]) max = right;
        //如果最大值不是根节点就交换
        if (max != i) {
            int temp = arr[i];
            arr[i] = arr[max];
            arr[max] = temp;
            // 递归堆化受影响的子树
            bigHeap(arr, n, max);
        }
    }

    public static void smallHeap(int[] arr, int n, int i) {
        int min = i;
        int left = 2 * i + 1;//左子节点
        int right = 2 * i + 2;//右子节点
        if (left < n && arr[left] < arr[min]) min = left;
        if (right < n && arr[right] < arr[min]) min = right;
        //如果最小值不是根节点就交换
        if (min != i) {
            int temp = arr[i];
            arr[i] = arr[min];
            arr[min] = temp;
            // 递归堆化受影响的子树
            smallHeap(arr, n, min);
        }
    }

睡眠排序

时间复杂度 ? 空间复杂度 O(n) 手动狗头(doge)

    public static void sleepSort(int[] arr) {
        for (int num : arr) {
            new Thread(() -> {
                try {
                    Thread.sleep(num * 10L);
                    System.out.print(num );
                } catch (Exception ignore ) {}
            }).start();
        }
    }

标签:arr,right,temp,int,常见,算法,排序,left
From: https://blog.csdn.net/guabghaibaoan/article/details/141196215

相关文章

  • 快速排序算法详解及Python实现
    目录引言快速排序算法步骤快速排序的Python实现性能分析注意事项引言快速排序(QuickSort)是一种高效的排序算法,由C.A.R.Hoare在1960年提出。它的基本思想是:通过一趟排序将待排序的数据分割成独立的两部分,其中一部分的所有数据都比另一部分的所有数据要小,然后再按此......
  • 文献分享: DiskANN查询算法的复杂度下界
    文章目录0.写在前面0.1.预备知识0.2.一些记号0.3.本文的研究概述1.DiskANN\text{DiskANN}DiskANN回顾1.0.Intro1.1.......
  • 2024“钉耙编程”中国大学生算法设计超级联赛(9)
    Preface最后一场HDU多校了,前期一直犯病但也堪堪签了前六题,但后期又是酣畅淋漓的后期三开三卡我写04,祁神写09,徐神写10,最后一个没调出来,赛后祁神和徐神都发现了很多修改点但因为题目还没公开、数据和题解也没法,就先坑着之后再来补了树异或价值首先不难发现\(k\)位这个限......
  • 基于三帧差算法的运动目标检测系统FPGA实现,包含testbench和MATLAB辅助验证程序
    目录1.算法运行效果图预览2.算法运行软件版本3.部分程序4.算法理论概述5.算法完整程序工程1.算法运行效果图预览(完整程序运行后无水印)将FPGA的仿真结果导入到MATLAB中,分别得到MATLAB的结果和FPGA的结果:2.算法运行软件版本vivado2019.2matlab2022a3.部分程序......
  • C语言学习 --- 冒泡排序与二分查找
    冒泡排序 排序        从小到大顺序排 轮数        数据个数-1 每一次比较的次数      数据个数-1-当前的轮数      每次比较开始从下标为0的地方开始比较     轮数:0~<数据个数-1次数:0~<数......
  • 算法笔记——可持久化线段树
    维护历史值当要修改一个节点时,把跟他有关的线段树中所有节点舍弃,并建立新节点连接.代码如下:#include<bits/stdc++.h>usingnamespacestd;constintN=1e6+5;intn,m,a[N],root[N],top;structnode{ intl,r,val;}t[N*40];intclone(intx)//新建节点{ top++; t......
  • 力扣面试经典算法150题:找出字符串中第一个匹配项的下标
    找出字符串中第一个匹配项的下标今天的题目是力扣面试经典150题中的数组的简单题:找出字符串中第一个匹配项的下标题目链接:https://leetcode.cn/problems/find-the-index-of-the-first-occurrence-in-a-string/description/?envType=study-plan-v2&envId=top-interview-......
  • 详解堆排序(内附代码实现)
    堆排序有两个过程下标为i的节点的父节点下标:(i-1)/2整除下标为i的节点的左孩子下标:i*2+1下标为i的节点的右孩子下标:i*2+2待排序序列为:​ 23814910716141.建大顶堆​ 首先建立无序堆 然后建立大顶堆:从右往左,从下往上,递归的选择子节点最大的往上浮首先14大......
  • KMP算法
    KMP算法介绍KMP(Knuth-Morris-Pratt)算法是一种高效的字符串匹配算法,用于在主串(text)中查找模式串(pattern)。KMP通过预处理模式串,避免了在匹配失败时回溯主串,提高了匹配效率。其时间复杂度为O(n+m),其中n是主串的长度,m是模式串的长度。KMP算法的关键思想KMP算法的核心思想......
  • CRC算法原理、推导及实现
    CRC,CyclicRedundancyCheck,循环冗余校验1.基本原理CRC的本质是除法,把待检验的数据当作一个很大(很长)的被除数,两边选定一个除数(有的文献叫poly),最后得到的余数就是CRC的校验值。判定方法:将消息和校验和分开。计算消息的校验和(在附加W个零后),并比较两个校验和。把校验......