1、排序算法

 

一、课程目标

1.了解排序算法及各自时间复杂度
2.排序算法的实现

二、目标详解

1.相关概念

稳定:如果a原本在b前面,而a=b,排序之后a仍然在b的前面。
不稳定:如果a原本在b的前面,而a=b,排序之后 a 可能会出现在 b 的后面。
时间复杂度:对排序数据的总的操作次数。反映当n变化时,操作次数呈现什么规律。
空间复杂度:是指算法在计算机内执行时所需存储空间的度量,它也是数据规模n的函数。

2.排序的分类

从大类来分:
非线性时间比较类排序:通过比较来决定元素间的相对次序,由于其时间复杂度不能突破O(nlogn),因此称为非线性时间比较类排序。冒泡排序、插入排序、选择排序、希尔排序、归并、快速排序、堆排序都是非线性时间比较类排序。

线性时间非比较类排序:不通过比较来决定元素间的相对次序,它可以突破基于比较排序的时间下界,以线性时间运行,因此称为线性时间非比较类排序。计数排序、桶排序、基数排序是线性时间非比较类排序。

3.算法复杂度对比

4.经典排序算法

  • 冒泡排序
  • 选择排序
  • 插入排序
  • 希尔排序
  • 计数排序
  • 归并排序
  • 快速排序
  • 堆排序

演示-冒泡排序 

一、实验目标

掌握冒泡算法.
冒泡排序(Bubble-sort)是一种简单的排序算法。它重复地走访过要排序的数列,一次比较两个元素,如果它们的顺序错误就把它们交换过来。走访数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。这个算法的名字由来是因为越小的元素会经由交换慢慢“浮”到数列的顶端。

输入:3 44 38 5 47 15 36 26 27 2 46 4 19 50 48

输出:2 3 4 5 15 19 26 27 36 38 44 46 47 48 50

二、分析

  • 比较相邻的元素。如果第一个比第二个大,就交换它们两个;
  • 对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对,这样在最后的元素应该会是最大的数;
  • 针对所有的元素重复以上的步骤,除了最后一个;
  • 重复步骤1~3,直到排序完成。

图示:

三、扩展需求

能否新加一个参数或变量,用来控制冒泡排序的顺序,有些场景会要求从小到大的顺序,但有时候会要求我们使用从大到小的顺序。

 

演示-选择排序 

一、实验目标

掌握选择排序算法.
选择排序(Selection-sort)是一种简单直观的排序算法,应该是初学者最容易想到的解决方法之一。它的工作原理:首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,然后,再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。以此类推,直到所有元素均排序完毕。

输入:3 44 38 5 47 15 36 26 27 2 46 4 19 50 48

输出:2 3 4 5 15 19 26 27 36 38 44 46 47 48 50

二、分析

n个记录的直接选择排序可经过n-1趟直接选择排序得到有序结果。具体算法描述如下:

  • 初始状态:无序区为R[1..n],有序区为空;
  • 第i趟排序(i=1,2,3…n-1)开始时,当前有序区和无序区分别为R[1..i-1]和R(i..n)。该趟排序从当前无序区中-选出关键字最小的记录 R[k],将它与无序区的第1个记录R交换,使R[1..i]和R[i+1..n)分别变为记录个数增加1个的新有序区和记录个数减少1个的新无序区;
  • n-1趟结束,数组有序化了。

图示:

三、扩展需求

能否新加一个参数或变量,用来控制排序结果从小到大还是从大到小。

 

演示-插入排序 

一、实验目标

掌握插入排序算法.
插入排序(Insertion-Sort)的算法描述是一种简单直观的排序算法。它的工作原理是通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。

输入:3 44 38 5 47 15 36 26 27 2 46 4 19 50 48

输出:2 3 4 5 15 19 26 27 36 38 44 46 47 48 50

二、分析

  • 从第一个元素开始,该元素可以认为已经被排序;
  • 取出下一个元素,在已经排序的元素序列中从后向前扫描;
  • 如果该元素(已排序)大于新元素,将该元素移到下一位置;
  • 重复步骤3,直到找到已排序的元素小于或者等于新元素的位置;
  • 将新元素插入到该位置后;
  • 重复步骤2~5,直到排序完成。

图示:

 

 

演示-希尔排序 

一、实验目标

掌握希尔排序。

算法描述

希尔排序Shell-sort,又叫缩小增量排序。
希尔排序是对插入排序的一种小改进,优化出发点是插入排序每次只能将数据移动一位,希尔排序可以先设置比较大的增量步长,这样可以跨比较大的步长进行插入,然后减小步长从后往前循环比较交换,直到步长减到1时做最后一遍的比较交换,即完成希尔排序。

