【排序算法】python之冒泡,选择,插入,快速,归并

参考资料:

  • 《Python实现5大排序算法》
  • 《六大排序算法:插入排序、希尔排序、选择排序、冒泡排序、堆排序、快速排序》 --代码似乎是C语言

————————
本文介绍5种常见的排序算法和基于Python实现:
冒泡排序(Bubble Sort):通过不断交换相邻元素,将较大的元素逐渐“冒泡”到数组的尾部,较小的元素逐渐沉到数组的头部。
选择排序(Selection Sort):每次从未排序的部分中选择最小(或最大)的元素,然后放到已排序部分的末尾(或开头)。
插入排序(Insertion Sort):将数组分为已排序和未排序两部分,逐个将未排序元素插入到已排序部分的适当位置,使其保持有序。
快速排序(Quick Sort):通过递归地选择一个基准元素,并将数组划分为小于基准和大于基准的两部分,然后对两部分继续进行快速排序。
归并排序(Merge Sort):将数组拆分成两个子数组,对每个子数组进行排序,然后再将两个有序子数组合并成一个有序数组。
不同的排序算法在时间复杂度、空间复杂度以及稳定性等方面有所不同,选择适合具体情况的排序算法可以提高排序效率。

一、冒泡排序

冒泡排序是一种简单的排序算法,它的思想是重复地走访过要排序的元素列,依次比较相邻的两个元素,如果它们的顺序错误就交换它们,直到没有任何再需要交换的元素,排序完成
在这里插入图片描述
案例
我们以升序为实例。【34,,64,,11,,12,,22,,25,,90】长度为7.
第一次循环,要判断交换n-1次=6.两两交换,要将最大值90沉到末尾。结果为:【34,,11,,12,,22,,25,,64,,90】
在这里插入图片描述

第二次循环,要判断n-2次=5.两两交换,要将次最大值64沉到尾部(90之前)。结果为:
【11,,12,,22,,25,,34,,64,,90】
第三次循环,要判断n-3次=4. 值得注意的是,在这次循环中,没有一次交换行为。
第四次循环,要判断n-4次=3.值得注意的是,在这次循环中,没有一次交换行为。
第五次循环,要判断n-5次=2.值得注意的是,在这次循环中,没有一次交换行为。
第六次循环,要判断n-6次=1.值得注意的是,在这次循环中,没有一次交换行为。

因此,设置一个“标志位”,用于优化。若某次循环中没有交换元素,说明数组已经有序,可以提前结束排序。即在第三次循环中,判断没有交换,则可以提前结束优化了。

1.1 代码

def bubble_sort(arr):
    n = len(arr)
    for i in range(n - 1):
        # 标志位,用于优化:若某次循环中没有交换元素,说明数组已经有序,可以提前结束排序
        swapped = False
        for j in range(n - i - 1):
            if arr[j] > arr[j + 1]:
                arr[j], arr[j + 1] = arr[j + 1], arr[j]
                swapped = True
        if not swapped:
            break

测试

# 使用示例
arr = [64, 34, 25, 12, 22, 11, 90]
bubble_sort(arr)
print(arr)  # 输出:[11, 12, 22, 25, 34, 64, 90]

在冒泡排序中,外层循环控制需要比较的次数,内层循环实际进行相邻元素的比较和交换。通过标志位swapped的引入,可以优化冒泡排序,当某次循环没有发生交换时,说明数组已经有序,可以提前结束排序。这样在数组近乎有序的情况下,冒泡排序的性能会有所提升。

改进
“swapped”是优化的循环次数,提前截止循环。当序列的长度足够大的时候,在循环的某次的内部是否可以提前截止交换呢?
【22,,12,,11,25, 34,,64 ,90】

在这里插入图片描述
假设,如果我们用last_swap_index记录当前轮次最后交换的位置。在下一轮的循环的时候,提前结束判断交换。则在每个循环轮次中节省很多判断和步骤。因此,优化后的代码为:

1.2 优化后的代码

def bubble_sort(arr):
    n = len(arr)
    for i in range(n - 1):
        swapped = False
        # 记录当前轮循环最后一次交换的位置,初始值为未排序部分的末尾
        last_swap_index = n - 1

        for j in range(n - i - 1):
            if arr[j] > arr[j + 1]:
                arr[j], arr[j + 1] = arr[j + 1], arr[j]
                swapped = True
                last_swap_index = j  # 更新最后一次交换的位置

        if not swapped:
            break
            

        # 更新下一轮未排序部分的边界
        n = last_swap_index + 1

