算法之排序总结

news2024/9/24 19:15:31

排序算法

最近,一直在学习业务上的知识,对基础没有怎么重视,因此,这篇文章想对于排序算法进行一个大致的总结🤓🤓🤓。

首先来说一下,关于排序一些相关的基础知识

排序概述

  1. 原地排序:空间复杂度为1的排序算法。即不借用外面的内存,就是在数组本身上排序。
  2. 稳定性:针对于待排序中存在值相等的元素。经过排序后,相等元素之间原有的先后顺序保持不变(稳定)。
  3. 排序方法:内排序(所有工作都是在内存中完成)和外排序(数据量太大,需要放在磁盘中,通过磁盘和内存的数据传输才能进行,占用额外内存)。

常见排序算法汇总

名词解释:

  • n:数据规模
  • k:桶的个数
排序算法平均时间复杂度最好情况最坏情况空间复杂度原地排序稳定性
冒泡排序O(n^2)O(n)O(n^2)O(1)
选择排序O(n^2)O(n^2)O(n^2)O(1)
插入排序O(n^2)O(n)O(n^2)O(1)
希尔排序O(n log n)O(n(log2/2n))O(n(log2/2n))O(1)
归并排序O(n log n)O(n log n)O(n log n)O(n)
快速排序O(n log n)O(n log n)O(n^2)O(log n)
堆排序O(n log n)O(n log n)O(n log n)O(n)
计数排序O(n + k)O(n + k)O(n + k)O(k)
桶排序O(n + k)O(n + k)O(n + k)O(n + k)
基数排序O(n * k)O(n * k)O(n * k)O(n + k)

从分类上来讲:

  • 比较类
    • 交换排序
      • 冒泡排序 Bubble Sort
      • 双向冒泡排序
      • 快速排序 Quick Sort
    • 插入排序
      • 直接插入排序 Insertion Sort
      • 二分插入排序 Binary Insertion Sort
      • 希尔排序 Shell Sort
    • 选择排序
      • 简单选择 Selection Sort
      • 堆 Heap Sort
    • 归并排序
      • 二路归并 Two-way Merge
      • 多路归并 Muti-way Merge
  • 非比较类(分布排序)
    • 计数排序 Counting Sort(*)
    • 桶排序 Bucket Sort(*)
    • 基数排序 Radix Sort(*)

带(*)的排序算法需要额外的空间。

提出一个问题

nlogn比n的平方快多少?

n^2nlognfaster
n = 10100333
n = 1001000066415
n = 100010^69966100
n = 1000010^8132877753
n = 10000010^1016609646020

数量级越大,快的倍数越多。

冒泡排序

顾名思义:冒泡排序的核心就是冒泡。把数组中最小的那个往上冒,冒的过程就是和他 相邻的元素 交换。

有两种方式进行冒泡:

一种是先把小的冒泡到前边去,

另一种是把大的元素冒泡到后边。

实现方案:

  • 第一个循环(外循环):负责把需要冒泡的值排除在外
  • 第二个循环(内循环):负责两两比较交换

代码实现:

const bubbleSort=(arr)=>{
    for(let i=0;i<arr.length;i++) {
        for(let j=i+1;j<arr.length;j++) {
            if(arr[i]>arr[j]) {
                [arr[i],arr[j]]=[arr[j],arr[i]];
            }
        }
    }
}

时间复杂度o(n2)

选择排序

未排序序列 中找到最大(最小)的元素,放到已排序序列的末尾,重复上述步骤,直到所有元素排序完毕。

实现思想:

从未排序的数列中选择最小(最大)的放到队首,然后进入下一个序列

实现方案:

外循环:控制序列的长度

内循环:记录最小的位置和排序序列的第一个进行交换

  • 平均时间复杂度:O(n^2)
const selectionSort = (arr)=>{
    let len=arr.length;
    for(let i=0; i<len-1; i++){
       let min = i;
        for(let j=i+1; j<len; j++){
            if(arr[j]<arr[min]){
                min=j;
            }
        }
        if(min!=i){
         [arr[i],arr[min]]=[arr[min],arr[i]]
        }
    }
}

