七大排序算法详解

news2024/12/25 14:25:16

1.概念

1.排序的稳定性

常见的稳定的排序有三种:直接插入排序,冒泡排序,归并排序

对于一组数据元素排列,使用某种排序算法对它进行排序,若相同数据之间的前后位置排序后和未排序之前是相同的,我们就成这种排序算法具有稳定性

单看单个属性的稳定性并无意义,稳定性主要体现在对具有多个属性的对象进行排序时才有意义
假设一个订单对象有两个属性,分别是下单时间与下单金额:
此时我们有一个需求,就是按照订单金额从高到低排序,若金额相同,则按照下单的先后时间排序

  • 方法一:就是先按照顺序排序,然后把金额相同的订单再次按照时间排序,但是这样就需要多次排序
  • 方法二:如果我们先把订单按照下单时间的先后排好序,然后再按照订单金额排序,此时如果我们使用的排序是稳定性的,那么它排序后同样是按照下单顺序先后排列的,此时我们就只需要两次排序

所以排序的稳定性也是非常重要的

2.排序分类

1.外部排序(不牵扯元素大小比较)

外部排序主要有,桶排序,计数排序,基数排序,时间复杂度近乎O(n)
这三种排序的集合元素非常大,内存放不下,需要使用外部存储器来存储排序,并且对数据的要求非常严格

2.内部排序(基于比较的方式)

在这里插入图片描述

1.插入排序

⭐直接插入排序(稳定)

1.核心思路

每次从无序区间中选择第一个元素,插入到有序区间的合适位置(相当于打扑克牌码牌的过程),有序区间+1,无序区间-1,直至整个数组有序
在这里插入图片描述
⭐⭐⭐直接插入排序在近乎有序的集合性能上非常好(是因为近乎有序的集合,不需要频繁的去交换),常常作为其他高阶排序的优化手段

2.详细代码
    /**
     * 直接插入排序
     * @param nums
     */
    public static void insertSort(int[] nums) {
        // 有序区间[0,i)
        // 无序区间[i,n)
        for (int i = 1; i < nums.length; i++) {
            for (int j = i; j-1 >= 0; j--) {
                // 若当前值比前一个位置值还小,交换位置
                if (nums[j] < nums[j-1]) {
                    swap(nums, j , j-1);
                }
            }
        }
    }

⭐希尔排序

希尔排序是对插入排序的优化,因为插入排序在近乎有序的数组上性能很好,希尔排序正是利用了这一点

1.核心思路

希尔排序不断地将原数组分为若干个子数组,先将子数组内部调整为有序,不断变大分组的长度,当分组长度为1时(一个元素天然有序),此时进行一次插入排序即可

在这里插入图片描述

2.详细代码
   // 希尔排序
    public static void shellSort(int[] arr) {
        int gap = arr.length >> 1;
        while (gap > 1) {
            // 先按照gap分组,组内使用插入排序
            insertionSortByGap(arr,gap);
            gap = gap >> 1;
        }
        // 当gap == 1时,整个数组接近于有序,此时来一个插入排序
        insertionSortByGap(arr,1);
    }

    // 按照gap分组,组内的插入排序
    private static void insertionSortByGap(int[] arr, int gap) {
    	// i初始化为0也可以,更容易理解
        for (int i = gap; i < arr.length; i++) {
            for (int j = i; j - gap >= 0 && arr[j] < arr[j - gap] ; j -= gap) {
                swap(arr,j,j - gap);
            }
        }
    }

2.选择排序

⭐直接选择排序(不稳定)

1.核心思路

每次在无序区间选择最小值与无序区间第一个位置元素交换,有序区间+1,无序区间-1,直至整个数组有序
在这里插入图片描述

2.详细代码
    /**
     * 直接选择排序
     * @param nums
     */
    public static void selectionSort(int[] nums) {
        // 起始状态:有序区间[0,i)
        // 无需区间[i,n)
        for (int i = 0; i < nums.length - 1; i++) {
            // 指向当前无需区间最小值的下标
            int minIndex = i;
            for (int j = i+1; j < nums.length; j++) {
                // 若当前值比最小值还小
                if (nums[j] < nums[minIndex]) {
                    // 更新最小值下标
                    minIndex = j;
                }
            }
            // 此时minIndex指向无需区间的最小值,将其加载有序区间的后面,有序区间+1,无序区间-1
            swap(nums, i, minIndex);
        }
    }

