在线编程——排序算法总结

找实习,阿里一面遇到手写快排,写出来感觉没错(VS2013能通过),但在阿里的测试平台上运行未通过。细思极恐,赶紧总结一波。有幸看到SteveWang的两篇博客:排序算法总结(1)与排序算法总结(2)以及基数排序、计数排序与桶排序,总结的相当详细,我这里算是重新拜读一遍,结合自己的理解写下来。

一、排序算法分类,稳定性分析,时间复杂度与空间复杂度总结表

我们通常所说的排序算法往往指的是内部排序算法,即数据记录在内存中进行排序。而外部排序是指大文件的排序,即待排序的记录存储在外存储器上,在排序过程中需要进行多次的内、外存之间的交换。

      (一)内部排序算法大体可分为两大类:

        1、比较排序,平均时间复杂度范围:O(nlogn) ~ O(n^2),主要有:冒泡排序,选择排序,插入排序,归并排序,堆排序,快速排序等。

然而,参考博客:https://blog.csdn.net/zlele0326/article/details/51281578,比较排序算法可以继续分为下面几类:

(1)插入排序:直接插入排序,二分法插入排序,希尔排序;

(2)选择排序:简单选择排序,堆排序;

(3)交换排序:冒泡排序,快速排序;

(4)归并排序;

  2、非比较排序,时间复杂度可以达到O(n),主要有:计数排序基数排序桶排序等。详见地址:戳我。

(二)稳定性分析

排序算法稳定性的简单形式化定义为:如果Ai = Aj,排序前Ai在Aj之前,排序后Ai还在Aj之前,则称这种排序算法是稳定的。通俗地讲就是保证排序前后两个相等的数的相对顺序不变。

  对于不稳定的排序算法,只要举出一个实例,即可说明它的不稳定性;而对于稳定的排序算法,必须对算法进行分析从而得到稳定的特性。需要注意的是,排序算法是否为稳定的是由具体算法决定的,不稳定的算法在某种条件下可以变为稳定的算法,而稳定的算法在某种条件下也可以变为不稳定的算法。

  例如,对于冒泡排序,原本是稳定的排序算法,如果将记录交换的条件改成A[i] >= A[i + 1],则两个相等的记录就会交换位置,从而变成不稳定的排序算法。

  其次,说一下排序算法稳定性的好处。排序算法如果是稳定的,那么从一个键上排序,然后再从另一个键上排序,前一个键排序的结果可以为后一个键排序所用。基数排序就是这样,先按低位排序,逐次按高位排序,低位排序后元素的顺序在高位也相同时是不会改变的。

(三)排序算法总结表

