排序算法的实现及时间复杂度分析——计数排序、选择排序、冒泡排序、插入排序

文章目录

    • 排序算法
    • 计数排序
    • 选择排序
    • 冒泡排序
    • 插入排序

排序算法

排序算法是解决问题中常见且非常重要的一环,针对相应的问题选择相应的排序算法,能够提高问题解决速度,节省时间!!!

常见的排序算法有:

排序算法关键步骤时间复杂性
最好最坏
计数排序比较n(n - 1)/2 + nn(n - 1)/2 + 2n - 1
移动06(n - 1)
选择排序(及时终止的)比较n - 1n(n - 1)/2
移动33(n - 1)
冒泡排序(及时终止的)比较n - 1n(n - 1)/2
移动03n(n - 1)/2
插入排序比较n - 1n(n - 1)/2
移动2(n - 1)2(n - 1) + n(n - 1)/2

下面就这些排序算法进行实现及分析。

计数排序

  1. 主要思路及实现
    1. 步骤一:先对数组中的元素计算相应的名次,并将相应的名次保存在 rank 数组中;
      • 如何计算名次?
        每一个元素 a[k]a[k]a[k] 都和它前面的 k−1k - 1k1 个元素进行比较,若 a[k]>a[i](i∈[0,k−2])a[k] > a[i](i \in [0, k - 2])a[k]>a[i](i[0,k2]),则 rank[k]++;反之则 rank[i]++;
        for (int i = 1; i < n; ++i){for (int j = 0; j < i; ++j){if (a[i] < a[j])++rank[j];else++rank[i];}}
        
    2. 步骤二:根据对应的名字对原数组中的元素进行原地重排;
      • 怎样进行重排?
        rank[i]!=irank[i] != irank[i]!=i 时,使用 swap() 函数对 a[i]a[i]a[i]a[rank[i]]a[rank[i]]a[rank[i]] 进行交换即可;注意交换不只是 a 数组要进行交换,rank 数组也要跟着一起!!!
        for (int i = 0; i < n; ++i){while (rank[i] != i){swap(a[i], a[rank[i]]);swap(rank[i], rank[rank[i]]);}}
        
    具体实现:
    //计数排序
    template<typename T>
    void countSort(T* a, int n)
    {//计算名次int* rank = new int[n] ();for (int i = 1; i < n; ++i){for (int j = 0; j < i; ++j){if (a[i] < a[j])++rank[j];else++rank[i];}}//按照名次进行原地重排for (int i = 0; i < n; ++i){while (rank[i] != i)			//注意 swap 中 a 和 rank 都得换哦{swap(a[i], a[rank[i]]);swap(rank[i], rank[rank[i]]);}}delete[] rank;
    }
    
  2. 时间复杂性分析
    1. 比较次数

      1. 在计算名次的时候,每一个元素都要和它前面的元素进行比较,因此此时经历了compare1=n(n−1)2次比较compare_1 = \frac{n(n - 1)}{2} \text{次比较}compare1=2n(n1)次比较

      2. 在原地重排时,我们需要考虑最好和最坏两种情况(不考虑循环变量的比较)。
        最好情况下:
        所有元素开始均已有序,因此 r[i] != i 只需要执行 n 次即可;

        最坏情况下:
        每一次交换都会是一个元素到达正确的位置,假设第 1 个位置在不停地进行交换,那么进行 n - 1 次后,所有的元素都会到达其正确的位置。此后只需进行 n 次 r[i] != i 判断即可。因此共 2n - 1 次;

    2. 交换次数与移动次数
      在按照名次进行原地重排时,
      最好情况下:
      一次交换也不做,因此此时交换次数为 0;

      最坏情况下:
      每次都要作交换,并且 n 个元素进行 n - 1 次交换之后,第 n 个元素必有序,此时只需 n - 1 次 swap 即可;

      因此,交换次数:0−2(n−1)0 - 2(n - 1)02(n1),由于每次交换涉及 3 次移动,所以移动次数:0−3∗2(n−1)0 - 3 * 2(n - 1)032(n1)