⭐堆排序

堆排详情博客:
原地堆排序

3.交换排序

⭐冒泡排序(稳定)

1.核心思路

每次遍历将无序数组的最大元素交换至末尾,无序数组-1,有序数组+1,直至整个数组有序

2.详细代码
    // 无序区间[0...i]
    // 有序区间[]
    public static void bubbleSort(int[] arr) {
        // 外层循环表示要进行元素操作的趟数
        for (int i = 0; i < arr.length - 1; i++) {
            boolean isSwaped = false;
            for (int j = 0; j < arr.length - i - 1; j++) {
                if (arr[j] > arr[j + 1]) {
                    isSwaped = true;
                    swap(arr,j,j + 1);
                }
            }
            if (!isSwaped) {
                break;
            }
        }
    }

⭐快速排序(不稳定)

1.核心思路

每次从无序数组中选取一个元素作为分区点(pivot),将原集合中所有<pivot的元素都放在分区点的左侧,将所有>pivot的元素都放在分区点的右侧,这样分区点最终位置就确定了,继续在左半区间和右半区间重复此过程即可
在这里插入图片描述

快速排序和直接选择排序不同的是在快排选择的过程中在不断地调整元素的顺序,在这个过程中原数组已经不断有序了

2.详细代码
    /**
     * 快速排序(挖坑法)
     */
    public static void quitSort(int[] nums) {
        quitSortInternal(nums, 0, nums.length-1);
    }

    private static void quitSortInternal(int[] nums, int l, int r) {
        if (l >= r) {
            return;
        }
        // 分区之后,返回已经放好位置的元素下标
        int p = quitSortByHole(nums, l, r);
        // 将放好位置元素的左侧丢进去排序
        quitSortInternal(nums, l, p - 1);
        // 在将放好位置元素的右侧丢进去排序
        quitSortInternal(nums, p + 1, r);
        // 此时数组就整体有序了
    }

    private static int quitSortByHole(int[] nums, int l, int r) {
        // 分区数字
        int pivot = nums[l];
        int i = l;
        int j = r;
        while (i < j) {
            while (i < j && nums[j] >= pivot) {
                j--;
            }
            // 走到这说明nums[j] < pivot
            nums[i] = nums[j];
            while (i < j && nums[i] <= pivot) {
                i++;
            }
            // 走到这说明nums[i] > pivot
            nums[j] = nums[i];
        }
        // 出循环说明i==j
        // 将pivot写回i
        nums[i] = pivot;
        // 返回分区点最终位置
        return i;
    }

4.归并排序

⭐归并排序(稳定,时间复杂度O(nlogn),空间复杂度O(n))

对原始数据不敏感

1.核心思路

1. 先不断地将原数组一分为二,直至子数组长度为1
2. 不断地将两个相邻的有序子数组合并成一个更大的有序子数组,直至合并后的数组与原数组长度相同,此时排序完成
在这里插入图片描述
核心操作:合并两个子数组merge(nums, l, mid, r)
int i=l代表左边数组走到的下标,
int j=mid+1代表右边数组走到的下标,
k代表当前原数组合并到哪个位置

2.归并排序应用

在这里插入图片描述

3.详细代码
    /**
     * 归并排序
     */
    public static void mergeSort(int[] nums) {
        mergeSortInternal(nums, 0, nums.length-1);
    }

    /**
     * 在nums的l-r内进行归并排序
     * @param nums
     * @param l
     * @param r
     */
    private static void mergeSortInternal(int[] nums, int l, int r) {
        if (l >= r) {
            return;
        }
        int mid = l + ((r - l) >> 1);;
        // 将左边和右边丢到merge
        mergeSortInternal(nums, l, mid);
        mergeSortInternal(nums, mid+1, r);
        // 此时说明左右两个数组都已经排好序
        merge(nums, l, mid, r);
    }

    private static void merge(int[] nums, int l, int mid, int r) {
        // 创建一个大小与r-l+1一样大的数组
        int[] aux = new int[r - l + 1];
        System.arraycopy(nums, l, aux, 0, r - l + 1);
        int i = l;
        int j = mid+1;
        // i代表左边数组走到的下标,j代表右边数组走到的下标,k代表当前原数组合并到哪个位置
        for (int k = l; k <= r; k++) {
            if (i > mid) {
                // 说明左边的数组已经走完,把右边的全部写回原数组即可
                nums[k] = aux[j-l];
                j++;
            } else if (j > r) {
                // 说明右边的数组已经走完,把左边的全部写回原数组即可
                nums[k] = aux[i-l];
                i++;
            } else if (aux[i-l] <= aux[j-l]) {
                // 说明左边的数字小,把左边数组内的数字写回原数组
                nums[k] = aux[i-l];
                i++;
            } else {
                // 说明右边的数字小,把右边数组内的数字写回原数组
                nums[k] = aux[j-l];
                j++;
            }
        }
    }

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

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