尽管记了很多次几个排序算法的时间复杂度与空间复杂度,日子一长又忘了,心累,各种排序的稳定性,时间复杂度、空间复杂度、稳定性总结如下图(参考:https://blog.csdn.net/foreverling/article/details/43798223):

图1

图2

网上现主要有两个版本的排序表格,如上图1,图2(来自《大话数据结构》)所示。可以看到两张图中希尔排序的时间复杂度不同,以及快速排序的空间复杂度不同,那究竟是什么?

(1)希尔排序时间复杂度分析:(摘自百度百科),希尔排序算法是直接插入排序算法的一种改进,减少了其复制的次数,速度要快很多。希尔排序的时间复杂度与增量序列的选取有关,例如希尔增量时间复杂度为O(n^2),而Hibbard增量的希尔排序时间复杂度为O(n^3/2),希尔排序时间复杂度的下界是n*log2n。希尔排序没有快速排序算法快 O(n(logn)),因此中等大小规模表现良好,对规模非常大的数据排序不是最优选择,但是比O( n^2 )复杂度的算法快得多。并且希尔排序非常容易实现,算法代码短而简单。此外,希尔算法在最坏的情况下和平均情况下执行效率相差不是很多,与此同时快速排序在最坏的情况下执行的效率会非常差。专家们提倡,几乎任何排序工作在开始时都可以用希尔排序,若在实际使用中证明它不够快,再改成快速排序这样更高级的排序算法. 希尔排序算法的性能与所选取的分组长度序列有很大关系。只对特定的待排序记录序列,可以准确地估算关键词的比较次数和对象移动次数。想要弄清关键词比较次数和记录移动次数与增量选择之间的关系,并给出完整的数学分析,至今仍然是数学难题。

(2)快速排序空间复杂度分析:详见博客(https://blog.csdn.net/yuzhihui_no1/article/details/44198701),首先就地快速排序使用的空间是O(1)的,也就是个常数级;而真正消耗空间的就是递归调用了,因为每次递归就要保持一些数据,即快速排序的空间复杂度为:O(logn) ~O( n ) 。

最优的情况下空间复杂度为:O(logn)  ;每一次都平分数组的情况

最差的情况下空间复杂度为:O( n )      ;退化为冒泡排序的情况

二、比较排序

1、直接插入排序

插入排序是一种简单直观的排序算法。它的工作原理非常类似于我们抓扑克牌

对于未排序数据(右手抓到的牌),在已排序序列(左手已经排好序的手牌)中从后向前扫描,找到相应位置并插入。插入排序在实现上,通常采用in-place排序(即只需用到O(1)的额外空间的排序),因而在从后向前扫描过程中,需要反复把已排序元素逐步向后挪位,为最新元素提供插入空间。

  具体算法描述如下:

(1) 从第一个元素开始,该元素可以认为已经被排序;

(2) 取出下一个元素,在已经排序的元素序列中从后向前扫描;

(3) 如果该元素(已排序)大于新元素,将该元素移到下一位置;

(4) 重复步骤3,直到找到已排序的元素小于或者等于新元素的位置;

(5) 将新元素插入到该位置后;

(6) 重复步骤2~5;

C++代码为:

#include <iostream>
#include <vector>
using namespace std;// 分类 ------------- 内部比较排序
// 数据结构 ---------- 向量
// 最差时间复杂度 ---- 最坏情况为输入序列是降序排列的,此时时间复杂度O(n^2)
// 最优时间复杂度 ---- 最好情况为输入序列是升序排列的,此时时间复杂度O(n)
// 平均时间复杂度 ---- O(n^2)
// 所需辅助空间 ------ O(1)
// 稳定性 ------------ 稳定vector<int> InsertionSort(vector<int> A)
{for (int i = 1; i < A.size(); i++)         // 类似抓扑克牌排序{int get = A[i];                 // 右手抓到一张扑克牌int j = i - 1;                  // 拿在左手上的牌总是排序好的while (j >= 0 && A[j] > get)    // 将抓到的牌与手牌从右向左进行比较{A[j + 1] = A[j];            // 如果该手牌比抓到的牌大,就将其右移j--;}A[j + 1] = get; // 直到该手牌比抓到的牌小(或二者相等),将抓到的牌插入到该手牌右边(相等元素的相对次序未变,所以插入排序是稳定的)}return A;
}int main()
{//输入不定长的待排序列vector<int> vec;int temp;char t;cout << "输入待排序列为:";do{cin >> temp;vec.push_back(temp);} while ((t=cin.get())!='\n');/int n = vec.size();//待排序列的长度 ,若输入为数组A,则int n = sizeof(A) / sizeof(int);cout << "排序前的结果为:";for (int i = 0; i < n; i++){cout << vec[i] << " ";}cout << endl;/vector<int> res = InsertionSort(vec); //调用直接插入排序算法cout << "排序后的结果为:";for (int i = 0; i < n; i++){cout << res[i] << " ";}cout << endl;system("pause");return 0;
}

运行结果:

插入排序过程:动态图来自http://www.cnblogs.com/eniac12/p/5329396.html#3972917

        插入排序不适合对于数据量比较大的排序应用。但是,如果需要排序的数据量很小,比如量级小于千,那么插入排序还是一个不错的选择。 插入排序在工业级库中也有着广泛的应用,在STL的sort算法和stdlib的qsort算法中,都将插入排序作为快速排序的补充,用于少量元素的排序(通常为8个或以下)。

python代码如下:

def InsertSort(myList):length = len(myList)for i in range(1,length):# 设置当前值前一个元素的标识j = i - 1#如果当前值小于前一个元素,则将当前值作为一个临时变量存储,将前一个元素后移一位if(myList[i] < myList[j]):temp = myList[i]myList[i] = myList[j]# 继续往前寻找,如果有比临时变量大的数字,则后移一位,直到找到比临时变量小的元素或者达到列表第一个元素j = j-1while j>=0 and myList[j] > temp:myList[j+1] = myList[j]j = j-1# 将临时变量赋值给合适位置myList[j+1] = tempif __name__=="__main__":arr = [int(i) for i in input().split()] # 输入待排序列InsertSort(arr)                         # 插入排序print(" ".join(str(i) for i in arr))    # 输出排序结果

运行结果:

2、快速排序算法

    快速排序是由东尼·霍尔所发展的一种排序算法。在平均状况下,排序n个元素要O(nlogn)次比较。在最坏状况下则需要O(n^2)次比较,但这种状况并不常见。事实上,快速排序通常明显比其他O(nlogn)算法更快,因为它的内部循环可以在大部分的架构上很有效率地被实现出来。

  快速排序使用分治策略把一个序列分为两个子序列。步骤为:

(1)从序列中挑出一个元素,作为"基准"(pivot).

(2)把所有比基准值小的元素放在基准前面,所有比基准值大的元素放在基准的后面(相同的数可以到任一边),这个称为分区(partition)操作。

(3)对每个分区递归地进行步骤1~2,递归的结束条件是序列的大小是0或1,这时整体已经被排好序了。

C++代码:

#include <iostream>
#include <vector>
#include <exception>
using namespace std;
// 分类 ------------ 内部比较排序
// 数据结构 --------- 向量
// 最差时间复杂度 ---- 每次选取的基准都是最大(或最小)的元素,导致每次只划分出了一个分区,需要进行n-1次划分才能结束递归,时间复杂度为O(n^2)
// 最优时间复杂度 ---- 每次选取的基准都是中位数,这样每次都均匀的划分出两个分区,只需要logn次划分就能结束递归,时间复杂度为O(nlogn)
// 平均时间复杂度 ---- O(nlogn)
// 所需辅助空间 ------ 主要是递归造成的栈空间的使用(用来保存left和right等局部变量),取决于递归树的深度,一般为O(logn),最差为O(n)
// 稳定性 ---------- 不稳定void Swap(vector<int>& data, int i, int j)
{int temp = data[i];data[i] = data[j];data[j] = temp;
}int RandomInRange(int min, int max)   //在min~max中随机生成一个值
{int random = rand() % (max - min + 1) + min;return random;
}int Partition(vector<int>& data, int start, int end) //Partition函数
{if (data.size() <= 0 || start < 0 || end >= data.size()) throw new std::exception("Invalid Parameters");//int index = RandomInRange(start, end); //可以不调用RandomInRange函数//Swap(data, index, end);int small = start - 1;for (int index = start; index < end; ++index){if (data[index] < data[end]){++small;if (small != index)Swap(data, index, small);}}++small;Swap(data, small, end);return small;
}//递归快速排序
void QuickSort(vector<int>& data, int start, int end)
{if (start >= end)return;int index = Partition(data, start, end); //基准的索引QuickSort(data, start, index - 1);QuickSort(data, index + 1, end);
}int main()
{//输入不定长的待排序列vector<int> vec;int temp;char t;cout << "输入待排序列为:";do{cin >> temp;vec.push_back(temp);} while ((t = cin.get()) != '\n');/int n = vec.size();//待排序列的长度 ,若输入为数组A,则int n = sizeof(A) / sizeof(int);cout << "排序前的结果为:";for (int i = 0; i < n; i++){cout << vec[i] << " ";}cout << endl;/QuickSort(vec, 0, n - 1); //调用快速排序算法/cout << "排序后的结果为:";for (int i = 0; i < n; i++){cout << vec[i] << " ";}cout << endl;system("pause");return 0;
}

运行结果:

快速排序过程展示:

    快速排序是不稳定的排序算法,不稳定发生在基准元素与A[small+1]交换的时刻。

  比如序列:{ 1, 3, 4, 2, 8, 9, 8, 7, 5 },基准元素是5,一次划分操作后5要和第一个8进行交换,从而改变了两个元素8的相对次序。

  Java系统提供的Arrays.sort函数。对于基础类型,底层使用快速排序。对于非基础类型,底层使用归并排序。请问是为什么?

答:这是考虑到排序算法的稳定性。对于基础类型,相同值是无差别的,排序前后相同值的相对位置并不重要,所以选择更为高效的快速排序,尽管它是不稳定的排序算法;而对于非基础类型,排序前后相等实例的相对位置不宜改变,所以选择稳定的归并排序。 

       需要强调的是,很多公司的面试官喜欢在面试环节要求应聘者写出快速排序的代码,剑指offer上一个比较好的方法,保存了数组的两个位置index向前遍历数组,small用于保存交换的小于轴值的数,找到一个前移一步,即上面的函数:

void Swap(vector<int> &data, int i, int j)
{int temp = data[i];data[i] = data[j];data[j] = temp;
}int RandomInRange(int min, int max)   //在min~max中随机生成一个值
{int random = rand() % (max - min + 1) + min;return random;
}int Partition(vector<int> &data, int start, int end) //Partition函数
{if (data.size() <= 0 || start < 0 || end >= data.size()) throw new std::exception("Invalid Parameters");//int index = RandomInRange(start, end);//Swap(data, index, end);int small = start - 1;for (int index = start; index < end; ++index){if (data[index] < data[end]){++small;if (small != index)Swap(data, index, small);}}++small;Swap(data, small, end);return small;
}//递归快速排序
void QuickSort(vector<int> &data, int start, int end)
{if (start >= end)return;int index = Partition(data, start, end); //基准的索引QuickSort(data, start, index - 1);QuickSort(data, index + 1, end);
}

现在回答,为什么在阿里笔试过程中,自己写出来的快排,感觉没错,但是运行结果不对。主要原因:

void QuickSort(vector<int>& data, int start, int end) //快速排序
int Partition(vector<int>& data, int start, int end) //Partition函数

写成了

void QuickSort(vector<int> data, int start, int end) //快速排序
int Partition(vector<int> data, int start, int end) //Partition函数

运行结果:

本质原因为:vector的传参方式理解不透彻,详见https://www.cnblogs.com/xiaoxi666/p/6843211.html。

为了在面试过程中快速写出代码,如果允许写python代码,则为:

# 快速排序
def quick_sort(array, l, r):if l < r:q = partition(array, l, r)quick_sort(array, l, q - 1)quick_sort(array, q + 1, r)def partition(array, l, r):x = array[r]i = l - 1for j in range(l, r):if array[j] <= x:i += 1array[i], array[j] = array[j], array[i]array[i + 1], array[r] = array[r], array[i + 1]return i + 1if __name__=="__main__":arr = [int(i) for i in input().split()] # 输入待排序列quick_sort(arr,0,len(arr)-1)            # 快速排序print(" ".join(str(i) for i in arr))    # 输出排序结果

在线编程——排序算法总结相关推荐

  1. 免费下载 | 超全算法题精解,一本能“在线”编程的面试宝典

    点击这里立即下载:<程序员面试宝典> 内容介绍 备战大厂怎么能少了尖兵利刃? <程序员面试宝典>正式发布,助你一臂之力!70+算法面试模拟题深度解析,涵盖 树.排序.二分查找. ...

  2. 牛客网在线编程----算法入门篇

    标题本篇博文主要是记录下自己的在线编程情况,初次练习,有的算法还待改进,大家有需要可以去牛客网上面多练练! 有需戳–>牛客网在线编程 NC65.题目描述 大家都知道斐波那契数列,现在要求输入一个 ...

  3. DayDayUp之Job:牛客网—算法工程师—剑指offer之66道在线编程(解决思路及其代码)——1~20

    DayDayUp之Job:牛客网-算法工程师-剑指offer之66道在线编程(解决思路及其代码)--01~20 目录 剑指offer--66道在线编程--01~20 1.二维数组中的查找某个targe ...

  4. DayDayUp之Job:牛客网—算法工程师—剑指offer之66道在线编程(解决思路及其代码)——41~66

    DayDayUp之Job:牛客网-算法工程师-剑指offer之66道在线编程(解决思路及其代码)--41~66 目录 剑指offer之66道在线编程--41~66 42.和为s的两个数字 43.左旋转 ...

  5. DayDayUp之Job:牛客网—算法工程师—剑指offer之66道在线编程(解决思路及其代码)——21~40

    DayDayUp之Job:牛客网-算法工程师-剑指offer之66道在线编程(解决思路及其代码)--21~41 目录 剑指offer之66道在线编程--21~41 21.栈的压入.弹出序列 22.从上 ...

  6. Java | 用Java实现选择排序算法(记录写程序全过程的编程哲学)

    最近听了马士兵老师的java算法课,感觉这不错,我很欣赏其中的编程哲学. 一.编程哲学 有简单到复杂 1.1 验证一步走一步 1.2 多打印中间结果 先局部后整体 先粗糙后精细 3.1 变量更名 3. ...

  7. 面向程序员编程——精研排序算法

    这篇文章很长,我花了好久的时间(中间公司出了bug,加班了好几天( ¯ ¨̯ ¯̥̥ ))进行整理,如有任何疑问,欢迎随时留言. 关键字:排序算法,时间复杂度,空间复杂度 排序就是研究如何将一系列数据 ...

  8. 格灵深瞳发起 AI · 爱 算法 在线编程挑战赛

    点击我爱计算机视觉标星,更快获取CVML新技术 DEEP SEA FISH 编 程 高 手 集 结 令 一 决 高 下 你热爱编程,喜欢AI么?如果你的答案是Yes,这里有一场绝不可错过的在线编程挑战 ...

  9. 【Scratch算法讲解】04-Scratch快速排序 少儿编程Scratch常见排序算法案例分析讲解

    scratch快速排序 一.案例演示 [Scratch案例演示]Scratch快速排序 Scratch快速排序算法 高阶编程 二.案例介绍 什么是选择排序呢,在讲排序之前,要先跟小朋友们讲一下算法:什 ...

最新文章

  1. 麦肯锡顾问的整体设计:从大局需要安排工作
  2. 《迷人的8051单片机》----3.4 程序
  3. python until语句_Python3 循环
  4. javascript深入理解js闭包[转]
  5. “笨方法”学习Python笔记(1)-Windows下的准备
  6. idea 的lombok安装完不生效的办法
  7. Failed to maintain projects LRU cache for dir *********
  8. 1.1 STL 概述
  9. Jekyll本地搭建开发环境以及Github部署流程
  10. OSI七层协议模型和各自的功能
  11. spring学习---IOC--基于xml--bean管理--spring创建对象--spring注入属性--其他属性注入--外部bean--内部bean
  12. mysql 5.1 到 mysql 5.2的出现的索引BTREE问题 use near 'USING BTREE
  13. 新兴IT企业特斯拉(二)——特斯拉的诞生
  14. 得力计算机1526弹音乐,得力1526计算器乐谱 | 手游网游页游攻略大全
  15. jquery form表单提交
  16. cad命令栏还原默认_将CAD恢复到默认界面的两种方法,来看看吧
  17. cwRsync-windows下的rsync工具
  18. android心率曲线绘制,巧妙绘制心率曲线图的方法实践
  19. 学校计算机硬件管理制度,学校规章制度之计算机硬件管理制度.doc
  20. 数据仓库历史数据存储-拉链表

热门文章

  1. Windows2000源代码下载 (转)
  2. 音响Dolby与DTS区别
  3. 一缕夏风涌动,掠过翠绿色的日子
  4. python用fun判断水仙花数_功能:调用函数fun判断一个三位数是否水仙花数。
  5. 苹果cms播放器添加教程
  6. 技术测评:ZStack网络性能测试
  7. input文本输入框的type类型
  8. CUIT Online Judge 最大值与最小值
  9. 淘宝开网店的详细教程
  10. java-如何在centos7中切换java版本