常见排序算法总结

news2024/12/23 10:59:43

文章目录

  • 比较排序
    • 冒泡排序
    • 选择排序
    • 插入排序
    • 归并排序
    • 快速排序
    • 堆排序
    • 希尔排序
  • 非比较排序(桶排序)
    • 计数排序
    • 基数排序

比较排序

冒泡排序

嵌套循环,每次内层循环执行时,数组的每两个元素交换,将一个最大/小的数排到数组末尾

在这里插入图片描述

public void bubbleSort(int[] arr){
    for (int i = 0; i < arr.length; i++) {
        // 内层循环中,每轮都是让最后一个位置排好序,然后外层循环向前递进
        for (int j = 0; j < arr.length - i - 1; j++) {
            if (arr[j] > arr[j+1]){  // 把大的数挪到后面
                int tmp = arr[j];
                arr[j] = arr[j+1];
                arr[j+1] = tmp;
            }
        }
    }
}

选择排序

嵌套循环,每次内层循环执行时,子数组的后m-1个元素寻找最小值,再与子数组的首元素比较,如果更大/小就交换

在这里插入图片描述

public void selectSort(int[] arr){
    for (int i = 0; i < arr.length - 1; i++){    // len个元素只需比较n-1次
        int min_idx = i;
        for (int j = i + 1; j < arr.length; j++){
            if (arr[j] < arr[min_idx])     // 在i+1后面的数两两比较,找到最小下标
                min_idx = j;
        }
        // 内层遍历完之后,我们拿到后面的元素的最小值下标min_id,要和前面的下标i元素进行值的对比
        if (arr[min_idx] < arr[i]){        // 能篡位就篡位!
            arr[min_idx] = arr[min_idx] ^ arr[i];
            arr[i]       = arr[min_idx] ^ arr[i];
            arr[min_idx] = arr[min_idx] ^ arr[i];
        }
    }
}

插入排序

嵌套循环,默认第一个元素不处理,每插入/增加一个数,就依次跟前面的数两两对比,将前面的所有数进行排好序

在这里插入图片描述

public void insertSort(int[] arr){
    for (int i = 1; i < arr.length; i++) {  // 外层循环len-1次
        // 对应外层的len-1次,内层分别循环1到len-1次,且后一个数 < 前一个数才会进循环交换
        for (int j = i - 1; j >= 0 && arr[j + 1] < arr[j]; j--){
            // 如果后一个元素更小,就交换到前面来
            arr[j]     = arr[j] ^ arr[j + 1];
            arr[j + 1] = arr[j] ^ arr[j + 1];
            arr[j]     = arr[j] ^ arr[j + 1];
        }
    }
}

归并排序

递归到最底层时会执行merge排序,回溯时排序保证了此时两个子数组都必然有序

在这里插入图片描述

// 不传参默认对整个数组进行归并排序
public void mergeSort(int[] arr) {
    if (arr == null || arr.length < 2) return;
    mergeSort(arr, 0, arr.length - 1);
}
// 传参则对[l, r]区间进行归并排序
public void mergeSort(int[] arr, int l, int r) {
    if (l == r) return;
    int mid = l + ((r - l) >> 1);
    mergeSort(arr, l, mid);
    mergeSort(arr, mid + 1, r);
    merge(arr, l, mid, r);  // 回溯位置归并,所以此时的子数组皆已排序完成
}
// 对 arr 的 [l, m]  [m+1, r] 两部分子数组进行归并排序
public void merge(int[] arr, int l, int m, int r) {
    int[] help = new int[r - l + 1];  // 辅助数组,暂时存放归并后的数组
    int i = 0;
    // 两个子数组的起始索引位置 p1 p2
    int p1 = l, p2 = m + 1;
    // 精髓:看数据哪边小,就先挪哪边的数据到help数组里
    while (p1 <= m && p2 <= r) 
        help[i++] = arr[p1] < arr[p2] ? arr[p1++] : arr[p2++];
    while (p1 <= m) help[i++] = arr[p1++];
    while (p2 <= r) help[i++] = arr[p2++];
    // 将暂存的归并后的数组,覆盖到 arr 上,使原数组排好序
    for (i = 0; i < help.length; i++) {
        arr[l + i] = help[i];
    }
}

快速排序