输入:84 83 88 87 61 50 70 60 80 99

输出:50 60 61 70 80 83 84 87 88 99

二、分析

怎么取增量

这还是一个数学难题,迄今为止还没有人找到一种最好的增量序列。不过大量的研究表明,当增量序列为dlta[k]=2^(t-k+1)-1(0≤k≤t≤⌊log2(n+1)⌋)时,可以获得不错的效率。另外,不论采用什么样的增量算法,增量步长是递减的,最后一次增量步长必须是1。

简单起见,我们就是队列的长度len/2为头一次增量,依次循环除以2,直到增量商为1做最后一次循环。

步骤

  • 初始化gap = len 初始化变量gap,即增量步长
  • gap = gap / 2 增量步长递减 ,假如len=10,那么gap依次为5、2、1
  • gap = 5时,分割成2个子序列对比, gap = 2时,分割成5个子序列,gap=1时,分割成10个子序列
  • 循环从第2个序列的第1个元素开始,取出元素,与它相隔delta的前一个元素比较,如果比它大,不进入子循环,如果比它小,进入子循环,把当前位置的元素置成前前序列对应的元素值,按增量继续找前一个序列对应值做比较交换,直到前一序序值比当前值小或者到了队列的最前端退出子循环。把当前值与循环到的序列的位置的值做交换。
  • 取下一下元素比较交换,直到队尾,结果当前增量delta的循环比较,delta/2,进入新的delta重新循环
  • 当gap < =0 时,结束处理

过程图示

数据实例变化过程
84 83 88 87 61 50 70 60 80 99

这里略去了最内循环次数为0的循环过程

gap=5时
| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |最内循环次|
|–|–|–|–|–|–|–|–|–|–|–|
|84|83|88|87|61|50|70|60|80|99|1|
|50|83|88|87|61|84|70|60|80|99|1|
|50|70|88|87|61|84|83|60|80|99|1|
|50|70|60|87|61|84|83|88|80|99|1|
|50|70|60|80|61|84|83|88|87|99|1|

gap=2时,没有任何序列满足条件进入内循环1次

gap=1时,请注意61内循环了2次
| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |最内循环次|
|–|–|–|–|–|–|–|–|–|–|–|
|50|60|70|80|61|84|83|88|87|99|1|
|50|60|70|61|80|84|83|88|87|99|1|
|50|60|61|70|80|84|83|88|87|99|2|
|50|60|61|70|80|83|84|88|87|99|1|
|50|60|61|70|80|83|84|87|88|99|1|

 

演示-计数排序 

一、实验目标

掌握计数排序Counting Sort.
计数排序不是基于比较的排序算法,其核心在于将输入的数据值转化为键存储在额外开辟的数组空间中。 作为一种线性时间复杂度的排序,计数排序要求输入的数据必须是有确定范围的整数。很明显的是如果排序值是差别很大的散列值,计数排序就不太合适。

输入:2 3 8 7 1 2 2 2 7 3 9 8 2 1 4 2 4 6 9 2

输出:1 1 2 2 2 2 2 2 2 3 3 4 4 6 7 7 8 8 9 9

二、分析

  • 找出待排序的数组中最大和最小的元素;
  • 统计数组中每个值为i的元素出现的次数,存入数组b的第i项;
  • 对所有的计数累加(从b中的第一个元素开始,每一项和前一项相加);

过程示意图

扩展理解

  • 桶排序
    桶排序是计数排序的升级版。它利用了函数的映射关系,高效与否的关键就在于这个映射函数的确定。桶排序 (Bucket sort)的工作的原理:假设输入数据服从均匀分布,将数据分到有限数量的桶里,每个桶再分别排序(有可能再使用别的排序算法或是以递归方式继续使用桶排序进行排)。比如1-10000,我们分成1-1000、1001-2000、2001-3000、3001-4000、4001-5000、5001-6000、6001-7000、7001-8000、8001-9000、9001-10000,分成10个桶,然后10个桶里再按计数排序或其他算法的排序,最后把各桶里的数据从小到大再合起来形成总排序。

  • 基数排序
    基数排序是按照低位先排序,然后收集;再按照高位排序,然后再收集;依次类推,直到最高位。有时候有些属性是有优先级顺序的,先按低优先级排序,再按高优先级排序。最后的次序就是高优先级高的在前,高优先级相同的低优先级高的在前。

总结:计数排序、桶排序、基数排序都是线性的时间复杂度,是空间换时间的典型案例,但是对空间要求太高,数组数据结构无法支持,因此对于稍大一点数据来说,就无能为力了。因此一定要注意其应用场合,如果要排序的数据值本身是在一定的小范围内,这种算法效率是很高的。桶排序在计数排序的基础上优化,可以支持稍大范围的数据排序,基数排序按基数高优先级排序,但基数相差太大的话,一是时间复杂度也上来,二是单个基数下的数量太大,得继续计数排序或基它排序算法。总之要看清数值范围再决定是否能采用此三种排序算法。

 

