1 如何评价、分析一个排序算法?

很多语言、数据库都已经封装了关于排序算法的实现代码。所以我们学习排序算法目的更多的不是为了去实现这些代码,而是灵活的应用这些算法和解决更为复杂的问题,所以更重要的是学会如何评价、分析一个排序算法并在合适的场景下正确使用。

分析一个排序算法,主要从以下3个方面入手:

1.1 排序算法的执行效率

1)最好情况、最坏情况和平均情况时间复杂度

待排序数据的有序度对排序算法的执行效率有很大影响,所以分析时要区分这三种时间复杂度。除了时间复杂度分析,还要知道最好、最坏情况复杂度对应的要排序的原始数据是什么样的。

2)时间复杂度的系数、常数和低阶

时间复杂度反映的是算法执行时间随数据规模变大的一个增长趋势,平时分析时往往忽略系数、常数和低阶。但如果我们排序的数据规模很小,在对同一阶时间复杂度的排序算法比较时,就要把它们考虑进来。

3)比较次数和交换(移动)次数

内排序算法中,主要进行比较和交换(移动)两项操作,所以高效的内排序算法应该具有尽可能少的比较次数和交换次数。

1.2 排序算法的内存消耗

也就是分析算法的空间复杂度。这里还有一个概念—原地排序,指的是空间复杂度为O(1)的排序算法。

1.3 稳定性

如果待排序的序列中存在值相等的元素,经过排序之后,相等元素之间原有的先后顺序不变,那么这种排序算法叫做稳定的排序算法;如果前后顺序发生变化,那么对应的排序算法就是不稳定的排序算法。

在实际的排序应用中,往往不是对单一关键值进行排序,而是要求排序结果对所有的关键值都有序。所以,稳定的排序算法往往适用场景更广。

2 三种时间复杂度为O(n2)的排序算法

2.1 冒泡排序

2.1.1 原理

两两比较相邻元素是否有序,如果逆序则交换两个元素,直到没有逆序的数据元素为止。每次冒泡都会至少让一个元素移动到它应该在的位置。

2.1.2 实现

void BubbleSort(int *pData, int n)    //冒泡排序
{
int temp = ;
bool orderlyFlag = false; //序列是否有序标志 for (int i = ; i < n && !orderlyFlag; ++i) //执行n次冒泡
{
orderlyFlag = true;
for (int j = ; j < n - - i; ++j) //注意循环终止条件
{
if (pData[j] > pData[j + ]) //逆序
{
orderlyFlag = false;
temp = pData[j];
pData[j] = pData[j + ];
pData[j + ] = temp;
}
}
}
}

测试结果

2.1.3 算法分析

1)时间复杂度

最好情况时间复杂度:当待排序列已有序时,只需一次冒泡即可。时间复杂度为O(n);

最坏情况时间复杂度:当待排序列完全逆序时,需要n次冒泡。时间复杂度为O(n2);

平均情况时间复杂度:当待排序列完全逆序时,逆序度为n * (n - 1) / 2。只有当交换逆序对时才会才会使得有序,取中间逆序度n * (n - 1) / 4,那么就要进行n * (n - 1) /4次交换,而比较的次数大于交换次数,所以平均情况时间复杂度为O(n2)。

2)空间复杂度

只借助了一个临时变量temp,所以空间复杂度为O(1)。

3)稳定性

该算法中只有交换操作会改变数据元素的顺序,只要我们在数据元素值相等时不交换数据元素,那么算法就是稳定的。

4)比较和交换的次数

交换操作的执行次数与逆序度相等,比较操作的执行次数大于等于逆序度小于等于n * (n - 1) / 2。

2.2 插入排序

2.2.1 原理

将待排序序列分为已排序区间和未排序区间,开始时已排序区间只有一个数据元素也就是序列的第一个元素,将未排序区间中的数据元素插入已排序区间中同时保持已排序区间的有序,直到未排序区间没有数据元素。

2.2.2 实现

void InsertSort(int *pData, int n)    //插入排序
{
int temp = , i, j; for (i = ; i < n; ++i) //未排序区间
{
if (pData[i] < pData[i - ]) //逆序
{
temp = pData[i];
for (j = i - ; pData[j] > temp; --j) //搬移数据元素
pData[j + ] = pData[j];
pData[j + ] = temp; //插入数据
}
}
}

测试结果:

2.2.3 算法分析

1)时间复杂度

最好情况时间复杂度:当待排序列已有序时,只需遍历一次即可完成排序。时间复杂度为O(n);