整体的quickSort函数swap后,再进行partition,划分三部分后,<区域 >区域 分别递归调用局部的quicksort

在这里插入图片描述

public void quickSort(int[] arr) {
    if (arr == null || arr.length < 2) return;
    quickSort(arr, 0, arr.length - 1);
}

public void quickSort(int[] arr, int l, int r) {
    if (l < r) {
        // 左神的随机:让最右的r与[l,r-1]之间的随机一个数交换
        swap(arr, l + (int) (Math.random() * (r - l + 1)), r);
        // 先partition再进递归(与归并的回溯时merge区分)
        int[] p = partition(arr, l, r);
        quickSort(arr, l, p[0] - 1); // = 区域的左部分
        quickSort(arr, p[1] + 1, r); // = 区域的右部分
    }
}

public int[] partition(int[] arr, int l, int r) {
    int less = l - 1;
    int more = r;
    while (l < more) {
        if (arr[l] < arr[r]) {
            swap(arr, ++less, l++);
        } else if (arr[l] > arr[r]) {
            swap(arr, --more, l);
        } else {
            l++;
        }
    }
    swap(arr, more, r);  // 最右那个 = 划分值挪回原位
    return new int[] { less + 1, more };  // = 区域:[ less+1, more ]
}

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

堆排序

堆是一种比较特殊的完全二叉树

分为:大根堆小根堆

由于完全二叉树的结构性质,可以使用数组或列表等线性数据结构来存储堆(此处用优先级队列)

大根堆:每个子树的最大节点是头结点

小根堆:每个子树的最小节点是头结点

在这里插入图片描述

public static void heapSort(int[] arr) {
    if (arr == null || arr.length < 2) return;
    // 1. 先构建大根堆,完成后就已知arr最大值(根节点的value)
    // 传统 heapInsert1
    // for (int i = 0; i < arr.length; i++) {
    //    heapInsert1(arr, i);
    // }
    // 进化 heapInsert2
    heapInsert2(arr);
    // 2. 取出根节点这个最大值,与末尾节点做交换,然后最大值相当于到末尾排好序了,所以移除这个末尾(最大值)元素
    //    末尾节点到根节点位置后就heapify,再去重新进行大根堆的构建
    int size = arr.length;
    swap(arr, 0, --size);
    while (size > 0) {
        heapify(arr, 0, size);
        swap(arr, 0, --size);
    }
}
// 1. 构建大根堆   v1.0 传统版
public static void heapInsert1(int[] arr, int index) {
    while (arr[index] > arr[(index - 1) / 2]) {
        swap(arr, index, (index - 1) /2);
        index = (index - 1)/2 ;
    }
}
// 1. 构建大根堆   v2.0 进化版
public static void heapInsert2(int[] arr){
    for (int i = arr.length - 1; i >= 0; i--){
        heapify(arr, i, arr.length);
    }
}
// 2. 某个数在index位置,能否往下(数组下标越大的方向)移动
public static void heapify(int[] arr, int index, int size) {
    int left = index * 2 + 1;
    while (left < size) {
        int largest = left + 1 < size && arr[left + 1] > arr[left] ? left + 1 : left;
        largest = arr[largest] > arr[index] ? largest : index;
        if (largest == index)
            break;
        swap(arr, largest, index);
        index = largest;
        left = index * 2 + 1;
    }
}

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

希尔排序

间隔分组,且分组间隔依次减半,每次分组后,每个组内排序都是插入排序

相比于一开始就用插入排序,希尔排序的比较次数更少,效率更高

为什么呢?大概是因为插入排序没预处理,极端情况可能让一个很小很右的数一直比比比比比,比到最开头,浪费比较次数

而希尔排序会让每次分组比较后,基本上左侧大区间 < 右侧大区间 类似 log(n) 但实际复杂度为 O ( n 1.3 − 2 ) O(n^{1.3-2}) O(n1.32) -表示范围,不是减号

所以其实最坏情况和插入排序一样,只不过可能性较小,正常都比插入排序好些

