基础算法系列之排序算法(一)[快速排序,归并排序,二分查找]

news2024/11/25 20:25:49

文章目录

  • 前言
  • 快速排序
    • 关键点
    • 实现
      • 选角
      • 排序
      • 重复
    • 实现
    • 稳定性分析
    • 记忆模板
  • 归并排序
    • 关键点
    • 实现
  • 二分查找
  • 总结

前言

先来一波预热,本次寒假将要更新的博文系列为:基础算法部分,最新前言论文研读(不包含论文复现-耗时太长),C++基础博文系列,WhiteHole开发日志系列。那么在基础算法系列的话,我们这边采用的演示及其开发语言暂时选定为python,后续我们再考虑引入C++,也就是双版本。

快速排序

首先我们来看到最简单比较好理解的快速排序算法。这里我们假设都是从小打到排。

首先我们来理解一下这个快速排序的思路。快速排序要做到的其实非常简单,它的核心思想解释起来就一个句话:认清楚自己的位置
什么意思,就是让每一个数字都认识到自己应该放在哪里。找清楚自己的位置在哪里,所以整个算法的核心就在于这里,如何能够让一个数字找到自己的位置,摆正自己的方向。

我们拿最朴素的例子来讲解,那就是身高排序嘛,我们随便先拉出一个同学A,然后我们只需要保证,比这个同学A个子矮的排在左边,高的排在右边。之后我们在从比较矮小的个子的同学里面再挑选一个,然后再去对比,由于刚刚那个同学A往右都是高个子,左边的不可能比A右边的高,因此我们只需要比较到A就好了,同理,我们从A开始再从右边开始选一个同学,然后比较,(这里我们需要注意的就是不能再选到A同学去和右边的同学比较因为右边的都是比A高的)这样一来,我们重复几次操作,不就把整个队列都排好序号了吗。所以我们的快排其实就是仿造这个例子,来进行实现的。

关键点

所以通过刚刚的例子我们不难发现,快速排序其实就是大概三个关键步骤。

  1. 找一个标杆,需要参与对比的同学A(选角)
  2. 做一个排序,把比A小的放在左边,比A大的放在右边(排序)
  3. 重复操作,把A左边的和右边的按照1,2步骤进行重复操作,直到整个序列排完了。(重复)

整个过程就像,卷心菜一样,一层一层往里面拨开,先拨开最外面的,然后拨开到最里面。

实现

选角

那么接下来我们就进行简单实现一下喽。首先是我们要找到一个标杆,那么此时我们假设有这样的一个序列:

a = [2,5,4,3,8,4,2,1]

那么这个时候呢,有好多种做法,要么就是找中间的,要么就是找最左边的,或者随机找一个,这些都可以,至于哪一个更快这个得看情况,不好说。那么这边咱们也是来个最简单的那就是直接拿最左边的就好了。

排序

之后就是如何排序了,那么毫无疑问我们这边有非常多的方案,我们可以直接暴力排序,也就是直接遍历,我们再拿一个数组,然后开始比较,比选出来的数字A小的我们就直接放到新的数组里面的第一个位置,依次序放置,然后遇到大的我们就先放到最后面那个位置,然后往前依次序放置。

当然这样做有失风范,因此的话我们有更加优雅一点点的方案。还是基于刚刚的思路,我们搞一个双指针,分别放置在左右两侧
在这里插入图片描述
也就是i,j,然后呢我们还是一样的,左边的找到比A大的,右边的找到比A小的,然后我们在交换他们的位置,之后两个指针继续往中间移动,当指针重合的时候,那么这个A就找到了位置,此时已经是左小,右大了。

重复

之后就是重复了,那么这个不就更好办了嘛,我们直接把A左边的看着新的序列,右边的也看成新的序列呗。这个不就是所谓的分治思想嘛。

OK,现在我们问题都解决了,是时候亮出代码了

实现

def quick_sort(a:list,l:int,r:int):

    if(l>=r):
        return
    # 选择一个需要被比较的玩意
    division_key=a[l]
    # 设置两个指针,初始化先指向左右两侧
    # 这样做的目的是为了方便下标索引
    i,j=l-1,r+1
    while(i<j):
        j-=1
        i+=1
        while(a[j]>division_key):
            j -= 1
        while(a[i]<division_key):
            i += 1
        if(i<j):
            a[i] = a[i]+a[j]
            a[j] = a[i]-a[j]
            a[i] = a[i]-a[j]


    quick_sort(a,l,j)
    quick_sort(a,j+1,r)