1.3 复杂度

时间复杂度:最坏情况:O(N^2)
      最好情况:O(N)
空间复杂度:O(1)

二、选择排序

选择排序(Selection Sort)是一种简单直观的排序算法,其思想是在未排序部分找到最小(或最大)元素,然后与未排序部分的第一个元素交换位置。重复这个过程直到所有元素都有序
在这里插入图片描述

2.1 代码

def selection_sort(arr):
    n = len(arr)
    for i in range(n - 1):
        # 假设当前未排序部分的第一个元素是最小的
        min_idx = i

        # 在未排序部分中找到最小元素的索引
        for j in range(i + 1, n):
            if arr[j] < arr[min_idx]:
                min_idx = j

        # 将找到的最小元素与未排序部分的第一个元素交换位置
        arr[i], arr[min_idx] = arr[min_idx], arr[i]

测试

arr = [64, 34, 25, 12, 22, 11, 90]
selection_sort(arr)
print(arr)  # 输出:[11, 12, 22, 25, 34, 64, 90]

在选择排序中,外层循环控制需要排序的次数,内层循环在未排序部分中找到最小元素的索引。然后将找到的最小元素与未排序部分的第一个元素交换位置。这样每一轮循环会将未排序部分的最小元素放到已排序部分的末尾,从而实现排序。选择排序的时间复杂度是O(n^2),它是一个不稳定的排序算法。
改进
换个角度看,外层循环指示了已排序部分的末尾,要求所有的元素都必须经历排序,因此,外层循环是不可能像冒泡排序一样提前截止的。在每次循环的内部选择最小元素的时候,我们也必须在所有未排序的元素中选择,也不可以提前截止。因此,我们只能将优化的注意力放在“交换”上。
例如:【11,,12,,22,,25,,34,,90,,64】
第一次循环,位置arr[0], 选择最小的arr[0]–>交换
第二次循环,位置arr[1], 选择最小的arr[1]–>交换
第二次循环,位置arr[2], 选择最小的arr[2]–>交换
。。。
可以看到,出现了很多不必要的交换。因此,添加一个判断因素,从而停止交换。—提醒的是,计算机做判断的消耗的运行时间远远小于数组的位置交换所消耗的时间。

2.2 优化的代码

def selection_sort(arr):
    n = len(arr)
    for i in range(n - 1):
        min_idx = i

        for j in range(i + 1, n):
            if arr[j] < arr[min_idx]:
                min_idx = j

        # 如果最小元素的索引不是当前的第一个元素,才进行交换
        if min_idx != i:
            arr[i], arr[min_idx] = arr[min_idx], arr[i]

通过增加一个条件判断,只有当最小元素的索引不等于当前的第一个元素的索引时,才进行交换操作。这样就可以减少不必要的交换,进而优化选择排序的性能。这个优化对于已经近乎有序的数组尤为有效,因为这些情况下交换次数较少。尽管选择排序的时间复杂度仍然是O(n^2),但这样的优化可以提高它的效率。

2.3 复杂度

时间复杂度:最坏情况:O(N^2)
      最好情况:O(N^2)
空间复杂度:O(1)

三、插入排序

插入排序(Insertion Sort)是一种简单的排序算法,其基本思想是将数组分为已排序和未排序两个部分,逐个将未排序元素插入到已排序部分的适当位置,使其保持有序
在这里插入图片描述

在待排序的元素中,假设前n-1个元素已有序,现将第n个元素插入到前面已经排好的序列中,使得前n个元素有序。按照此法对所有元素进行插入,直到整个序列有序。
  但我们并不能确定待排元素中究竟哪一部分是有序的,所以我们一开始只能认为第一个元素是有序的,依次将其后面的元素插入到这个有序序列中来,直到整个序列有序为止。
在这里插入图片描述
案例
在这里插入图片描述

3.1 代码

def insertion_sort(arr):
    n = len(arr)
    for i in range(1, n):
        key = arr[i]  # 当前要插入的元素
        j = i - 1  # 已排序部分的最后一个元素的索引

        # 逐个将已排序部分中比key大的元素后移,为key找到合适的插入位置
        while j >= 0 and key < arr[j]:
            arr[j + 1] = arr[j]
            j -= 1

        # 插入key到合适的位置
        arr[j + 1] = key