public void shellSort(Comparable[] arr) {
    // 不断减半分组排序
    for (int gap = arr.length / 2; gap > 0; gap /= 2) {
        // 对每个步长的整个数组进行插入排序
        for (int i = gap; i < arr.length; i++) {
            // 内层就是插入排序,但交换的间隔单位为gap,不会影响其他分组
            for (int j = i; j >= gap && arr[j].compareTo(arr[j - gap]) < 0; j -= gap) {
                // 交换元素
                Comparable temp = arr[j];
                arr[j] = arr[j - gap];
                arr[j - gap] = temp;
            }
        }
    }
}

非比较排序(桶排序)

计数排序

省流:词频表

// only for 0~200 value,可以更大,但太占内存空间了,还不如换别的
// 仅适用于数组数据较为集中密集的情况,太稀疏的话,中间一堆内存空间浪费
public void countSort(int[] arr) {
    if (arr == null || arr.length < 2) return;
    int max = Integer.MIN_VALUE;
    for (int i = 0; i < arr.length; i++) {
        max = Math.max(max, arr[i]);
    }
    // 一个词频表
    int[] bucket = new int[max + 1];
    for (int i = 0; i < arr.length; i++) {
        bucket[arr[i]]++;
    }
    // 词频表中的数据依次取出放到arr,保证有序
    int i = 0;
    for (int j = 0; j < bucket.length; j++) {
        while (bucket[j]-- > 0) {
            arr[i++] = j;
        }
    }
}

基数排序

数字按最多多少位,先补齐

从个位开始排,开始进桶再出桶,完成个位上的优先级排序

从十位开始排,还是进桶出桶,完成十位上的优先级排序

依次百位,千位… 越往后,越晚排,优先级越高

同时之前的优先级也会保留下来

所有都排完,数组就有序了(升序/降序)

某种情况比计数排序好,因为数字,如果按照普通十进制理解,则只需准备10个不同数字的桶就好了!

局限:得根据多少进制准备多个桶,需要有进制这个前提规则!

前提得知道空间范围,比如人的年龄,正数[0-200],否则内存爆炸!

所以这种不基于比较的算法应用范围很局限,大部分情况下,不如之前的所有比较算法!

图解如下:
在这里插入图片描述

前缀和处理,倒序入桶,以及词频——比较难理解,可以看下面左神讲解

左神桶排序 2:15:00

// 只适合非负数
public void radixSort(int[] arr) {
    if (arr == null || arr.length < 2) return;
    radixSort(arr, 0, arr.length - 1, maxbits(arr));
}
// 计算数组中最大元素的位数(比如324就是三位数)
public int maxbits(int[] arr) {
    int max = Integer.MIN_VALUE;
    for (int i = 0; i < arr.length; i++) {
        max = Math.max(max, arr[i]);
    }
    int res = 0;
    while (max != 0) {
        res++;
        max /= 10;
    }
    return res;
}