def main():
    a = [2,5,4,3,8,4,2,1]
    print("排序前",a)
    quick_sort(a,0,len(a)-1)
    print("排序后",a)


if __name__ == '__main__':
    main()

在这里我们做了一点点的处理,首先就是,为了方便下标索引同时为了避免出错,我们这边现将两个指针移到边界外面。

第二个就是,由于是数值交换,所以的话我们这边可以选择直接

    a[i] = a[i]+a[j]
    a[j] = a[i]-a[j]
    a[i] = a[i]-a[j]

完成位置交换

稳定性分析

之后我们来到稳定性分析,首先我们都知道这个玩意是不稳定的,为什么不稳定,因为元素的相对位置会发生改变。

我们来把我们刚刚的例子做一个改动:

def quick_sort(a:list,l:int,r:int):
    if(l>=r):
        return
    # 选择一个需要被比较的玩意
    division_key=a[l][0]
    # 设置两个指针,初始化先指向左右两侧
    # 这样做的目的是为了方便下标索引
    i,j=l-1,r+1
    while(i<j):
        j-=1
        i+=1
        while(a[j][0]>division_key):
            j -= 1
        while(a[i][0]<division_key):
            i += 1
        if(i<j):
            temp = a[j]
            a[j] = a[i]
            a[i] = temp

    quick_sort(a,l,j)
    quick_sort(a,j+1,r)

def main():
    a = [[2,1],[5,2],[4,3],[3,4],[8,5],[4,6],[2,7],[1,8]]
    print("排序前",a)
    quick_sort(a,0,len(a)-1)
    print("排序后",a)


if __name__ == '__main__':
    main()

之后我们来看到结果:
在这里插入图片描述
我们把刚刚的数据格式换一下,我们可以发现相同数字2的相对次序发生了改变。如果我们把这个次序当做是一个先后顺序,并且期望按照次序实现越早到达的越先进行显然就不合理了。那么为什么会出现这种情况你呢,因为每次我们都是直接进行交换的,根本不会去考虑次序,这个时候有小伙伴可能会问了,如果我判断一下,就是说,相等我们不交换行不行。

我们把交换的代码这样改一下:

        if(i<j):
            if(a[j][0]!=division_key):
                temp = a[j]
                a[j] = a[i]
                a[i] = temp

然后我们多加一点干扰

a = [[2,1],[5,2],[4,3],[3,4],[8,5],[4,6],[2,7],[1,8],[2,9],[4,10],[8,11]]

理论上来说,i,j所指向的位置,要么是>=A ,要不及时<=A,当j指向的值!=A的时候那就是一定小于A,那么必然要到左边,i此时也将可以保证右边一定是>=A,因为此时i所指向的就是>=A的重复操作后一定可以保证这个结果。
结果上看
在这里插入图片描述
但是事实一定如此嘛?
我们不妨直接用代码伪造数据:

def getList():
    size = 1000
    rand = random.randint
    res = []
    for i in range(1,size+1):
        temp = [rand(0,1e5),i]
        res.append(temp)
    return res

def isAsc(a):
    i,j=0,1
    for i in range(len(a)-2):
        if(a[i][0]>a[j][0]):
            return False
        if(a[i][0]==a[j][0] and a[i][1]>a[j][1]):
            return False
        i+=1;j+=1
    return True

之后我们再来看看效果

def main():
    a = getList()
    print("排序前",a)
    quick_sort(a,0,len(a)-1)
    print(isAsc(a))
    print("排序后",a)

在这里插入图片描述

可以看到,结果并非我们所想,为什么呢,其实我们刚刚只是保证了A在排序A的时候一定不会乱序,但是在排序比较的过程中,别人的身高或者大小是一样的时候我们还是直接交换了,这样一来次序有可能就会被打乱。例如左边两个相同的值挨着那就直接会进行交换,这样一来相同的值自然就乱序了,这个时候你可能又会说了,拿相同我也不交换呗,我再拿几个指针去判断呗,那问题来了,我们一次性可以做到同时排序多个值?我们最终只能确定一个A在当前的序列里面的相对位置吧,当序列划分之后,排序后的相对位置可能还是有序的,但是在整体就就不一定了呀。

因此快速排序,很难是可以变成稳定的,难点就在于,你必须处理中间比较的时候,那些相同的值也需要保证有序。那么如何保证,其实也简单,前面就已经给了答案,给点提示就是一开始我们已经给出了一个比较暴力的排序方案。

记忆模板

OK,我们现在可以提取出我们快排的一个模板了,就是这个:

def quick_sort(a:list,l:int,r:int):
    if(l>=r):
        return
    # 选择一个需要被比较的玩意
    division_key=a[l]
    # 设置两个指针,初始化先指向左右两侧
    # 这样做的目的是为了方便下标索引
    i,j=l-1,r+1
    while(i<j):
        j-=1
        i+=1
        while(a[j]>division_key):
            j -= 1
        while(a[i]<division_key):
            i += 1
        if(i<j):
            temp = a[j]
            a[j] = a[i]
            a[i] = temp

    quick_sort(a,l,j)
    quick_sort(a,j+1,r)

是的和一开始的代码几乎一样。

  1. 终止条件 l>=r
  2. 选择一个数
  3. 找出两边小于和大于的位置,然后交换元素
  4. 通过j去划分左右两边的序列
    为什么按照j去划分,因为有可能i往右边偏向的更多,二者相差不一定是1,所以在这个模板里面按照j去,j是右边那个指针

归并排序

OK,说完了快速我们再来说一下归并排序,这个排序的话和我们先前提到的快速排序是有一点点的不同,先前我们按照数来极限划分,现在的话我们按照这个区域来划分。

什么意思,一句话总结:局部有序到全局有序。这个的话不太好理解,我们就先来分析一下这个归并排序的时间复杂度吧。

首先归并排序呢,会先把一个序列进行划分,分割,对半划分,也就是最后的话,我们会把一个序列一直划分为长度为1,那么在每一层进行一个排序,由底层向上,最终完成一个排序,大概图例如下:

在这里插入图片描述

那么如果理解了这个,那么后面就好办了。
我们要做其实就是不断划分,划分,划分完毕之后的话,我们在进行一个排序,为了使得算法稳定,因此我们这边选择稍微暴力一点的方案,那就是直接两两对比,之后按次序放到零时数组里面。

当我们判断到只有一个数字的时候,跳出递归,此时对周围两个数字进行排序,完毕之后,再次跳出当前递归,对四个进行排序,(当然值得一提的是,每一层其实在计算的时候也是一棵树)

关键点

  1. 先划分
  2. 做排序,我们对两边进行归并
  3. 复制转移排序后的数字

实现

OK,这块我们直接看到代码:


def merge_sort(a:list,temp:list,l:int,r:int):
    # 这个时候是说明已经划分到只有一个了
    if(l>=r): return
    mid = int((l+r)/2)
    merge_sort(a,temp,l,mid)
    merge_sort(a,temp,mid+1,r)
    #这个时候我们需要进行一个归并
    k=0
    i,j=l,mid+1
    while(i<=mid and j<=r):
        if(a[i]<a[j]):
            temp[k]=a[i]
            i+=1
        else:
            temp[k]=a[j]
            j+=1
        k+=1
    #判断左半边和右半边那一边没有循环完毕
    while(i<=mid):
        temp[k]=a[i]
        k+=1
        i+=1
    while(j<=r):
        temp[k] = a[j]
        k += 1
        j += 1
    #对结果重新赋值
    i = l
    k = 0
    while(i<=r):
        a[i]=temp[k]
        k+=1
        i+=1

def main():
    a = [2,5,4,3,8,4,2,1]
    temp = [0 for _ in range(len(a))]
    print("排序前",a)
    merge_sort(a,temp,0,len(a)-1)
    print("排序后",a)


if __name__ == '__main__':
    main()

至于模板,就是上面这个。用其他的语言支持++操作的可能会更简洁一点。

二分查找

这个算法,我想应该就不太需要在本文中进行仔细阐述了吧。我们还是可以直接看到算法:
现在我们给定一个有序的序列,然后找到值为A的数字在数组中是第几个。


def binary_search(a,l,r,target):
    if(l>r):
        return -1
    mid = l+int((r-l)/2)
    if(target==a[mid]):
        return mid
    if(target<a[mid]):
        #更小可能在左边
        return binary_search(a,l,mid-1,target)
    else:
        return binary_search(a,mid+1,r,target)

def main():

    a = [10, 14, 19, 26, 27, 31, 33, 35, 42, 44]
    print("目标为第:",binary_search(a,0,len(a)-1,10)+1,"个数字")


if __name__ == '__main__':
    main()

核心就是,二分递归查找嘛,那么有什么注意点呢,第一序列一定是有序,或者满足一个条件的。也会是说如果写出模板的话就应该是这样的:

def binary_search(a,l,r,target):
    if(l>r):
        return -1
    mid = l+int((r-l)/2)
    if(accept()):
        return mid
    if(check()):
        #更小可能在左边
        return binary_search(a,l,mid-1,target)
    else:
        return binary_search(a,mid+1,r,target)

