当前位置: 首页 > news >正文

算法学习 - 基础排序算法

最近在学习算法与数据结构,算法是一个程序员的基本功,但是我不是科班出身,所以这方面的知识有所欠缺。在慕课网上找到一套对应的课程,主讲老师是liuyubobobo,从我学习的感受和体验来看,bobo老师对一个问题讲解的相当清晰和透彻,普通话说的也好,适合初学者理解和学习。大家如果想学习算法与数据结构的知识,我推荐这一套教程。地址链接:coding.imooc.com/class/71.ht… 这一系列文章主要是对这套课程的内容以文字的形式展示。做这个事情一是想对视频上的知识点在通过形成文字的过程中,加深自己的理解。二是想加强自己的表达能力。

这篇文章主要讲解3个基础的排序算法,选择排序,插入排序,以及冒泡排序,其时间复杂度都是0(n^2)级别的,实现代码使用c++语言。

选择排序

首先给定一个元素是无序的整数数组:

需要对这个数组中的8个整数进行从小到大的排序。

选择排序的基本思路

首先从起始位置index = 0开始,遍历一遍数组,获取到最小的元素值index = 4 的元素,元素值为1,然后将index = 0与index = 4 上的两个元素交换位置,此时index = 0 上的元素值1。index = 4上的元素值为5。红色为已排序完成的有序区域。

找到了最小的元素放在数组的第一位后,接着从index = 1开始往后遍历数组。找到第二小的元素后再与index = 1的位置交换元素,此时,index = 1上的元素值为2,index = 2 上的元素为4.

接着再从index = 2的位置往后遍历数组,找到第三小的元素后再与index = 2上的元素交换位置,交换后,index = 2上的元素为3,index = 5 上的元素为4。

然后依此遍历的方法,直到数组的最后一个位置存放的是最大的元素。选择排序的整体思想不难理解,就是循环一遍数组找到循环元素中最小的元素,再与已排好序的下一个索引上的元素交换位置。

代码实现

我写成一个函数,传入一个数组以及元素的个数:

template <typename T>
void selectionSort(T arr[],int n){
    
    for (int i = 0; i < n; i++) {
        int minIndex = i; // minIndex保存内层循环中最小值的索引
        for (int j = i + 1; j < n; j++) {
            if (arr[j] < arr[minIndex]) {
                minIndex = j;
            }
        }
        swap(arr[i], arr[minIndex]);
    }
}
复制代码

插入排序

插入排序的基本思路

bobo老师打个这样一个比方,我觉得用来理解插入排序非常合适:就好像有一副牌,三个人用这副牌来斗地主,分牌阶段,你需要将你摸到的牌放在手中的牌中的合适位置,手中的牌是整理好的,是有序的,放入后,使其整体依旧有序。我们来模拟一下这个过程。首先固定手中有一张牌,用红色标记为手中已整理好的牌。

接着摸第二张牌,为4,比5小,所以放在5的前面,4与5交换位置。

接着摸第三张牌,为2,2比5小,所以先放在5的前面,4的后面。

此时这里并不是2合适的位置,继续和前面的元素比较,2比4小,所以要放到4的前面。此时变成有序了。

接着再摸第四张牌,为7,7比5小,发现不用交换位置,放在原地就好了。

就这样,按照这个方法,将摸到的牌与整理好的牌依次做比较,使其放在合适的位置,直到摸完最后一张牌。

代码实现

template <typename T>
void insertionSort(T arr[],int n){
    
    for (int i = 1; i < n; i++) {
        
        for (int j = i; j > 0; j--) {
            if (arr[j-1] > arr[j]) {
                swap(arr[j - 1],arr[j]);
            } else {
                break;
            }
        }
    }
}
复制代码

注意:第一层for循环时 i = 1,i不是从零开始。因为第一张牌不需要排序,从第二张牌开始。所以设置i = 1。

在[0 i) 前闭后开这个区间中为已排好序的元素,i为待排序的元素的索引,第二层for循环是从待整理的元素索引i开始。在已排好序的区间[0,i)中,从后往前遍历。 如果发现前面的元素比它大,则两个元素交换位置。当发现前面的元素比它小的时候,此时它就找到了合适的位置。就不需要再遍历了,直接结束这一层循环。

插入排序优化

在交换两个元素位置的时候,调用了swap(arr[j - 1],arr[j]),而一次交换其实是三次赋值,这句代码其实也可以改写为:

int temp = arr[j-1];
arr[j-1] = arr[j];
arr[j] = temp;
复制代码

如果一个较小的元素要插入到合适的位置,肯定要一路交换很多次。这无形中就增加了排序时间。优化思路就是只进行赋值操作而不进行交换操作,先保存一份待排序的元素,然后遍历已排好序的元素,从后往前进行逐一比较,如果当遍历到index = j的元素时,j - 1索引上的元素要比j位置上的元素大,则将j - 1索引位置的元素往后挪,往后挪也就是将j索引位置的元素赋值为j-1索引位置的元素,当遍历到j-1索引上的元素比元素j位置上的元素小或相等时,就说明上一次循环往后挪而腾出来的位置为待排序的合适位置,在将该位置的值赋值为待排序的值就好了。

假定现在按照优化的思路对1进行排序,红色为已排好序的部分。

首先先复制一份待排序的元素1

然后将待排序的元素与已排好序的index = 3位置,值为7的元素比较,如果这个元素比待排序的元素要大,则直接将待排序的值复制为这个元素。7 比 1 大,7往后挪,所以待排序位置上的元素值赋值为7,而index = 3位置就腾出来了。

然后依次往前与带排序的元素比较。这次是index = 2 时的 5 与 1比较, 5比1大,5往后挪,将index = 3 位置上的元素赋值为5,index = 2这个位置就腾出来了。

再将index = 1 时的4 与 待排序的元素1比较,4 还是比1 大,4往后挪,将 index = 2位置赋值为4,index = 1这个位置就腾出来了。

继续。再将index = 0时的2, 与待排序的元素1比较,2还是比1大,2往后挪,将index = 1位置赋值为2,index = 0这个位置腾出来了。

那么腾出来的位置上为元素的前面已没有元素可以比较了。所以此时在index = 0的位置上放置上待排序的元素。

元素1经过一番波折,尘埃落定,终于找到了他合适的位置,元素1排序完毕

然后index = 5上的元素3按照上述方法找到合适的位置,index = 6 上的元素按照上述方法找到合适的位置,接着是index = 7上的元素。将所有的元素都按照上述方法找到自己合适的位置。

代码实现

template <typename T>
void insertionSort(T arr[],int n){
    
    for (int i = 1; i < n; i++) {
        T e = arr[i]; // 保存待插入索引为i时的元素e
        int j = i; // j保存元素e应该插入的位置
        for (j = i; j > 0; j--) {
            if (arr[j-1] > e) {
                arr[j] = arr[j-1];
            }
        }
        arr[j] = e;
    }
}
复制代码

冒泡排序

冒泡排序实现思路

首先还是这一个由8个数组成的一个无序的数组

冒泡排序的核心思想就是将相邻的两个元素两两比较,根据大小来交换元素的位置

首先,5与4比较,5比4大,我们的需求是从小到达排列,4需要在5的前面,所以4与5交换位置,红色表示两个要交换位置的元素。

交换后

然后, 5与2开始比较,5比2大,交换位置

交换后

继续, 5和7比较,5比7小,位置保持不变

go on, 7与1比较,7比1大,交换位置

交换后

不要停,7与3比较,7比3大,交换位置

交换后

继续深入, 7与8比较,7比8小,保持不变

还有最后一个,8与6比,8比6大,交换位置

交换后

这样一轮下来,元素8排到了最右侧,此时红色代表️已排好序的区域

接着按照上述方法,重新重头开始相邻元素两两遍历,前一个元素比后一个元素大则交换位置,小则保持位置不变,一路比较下来。找到第二大的元素放到元素8的左侧。

然后是

然后是

给定的一组无序的整数数组,按照排序的思路来讲,蓝色区域属于无序的区域,还需要比较,但是上面图片中蓝色部分已经是有序的了,这可以作为一个优化的方面,就是当发现代排区域中的元素已经是有序了的时候,排序完成,结束排序,下面的代码中不涉及这部分的优化。

实现代码

template <typename T>
void BubbleSort(T arr[],int n){
    
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < n - i - 1; j++) {
            if (arr[j] > arr[j+1]) {
                swap(arr[j],arr[j+1]);
            }
        }
    }
}
复制代码

代码不难理解,双层循环,外层每一次循环确定一个最大值放在数组的右侧,内层寻找这个最大值。先进行元素比较,满足则交换。

冒泡排序的优化