插入排序

通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。每次从无序序列中取出一个数据,有序的插入到有序序列中,最终元素取完了,整个序列就变成有序的了。

实现思路:

外循环:控制有序序列

内循环:控制无序序列

代码实现:

const insertSort = (arr) => {
    let len=arr.length;
    // i以1为起始下标,说明第一个已经排序好了。
    for(let i=1; i<len; i++) {
        let cur=arr[i];
        let j=i-1; // 遍历前面有序的序列,然后寻找小的与之小的数
        while(j>=0&&cur<=arr[j]){
            arr[j+1]=arr[j];
            j--;
        }
        arr[j+1]=cur;
    }
}

希尔排序

希尔排序(Shell Sort)也称 递减增量排序算法,是插入排序的一种更高效的改进版本。

希尔排序是基于插入排序的以下两点性质而提出改进方法的:

  • 插入排序在对几乎已经排好序的数据操作时,效率高,即可以达到线性排序的效率;
  • 但插入排序一般来说是低效的,因为**插入排序每次只能将数据移动一位**;

基本思想:

先将整个待排序的记录序列分割成为若干子序列分别进行直接插入排序,待整个序列中的记录 基本有序 时,再对全体记录进行依次直接插入排序。采用了分组的思想。

实现思路:

  1. 分组(设置间隔)
  2. 组内排序(使用的插入排序方法)
  3. 重新设置间隔(在原来基础上减半)
  4. 再次减半设置间隔
  5. 一直到一 整个数组就为有序的了。

时间复杂度:nlog2n

tips:希尔排序的效率取决于增量值 gap 的选取,时间复杂度并不是一个定值。

开始时,gap 取值较大,子序列中的元素较少,排序速度快,克服了直接插入排序的缺点(直接插入排序如果数据量较大的话,每个元素都要向右移动,导致整个时间复杂度较高);其次,gap 值逐渐变小后,虽然子序列的元素逐渐变多,但大多元素已基本有序,所以继承了直接插入排序的优点,能以近线性的速度排好序。

希尔排序并不只是相邻元素的比较,有许多跳跃式的比较,难免会出现相同元素之间的相对位置发生变化比如上面的例子中希尔排序中相等数据 5 就交换了位置,所以希尔排序是不稳定的算法。

代码实现:

// 这里的gap取n/2,当然gap取值也有其它的方案
// gap的取值凭经验取值,不同的数据合适的gap可能存在差异
const ShellSort = (arr) => {
    let len=arr.length;
    let gap=Math.floor(len/2);
    while(gap>=1){
        for(let i=gap;i<len;i++){
           let cur=arr[i];
           j=i-gap;
           // 插入排序的思想
           while(j>=0&&cur<arr[j]){
                arr[j+gap]=arr[j];
                j-=gap;
           } 
           arr[j+gap]=cur;
        }
        console.log("gap: " + gap);
        gap=Math.floor(gap/2);
    }
}

归并排序

采用 分治法(Divide and Conquer)的一个非常典型的应用。

原理:

归并排序是用分治思想,分治模式在每层递归上有三个步骤

  • 分解(Divide):将 n 个元素分成含 n / 2 个元素的子序列
  • 解决(Comquer):用合并排序法对两个子序列递归的排序
  • 合并(Combine):合并两个已排序的子序列已得到排序结果

归并排序算法中,归并最后到底都是相邻元素之间的比较交换,并不会发生相同元素的相对位置发生变化,故是稳定性算法img

算法实现:

1.主函数,将数组变为小的两个数组,然后这两个小的数组进行归并排序

2.将这两个小的数组进行合并

3.最后返回结果

代码如下:


// 分
const MergeSort = (arr) => {
    if(arr.length<=1)return arr;
    let len = arr.length;
    let left = arr.slice(0, Math.floor(len / 2));
    let right = arr.slice(Math.floor(len / 2));
    return Merge(MergeSort(left), MergeSort(right));
}