注意点就是注意边界的问题。然后是我们中间点mid的取值的方式的问题,这个需要结合实际。

总结

今天的话就先水到这里,先缓缓恢复一下状态。

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

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

相关文章

day21【代码随想录】二叉树的层序遍历、二叉树的层序遍历|| 、二叉树的层平均值 、二叉树的锯齿形层序遍历 、二叉树的右视图 、N叉树的层序遍历

文章目录前言一、二叉树的层序遍历&#xff08;力扣102&#xff09;二、二叉树的层序遍历||&#xff08;力扣107&#xff09;三、二叉树的层平均值&#xff08;力扣637&#xff09;四、二叉树的锯齿形层序遍历&#xff08;力扣103&#xff09;五、二叉树的右视图&#xff08;力…

吃透Chisel语言.39.Chisel实战之单周期RISC-V处理器实现(一)——需求分析和初步设计

Chisel实战之单周期RISC-V处理器实现&#xff08;一&#xff09;——需求分析和初步设计 需求分析 首先明确我们要做的是什么&#xff0c;这个在标题里面已经说明了&#xff0c;我们要做的是一个单周期RISC-V处理器。 但光是个短语不足以支撑我们开展项目&#xff0c;我们需…

大数据学习:shell基础(3)

文章目录history命令参数说明任务一&#xff1a;查看历史操作记录任务二&#xff1a;查看最近10条历史命令任务三&#xff1a;查看最开始10条历史命令任务四&#xff1a;曾多少次使用vim编辑文本文件&#xff1f;任务五&#xff1a;执行历史第5条命令任务六&#xff1a;执行上一…

【深度学习】李宏毅2021/2022春深度学习课程笔记 - Recurrent Neural NetWork(RNN)

文章目录一、Slot Filling二、Recurrent Neural NetWork&#xff08;RNN&#xff09;三、Bidirectional RNN&#xff08;双向RNN&#xff09;四、Long Short Term Memory&#xff08;LSTM&#xff09;五、Learning Target六、RNN 很难 Train七、Helpful Techniques7.1 LSTM7.2 …

CSDN竞赛14期·12月11日考试

CSDN竞赛14期12月11日考试 1、题目名称&#xff1a;字符串全排列 // 请关闭中文输入法&#xff0c;用英文的字母和标点符号。 // 如果你想运行系统测试用例&#xff0c;请点击【执行代码】按钮&#xff0c;如果你想提交作答结果&#xff0c;请点击【提交】按钮&#xff0c; //…

半入耳式蓝牙耳机哪款音质好?音质好的半入耳式蓝牙耳机推荐

对于喜欢听歌的朋友来讲&#xff0c;你只佛会关注到蓝牙耳机的佩戴舒适度&#xff0c;音质清晰这种情况&#xff0c;入耳式的带有耳塞&#xff0c;往往更加佩戴有更好的密闭性&#xff0c;半入耳的不完全进入耳道&#xff0c;佩戴更加舒适&#xff0c;下面整理了几款音质不错的…

[附源码]Python计算机毕业设计非处方药的查询与推荐系统Django(程序+LW)

该项目含有源码、文档、程序、数据库、配套开发软件、软件安装教程 项目运行 环境配置&#xff1a; Pychram社区版 python3.7.7 Mysql5.7 HBuilderXlist pipNavicat11Djangonodejs。 项目技术&#xff1a; django python Vue 等等组成&#xff0c;B/S模式 pychram管理等…

Prometheus+Grafana监控一网打尽

PrometheusGrafana监控一、Prometheus介绍二、监控组件node-exportermysqld-exportercadvisorprometheus三、Grafana 展示平台docker启动配置Data sources导入Dashboard模板Linux主机监控Mysql监控Nginx监控Redis监控PostgreSQL监控Kafka监控ElasticSearch监控一、Prometheus介…

这四类项目经理一定带不好项目

项目经理就一定可以带好项目嘛&#xff1f;&#xff0c;当然不一定 失败的项目不少&#xff0c;除去一些本身就很坑的项目&#xff0c;大多项目失败&#xff0c;都和项目经理的个性有关。 也总结了2组极端特质&#xff1a; 第一&#xff0c;烂好人VS劳模型 第二&#xff0c;马…

[ vulhub漏洞复现篇 ] struts2远程代码执行漏洞s2-059(CVE-2019-0230)

&#x1f36c; 博主介绍 &#x1f468;‍&#x1f393; 博主介绍&#xff1a;大家好&#xff0c;我是 _PowerShell &#xff0c;很高兴认识大家~ ✨主攻领域&#xff1a;【渗透领域】【数据通信】 【通讯安全】 【web安全】【面试分析】 &#x1f389;点赞➕评论➕收藏 养成习…