相关文章

stm32之5.长按按键(使用时钟源)调整跑马灯速度

------------------------------ 源码 #include <stm32f4xx.h> #include "led.h" #include "delay.h" #include "my_str.h" #include "beep.h" #include "key.h" int main(void) { key_init(); Led_init();…

Java【手撕双指针】LeetCode 57. “两数之和“, 图文详解思路分析 + 代码

文章目录 前言一、两数之和1, 题目2, 思路分析3, 代码展示 前言 各位读者好, 我是小陈, 这是我的个人主页, 希望我的专栏能够帮助到你: &#x1f4d5; JavaSE基础: 基础语法, 类和对象, 封装继承多态, 接口, 综合小练习图书管理系统等 &#x1f4d7; Java数据结构: 顺序表, 链表…

M1使用android模拟器的办法

问题背景 在产品的某一期需求中&#xff0c;是关于对于模拟器使用app的用户进行一定的管控&#xff0c;比如说封禁。那么这时候测试就需要模拟器了。对于Windows平台&#xff0c;网上有很多的平台可供选择。使用M1的我&#xff0c;只能不断的找可以用的模拟器。 解决过程 首…

uniapp-form表单

<template><view class"ptb-20 plr-30 bg min100"><view class"bg-white radius-20 pd-30"><view class"bold mt-30 mb-50 size-32">选择方式&#xff1a;</view><u--form labelPosition"left" :mod…

Orchestrator自身高可用性方案

目录 获得 HA 的方法 一 没有高可用性 &#xff08;No high availability&#xff09; 使用场景 架构组成 架构图 二 半高可用性&#xff08;Semi HA&#xff09; 三 基于共享数据库后端高可用&#xff08;HA via shared backend&#xff09; 四 基于Raft协议高可用 五…

python中的matplotlib画折线图(数据分析与可视化)

先导包&#xff08;必须安装了numpy 、pandas 和matplotlib才能导包&#xff09;&#xff1a; import numpy as np import pandas as pd import matplotlib.pyplot as plt核心代码&#xff1a; import numpy as np import pandas as pd import matplotlib.pyplot as pltpd.se…

什么是软件压力测试?软件压力测试工具和流程有哪些?

软件压力测试 一、含义&#xff1a;软件压力测试是一种测试应用程序性能的方法&#xff0c;通过模拟大量用户并发访问&#xff0c;测试应用程序在压力情况下的表现和响应能力。软件压力测试的目的是发现系统潜在的问题&#xff0c;如内存泄漏、线程锁、资源泄漏等&#xff0c;…

小猫爪:嵌入式小知识15-XCP基础简介

小猫爪&#xff1a;嵌入式小知识15-XCP基础简介 0 目录1 前言2 XCP的由来3 XCP基础简介3.1 XCP的协议组成3.2 XCP的通信模式3.2.1 Standard communication model3.2.2 Block Transfer communication model3.2.3 Interleaved communication model 3.3 XCP帧格式3.4 XCP的专业术语…

又一个国产操作系统将现身,基于AOSP,兼容安卓应用

日前媒体报道指又一家手机企业似乎自研自主操作系统&#xff0c;名字为*IOS&#xff0c;已申请域名备案&#xff0c;这被认为是它开始自研手机操作系统的迹象&#xff0c;自研操作系统似乎已成为国产手机努力的方向。 关于*IOS最早在2014年就曾传出&#xff0c;但是后来因故而最…

【Unity】Text文本组件的一些操作

Unity的Text组件的几种常见的操作方法 Text组件是Unity中用于在UI界面上显示文本的组件。它包含了一些常见的属性和方法&#xff0c;可以用来控制文本的内容、外观和交互。以下是一些常见的Text组件的操作&#xff1a; 设置文本内容&#xff1a;通过直接在Unity编辑器中的Text…