演示-归并排序 

前言

前面五种排序除计数排序外,其他排序算法的时间复杂度为o(n^2)或在此基础上稍做优化,在大数据量的情况下可能会超时,下面我们来学习归并排序、快速排序、堆排序,这三种排序时间复杂度o(nlog2(n)),比起前四种有量级上的提升。

一、实验目标

要求熟练掌握二分法,递归函数,对完全二叉树有所了解,最后能熟练掌握归并排序。

归并排序(Merge-sort)是利用归并的思想实现的排序方法,该算法采用经典的分治(divide-and-conquer)策略(分治法将问题分(divide)成一些小的问题然后递归求解,而治(conquer)的阶段则将分的阶段得到的有序子序列按顺序合并在一起,即分而治之)。

归并排序是稳定排序,它也是一种十分高效的排序,能利用完全二叉树特性的排序一般性能都不会太差。从下图示中可看出,每次合并操作的平均时间复杂度为O(n),而完全二叉树的深度为|log2n|。总的平均时间复杂度为O(nlogn)。

已有数据用例8 4 5 7 1 3 6 2

要求使用归并排序并输出 1 2 3 4 5 6 7 8

二、分析

整体归并过程图示

可以看到这种结构很像一棵完全二叉树,我们首先想到的是通过递归来实现归并排序。分阶段可以理解为就是递归拆分子序列的过程,递归深度为log2n。

具体的一个合并过程图示

注意的点

  • 队列是从0还是从1计起,这些细节对于mid是否加1会有影响。我们把数据结构归结为完全二叉树,因此当结点是奇数时,分成左右树时,左边应该比右边多一个。
  • 上面的合并过程图示,当左右对比完了之后,可能左边或右边还有一部分元素需要全部追加到temp里,不要遗漏。就如上面剩下的7、8。

 

演示-快速排序 

一、实验目标

熟练掌握二分法,递归的基础用法,最后能熟练掌握快速排序。

快速排序是通过一趟排序将待排记录分隔成独立的两部分,其中一部分记录的关键字均比另一部分的关键字小,则可分别对这两部分记录继续进行排序,通过递归的方式直到子序列左边界大于右边界为止,这时从整修序列来说已达到整个序列有序。快排总的平均时间复杂度为O(nlogn),与归并排序接近,但快排不用开临时空间。

已有数据用例6 1 2 7 9 3 4 5 10 8

要求使用归并排序并输出 1 2 3 4 5 6 7 8 9 10

二、分析

1.获取基准数pivot

获取基准有很多种方法,比如取首尾的中间坐标,比如上面的取中间(0+9)/2=4,即9做第一次的基准数。也可以取数据值的平均值,还有其他各样的算法,我们这里主要讲快排本身算法,基准数就简单的取序列的最左边第一个,比如上面的数据,第一次的基准就是最左边的6。

2.递归过程

说明0123456789
选中基准661279345108
r=9,往左找比6小的,找到r=761279345108
l=0,往右找比6大的,找到l=361279345108
l r做交换61259347108
r=7,往左找比6小的,找到r=661259347108
l=3,往右找比6大的,找到l=461259347108
l r做交换61254397108
r=6,往左找比6小的,找到r=561254397108
l=4,往右找比6大的,发现l=r=561254397108
l=r=5与基准交换31254697108

上面演示了一次完整的序列快排,接下来在6的左边和右边用同样的算法进行子序列的排序
左边
|说明|0|1|2|3|4|
|–|–|–|–|–|–|
|选中基准3|3|1|2|5|4|
|r=4,往左找比3小的,找到r=2|3|1|2|5|4|
|l=0,往右找比3大的,找到l=2时与r相碰了|3|1|2|5|4|
|l=r=2与基准交换|2|1|3|5|4|

继续分成左右两个子序列
左边
|说明|0|1|
|–|–|–|
|选中基准2|2|1|
|r=1,往左找比2小的,找到r=1|2|1|
|l=0,往右找比2大的,找到l=1时与r相碰了|2|1|
|l=r=1与基准交换|1|2|

再往下2左边的元素只有一个,右边没有元素,满足递归退出关系式。其他的子序列按照上面的模板进行递归,最后完成快排运算。

全过程图示

注意
方便起见,交换大小时,都先从右向左找比基准小的,再从左向右找比基准大的,两者相遇的位置与基准进行交换,结束本次子序列排序,同时开始分成左右两部分子序列并进入子序列的排序,一直递归调用直到左位置大于右位置。

 