最坏情况时间复杂度:当待排序列完全逆序时,需要进行n-1次数据搬移和插入操作。时间复杂度为O(n2);

平均情况时间复杂度:与冒泡法的分析过程一样,平均情况时间复杂度为O(n2)。

2)空间复杂度

排序过程中只需要一个临时变量存储待插入数据,空间复杂度为O(1)。

3)稳定性

插入排序过程中只有插入操作会改变数据元素的相对位置,只要元素大小比较时相等情况下不进行插入操作,插入排序算法就是稳定的。

4)比较操作和数据搬移操作执行次数

数据搬移操作执行次数和逆序度相同。比较操作次数大于等于逆序度,小于等于n * (n - 1) / 2。

2.3 选择排序

2.3.1 原理

选择排序的原理类似于插入排序都分为已排序区间和未排序区间,选择排序的已排序区间初始大小为零,每次从未排序区间取关键值最大(或最小)的数据元素放在已排序区间的后一个位置,直到未排序区间没有数据元素则完成排序。

2.3.2 实现

void SelectSort(int *pData, int n)    //选择排序
{
int i, j, min, temp; for (i = ; i < n; ++i) //未排序区间
{
min = i; //最小值下标
for (j = i + ; j < n; ++j)
{
if (pData[min] > pData[j]) //逆序
min = j; //保存当前较小值下标
}
if (i != min) //如果不是最小值,交换元素
{
temp = pData[i];
pData[i] = pData[min];
pData[min] = temp;
}
}
}

测试结果:

2.3.3 算法分析

1)时间复杂度

不管是已有序序列还是完全逆序序列,都要进行n次遍历无序区间操作,时间复杂度为O(n2)。

2)空间复杂度

排序过程中只需要保存每次遍历无序区间最小值的下标和第i个元素的数值,所以空间复杂度为O(1)。

3)稳定性

选择排序算法中改变数据元素相对位置的操作为交换操作,当第i次中第i个数据元素不为当前无序区间最小值时则和最小值交换数据元素。当有重复元素时,就有可能发生相对位置改变。例如5,3,4,5,1第一次选择操作后为1,3,4,5,5,此时两个5的相对位置已经改变。所以选择排序算法不是稳定的。

4)比较操作和交换操作的执行次数

比较操作执行次数为n * (n - 1) / 2,交换操作执行次数小于等于n-1。

2.4 三种算法之间的比较

1)一般待排序列长度n较小时,我们选择这三种排序算法;

2)当排序要求稳定时,一般选择插入排序,因为相同的情况下,移动数据比交换数据执行速度快;

3)当数据元素信息量较大时,可以考虑用选择排序,因为它交换操作执行次数最少。

该篇博客是自己的学习博客,水平有限,如果有哪里理解不对的地方,希望大家可以指正!