// 合并数组		治
function Merge(left,right){
    let len1=left.length;
    let len2=right.length;
    let i=0,j=0;
    let res=[];
    while(i<len1&&j<len2){
        if(left[i]>=right[j]){
            res.push(right[j]);
            j++;
        }
        else
        {
            res.push(left[i]);
            i++;
        }
    }
    while(i<len1){
        res.push(left[i]);
        i++;
    }
    while(j<len2){
        res.push(right[j]);
        j++;
    }
    return res;
}

分治法:分就是进行分割,治就是进行合并。

快速排序

快速排序又是一种分而治之思想在排序算法上的典型应用。本质上来看,快速排序应该算是在冒泡排序基础上的 递归分治法

思路:

  1. 选择基准值:从数列中挑出一个元素,称为 基准(中心轴)(Pivot)(有不同的选择方法)
  2. 分割:重新排序数列,所有元素比基准值小的摆放在基准前面,所有比基准大的元素摆在基准的后面(相同的数可以到任意一边)。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区(partition)操作
  3. 递归排序子序列递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序

基准值也很重要。

const QuickSort= (arr) => {
    // 选取基准元素
    if(arr.length<=1)return arr;
    let pivotIndex = Math.floor(arr.length/2);
    let pivot = arr.splice(pivotIndex,1)[0]
    let left=[];
    let right=[]; 
    for(let i=0;i<arr.length;i++){
        if(arr[i]<pivot){
            left.push(arr[i]);
        }
        else
        {
            right.push(arr[i]);
        }
    }
    return [...QuickSort(left), pivot, ...QuickSort(right)]
}


堆排序

堆排序(Heap Sort)是指利用堆这种数据结构所设计的一种排序算法堆积是一个近似完全二叉树的结构,但不是排序二叉树,堆排序可以说是一种利用堆的概念来排序的选择排序,并同时满足堆积的性质:即子结点的键值或索引总是小于(或者大于)它的父节点。分为两种

  • 大顶堆:每个节点的值都大于或等于其子节点的值,在堆排序算法中用于升序排列;
  • 小顶堆:每个节点的值都小于或等于其子节点的值,在堆排序算法中用于降序排列;

算法原理:

  1. 先将初始的 Heap[0...n-1] 建立成**最大堆**,此时是无序堆,而堆顶是最大元素
  2. 再将堆顶 Heap[0] 和无序区的最后一个记录 Heap[n-1] 交换,由此得到新的 无序区 Heap[0...n-2]有序区 Heap[n-1],且满足 Heap[0...n-2].keys <= Heap[n-1].key
  3. 由于交换后新的根 Heap[1] 可能违反堆性质,故应将当前无序区 Heap[1..n-1] 调整为堆。然后再次将 Heap[1..n-1] 中关键字最大的记录 Heap[1] 和该区间的最后一个记录 Heap[n-1] 交换,由此得到新的无序区 Heap[1..n-2] 和有序区 Heap[n-1..n],且仍满足关系 Heap[1..n-2].keys≤R[n-1..n].keys,同样要将 Heap[1..n-2] 调整为堆。
  4. 直到无序区只有一个元素为止。

算法实现:

  1. 构建最大堆
  2. 然后取出堆顶元素进行排序。

代码如下:

const heapSort = (arr) => {
    // 创建堆
    let len = arr.length;
    for (let i = Math.floor(len / 2) - 1; i >= 0; i--) {
        heapify(arr,len, i);
    }

    for (let i = len - 1; i > 0; i--) {
        [arr[0],arr[i]] = [arr[i],arr[0]];
        heapify(arr,i,0)
    }


    // 将最大堆/最小堆放在队尾元素,然后进行排序
}