演示-堆排序 

一、实验目标

理解完全二叉树特性、实现堆排序

已有数据用例9 4 8 5 6

要求使用归并排序并输出 4 5 6 8 9

堆排序

堆排序是利用堆这种数据结构而设计的一种排序算法,堆排序是一种选择排序,但它利用了二叉树的特性来定位最大值或最小值,使平均时间复杂度均为O(nlogn),它是不稳定排序。

完全二叉树

完全二叉树是效率很高的数据结构,完全二叉树是由满二叉树而引出来的。对于深度为K的,有n个结点的二叉树,当且仅当其每一个结点都与深度为K的满二叉树中编号从1至n的结点一一对应时称之为完全二叉树。

从存贮结构上来讲,规定完全二叉树按中左右的顺序存入数据一维组。完全二叉树有很多特性,这里要求我们掌握的两个特性:

特性1.最后一个非叶结点的们置:n/2-1

n表示结点数,我们最简单的核实下数据,当只有1个结点时,n=1,那么没有非叶结点,值为-1;n=2或3时,非叶结点为0,当n=10时,最后一个非叶结点是4。也就是说,如果我们把n个元素放在数组里,0-n/2-1是非叶结点,n/2-n-1是叶结点。

特性2.非叶结点i的子结点位置:2i+1、2i+2

子结点可能是非叶结点,也可能是叶结点,可以根据是否>n/2-1来判断;2i+2可能不存在,可以根据是否2i+2>n-1来判断。

大顶堆 小顶堆

每个结点的值都大于或等于其左右孩子结点的值,称为大顶堆;或者每个结点的值都小于或等于其左右孩子结点的值,称为小顶堆。

大顶堆:arr[i] >= arr[2i+1] && arr[i] >= arr[2i+2]

小顶堆:arr[i] <= arr[2i+1] && arr[i] <= arr[2i+2]

大顶堆小顶堆实例图

二、分析

过程描述

将待排序序列构造成一个大顶堆,此时,整个序列的最大值就是堆顶的根节点。将其与末尾元素进行交换,此时末尾就为最大值。然后将剩余n-1个元素重新构造成一个堆,这样会得到n个元素的次小值。如此反复执行,便能得到一个有序序列了。

形成一次大顶堆过程演示

1)初始化数据图

2)找到第一个非叶结点6,与它的两个子节点,比较并交换图

3)找到第二个非叶结点4,与它的两个子点,比较并交换图

3.1)第二个非叶结点的左子点是非叶结点,这时已经乱掉,需要比较并交换,这个过程实际上是个循环体,循环检查非叶子结点,如果乱掉的需要重新交换过来,因为在这之前已经被调整交换过,所以只要碰到不用交换的情况,就可以退出循环。这里继续调整交换如下图

到这里,一个无序序列被构造成了一个大顶堆。

首尾交换过程演示

大顶堆形成后,把树的最后一个元素与根节点元素做交换,之后有序区加入这个最大数,无序区的总元素要减掉1。如下图

继续循环对无序区构造成大顶堆,首尾交换,直到只剩一个元素,这样就完成了排序过程,结果如下图

 

 

演示-c++自带sort 

一、实验目标

掌握c++自带sort

初始化以下一组数据如下
[{id:1,name:”伊一”,score:11},{id:2,name:”尔二”,score:22},{id:3,name:”桑三”,score:33},
{id:4,name:”司马四”,score:44},{id:5,name:”乌五”,score:55},{id:6,name:”柳六”,score:66},
{id:7,name:”戚七”,score:77},{id:8,name:”巴八”,score:88},{id:9,name:”舅舅”,score:99},
{id:10,name:”龟零”,score:0}]

要求使用c++ sort进行从大到小排序并按一人一行输出排序好的数据,信息之间用一个空格格开。

sort使用

在c语言中有qsort,很明显是quicksort的缩写,因此qsort就是c语言中自带的快排,而在c++中的sort呢?sort也是基于quicksort的优化,因此如果场景适合,还是鼓励使用c++的sort方法的。

引入algorithm

#include <algorithm>

sort(a,a+n,cmp)参数说明

  • a排序的起始地址,这个要根据你是从0还是从1开始计数,如上所写就是从0开始
  • a+n排序的结束地址-1,如上a+n,实际是对0-(n-1)下标的数据进行排序
    如果你从1开始计数,上面的参数值就要写成sort(a+1,a+n+1,cmp)
  • cmp可选参数,默认按从小到大的递增,如果要变成递减排序或是复杂的结构体里的某个元素进行排序,那就自定义cmp函数
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值