JavaEE-多线程初阶3

✏️作者&#xff1a;银河罐头 &#x1f4cb;系列专栏&#xff1a;JavaEE &#x1f332;“种一棵树最好的时间是十年前&#xff0c;其次是现在” 目录volatile关键字wait 和 notify多线程案例单例模式饿汉模式懒汉模式volatile关键字 volatile : 易变的&#xff0c;易失的 vo…

关于实体类中VO、DTO、Entity的区别

关于实体类中VO、DTO、Entity的区别 1、entity 里的每一个字段&#xff0c;与数据库相对应&#xff0c; 2、vo 里的每一个字段&#xff0c;是和你前台 html 页面相对应&#xff0c; 3、dto 这是用来转换从 entity 到 vo&#xff0c;或者从 vo 到 entity 的中间的东西 。(DTO中拥…

leetcode: 1091. 二进制矩阵中的最短路径

1091. 二进制矩阵中的最短路径 来源:力扣(LeetCode) 链接: https://leetcode.cn/problems/shortest-path-in-binary-matrix/description/ 给你一个 n x n 的二进制矩阵 grid 中,返回矩阵中最短 畅通路径 的长度。如果不存在这样的路径,返回 -1 。 二进制矩阵中的 畅通…

刚刚接触编程,纠结应该先学Python还是Java?看完心里就有数了。

准备学习编程前&#xff0c;你一定会打开电脑搜索一下“先学Python还是先学Java&#xff1f;”扑面而来的是海量回答。 如果你问一名Java程序员&#xff0c;肯定会建议你先学Java&#xff0c;因为他们认为Python像个乳臭未干的黄毛小子。但如果你问Python程序员&#xff0c;可…

MacOS Ventura 13.1 (22C65) 正式版带 OC 0.8.7 and winPE 双分区原版黑苹果镜像

12 月 14 日&#xff0c;苹果向 Mac 电脑用户推送了 macOS 13.1 更新&#xff08;内部版本号&#xff1a;22C65&#xff09;&#xff0c;本次更新距离上次发布隔了 34 天。本次更新引入了无边记应用&#xff0c;旨在让用户在空白的白板式画布上进行素描、绘画和写作&#xff0c…

[附源码]Nodejs计算机毕业设计基于Web的在线音乐网站Express(程序+LW)

该项目含有源码、文档、程序、数据库、配套开发软件、软件安装教程。欢迎交流 项目运行 环境配置&#xff1a; Node.js Vscode Mysql5.7 HBuilderXNavicat11VueExpress。 项目技术&#xff1a; Express框架 Node.js Vue 等等组成&#xff0c;B/S模式 Vscode管理前后端分…

能力评估网站

开发工具(eclipse/idea/vscode等)&#xff1a; 数据库(sqlite/mysql/sqlserver等)&#xff1a; 功能模块(请用文字描述&#xff0c;至少200字)&#xff1a; (1)高校学生评估系统用户页面设计 前台页面主要是为用户设计&#xff0c;经过对用户和评估系统分析&#xff0c;目前主要…

足球视频位置与平面坐标的转换

依赖&#xff1a; C# OpenCVSharp WPF Numpy 目的&#xff1a;解决足球场上&#xff0c;人物投影到二维平面的位置坐标 图A / B/ C 一、基础概念 1.1标准球场的定义&#xff1a; 参考&#xff1a;https://zh.m.wikipedia.org/zh/%E8%B6%B3%E7%90%83%E5%A0%B4图 D 1.2 配准…

m软件接收机中同步技术以及LMS-RLS自适应均衡技术的matlab仿真

目录 1.算法描述 2.仿真效果预览 3.MATLAB核心程序 4.完整MATLAB 1.算法描述 自适应均衡属于自适应信号处理的应用范畴&#xff0c;各种各样的自适应均衡算法如迫零(ZF)算法、最小均方(LMS)算法、递归最小二乘(RLS)算法、变换域均衡算法、Bussgang算法、高阶或循环统计量算…

使用CreateJS实现一个【拯救球员】小游戏,快@你的小伙伴一起来玩儿吧

拯救球员游戏需求&#x1f447;核心玩法&#x1f447;&#x1f447;界面原型&#x1f447;&#x1f447;成品演示&#x1f447;游戏开发1.游戏素材准备2.代码实现1.创建index.html页面2.首页转场动画实现3.添加分数倒计时4.卡片排序展示5.游戏结束世界杯开赛前夕&#xff0c;球…