const heapify = (arr,len, i) => {
    let left = i * 2 + 1;
    let right = i * 2 + 2;
    let maxIndex = i; // 假设根节点为最大,后序还可以进行调整
    if (left < len && arr[left] > arr[maxIndex]) {
        maxIndex = left;
    }
    if (right < len && arr[right] > arr[maxIndex]) {
        maxIndex = right;
    }
    if (maxIndex !== i) {
        [arr[maxIndex], arr[i]] = [arr[i], arr[maxIndex]]
        heapify(arr, maxIndex)
    }
}

计数排序

计数排序的核心在于将输入的数据值转化为键存储在额外开辟的数组空间中。作为一种线性时间复杂度的排序,计数排序要求输入的数据必须是 有确定范围的整数

应用场景:适用于量大但是范围小的场景

算法原理:

​ 使用一个额外的数组counter来计数,这个counter取决于整个数的范围。比如整个数组的最小数为1,最大数为9,那么counter数组就要写一个1~9的数字,然后进行计数。

实现思路:

  1. 找出最小数和最大数
  2. 填充数组
  3. 遍历数组对数进行累加
  4. 反向填充原数组

代码实现:

const countSort = (arr) => {
    let min = Number.MAX_SAFE_INTEGER;
    let max = Number.MIN_SAFE_INTEGER;
    // let count;
    let sort = [];
    let currentIndex = 0;
    // 找到了最大和最小值 , count数值的值
    for (let i = 0; i < arr.length; i++) {
        if (min >= arr[i]) {
            min = arr[i];
        }
        if (max <= arr[i]) {
            max = arr[i];
        }

    }
    let count = new Array(max - min + 1).fill(0);

    for (let i = 0; i < arr.length; i++) {
        count[arr[i]] = count[arr[i]] ? count[arr[i]] + 1 : 1;
    }

    for(let i = 0 ; i < count.length ; i++) {
        while(count[i]>0){
            sort[currentIndex++]=i;
            count[i]--;
        }
    }
    return sort
}

时间复杂度:为线性的 O(n)

桶排序

桶排序(Bucket Sort)是计数排序的升级版。它利用了函数的映射关系,高效与否的关键就在于这个映射函数的确定。

算法思想:

  • 确定每个桶的范围
  • 然后将元素均匀地分布在每个桶中
  • 然后在每个桶中各自进行排序
  • 最后将元素按顺序取出,就是正确地排序了。

算法实现:

const bucketSort=(arr, bucketSize=5) => {
    let min=Number.MAX_SAFE_INTEGER;
    let max=Number.MIN_SAFE_INTEGER;
    let len= arr.length
    // 找出最小值和最大值
    
    for(let i=0; i<len; i++) {
        min<=arr[i]?min=min:min=arr[i];
        max>=arr[i]?max=max:max=arr[i];
    }



    let buckCount=Math.floor((max-min)/bucketSize)+1;
    let bucket=new Array(buckCount).fill(0).map(()=>{
        return [];
    })

    for(let i=0; i<arr.length; i++) {
        const bucketIndex=Math.floor((arr[i]-min)/bucketSize);
        bucket[bucketIndex].push(arr[i]);
    }
    let sort=[];
    for(let i=0;i<bucket.length; i++) {
        selectionSort(bucket[i]);
        sort.push(...bucket[i])
    }
    return sort;
}


const selectionSort=(arr)=>{
    let len=arr.length;
    for(let i=0;i<len-1;i++){
        let min=i;
        for(let j=i+1;j<len;j++){
            if(arr[j]<arr[min]){
                min=j;
            }
        }
        if(min!=i){
            [arr[i],arr[min]]=[arr[min],arr[i]];
        }
    }
}

let arr=[1,8,0,6,2,2,2,2,2,6,1]
console.log(bucketSort(arr));

基数排序

算法原理:

  • 比较个位,放入对应的桶。
  • 然后十位
  • 依次进行排序,最后就变成有序的序列了。

最后给大家介绍一种常见的算法:滑动窗口

滑动窗口核心思路:

在这里插入图片描述

在这里插入图片描述

到这,排序算法算是介绍完了。希望对大家有所帮助!😚😚😚

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

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

相关文章