public void radixSort(int[] arr, int begin, int end, int digit) {
    final int radix = 10;  // 默认十进制
    int i = 0, j = 0;
    // 有多少个数就准备多少个辅助空间
    int[] bucket = new int[end - begin + 1];
    // 有多少个“十进制位”,就出桶进桶多少次,所以循环digit次(从个位开始算)
    for (int d = 1; d <= digit; d++) {
        int[] count = new int[radix];
        // 1. 遍历每一个arr元素,根据外层循环次数,取出个/十/百...位上的数字(getDigit的作用)
        for (i = begin; i <= end; i++) {
            j = getDigit(arr[i], d);
            count[j]++;
        }
        // 2. 遍历好个/十/百...位上的所有数字后,count数组记录了对于数字出现的频数
        //    接下来,把词频数换成前缀数,记录<=当前索引的数字个数,处理成如下效果:
        //                        "10个空间"
        //          count[0] 当前位(d位)是 0      的数字有多少个
        //          count[1] 当前位(d位)是 0和1   的数字有多少个
        //          count[2] 当前位(d位)是 0,1和2 的数字有多少个
        //          count[i] 当前位(d位)是 0-i    的数字有多少个
        for (i = 1; i < radix; i++) {
            count[i] = count[i] + count[i - 1];
        }
        // 3. 入桶操作:从右往左遍历,根据个/十/百...位上的数字大小进行相应入桶,排好序
        for (i = end; i >= begin; i--) {
            j = getDigit(arr[i], d);
            bucket[count[j] - 1] = arr[i];
            count[j]--;
        }
        // 4. 出桶操作:将bucket(help)辅助数组,覆盖赋值到原来的数组arr中
        for (i = begin, j = 0; i <= end; i++, j++) {
            arr[i] = bucket[j];
        }
    }
}
// 获取一个整数 x 在指定位数 d 上的数字
public int getDigit(int x, int d) {
    return ((x / ((int) Math.pow(10, d - 1))) % 10);
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1936910.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

语音合成-TTS文字转语音(专业版)

语音合成-TTS文字转语音(专业版) 一、工具简介 *使用强大的智能AI语音库&#xff0c;合成独具特色接近真人语音的朗读音频。 *使用极具表现力和类似人类的声音&#xff0c;使文本阅读器和已启用语音的助理等方案栩栩如生。 *用途&#xff1a;这个语音工具&#xff0c;不仅可…

「实战应用」如何用DHTMLX将上下文菜单集成到JavaScript甘特图中(三)

DHTMLX Gantt是用于跨浏览器和跨平台应用程序的功能齐全的Gantt图表。可满足项目管理应用程序的所有需求&#xff0c;是最完善的甘特图图表库。 DHTMLX Gantt是一个高度可定制的工具&#xff0c;可以与项目管理应用程序所需的其他功能相补充。在本文中您将学习如何使用自定义上…

LeetCode岛屿的最大面积(深度搜索)/什么是深搜,简单案例回顾图用邻接表实现图的深度优先遍历。

看这道题不懂深度搜索的可以看看下面讲述 岛屿的最大面积 解题思路 代码 class Solution {int dfs(vector<vector<int>>& grid, int cur_i, int cur_j) {//确定边界if((cur_i >0 && cur_i < grid.size()) && (cur_j >0 &&…

【C语言】 作业11 链表+实现函数封装

递归实现链表数据互换&#xff0c;纯不会&#xff0c;明天再说 1、链表实现以下功能 链表&#xff0c;创建链表&#xff0c;申请节点&#xff0c;判空&#xff0c;头插&#xff0c;遍历输出&#xff0c;通过位置查找节点&#xff0c;任意位置插入&#xff0c;头删&#xff0c;…

技术成神之路:设计模式(八)责任链模式

介绍 责任链模式&#xff08;Chain of Responsibility Pattern&#xff09;是一种行为设计模式&#xff0c;它允许多个对象依次处理请求&#xff0c;避免请求的发送者和接收者之间的显式耦合。该模式通过将多个可能处理请求的对象连接成一条链&#xff0c;并沿着这条链传递请求…

通过角点进行水果的果梗检测一种新方法

一、前言 在前面的《数字图像处理与机器视觉》案例一&#xff08;库尔勒香梨果梗提取和测量&#xff09;中主要使用数学形态学的方法进行果梗提取&#xff0c;下面给出一种提取果梗的新思路。 众所周知&#xff0c;一般果梗和果实在边缘处角度有较大突变&#xff0c;可以通过合…

Kafka介绍及Go操作kafka详解

文章目录 Kafka介绍及Go操作kafka详解项目背景解决方案面临的问题业界方案ELKELK方案的问题日志收集系统架构设计架构设计组件介绍将学到的技能消息队列的通信模型点对点模式 queue发布/订阅 topicKafka介绍Kafka的架构图工作流程选择partition的原则ACK应答机制Topic和数据日志…

亚信安全终端一体化解决方案入选应用创新典型案例

近日&#xff0c;由工业和信息化部信息中心主办的2024信息技术应用创新发展大会暨解决方案应用推广大会成功落幕&#xff0c;会上集中发布了一系列技术水平先进、应用效果突出、产业带动性强的信息技术创新工作成果。其中&#xff0c;亚信安全“终端一体化安全运营解决方案”在…

Jolt路线图

1. 引言 a16z crypto团队2024年7月更新了其Jolt路线图&#xff1a; 主要分为3大维度&#xff1a; 1&#xff09;链上验证维度&#xff1a; 1.1&#xff09;Zeromorph&#xff1a;见Aztec Labs团队2023年论文 Zeromorph: Zero-Knowledge Multilinear-Evaluation Proofs from…

iOS——编译链接

编译连接的过程 预处理编译汇编链接 预处理 clang -E main.m -o main.i“#define"删除并展开对应宏定义。处理所有的条件预编译指令。如#if/#ifdef/#else/#endif。”#include/#import"包含的文件递归插入到此处。删除所有的注释"//或/**/"。添加行号和文…

python—爬虫的初步了解

Python 爬虫&#xff08;Web Scraping&#xff09;是一种自动化从网站上提取数据的技术。Python 由于其简洁的语法、丰富的库和强大的社区支持&#xff0c;成为了实现网络爬虫的首选语言之一。下面是一些Python爬虫的基本概念和步骤&#xff1a; 1. 爬虫的基本概念 请求&…

JavaEE:Spring Web简单小项目实践三(留言板实现)

学习目的&#xff1a; 1、理解前后端交互过程 2、学习接口传参&#xff0c;数据返回以及页面展示 目录 1、准备工作 2、约定前后端交互接口 1、获取全部留言 2、发表新留言 3、实现服务器端代码 4、调整前端页面代码 5、运行测试 1、准备工作 创建SpringBoot项目&#x…

WebRTC音视频-环境搭建

目录 期望效果 1:虚拟机和系统安装 2:WebRTC客户端环境搭建 2.1&#xff1a;VScode安装 2.2&#xff1a;MobaXterm安装 3:WebRTC服务器环境搭建 3.1&#xff1a;安装openssh服务器 3.2&#xff1a;安装Node.js 3.3&#xff1a;coturn穿透和转发服务器 3.3.1&a…

构建高效Node.js中间层:探索请求合并转发的艺术

&#x1f389; 博客主页&#xff1a;【剑九 六千里-CSDN博客】 &#x1f3a8; 上一篇文章&#xff1a;【CSS盒模型&#xff1a;掌握网页布局的核心】 &#x1f3a0; 系列专栏&#xff1a;【面试题-八股系列】 &#x1f496; 感谢大家点赞&#x1f44d;收藏⭐评论✍ 引言&#x…

阿里云 https证书部署

一.申请证书 二.查看状态 查看状态&#xff0c;已签发是完成了申请证书 三.部署 我在nginx服务器上部署 具体操作链接:阿里云文档 修改前 修改后 四.重启ngnix 五.验证是否成功 在浏览器输入域名查看

maven 私服搭建(tar+docker)

maven私服搭建 一、linux安装nexus1、工具下载 二、 docker 搭建nexus1、镜像下载创建目录2、运行nexus3、访问确认&#xff0c;修改默认密码&#xff0c;禁用匿名用户登录4、创建仓库5、创建hostd仓库6、创建Blob Stores7、创建docker私服1、创建proxy仓库2、创建hotsed本地仓…

Qt自定义下拉列表-可为选项设置标题、可禁用选项

在Qt中,ComboBox&#xff08;组合框&#xff09;是一种常用的用户界面控件,它提供了一个下拉列表,允许用户从预定义的选项中选择一个。在项目开发中&#xff0c;如果简单的QComboBox无法满足需求&#xff0c;可以通过自定义QComboBox来实现更复杂的功能。本文介绍一个自定义的下…

144. 字典序最小的 01 字符串(卡码网周赛第二十六期(23年阿里淘天笔试真题))

题目链接 144. 字典序最小的 01 字符串&#xff08;卡码网周赛第二十六期&#xff08;23年阿里淘天笔试真题&#xff09;&#xff09; 题目描述 小红有一个 01 字符串&#xff0c;她可以进行最多 k 次提作&#xff0c;每次操作可以交换相邻的两个字符&#xff0c;问可以得到的…

C++的STL简介

0.STL简介 C的STL&#xff08;Standard Template Library&#xff0c;标准模板库&#xff09;是C标准库的一部分&#xff0c;它提供了一套通用的类和函数模板&#xff0c;用于处理数据结构和算法。STL的主要组件包括&#xff1a; 容器分配器算法迭代器适配器仿函数 容器 容…

制造运营管理系统(MOM系统),企业实现先进制造的关键一步

随着全球制造业的快速发展&#xff0c;企业对于生产效率和成本控制的要求日益增高。在这个背景下&#xff0c;制造运营管理系统&#xff08;MOM系统&#xff09;成为了企业提升竞争力的关键工具。盘古信息作为业内领先的智能制造解决方案提供商&#xff0c;其MOM系统更是以其卓…