在插入排序中,外层循环控制需要插入的次数,从第2个元素开始(索引为1),依次向已排序部分插入未排序部分的元素。内层循环用于将已排序部分中比当前元素key大的元素后移,为key找到合适的插入位置。最后,将key插入到合适的位置,完成一次插入操作

改进
在外层循环控制的插入次数,所有的元素都要被插入,因此无法提前截止。内层循环控制的是插入位置,一一倒序比较插入的位置是否合适。为了更快的找到插入位置,可以使用二分法判断插入位置。

3.2 优化的代码

def insertion_sort(arr):
    n = len(arr)
    for i in range(1, n):
        key = arr[i]
        left, right = 0, i - 1

        # 使用二分查找找到key应该插入的位置
        while left <= right:
            mid = (left + right) // 2
            if arr[mid] > key:
                right = mid - 1
            else:
                left = mid + 1

        # 将已排序部分中大于key的元素都后移一位
        for j in range(i, left, -1):
            arr[j] = arr[j - 1]

        arr[left] = key

# 使用示例
arr = [64, 34, 25, 12, 22, 11, 90]
insertion_sort(arr)
print(arr)  # 输出:[11, 12, 22, 25, 34, 64, 90]

3.3 复杂度

时间复杂度

最坏情况下为O(N*N),此时待排序列为逆序,或者说接近逆序
最好情况下为O(N),此时待排序列为升序,或者说接近升序。

空间复杂度:O(1)

四、快速排序

快速排序(Quick Sort)是一种高效的排序算法,它使用分治法来对数组进行排序。快速排序的基本思想是选择一个基准元素(pivot),然后将数组中小于等于基准的元素放在左边,大于基准的元素放在右边,然后分别对左右两部分递归地进行快速排序,直到整个数组有序为止

案例
以【34,,25,,11,,22,,12,,64,,90】为例。

在这里插入图片描述

4.1 代码