代码随想录第25天|216.组合总和III ​​​​​​​,17. 电话号码的字母组合

216.组合总和III 回溯三部曲 确定递归函数参数 targetSum&#xff08;int&#xff09;目标和&#xff0c;也就是题目中的n。k&#xff08;int&#xff09;就是题目中要求k个数的集合。sum&#xff08;int&#xff09;为已经收集的元素的总和&#xff0c;也就是path里元素的…

(学习笔记-进程管理)什么是悲观锁、乐观锁?

互斥锁与自旋锁 最底层的两种就是 [互斥锁和自旋锁]&#xff0c;有很多高级的锁都是基于它们实现的。可以认为它们是各种锁的地基&#xff0c;所以我们必须清楚它们之间的区别和应用。 加锁的目的就是保证共享资源在任意时间内&#xff0c;只有一个线程访问&#xff0c;这样就…

LabVIEW模拟化学反应器的工作

LabVIEW模拟化学反应器的工作 近年来&#xff0c;化学反应器在化学和工业过程领域有许多应用。高价值产品是通过混合产品&#xff0c;化学反应&#xff0c;蒸馏和结晶等多种工业过程转换原材料制成的。化学反应器通常用于大型加工行业&#xff0c;例如酿酒厂公司饮料产品的发酵…

C 基础拾遗

C基础拾遗 预处理器 预处理器 14.1 预定义符号 14.2 #define

5种常见的3D游戏艺术风格及工具栈

在游戏开发领域&#xff0c;3D 艺术风格已成为为玩家创造身临其境、引人入胜的体验的重要组成部分。 随着技术的进步&#xff0c;创造令人惊叹的 3D 视觉效果的可能性已经大大扩展&#xff0c;为游戏开发人员提供了广泛的选择。 在本文中&#xff0c;我们将探讨当今游戏开发中…

Seaborn数据可视化(一)

目录 1.seaborn简介 2.Seaborn绘图风格设置 21.参数说明&#xff1a; 2.2 示例&#xff1a; 1.seaborn简介 Seaborn是一个用于数据可视化的Python库&#xff0c;它是建立在Matplotlib之上的高级绘图库。Seaborn的目标是使绘图任务变得简单&#xff0c;同时产生美观且具有信…

micropython SSD1306/SSD1315驱动

目录 简介 代码 功能 显示ASCII字符 ​编辑 画任意直线 画横线 画竖线 画矩形 画椭圆 画立方体 画点阵图 翻转 反相 滚动 横向滚动 纵向滚动 奇葩滚动 简介 我重新写了一个驱动&#xff0c;增加了一些功能&#xff0c;由于我的硬件是128*64oled单色I2C&#xff0c;我只…

lvs-DR模式:

lvs-DR数据包流向分析 客户端发送请求到 Director Server&#xff08;负载均衡器&#xff09;&#xff0c;请求的数据报文&#xff08;源 IP 是 CIP,目标 IP 是 VIP&#xff09;到达内核空间。 Director Server 和 Real Server 在同一个网络中&#xff0c;数据通过二层数据链路…

08.异常处理与异常Hook(软件断点Hook,硬件断点Hook)

文章目录 异常处理异常Hook&#xff1a;VEH软件断点HOOKVEH硬件断点HOOK 异常处理 1.结构化异常SEH #include <iostream>int main() {goto Exit;__try {//受保护节int a 0;int b 0;int c a / b;std::cout << "触发异常" << std::endl;}/*EXCE…

学习笔记:Opencv实现图像特征提取算法SIFT

2023.8.19 为了在暑假内实现深度学习的进阶学习&#xff0c;特意学习一下传统算法&#xff0c;分享学习心得&#xff0c;记录学习日常 SIFT的百科&#xff1a; SIFT Scale Invariant Feature Transform, 尺度不变特征转换 全网最详细SIFT算法原理实现_ssift算法_Tc.小浩的博客…

如何获得Android 14复活节彩蛋