刚刚在逐步比较的时候,出现了这样一种情况:蓝色的无序区域中的元素已经是有序的了。

但是上面的冒泡排序的代码还会继续的比较下去。每一个元素都会参与外层循环,直到循环结束。所以优化方向是在无序区域中的元素已经有序了的情况下结束循环。 优化后的代码,部分解释见注释。

void BubbleSort(T arr[],int n){
    
    for (int i = 0; i < n; i++) {
        bool isSorted = true; // 用一个bool值来标记每一层是否是有序的,默认为true.
        
        for (int j = 0; j < n - i - 1; j++) {
            if (arr[j] > arr[j+1]) {
                swap(arr[j],arr[j+1]);
                isSorted = false; // 如果有交换元素,那么不是有序,该bool值改为false.
            }
        }
        if (isSorted) { // 在内层中,如果该bool值没有被改为false,那么就说明内层没有元素交换,那么该数列排序完成了,直接结束循环。
            break;
        }
    }
}
复制代码

这个优化主要是用一个bool值做标记,来确定是否有序。在内层循环中如果没有元素交换,那么这个数列肯定就是有序的了,那么外层就无需在循环了。

下次学习内容:归并排序

相关文章:

  • TensorFlow 生成 .ckpt 和 .pb
  • 分享一份非常强势的Android面试题
  • Linux中进行抓包
  • Cordova 笔记
  • VLAN及三层交换机实例
  • 易百教程人工智能python修正-人工智能监督学习(回归)
  • jenkins war下载地址
  • 企业开发的顶级语言调查;南大用“推荐算法”分宿舍;黑客每 60 秒可盗走超 100 万美元资产...
  • OpenCV图像哈希计算及汉明距离的计算
  • 【译Py】2018年8月,GitHub上的Python数据科学明星项目:自动化机器学习、自然语言处理、可视化、机器学习工作流...
  • ElasticSearch(九):springboot项目集成消息中间件activeMQ
  • BZOJ2157旅游——树链剖分+线段树
  • linux中快速清空文件内容的几种方法
  • JS中的继承
  • MyBatis拦截器原理探究
  • 【MySQL经典案例分析】 Waiting for table metadata lock
  • android 一些 utils
  • CSS 三角实现
  • CSS 提示工具(Tooltip)
  • Date型的使用
  • download使用浅析
  • Java 9 被无情抛弃,Java 8 直接升级到 Java 10!!
  • JAVA SE 6 GC调优笔记
  • JavaScript异步流程控制的前世今生
  • maven工程打包jar以及java jar命令的classpath使用
  • mysql 数据库四种事务隔离级别
  • Netty 4.1 源代码学习:线程模型
  • Phpstorm怎样批量删除空行?
  • React-flux杂记
  • vue-router 实现分析
  • 阿里中间件开源组件:Sentinel 0.2.0正式发布
  • 从tcpdump抓包看TCP/IP协议
  • 和 || 运算
  • 扫描识别控件Dynamic Web TWAIN v12.2发布,改进SSL证书
  • 使用SAX解析XML
  • 通过获取异步加载JS文件进度实现一个canvas环形loading图
  • 学习笔记TF060:图像语音结合,看图说话
  • Unity3D - 异步加载游戏场景与异步加载游戏资源进度条 ...
  • #LLM入门|Prompt#1.8_聊天机器人_Chatbot
  • (1)(1.19) TeraRanger One/EVO测距仪
  • (八十八)VFL语言初步 - 实现布局
  • (二)hibernate配置管理
  • (附源码)ssm航空客运订票系统 毕业设计 141612
  • (三维重建学习)已有位姿放入colmap和3D Gaussian Splatting训练
  • (四)Controller接口控制器详解(三)
  • (淘宝无限适配)手机端rem布局详解(转载非原创)
  • (转)3D模板阴影原理
  • (转)四层和七层负载均衡的区别
  • .net core 微服务_.NET Core 3.0中用 Code-First 方式创建 gRPC 服务与客户端
  • .Net 中的反射(动态创建类型实例) - Part.4(转自http://www.tracefact.net/CLR-and-Framework/Reflection-Part4.aspx)...
  • [ NOI 2001 ] 食物链
  • [AHOI2009]中国象棋 DP,递推,组合数
  • [AIGC 大数据基础]hive浅谈
  • [AS3]URLLoader+URLRequest+JPGEncoder实现BitmapData图片数据保存
  • [BZOJ 4598][Sdoi2016]模式字符串