算法之排序总结

排序算法

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

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

排序概述

  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));

基数排序

算法原理:

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

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

滑动窗口核心思路:

在这里插入图片描述

在这里插入图片描述

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

操作系统的体系结构、内核、虚拟机

&#x1f40c;个人主页&#xff1a; &#x1f40c; 叶落闲庭 &#x1f4a8;我的专栏&#xff1a;&#x1f4a8; c语言 数据结构 javaweb 石可破也&#xff0c;而不可夺坚&#xff1b;丹可磨也&#xff0c;而不可夺赤。 操作系统结构 一、操作系统体系结构1.1操作系统的内核1.1.…

GO学习之 数据库(mysql)

GO系列 1、GO学习之Hello World 2、GO学习之入门语法 3、GO学习之切片操作 4、GO学习之 Map 操作 5、GO学习之 结构体 操作 6、GO学习之 通道(Channel) 7、GO学习之 多线程(goroutine) 8、GO学习之 函数(Function) 9、GO学习之 接口(Interface) 10、GO学习之 网络通信(Net/Htt…

VS2019生成的DLL,给QT(MinGW版本)使用的小结

VS2019端&#xff1a; a 基于生成一个DLL的工程&#xff08;要注意生成是x86&#xff0c;还是x64的&#xff0c;需要和后面的QT的App工程对应&#xff09;&#xff0c;这里不多解释了&#xff0c;网上多的是&#xff1b; b 在cpp实现文件里&#xff0c;假如要导出一个这样的…

React Native文本添加下划线

import { StyleSheet } from react-nativeconst styles StyleSheet.create({mExchangeCopyText: {fontWeight: bold, color: #1677ff, textDecorationLine: underline} })export default styles

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

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

actuator/prometheus使用pushgateway上传jvm监控数据

场景 准备 prometheus已经部署pushgateway服务&#xff0c;访问{pushgateway.server:9091}可以看到面板 实现 基于springboot引入支持组件&#xff0c;版本可以 <!--监控检查--><dependency><groupId>org.springframework.boot</groupId><artifa…

java Spring Boot yml多环境拆分文件管理优化

上文 java Spring Boot yml多环境配置 我们讲了多环境开发 但这种东西都放在一起 还是非常容易暴露信息的 并且对维护来讲 也不是非常的友好 这里 我们在resources下创建三个文件 分别叫 application-pro.yml application-dev.yml application-test.yml 我们直接将三个环境 转…

Seaborn数据可视化(一)

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

图像处理常见的两种拉流方式

传统算法或者深度学习在进行图像处理之前&#xff0c;总是会首先进行图像的采集&#xff0c;也就是所谓的拉流。解决拉流的方式有两种&#xff0c;一个是直接使用opencv进行取流&#xff0c;另一个是使用ffmpeg进行取流&#xff0c;如下分别介绍这两种方式进行拉流处理。 1、o…

mybatis入门的环境搭建及快速完成CRUD(增删改查)

又是爱代码的一天 一、MyBatis的介绍 ( 1 ) 背景 MyBatis 的背景可以追溯到 2002 年&#xff0c;当时 Clinton Begin 开发了一个名为 iBATIS 的持久化框架。iBATIS 的目标是简化 JDBC 编程&#xff0c;提供一种更直观、易用的方式来处理数据库操作。 在传统的 JDBC 编程中&…

DevOps系列文章之 GitlabCICD自动化部署SpringBoot项目

一、概述 本文主要记录如何通过Gitlab CI/CD自动部署SpringBoot项目jar包。 二、前期准备 准备三台 CentOS7服务器&#xff0c;分别部署以下服务&#xff1a; 序号系统IP服务1CentOS7192.168.56.10Gitlab2CentOS7192.168.56.11Runner &#xff08;安装Docker&#xff09;3Cen…

对前端PWA应用的部分理解和基础Demo

一、什么是PWA应用&#xff1f; 1、PWA简介 ​ 渐进式Web应用&#xff08;Progressive Web App&#xff09;&#xff0c;简称PWA&#xff0c;是 Google 在 2015 年提出的一种使用web平台技术构建的应用程序&#xff0c;官方认为其核心在于Reliable&#xff08;可靠的&#xf…

华为ENSP网络设备配置实战4(OSPF+BGP+VPN+单臂路由)

题目要求 1、loopback口通过OSPF连通&#xff0c;合理规划OSPF开销&#xff0c;通过设置AR1->AR2->AR4链路&#xff0c;来消除负载链路。 2、AR3、AR4分别与AR1、AR2建立BGP邻居 3、AR3、AR4作为PC机网关设备 4、PC1、PC3由VPN-spi承载&#xff0c;PC2、PC4由VPN-spims承…

物联网智慧安防实训综合实训基地建设方案

一、系统概述 物联网智慧安防实训综合实训基地是一个为学生提供综合实践、培养技能的场所&#xff0c;专注于物联网技术与智慧安防应用的培训和实训。通过物联网智慧安防实训综合实训基地的建设和运营&#xff0c;学生可以在真实的环境中进行实践训练&#xff0c;提高其物联网技…

【高频面试题】 消息中间件

文章目录 1、RabbitMQ1.1 RabbitMQ-如何保证消息不丢失1.2 RabbitMQ消息的重复消费问题如何解决的1.3 RabbitMQ中死信交换机 ? (RabbitMQ延迟队列有了解过嘛)1.4 RabbitMQ如果有100万消息堆积在MQ , 如何解决(消息堆积怎么解决)1.5 RabbitMQ的高可用机制有了解过嘛 2、Kafka2.…

LlamaGPT -基于Llama 2的自托管类chatgpt聊天机器人

LlamaGPT一个自托管、离线、类似 ChatGPT 的聊天机器人&#xff0c;由 Llama 2 提供支持。100% 私密&#xff0c;不会有任何数据离开你的设备。 推荐&#xff1a;用 NSDT编辑器 快速搭建可编程3D场景 1、如何安装LlamaGPT LlamaGPT可以安装在任何x86或arm64系统上。 首先确保…

【微服务】一文了解 Nacos

一文了解 Nacos Nacos 在阿里巴巴起源于 2008 2008 2008 年五彩石项目&#xff08;完成微服务拆分和业务中台建设&#xff09;&#xff0c;成长于十年双十一的洪峰考验&#xff0c;沉淀了简单易用、稳定可靠、性能卓越的核心竞争力。 随着云计算兴起&#xff0c; 2018 2018 20…

C++11并发与多线程笔记(3)线程传参详解,detach()大坑,成员函数做线程函数

C11并发与多线程笔记&#xff08;3&#xff09;线程传参详解&#xff0c;detach 大坑&#xff0c;成员函数做线程函数 1、传递临时对象作为线程参数1.1 要避免的陷阱11.2 要避免的陷阱21.3 总结 2、临时对象作为线程参数2.1 线程id概念2.2 临时对象构造时机抓捕 3、传递类对象…

vscode 安装勾选项解释

1、通过code 打开“操作添加到windows资源管理器文件上下文菜单 &#xff1a;把这个两个勾选上&#xff0c;可以对文件使用鼠标右键&#xff0c;选择VSCode 打开。 2、将code注册为受支持的文件类型的编辑器&#xff1a;不建议勾选&#xff0c;这样会默认使用VSCode打开支持的相…

虫情测报灯

在农业生产过程中&#xff0c;农作物的虫害问题永远都是放在首位的。随着现代生活科技的发展和社会进步&#xff0c;人们对物质也有了新的要求。伴随农作物品种的增加&#xff0c;农药和化肥的使用也在导致农业虫害问题日益加剧&#xff0c;在这种不良的耕作状态下&#xff0c;…