def quick_sort(arr):
    if len(arr) <= 1:
        return arr

    pivot = arr[len(arr) // 2]  # 选择中间的元素作为基准
    left = [x for x in arr if x < pivot]
    middle = [x for x in arr if x == pivot]
    right = [x for x in arr if x > pivot]

    return quick_sort(left) + middle + quick_sort(right)

改进
在优化快速排序的代码中,主要关注减少递归调用的次数和减少额外空间的使用。以下是针对这两方面的优化方法:

  • 优化递归调用次数:对于小规模的子数组,可以切换到其他排序算法,例如插入排序。这是因为在小规模数据下,插入排序的常数项较小,比快速排序的递归开销更小。
  • 减少额外空间的使用:原始的快速排序需要额外的数组空间用于存放左右子数组,但我们可以通过就地分区的方式来减少空间使用。

或者说是用挖坑法 减少额外空间的使用。注意,在减少空间使用的的快速排序算法有很多,下面这个动态图比较形象,所以放在这里的。这与我们 下面使用的partition的代码不一致。

在这里插入图片描述

4.2 优化的代码

对于小规模的子数组,切换到插入排序

def insertion_sort(arr, low, high):
    for i in range(low + 1, high + 1):
        key = arr[i]
        j = i - 1
        while j >= low and key < arr[j]:
            arr[j + 1] = arr[j]
            j -= 1
        arr[j + 1] = key


def partition(arr, low, high):
    pivot = arr[high] ##注意这里的坑位与前面的动态图相反,选择的是最后一个位置而不是首位置
    i = low - 1
    for j in range(low, high):
        if arr[j] <= pivot:
            i += 1
            arr[i], arr[j] = arr[j], arr[i]
    arr[i + 1], arr[high] = arr[high], arr[i + 1]
    return i + 1

def quick_sort(arr, low, high):
    if low < high:
        # 对于小规模的子数组,切换到插入排序
        if high - low + 1 <= 10:
            insertion_sort(arr, low, high)
        else:
            pivot_index = partition(arr, low, high)
            quick_sort(arr, low, pivot_index - 1)
            quick_sort(arr, pivot_index + 1, high)

测试

arr = [64, 34, 25, 12, 22, 11, 90]
quick_sort(arr, 0, len(arr) - 1)
print(arr)  # 输出:[11, 12, 22, 25, 34, 64, 90]

在优化后的代码中,我们引入了insertion_sort函数用于对小规模子数组进行插入排序。当子数组大小不超过10个元素时,切换到插入排序。同时,在quick_sort函数中采用就地分区的方法,减少了额外的空间使用。
这些优化方法可以提高快速排序在小规模数据和近乎有序数组的性能。但需要注意的是,优化的效果也依赖于具体的数据情况

4.3 案例讲解–partition的应用

以【72,,64,,34,,25,,12,,22,,90,,17,,45,,58】为例。当子数组规模小于3的时候,切换到“插入排序”。以其中一轮为例。
在这里插入图片描述

递归调用的树状图
在这里插入图片描述

五、 归并算法

归并排序(Merge Sort)是一种高效稳定的排序算法,它使用分治法将数组分为两个子数组,递归地对子数组进行排序,然后将排好序的子数组合并成一个有序数组。
在这里插入图片描述

5.1 代码

def merge_sort(arr):
    if len(arr) <= 1:
        return arr

    # 将数组一分为二
    mid = len(arr) // 2
    left = arr[:mid]
    right = arr[mid:]

    # 递归对左右两部分进行归并排序
    left = merge_sort(left)
    right = merge_sort(right)

    # 合并已排序的左右两部分
    return merge(left, right)

def merge(left, right):
    merged = []
    left_idx, right_idx = 0, 0

    while left_idx < len(left) and right_idx < len(right):
        if left[left_idx] < right[right_idx]:
            merged.append(left[left_idx])
            left_idx += 1
        else:
            merged.append(right[right_idx])
            right_idx += 1

    # 将剩余的元素加入合并后的数组
    merged.extend(left[left_idx:])
    merged.extend(right[right_idx:])

    return merged


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

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

相关文章

Python web实战之 Django 的 ORM 框架详解

本文关键词&#xff1a;Python、Django、ORM。 概要 在 Python Web 开发中&#xff0c;ORM&#xff08;Object-Relational Mapping&#xff0c;对象关系映射&#xff09;是一个非常重要的概念。ORM 框架可以让我们不用编写 SQL 语句&#xff0c;就能够使用对象的方式来操作数据…

vue3+uniapp自定义tabbar

首先把tabbar中的元素写在一个list中用v-for进行渲染 用一个interface进行定义接口&#xff0c;这样别人在review你的代码就可以清晰知道你的tabbar包含什么元素。 利用typescript特性进行类型定义&#xff0c;可以省去很多麻烦 import { reactive } from "vue" imp…

Android kotlin系列讲解之最佳的UI体验 - Material Design 实战

目录 一、什么是Material Design二、Toolbar三、滑动菜单1、DrawerLayout2、NavigationView 四、悬浮按钮和可交互提示1、FloatingActionButton2、Snackbar3、CoordinatorLayout 五、卡片式布局1、MaterialCardView2、AppBarLayout 六、可折叠式标题栏1、CollapsingToolbarLayo…

无脑入门pytorch系列(一)—— nn.embedding

本系列教程适用于没有任何pytorch的同学&#xff08;简单的python语法还是要的&#xff09;&#xff0c;从代码的表层出发挖掘代码的深层含义&#xff0c;理解具体的意思和内涵。pytorch的很多函数看着非常简单&#xff0c;但是其中包含了很多内容&#xff0c;不了解其中的意思…

线扫激光算法原理

一:线扫激光算法原理 激光器发出的激光束经准直聚焦后垂直入射到物体表面上,表面的散射光由接收透镜成像于探测器的阵列上。光敏面于接收透镜的光轴垂直。如图: 当被测物体表面移动x,反应到光敏面上像点位移为x’。a为接收透镜到物体的距离(物距),b为接收后主面到成像…

SQL-每日一题【1174. 即时食物配送 II】

题目 配送表: Delivery 如果顾客期望的配送日期和下单日期相同&#xff0c;则该订单称为 「即时订单」&#xff0c;否则称为「计划订单」。 「首次订单」是顾客最早创建的订单。我们保证一个顾客只会有一个「首次订单」。 写一条 SQL 查询语句获取即时订单在所有用户的首次订…

无人驾驶实战-第一课(自动驾驶概述)

在七月算法上报了《无人驾驶实战》课程&#xff0c;老师讲的真好。好记性不如烂笔头&#xff0c;记录一下学习内容。 课程入口&#xff0c;感兴趣的也可以跟着学一下。 ————————————————————————————————————————— 无人驾驶汽车的定义…

HTTP——五、与HTTP协作的Web服务器

HTTP 一、用单台虚拟主机实现多个域名二、通信数据转发程序 &#xff1a;代理、网关、隧道1、代理2、网关3、隧道 三、保存资源的缓存1、缓存的有效期限2、客户端的缓存 一台 Web 服务器可搭建多个独立域名的 Web 网站&#xff0c;也可作为通信路径上的中转服务器提升传输效率。…

windows服务器iis PHP套件出现FastCGI等错误解决方法汇总

如果您的服务器安装了PHP套件&#xff0c;出现了无法打开的情况&#xff0c;请参照如下办法解决&#xff1a; 首先&#xff0c;需要设置IIS允许输出详细的错误信息到浏览器&#xff0c;才好具体分析 错误一&#xff1a; 处理程序“FastCGI”在其模块列表中有一个错误模块“Fast…

应用案例|基于3D视觉的高反光金属管件识别系统解决方案

Part.1 项目背景 在现代制造业中&#xff0c;高反光金属管件的生产以及质量的把控是一个重要的挑战。传统的2D视觉系统常常难以准确地检测和识别高反光金属管件&#xff0c;因为它们的表面特征不够明显&#xff0c;容易受到光照和阴影的干扰。为了应对这个问题&#xff0c;基于…

UE5 c++ 的文件操作(记录备忘)

函数库.h // Fill out your copyright notice in the Description page of Project Settings.#pragma once#include "CoreMinimal.h" #include "Kismet/BlueprintFunctionLibrary.h" #include "Microsoft/AllowMicrosoftPlatformTypes.h" #incl…

windows编译ncnn

官方代码https://github.com/Tencent/ncnn/wiki/how-to-build#build-for-windows-x64-using-visual-studio-community-2017 编译工具 visual studio 2017 一、编译protobuf 1、下载protobuf protobuf-3.11.2&#xff1a;https://github.com/google/protobuf/archive/v3.11…

基于SpringBoot+Vue的在线考试系统设计与实现(源码+LW+部署文档等)

博主介绍&#xff1a; 大家好&#xff0c;我是一名在Java圈混迹十余年的程序员&#xff0c;精通Java编程语言&#xff0c;同时也熟练掌握微信小程序、Python和Android等技术&#xff0c;能够为大家提供全方位的技术支持和交流。 我擅长在JavaWeb、SSH、SSM、SpringBoot等框架…

HarmonyOS学习路之方舟开发框架—学习ArkTS语言(状态管理 三)

Link装饰器&#xff1a;父子双向同步 子组件中被Link装饰的变量与其父组件中对应的数据源建立双向数据绑定。 概述 Link装饰的变量与其父组件中的数据源共享相同的值。 装饰器使用规则说明 Link变量装饰器 说明 装饰器参数 无 同步类型 双向同步。 父组件中State, Stor…

Linux常用命令——dpkg-deb命令

在线Linux命令查询工具 dpkg-deb Debian Linux下的软件包管理工具 补充说明 dpkg-deb命令是Debian Linux下的软件包管理工具&#xff0c;它可以对软件包执行打包和解包操作以及提供软件包信息。 语法 dpkg-deb(选项)(参数)选项 -c&#xff1a;显示软件包中的文件列表&am…

springBoot项目导入外部jar包

一、将外部的jar包复制到指定文件夹 二、修改pom文件 <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0" xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocati…

专家论道: 唐贤香云纱塑造中国非遗国际品牌

自“香云纱染整技艺”入选第二批国家级非物质文化遗产以来&#xff0c;被誉为纺织界“软黄金”的香云纱&#xff0c;重新焕发青春&#xff0c;频频登上时尚舞台&#xff0c;以不一样的面貌展示在世人面前&#xff0c;成为服装设计师、消费者青睐的材质。而随着北京卫视播出的《…

Win10查询硬盘序列号

添加wmic命令 winR cmd命令 wmic diskdrive get model, serialnumber

java -jar指定外部配置文件

场景 spingboot项目部署jar时,需要时常修改配置,为了方便,将配置文件放到jar包外 操作步骤 在jar包同级目录下创建config文件夹(位置没有强制要求,为了方便而已) 在jar包同级目录下创建start.bat文件,并编辑内容 echo off :: 命令窗口标题 title yudibei_performance_tes…

Spring-1-透彻理解Spring XML的必备知识

学习目标 能够说出Spring的体系结构 能够编写IOC-DI快速入门 思考:为什么学习Spring而不是直接学习SpringBoot 1 Spring介绍 思考:我们为什么要学习Spring框架&#xff1f; 工作上面 Java拥有世界上数量最多的程序员 最多的岗位需求与高额薪资 95%以上服务器端还是要用Jav…