排序—时间复杂度为O(n2)的三种排序算法的更多相关文章

  1. JavaScript新手学习笔记3——三种排序方式(冒泡排序、插入排序、快速排序)

    每种编程语言学到数组的时候,都会讲到排序算法,当时学C语言的时候,卡在排序算法.今天来总结一下javascript中如何实现三种排序算法. 1.冒泡排序(默认升序排列哦) 原理: 冒泡排序的原理,顾名 ...

  2. java数组中的三种排序方法中的冒泡排序方法

    我记得我大学学java的时候,怎么就是搞不明白这三种排序方法,也一直不会,现在我有发过来学习下这三种方法并记录下来. 首先说说冒泡排序方法:冒泡排序方法就是把数组中的每一个元素进行比较,如果第i个元素 ...

  3. FIFO、LRU、OPT这三种置换算法的缺页次数

    考虑下述页面走向: 1,2,3,4,2,1,5,6,2,1,2,3,7,6,3,2,1,2,3,6 当内存块数量分别为3时,试问FIFO.LRU.OPT这三种置换算法的缺页次数各是多少? 答:缺页定义 ...

  4. 基于C#程序设计语言的三种组合算法

    目录 基于C#程序设计语言的三种组合算法 1. 总体思路 1.1 前言 1.2 算法思路 1.3 算法需要注意的点 2. 三种组合算法 2.1 普通组合算法 2.2 与自身进行组合的组合算法 2.3 ...

  5. 网络中,FIFO、LRU、OPT这三种置换算法的缺页次数

    FIFO.LRU.OPT这三种置换算法的缺页次数 转载  由于要考计算机四级网络,这里遇到了问题,就搜了一些资料来解疑. 考虑下述页面走向: 1,2,3,4,2,1,5,6,2,1,2,3,7,6,3 ...

  6. 三种Hash算法对比以及秒传原理.

    三种Hash算法对比以及秒传原理 CRC (32/64)   MD5  Sha1 分5个点来说 1.校验值长度 2.校验值类别 3.安全级别 4.应用场景 1).校验值长度 CRC(32/64) 分别 ...

  7. 创建B树,动态添加节点,并使用三种遍历算法对树进行遍历

    ks17:algorithm apple$ cat btree_test.c ///********************************************************** ...

  8. 手写面试编程题- 数组去重 深拷贝 获取文本节点 设置奇数偶数背景色 JS中检测变量为string类型的方法 第6题闭包 将两个数组合并为一个数组 怎样添加、移除、移动、复制、创建和查找节点? 继承 对一个数组实现随机排序 让元素水平 垂直居中的三种方式 通过jQuery的extend方法实现深拷贝

    第1题==>实现数组去重 通过 new Set(数组名) // var arr = [12, 12, 3, 4, 5, 4, 5, 6, 6]; // var newarr1 = new Set ...

  9. JVM三种垃圾收集算法思想及发展过程

    JVM垃圾收集算法的具体实现有很多种,本文只是介绍实现这些垃圾收集算法的三种思想和发展过程.所有的垃圾收集算法的具体实现都是遵循这三种算法思想而实现的. 1.标记-清除算法 标记-清除(Mark-Sw ...

随机推荐

  1. LVS (一) 原理

    LVS原理概述 负载均衡就是,在多个提供相同服务主机的前段,增加一个分发器,根据用户请求,然后根据某种方式或者策略,将用户请求分发到提供服务的主机上.同时负载均衡应用还应该提供对后其后端服务健康检查的 ...

  2. Python:游戏:贪吃蛇原理及代码实现

    一.游戏介绍 贪吃蛇是个非常简单的游戏,适合练手.先来看一下我的游戏截图: 玩法介绍:回车键:开始游戏空格键:暂停 / 继续↑↓←→方向键 或 WSAD 键:控制移动方向. 食物分红.绿.蓝三种,分别 ...

  3. DS控件库 DS按钮多种样式

    在DS控件库(DSControls)中,DS按钮的功能非常多,通过设置不同的属性值来使按钮呈现不同的效果.DS按钮的常用属性如下: 使用不同的属性调出不同的外观样式示例

  4. C# 设置程序启动项

    托盘图标设置 新建一个NotifyIcon,会在托盘处显示一个图标. NotifyIcon.Icon可以直接设置一个ico图片,也可以延用原有程序的图标. notifyIcon.Icon = Syst ...

  5. 关于jQuery中的选择器

    1:选择器的作用 获取网页的上面的标签元素等等,然后对他进行一些列的操作(添加样式,添加行为...) 2:选择器有哪些 基本选择器,层次选择器,过滤选择器,表单选择器 一:基本选择器 基本选择器是jq ...

  6. thinkphp5路由心得

    路由的作用:1. 简化URL地址,方便大家记忆2. 有利于搜索引擎的优化,比如可以被百度的爬虫抓取到 优化URl1. 前后端分离修改入口文件,在public下新建admin.php文件,将下面的代码添 ...

  7. 基于mapnik做切片服务器的几点总结

    文章版权由作者李晓晖和博客园共有,若转载请于明显处标明出处:http://www.cnblogs.com/naaoveGIS/ 1.背景 在地图服务器的整体方案中,移动端采用矢量切片,样式解析采用th ...

  8. AsyncTask机制

    AsyncTask可以让我们更容易地使用UI线程.它允许执行后台操作,并把结果发布到UI线程上,而不需要操作线程或Handler.AsyncTask被设计成一个和Thread.Handler相关的一个 ...

  9. Python3 isdigit()方法

    描述 Python isdigit() 方法检测字符串是否只由数字组成. 语法 isdigit()方法语法: str.isdigit() 参数 无. 返回值 如果字符串只包含数字则返回 True 否则 ...

  10. 《SQL CookBook 》笔记-第一章-检索记录

    目录 第一章 检索记录 1.1检索所有行和列 1.2筛选行 1.3查找满足多个查询条件的行 1.4筛选列 1.5创建列的别名 1.6 在where子句中引用别名列 1.7 串联多列的值 1.8 在se ...