每个新的安卓版本都有隐藏复活节彩蛋的悠久传统&#xff0c;可以追溯到以前&#xff0c;每个版本都以某种甜食命名。安卓14也不例外&#xff0c;但这一次的主题都是围绕太空构建的——还有一个复活节彩蛋。 安卓14复活节彩蛋实际上是一款很酷的小迷你游戏&#xff0c;你可以乘…

[Mac软件]MacCleaner 3 PRO 3.2.1应用程序清理和卸载

应用介绍 MacCleaner PRO是一个应用程序包&#xff0c;将帮助您清除磁盘空间并加快Mac的速度&#xff01; MacCleaner PRO - 让您的Mac始终快速、干净和有条理。 App Cleaner & Uninstaller PRO - 完全删除未使用的应用程序并管理Mac扩展。 磁盘空间分析仪PRO-分析磁盘空…

飞天使-k8s简单搭建

文章目录 k8s概念安装部署-第一版无密钥配置与hosts与关闭swap开启ipv4转发安装前启用脚本开启ip_vs安装指定版本docker 安装kubeadm kubectl kubelet,此部分为基础构建模版 k8s一主一worker节点部署k8s三个master部署,如果负载均衡keepalived 不可用&#xff0c;可以用单节点做…

STM32 CubeMX (第四步Freertos内存管理和CPU使用率)

STM32 CubeMX STM32 CubeMX &#xff08;第四步Freertos内存管理和CPU使用率&#xff09; STM32 CubeMX一、STM32 CubeMX设置时钟配置HAL时基选择TIM1&#xff08;不要选择滴答定时器&#xff1b;滴答定时器留给OS系统做时基&#xff09;使用STM32 CubeMX 库&#xff0c;配置Fr…

【Spring Cloud 二】——Spring Cloud基本介绍

Spring Cloud基本介绍 一、Spring Cloud简介二、Spring Cloud核心组件Spring Cloud Netflix组件Spring Cloud Alibaba组件Spring Cloud原生组件微服务架构图 三、Spring Cloud与Spirng Boot的关系四、Spring Cloud的版本选择Spring Cloud Alibaba的版本选择 一、Spring Cloud简…

Gradio入门到进阶全网最详细教程:快速搭建AI算法可视化部署演示(侧重项目搭建和案例分享)

常用的两款AI可视化交互应用比较&#xff1a; Gradio Gradio的优势在于易用性&#xff0c;代码结构相比Streamlit简单&#xff0c;只需简单定义输入和输出接口即可快速构建简单的交互页面&#xff0c;更轻松部署模型。适合场景相对简单&#xff0c;想要快速部署应用的开发者。便…

Azure静态网站托管

什么是静态网站托管 Azure Blob的静态网站托管是一项功能&#xff0c;它允许开发人员在Azure Blob存储中托管和发布静态网站。通过这个功能&#xff0c;您可以轻松地将静态网页、图像、视频和其他网站资源存储在Azure Blob中&#xff0c;并直接通过提供的URL访问这些资源。 官…

kafka-- kafka集群 架构模型职责分派讲解

一、 kafka集群 架构模型职责分派讲解 生产者将消息发送到相应的Topic&#xff0c;而消费者通过从Topic拉取消息来消费 Kafka奇数个节点消费者consumer会将消息拉去过来生产者producer会将消息发送出去数据管理 放在zookeeper

python安装 jieba 后显示 ModuleNotFoundError: No module named ‘jieba‘

python安装 jieba 后显示 ModuleNotFoundError: No module named jieba Traceback (most recent call last): File "d:\python\python37\lib\runpy.py", line 193, in _run_module_as_main "__main__", mod_spec) File "d:\python\python37\l…

大文本的全文检索方案附件索引

一、简介 Elasticsearch附件索引是需要插件支持的功能&#xff0c;它允许将文件内容附加到Elasticsearch文档中&#xff0c;并对这些附件内容进行全文检索。本文将带你了解索引附件的原理和使用方法&#xff0c;并通过一个实际示例来说明如何在Elasticsearch中索引和检索文件附…