计算机竞赛 基于GRU的 电影评论情感分析 - python 深度学习 情感分类

文章目录 1 前言1.1 项目介绍 2 情感分类介绍3 数据集4 实现4.1 数据预处理4.2 构建网络4.3 训练模型4.4 模型评估4.5 模型预测 5 最后 1 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 基于GRU的 电影评论情感分析 该项目较为新颖&#xff0c;适合作为竞…

VM——获取图像中的圆环区域

、需求&#xff1a;下图是圆柱形铝罐&#xff0c;需要获取图像中的罐沿区域。 2、方法如下&#xff1a; (1)通过找外圆&#xff0c;提取圆形区域 &#xff08;2&#xff09;利用“拷贝填充”模块&#xff0c;绘制外圆ROI&#xff0c;选择“输出掩膜” &#xff08;3&#xff09…

【排错经验】树莓派4B摄像头问题集锦(USB摄像头)

1、E: Unable to locate package luvcview 这款软件不是在任意版本的操作系统下都适用的&#xff0c;要查看自己Ubuntu版本支持的uvcview。 方法&#xff1a;输入命令&#xff1a;sudo apt-cache search uvcview 由图可知&#xff0c;我这个版本的操作系统支持的是guvcview 所…

GMS基本模块TIN、Solids、Modflow2000/2005、MT3DMS、MODPATH。及其在地下水流动、溶质运移、粒子追踪方面的应用

解决地下水数值模拟技术实施过程中遇到的困难&#xff0c;从而提出切实可行的环境保护措施&#xff0c;达到有效保护环境、防治地下水污染&#xff0c;推动经济社会可持续发展的目的。 &#xff08;1&#xff09;水文地质学&#xff0c;地下水数值模拟基础理论&#xff1b;&am…

WPF中手写地图控件(3)——动态加载地图图片

瓦片增加一个Loading动画 可以查看我的另一个博客WPF中自定义Loading图 从中心扩散 进行从里到外的扩散&#xff0c;方向是上左下右。如下图所示 于是我们可以定义一个拥有坐标X跟Y的集合&#xff0c;他允许这个集合&#xff0c;内部使用枚举器的MoveNext自动排序&#xf…

K8S如何部署ZooKeeper以及如何进行ZooKeeper的平滑替换

前言 在之前的章节中&#xff0c;我们已经成功地将Dubbo项目迁移到了云环境。在这个过程中&#xff0c;我们选择了单机ZooKeeper作为注册中心。接下来&#xff0c;我们将探讨如何将单机ZooKeeper部署到云端&#xff0c;以及在上云过程中可能遇到的问题及解决方案。 ZooKeeper…

最优的家电设备交互方式是什么?详解家电设备交互的演进之旅

家电&#xff0c;在人们的日常生活中扮演着不可或缺的角色&#xff0c;也是提升人们幸福感的重要组成部分&#xff0c;那你了解家电的发展史吗&#xff1f; 70年代 结婚流行“四大件”&#xff1a;手表、自行车、缝纫机&#xff0c;收音机&#xff0c;合成“三转一响”。 80年…

精进语言模型:探索LLM Training微调与奖励模型技术的新途径

大语言模型训练&#xff08;LLM Training&#xff09; LLMs Trainer 是一个旨在帮助人们从零开始训练大模型的仓库&#xff0c;该仓库最早参考自 Open-Llama&#xff0c;并在其基础上进行扩充。 有关 LLM 训练流程的更多细节可以参考 【LLM】从零开始训练大模型。 使用仓库之…

36k字从Attention解读Transformer及其在Vision中的应用(pytorch版)

文章目录 0.卷积操作1.注意力1.1 注意力概述(Attention)1.1.1 Encoder-Decoder1.1.2 查询、键和值1.1.3 注意力汇聚: Nadaraya-Watson 核回归1.2 注意力评分函数1.2.1 加性注意力1.2.2 缩放点积注意力1.3 自注意力(Self-Attention)1.3.1 自注意力的定义和计算1.3.2 自注意…

DataFrame.query()--Pandas

1. 函数功能 Pandas 中的一个函数&#xff0c;用于在 DataFrame 中执行查询操作。这个方法会返回一个新的 DataFrame&#xff0c;其中包含符合查询条件的数据行。请注意&#xff0c;query 方法只能用于筛选行&#xff0c;而不能用于筛选列。 2. 函数语法 DataFrame.query(ex…