选择排序

  1. “最原始的” 选择排序
    1. 主要思路及实现

      1. 步骤一:在有 n−i(i∈0,1,2,⋯,n−1)n - i(i \in {0, 1, 2, \cdots, n - 1})ni(i0,1,2,,n1) 个元素的数据集中找出最大元素;
      2. 步骤二:将这个最大元素与数据集中最后一个元素进行交换;
      3. 重复步骤一、二,直到得到只有 1 个元素的数据集;

      具体实现:

      //寻找数据集中的最大值对应的索引
      template<typename T>
      int indexOfMax(T* a, int n)
      {if (n < 1)return -1;int indexOfMax = 0;for (int i = 0; i < n; ++i)if (a[indexOfMax] < a[i])indexOfMax = i;return indexOfMax;
      }//选择排序
      template<typename T>
      void selectSort(T* a, int n)
      {//找到最大元素,然后与最后一个元素进行交换for (int i = n; i > 0; --i)swap(a[i - 1], a[indexOfMax(a, i)]);
      }
      
    2. 时间复杂性分析:

      1. 比较次数:在对最大值对应的索引进行求取时,每个 iii 都要在 indexOfMax() 函数中比较 i - 1 次;因此:compare1=1+2+⋯+n−1=n(n−1)2次compare_1 = 1 + 2 + \cdots + n - 1 = \frac{n(n - 1)}{2} \text{次}compare1=1+2++n1=2n(n1)
      2. 交换次数与移动次数
        同理,进行 n - 1 次交换之后,第 n 个元素必在该在的位置上,因此,交换次数:n−1n - 1n1,移动次数:3(n−1)3(n - 1)3(n1)
  2. “及时终止” 的选择排序
    有时候不一定要一直进行排序到只剩下一个元素,当某一次函数执行时数据已经有序了,那么此时便可以不进行下面的过程了。
    1. 主要思路及实现

      1. 步骤一:设置 bool 值 sorted 来表示序列是否已经有序,并赋初始值为 false;
      2. 步骤二:在 for 循环中寻找最大索引时,我们假设序列已经有序,sorted = true;若 if 中条件一直满足,说明假设成立,最终 sorted = true,不再进行下一次循环;
        若序列仍无序,则必会进入 else 语句,sorted = false。从而达到了及时退出的目的。

      具体实现:

      //及时终止的选择排序
      template<typename T>
      void improved_selectSort(T* a, int n)
      {bool sorted = false;for (int i = n; !sorted && i > 0; --i){int indexOfMax = 0;//假设已经有序了sorted = true;//找最大索引for (int j = 0; j < i; ++j){   //如果是有序的,就会一直走 if,从而 sorted = true,下次不再进入循环;只要有一个不满足,sorted 就会置为false,说明无序if (a[indexOfMax] < a[j])indexOfMax = j;elsesorted = false;}swap(a[i - 1], a[indexOfMax]);}
      }
      
    2. 时间复杂性分析

      1. 比较次数(只考虑 a 数组中的元素的比较)
        最好情况下(即序列已经有序):只需进行 if 判断即可,故 comparebest=(n−1)次compare_{best} = (n - 1)\text{次}comparebest=(n1)
        最坏情况下:每次都要进行 if 判断,共需 compareworst=n−1+n−2+⋯+1=n(n−1)2次compare_{worst} = n - 1 + n - 2 + \cdots + 1 = \frac{n(n - 1)}{2}\text{次}compareworst=n1+n2++1=2n(n1)
      2. 交换次数
        最好情况下:只需进行一次交换即可(即第一次的时候),故swapbest=1次swap_{best} = 1\text{次}swapbest=1
        最坏情况下:每次都需要进行交换,故需 swapworst=(n−1)次swap_{worst} = (n - 1) \text{次}swapworst=(n1)

冒泡排序

  1. “最原始的” 冒泡排序
    1. 主要思路及实现

      1. 步骤一:每次对 n−i(i∈0,1,2,⋯,n−1)n - i(i \in {0, 1, 2, \cdots, n - 1})ni(i0,1,2,,n1) 个元素集中的相邻元素进行比较,若不满足顺序,则交换;反之保持原样。每次冒泡过程结果为对应数据集中的最大值被放到序列的最右端
      2. 步骤二:重复步骤一 n - 1 次即可;

      具体实现:

      //冒泡排序
      //一次冒泡过程
      template<typename T>
      void bubble(T* a, int n)    //一次冒泡过程
      {for (int i = 0; i < n; ++i){if (a[i] > a[i + 1])swap(a[i], a[i + 1]);}
      }template<typename T>
      void bubbleSort(T* a, int n)
      {for (int i = n; i > 0; --i)bubble(a, i);
      }
      
    2. 时间复杂性分析

      1. 比较次数(针对 a 数组中的元素)
        这样的比较主要发生在一次冒泡过程中,每一次都有 n−i(i∈0,1,2,⋯,n−1)n - i (i \in {0, 1, 2, \cdots, n - 1})ni(i0,1,2,,n1) 进行比较,因此:compare=n−1+n−2+⋯+2+1=n(n−1)2次compare = n - 1 + n - 2 + \cdots + 2 + 1 = \frac{n(n - 1)}{2} \text{次}compare=n1+n2++2+1=2n(n1)
      2. 交换次数
        最好情况下,所有元素开始就已经有序,无需交换,故:comparebest=0次compare_{best} = 0\text{次}comparebest=0
        最坏情况下,所有元素都得两两进行比较,故:compareworst=n−1+n−2+⋯+2+1=n(n−1)2次compare_{worst} = n - 1 + n - 2 + \cdots + 2 + 1 = \frac{n(n - 1)}{2}\text{次}compareworst=n1+n2++2+1=2n(n1)
  2. “及时终止的” 冒泡排序
    1. 主要思路及实现

      1. 与 “及时终止的” 选择排序一样,也是用一个 bool 值变量在 if 循环中控制循环的进行;

      具体实现:

      //及时终止的冒泡排序
      template<typename T>
      void improved_bubbleSort(T* a, int n)
      {bool stopped = false;for (int i = n; !stopped && i > 0; --i){stopped = true;for (int j = 0; j < i; ++j){if (a[j] > a[j + 1])swap(a[j], a[j + 1]);elsestopped = false;}}
      }
      
    2. 时间复杂性分析

      1. 比较次数
        最好情况下,只需在第一趟冒泡时进行元素的比较即可,因此:comparebest=(n−1)次compare_{best} = (n - 1)\text{次}comparebest=(n1)
        最坏情况下,每一次都会进行比较,因此:compareworst=n(n−1)2次compare_{worst} = \frac{n(n - 1)}{2}\text{次}compareworst=2n(n1)
      2. 交换次数
        交换次数和 “最原始的” 冒泡排序一样,并无变化。

插入排序

  1. 主要思路及实现

    1. 步骤一:假设第 kkk 个元素 x 之前的序列是有序的,将第 kkk 个元素 x 它们进行比较;
    2. 步骤二:若 x < a[i],则 a[i] 及之后的元素都向后移,直接某个元素 < x,那么 x 就应该插入在第 i + 1 个位置;

    具体实现:

    //插入排序
    template<typename T>
    void insert(T* a, int n, int key)
    {int i;for (i = n - 1; i >= 0 && key < a[i]; --i)a[i + 1] = a[i];a[i + 1] = key;
    }template<typename T>
    void insertSort(T* a, int n)
    {for (int i = 1; i <= n; ++i)insert(a, i, a[i]);
    }
    
    1. 时间复杂性分析
      1. 比较次数
        最好情况下,只需与最后一个元素进行比较即可,故 comparebest=n−1次compare_{best} = n - 1\text{次}comparebest=n1
        最坏情况下,每个元素均需进行比较,故 compareworst=n−1+n−2+⋯+2+1=n(n−1)2次compare_{worst} = n - 1 + n - 2 + \cdots + 2 + 1 = \frac{n(n - 1)}{2}\text{次}compareworst=n1+n2++2+1=2n(n1)
      2. 移动次数
        最好情况下,只有在传参和最后 key 的赋值时进行了移动,因此:movebest=2(n−1)次move_{best} = 2(n - 1)\text{次}movebest=2(n1)
        最坏情况下,在 comparebestcompare_{best}comparebest 的基础上,还要进行 a 数组元素向后移的操作,因此:moveworst=movebest+n−1+n−2+⋯+2+1=2(n−1)+n(n−1)2次move_{worst} = move_{best} + n - 1 + n - 2 + \cdots + 2 + 1 \\= 2(n - 1) + \frac{n(n - 1)}{2}\text{次}moveworst=movebest+n1+n2++2+1=2(n1)+2n(n1)

后续的排序方法还会进行补充!
请添加图片描述

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

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

相关文章

python基础(12)之匿名函数lambda

lambda lambda 函数是一个小的匿名函数。一个 lambda 函数可以接受任意数量的参数&#xff0c;但只能有一个表达式。 语法&#xff1a; lambda arguments : expression 执行表达式并返回结果&#xff1a; 示例将 10 添加到 argument a&#xff0c;并返回结果&#xff1a; …

C语言快速排序

快速排序是对冒泡法排序的一种改进。 快速排序算法 的基本思想是&#xff1a;将所要进行排序的数分为左右两个部分&#xff0c;其中一部分的所有数据都比另外一 部分的数据小&#xff0c;然后将所分得的两部分数据进行同样的划分&#xff0c;重复执行以上的划分操作&#xff0…

机器学习入门(1)之基本概念简介

目录 一、机器学习概述 1.1 什么是机器学习&#xff1f; 1.2 为什么需要机器学习&#xff1f; 1.3 机器学习应用场景 1.4 机器学习的一般流程 1.5 典型的机器学习过程​ 二、机器学习的基本术语 三.假设空间与版本空间 四、归纳偏好 1.哪种更好 2..假设的选择原则 …

山东大学 2020级计算机系统原理——拆解二进制炸弹

写在前面 第一次拿到这个实验还是有点慌&#xff01;之前没见过&#xff0c;不过还是慢慢做过来了。 这是个需要耐心的过程&#xff0c;请一定静下心来哦&#xff01; 环境及配置 环境&#xff1a;Ubuntu 20.04 GDB 调试工具 可参考配置&#xff1a;GDB调试工具配置&#…

机器学习入门(2)之模型评估与选择

目录 一、误差与拟合 1. 泛化误差与经验误差 2. 损失函数与训练误差 3. 过拟合与欠拟合 4. 过拟合的另一种现象&#xff1a;数据泄露 二、评估方法 1. 留出法 2. 交叉验证法&#xff08;留一法&#xff09; 3. 自助法 4. 调参与最终模型 三、性能度量 1. 混淆矩阵 …

pytorch MNIST 手写数字识别 + 使用自己的测试集 + 数据增强后再训练

文章目录1. MNIST 手写数字识别2. 聚焦数据集扩充后的模型训练3. pytorch 手写数字识别基本实现3.1完整代码及 MNIST 测试集测试结果3.1.1代码3.1.2 MNIST 测试集测试结果3.2 使用自己的图片进行测试3.2.1 测试图片预处理代码3.2.2 测试图片结果4. 数据增强4.1 手动读取 MNIST …

python基础(13)之数组

目录 数组 一、访问数组的元素 二、数组的长度 三、修改数组 四、数组的其它操作 数组 Python 没有对数组的内置支持&#xff0c;但可以使用Python 列表代替。 例如&#xff1a; ben ["笨小孩1", "笨小孩2", "笨小孩3"]一、访问数组的元…

C语言归并排序(合并排序)

归并排序也称合并排序&#xff0c;其算法思想是将待排序序列分为两部分&#xff0c;依次对分得的两个部分再次使用归并排序&#xff0c;之后再对其进行合并。仅从算法思想上了解归并排序会觉得很抽象&#xff0c;接下来就以对序列A[0], A[l]…, A[n-1]进行升序排列来进行解说&a…

python基础(14)之 类和对象

目录 Python类和对象 一、创建类 二、创建对象 三、init() 函数 四、对象方法 五、自参数 六、对象及其属性更改 七、pass语句 Python类和对象 Python 类/对象。Python 是一种面向对象的编程语言。Python 中的几乎所有东西都是一个对象&#xff0c;有它的属性和方法。…

C语言顺序查找

顺序査找是一种简单的査找算法&#xff0c;其实现方法是从序列的起始元素开始&#xff0c;逐个将序列中的元素与所要查找的元素进行比较&#xff0c;如果序列中有元素与所要查找的元素相等&#xff0c;那么査找成功&#xff0c;如果査找到序列的最后一个元素都不存在一个元素与…

python基础(15)之 继承

目录 Python继承 一、创建父类 二、创建子类 三、添加 init() 函数 四、使用 super() 函数 五、添加属性 六、添加方法 Python继承 继承允许我们定义一个从另一个类继承所有方法和属性的类。父类是被继承的类&#xff0c;也称为基类。子类是从另一个类继承的类&#xff…

C语言二分查找(折半查找)

二分査找也称折半査找&#xff0c;其优点是查找速度快&#xff0c;缺点是要求所要査找的数据必须是有序序列。该算法的基本思想是将所要査找的序列的中间位置的数据与所要査找的元素进行比较&#xff0c;如果相等&#xff0c;则表示査找成功&#xff0c;否则将以该位置为基准将…

python基础(16)之 日期

目录 Python日期 一、日期输入输出 二、创建日期对象 三、strftime() 方法 Python日期 Python 中的日期不是它自己的数据类型&#xff0c;但我们可以导入一个名为的模块datetime来处理日期作为日期对象。 一、日期输入输出 导入 datetime 模块并显示当前日期&#xff1a;…

python基础(17)之 JSON

Python JSON JSON 是一种用于存储和交换数据的语法。JSON 是文本&#xff0c;用 JavaScript 对象表示法编写。 Python 有一个名为 的内置包json&#xff0c;可用于处理 JSON 数据。 导入 json 模块&#xff1a; import json一.从 JSON 转换为 Python 如果您有 JSON 字符串&am…

python基础(18)之 异常处理

目录 异常处理 一、异常处理 二、else搭配 三、finally语句 四、引发异常 异常处理 try块可让您测试代码块的错误。except块可让您处理错误。finally无论 try- 和 except 块的结果如何&#xff0c;该块都允许您执行代码。 一、异常处理 例如该try块将产生异常&#xff0…

python基础(19)之 输入输出

目录 用户输入 一、格式化输入输出 二、格式化字符串字面值 三、字符串 format() 方法 四、手动格式化字符串 五、旧式字符串格式化方法 用户输入 实在太简单了&#xff0c;就是使用一个input(),将输入后的值传递给另一个变量&#xff0c;相当于动态赋值、 例如&#xff…

C语言函数返回值详解

函数的返回值是指函数被调用之后&#xff0c;执行函数体中的代码所得到的结果&#xff0c;这个结果通过 return 语句返回。 return 语句的一般形式为&#xff1a; return 表达式;或者&#xff1a; return (表达式);有没有( )都是正确的&#xff0c;为了简明&#xff0c;一般…

机器学习之线性回归(python)

目录 一、基本概念 二、概念的数学形式表达 三、确定w和b 1.读取或输入数据 2.归一化、标准化 2.1 均值 2.2 归一化 2.3 标准化 3.求解w和b 1.直接解方程 2.最小二乘法&#xff08;least square method&#xff09;求解&#xff1a; 4. 评估回归模型 四、sklearn中…

C语言函数的调用

函数调用&#xff08;Function Call&#xff09;&#xff0c;就是使用已经定义好的函数。 函数调用的一般形式为&#xff1a; functionName(param1, param2, param3 ...);functionName 是函数名称&#xff0c;param1, param2, param3 …是实参列表。实参可以是常数、变量、表…

机器学习之线性回归(matlab)

目录 一、基本概念 二、概念的数学形式表达 三、确定w和b 1.读取或输入数据 2.归一化、标准化 2.1 均值 2.2 归一化 2.3 标准化 3.求解w和b 1.直接解方程 2.最小二乘法&#xff08;least square method&#xff09;求解&#xff1a; 4. 评估回归模型 四、regress线…