做者:寒小阳
html
时间:2013年9月。
出处:http://blog.csdn.net/han_xiaoyang/article/details/12163251。
声明:版权全部,转载请注明出处,谢谢。node
从这一部分开始直接切入咱们计算机互联网笔试面试中的重头戏算法了,初始的想法是找一条主线,好比数据结构或者解题思路方法,将博主见过作过整理过的算法题逐个分析一遍(博主当年本身学算法就是用这种比较笨的刷题学的,囧),不过又想了想,算法这东西,博主本身学的过程当中一直深感,基础仍是很是重要的,不少难题是基础类数据结构和题目的思想综合发散而来。好比说做为最基本的排序算法就种类不少,而事实上笔试面试过程当中发现掌握的程度很通常,有不少题目,包括不少算法难题,其母题或者基本思想就是基于这些经典算法的,好比说快排的partition算法,好比说归并排序中的思想,好比说桶排序中桶的思想。
这里对笔试面试最常涉及到的12种排序算法(包括插入排序、二分插入排序、希尔排序、选择排序、冒泡排序、鸡尾酒排序、快速排序、堆排序、归并排序、桶排序、计数排序和基数排序)进行了详解。每一种算法都有基本介绍、算法原理分析、图解/flash演示/视频演示、算法代码、笔试面试重点分析、笔试面试题等板块,但愿能帮助你们真正理解这些排序算法,并能使用这些算法的思想解决一些题。很少说了,下面就进入正题了。ios
插入排序(Insertion Sort)的算法描述是一种简单直观的排序算法。它的工做原理是经过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。插入排序在实现上,一般采用in-place排序(即只需用到O(1)的额外空间的排序),于是在从后向前扫描过程当中,须要反复把已排序元素逐步向后挪位,为最新元素提供插入空间。web
通常来讲,插入排序都采用in-place在数组上实现。具体算法描述以下:面试
一、从第一个元素开始,该元素能够认为已经被排序算法
二、取出下一个元素,在已经排序的元素序列中从后向前扫描shell
三、若是该元素(已排序)大于新元素,将该元素移到下一位置数组
四、重复步骤3,直到找到已排序的元素小于或者等于新元素的位置数据结构
五、将新元素插入到该位置后架构
六、重复步骤2~5
若是目标是把n个元素的序列升序排列,那么采用插入排序存在最好状况和最坏状况。最好状况就是,序列已是升序排列了,在这种状况下,须要进行的比较操做需(n-1)次便可。最坏状况就是,序列是降序排列,那么此时须要进行的比较共有n(n-1)/2次。插入排序的赋值操做是比较操做的次数减去(n-1)次。平均来讲插入排序算法复杂度为O(n^2)。于是,插入排序不适合对于数据量比较大的排序应用。可是,若是须要排序的数据量很小,例如,量级小于千,那么插入排序仍是一个不错的选择。 插入排序在工业级库中也有着普遍的应用,在STL的sort算法和stdlib的qsort算法中,都将插入排序做为快速排序的补充,用于少许元素的排序(一般为8个或如下)。
http://v.youku.com/v_show/id_XMjU4NTY5MzEy.html
把插入排序放在第一个的缘由是由于其出现的频度不高,尤为是这里提到的直接排序算法,基本在笔试的选择填空问时间空间复杂度的时候才可能出现。毕竟排序速度比较慢,所以算法大题中考察的次数比较比较少。
请写出链表的插入排序程序
下列排序算法中最坏复杂度不是n(n-1)/2的是 D
A.快速排序 B.冒泡排序 C.直接插入排序 D.堆排序
二分(折半)插入(Binary insert sort)排序是一种在直接插入排序算法上进行小改动的排序算法。其与直接排序算法最大的区别在于查找插入位置时使用的是二分查找的方式,在速度上有必定提高。
通常来讲,插入排序都采用in-place在数组上实现。具体算法描述以下:
一、从第一个元素开始,该元素能够认为已经被排序
二、取出下一个元素,在已经排序的元素序列中二分查找到第一个比它大的数的位置
三、将新元素插入到该位置后
四、重复上述两步
1)稳定
2)空间代价:O(1)
3)时间代价:插入每一个记录须要O(log i)比较,最多移动i+1次,最少2次。最佳状况O(n log n),最差和平均状况O(n^2)。
二分插入排序是一种稳定的排序。当n较大时,总排序码比较次数比直接插入排序的最差状况好得多,但比最好状况要差,所元素初始序列已经按排序码接近有序时,直接插入排序比二分插入排序比较次数少。二分插入排序元素移动次数与直接插入排序相同,依赖于元素初始序列。
http://v.youku.com/v_show/id_XMTA1MTkwMTEy.html
这个排序算法在笔试面试中出现的频度也不高,但毕竟是直接排序算法的一个小改进算法,同时二分查找又是很好的思想,有可能会在面试的时候提到,算法不难,留心一下就会了。
下面的排序算法中,初始数据集的排列顺序对算法的性能无影响的是(B)
A、二分插入排序 B、堆排序 C、冒泡排序 D、快速排序
写出下列算法的时间复杂度。
(1)冒泡排序;(2)选择排序;(3)插入排序;(4)二分插入排序;(5)快速排序;(6)堆排序;(7)归并排序;
希尔排序,也称递减增量排序算法,因DL.Shell于1959年提出而得名,是插入排序的一种高速而稳定的改进版本。
一、先取一个小于n的整数d1做为第一个增量,把文件的所有记录分红d1个组。
二、全部距离为d1的倍数的记录放在同一个组中,在各组内进行直接插入排序。
三、取第二个增量d2<d1重复上述的分组和排序,
四、直至所取的增量dt=1(dt<dt-l<…<d2<d1),即全部记录放在同一组中进行直接插入排序为止。
希尔排序的时间复杂度与增量序列的选取有关,例如希尔增量时间复杂度为O(n^2),而Hibbard增量的希尔排序的时间复杂度为O(N^(5/4)),可是现今仍然没有人能找出希尔排序的精确下界。
http://ds.fzu.edu.cn/fine/resources/FlashContent.asp?id=92
http://v.youku.com/v_show/id_XMjU4NTcwMDIw.html
事实上希尔排序算法在笔试面试中出现的频度也不比直接插入排序高,但它的时间复杂度并非一个定值,因此偶尔会被面试官问到选择的步长和时间复杂度的关系,要稍微有点了解吧。算法大题中使用该方法或者其思想的题也很少。
写出希尔排序算法程序,并说明最坏的状况下须要进行多少次的比较和交换。
程序略,须要O(n^2)次的比较
设要将序列(Q, H, C, Y, P, A, M, S, R, D, F, X)中的关键码按字母序的升序从新排列,则:
冒泡排序一趟扫描的结果是 H, C, Q, P, A, M, S, R, D, F, X ,Y ;
初始步长为4的希尔(shell)排序一趟的结果是 P, A, C, S, Q, D, F, X , R, H,M, Y ;
二路归并排序一趟扫描的结果是 H, Q, C, Y,A, P, M, S, D, R, F, X ;
快速排序一趟扫描的结果是 F, H, C, D, P, A, M, Q, R, S, Y,X ;
堆排序初始建堆的结果是 A, D, C, R, F, Q, M, S, Y,P, H, X 。
选择排序(Selection sort)是一种简单直观的排序算法。它的工做原理以下。首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,而后,再从剩余未排序元素中继续寻找最小(大)元素,而后放到已排序序列的末尾。以此类推,直到全部元素均排序完毕。
n个记录的文件的直接选择排序可通过n-1趟直接选择排序获得有序结果:
一、初始状态:无序区为R[1..n],有序区为空。
二、第i趟排序(i=1,2,3...n-1)
第i趟排序开始时,当前有序区和无序区分别为R[1..i-1]和R(i..n)。该趟排序从当前无序区中选出关键字最小的记录 R[k],将它与无序区的第1个记录R交换,使R[1..i]和R分别变为记录个数增长1个的新有序区和记录个数减小1个的新无序区。
三、前n-1趟结束,数组有序化了
选择排序的交换操做介于0和(n-1)次之间。选择排序的比较操做为n(n-1)/2次之间。选择排序的赋值操做介于0和3(n-1)次之间。比较次数O(n^2),比较次数与关键字的初始状态无关,总的比较次数N=(n-1)+(n-2)+...+1=n*(n-1)/2。 交换次数O(n),最好状况是,已经有序,交换0次;最坏状况是,逆序,交换n-1次。 交换次数比冒泡排序少多了,因为交换所需CPU时间比比较所需的CPU时间多,n值较小时,选择排序比冒泡排序快。
最差时间复杂度 |
О(n²) |
最优时间复杂度 |
О(n²) |
平均时间复杂度 |
О(n²) |
最差空间复杂度 |
О(n) total, O(1) |
http://ds.fzu.edu.cn/fine/resources/FlashContent.asp?id=85
http://v.youku.com/v_show/id_XMjU4NTY5NTcy.html
就博主看过的笔试面试题而言,选择算法也大多出如今选择填空中,要熟悉其时间和空间复杂度,最好最坏的状况分别是什么,以及在那种状况下,每一轮的比较次数等。
在插入和选择排序中,若初始数据基本正序,则选用 插入排序(到尾部) ;若初始数据基本反序,则选用 选择排序 。
下述几种排序方法中,平均查找长度(ASL)最小的是
A. 插入排序 B.快速排序 C. 归并排序 D. 选择排序
冒泡排序是一种简单的排序算法。它重复地走访过要排序的数列,一次比较两个元素,若是他们的顺序错误就把他们交换过来。走访数列的工做是重复地进行直到没有再须要交换,也就是说该数列已经排序完成。这个算法的名字由来是由于越小的元素会经由交换慢慢“浮”到数列的顶端。
一、比较相邻的元素。若是第一个比第二个大,就交换他们两个。
二、对每一对相邻元素做一样的工做,从开始第一对到结尾的最后一对。在这一点,最后的元素应该会是最大的数。
三、针对全部的元素重复以上的步骤,除了最后一个。
四、持续每次对愈来愈少的元素重复上面的步骤,直到没有任何一对数字须要比较。
冒泡排序是与插入排序拥有相等的执行时间,可是两种法在须要的交换次数却很大地不一样。在最坏的状况,冒泡排序须要O(n^2)次交换,而插入排序只要最多O(n)交换。冒泡排序的实现(相似下面)一般会对已经排序好的数列拙劣地执行(O(n^2)),而插入排序在这个例子只须要O(n)个运算。所以不少现代的算法教科书避免使用冒泡排序,而用插入排序取代之。冒泡排序若是能在内部循环第一次执行时,使用一个旗标来表示有无须要交换的可能,也有可能把最好的复杂度下降到O(n)。在这个状况,在已经排序好的数列就无交换的须要。若在每次走访数列时,把走访顺序和比较大小反过来,也能够稍微地改进效率。有时候称为往返排序,由于算法会从数列的一端到另外一端之间穿梭往返。
最差时间复杂度 |
O(n^2) |
最优时间复杂度 |
O(n) |
平均时间复杂度 |
O(n^2) |
最差空间复杂度 |
总共O(n),须要辅助空间O(1) |
http://student.zjzk.cn/course_ware/data_structure/web/flashhtml/maopaopaixu.htm
http://v.youku.com/v_show/id_XMzMyOTAyMzQ0.html
通常咱们学到的第一个排序算法就是冒泡排序,不得不说,这个还真是一个很常见的考点,平均时间空间复杂度,最好最坏状况下的时间空间复杂度,在不一样状况下每一趟的比较次数,以及加标志位减小比较次数等,都是须要注意的地方。
对于整数序列100,99,98,…3,2,1,若是将它彻底倒过来,分别用冒泡排序,它们的比较次数和交换次数各是多少?
答:冒泡排序的比较和交换次数将最大,都是1+2+…+n-1=n(n-1)/2=50×99=4545次。
把一个字符串的大写字母放到字符串的后面,各个字符的相对位置不变,不能申请额外的空间。
事实上,这道题放到冒泡排序这里不知道是否是特别合适,只是有一种解法是相似冒泡的思想,以下解法一
每次遇到大写字母就日后冒,最后结果即为所求
步骤以下
1、两个指针p1和p2,从后往前扫描
2、p1遇到一个小写字母时停下, p2遇到大写字母时停下,二者所指向的char交换
3、p1, p2同时往前一格
代码以下:
鸡尾酒排序等因而冒泡排序的轻微变形。不一样的地方在于从低到高而后从高到低,而冒泡排序则仅从低到高去比较序列里的每一个元素。他能够获得比冒泡排序稍微好一点的效能,缘由是冒泡排序只从一个方向进行比对(由低到高),每次循环只移动一个项目。
一、依次比较相邻的两个数,将小数放在前面,大数放在后面;
二、第一趟可获得:将最大数放到最后一位。
三、第二趟可获得:将第二大的数放到倒数第二位。
四、如此下去,重复以上过程,直至最终完成排序。
鸡尾酒排序最糟或是平均所花费的次数都是O(n^2),但若是序列在一开始已经大部分排序过的话,会接近O(n)。
最差时间复杂度 |
O(n^2) |
最优时间复杂度 |
O(n) |
平均时间复杂度 |
O(n^2) |
参见http://zh.wikipedia.org/zh-cn/%E9%B8%A1%E5%B0%BE%E9%85%92%E6%8E%92%E5%BA%8F右侧flash动画
鸡尾酒排序在博主印象中出现的频度也不高,用到它的算法题大题不多,选择填空出现的话多以双向冒泡排序的名称出现,注意注意时间空间复杂度,理解理解算法应该问题就不大了。
考点基本相似冒泡排序,请参考上一节
恩,重头戏开始了,快速排序是各类笔试面试最爱考的排序算法之一,且排序思想在不少算法题里面被普遍使用。是须要重点掌握的排序算法。
快速排序是由东尼·霍尔所发展的一种排序算法。其基本思想是基本思想是,经过一趟排序将待排记录分隔成独立的两部分,其中一部分记录的关键字均比另外一部分的关键字小,则可分别对这两部分记录继续进行排序,以达到整个序列有序。
快速排序使用分治法来把一个串(list)分为两个子串行(sub-lists)。
步骤为:
一、从数列中挑出一个元素,称为 "基准"(pivot),
二、从新排序数列,全部元素比基准值小的摆放在基准前面,全部元素比基准值大的摆在基准的后面(相同的数能够到任一边)。在这个分区退出以后,该基准就处于数列的中间位置。这个称为分区(partition)操做。
三、递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序。
递归的最底部情形,是数列的大小是零或一,也就是永远都已经被排序好了。虽然一直递归下去,可是这个算法总会退出,由于在每次的迭代(iteration)中,它至少会把一个元素摆到它最后的位置去。
算法伪代码描述:
function quicksort(q)
var list less, pivotList, greater
if length(q) ≤ 1 {
return q
} else {
select a pivot value pivot from q
for each x in q except the pivot element
if x < pivot then add x to less
if x ≥ pivot then add x to greater
add pivot to pivotList
return concatenate(quicksort(less), pivotList, quicksort(greater))
}
在平均情况下,排序 n 个项目要Ο(n log n)次比较。在最坏情况下则须要Ο(n^2)次比较,但这种情况并不常见。事实上,快速排序一般明显比其余Ο(n log n) 算法更快,由于它的内部循环(inner loop)能够在大部分的架构上颇有效率地被实现出来。
最差时间复杂度 |
O(n^2) |
最优时间复杂度 |
O(n log n) |
平均时间复杂度 |
O(n log n) |
最差空间复杂度 |
根据实现的方式不一样而不一样 |
快速排序会递归地进行不少轮,其中每一轮称之为快排的partition算法,即上述算法描述中的第2步,很是重要,且在各类笔试面试中用到该思想的算法题层出不穷,下图为第一轮的partition算法的一个示例。
可一步步参见http://ds.fzu.edu.cn/fine/resources/FlashContent.asp?id=86中的快排过程
http://v.youku.com/v_show/id_XMzMyODk4NTQ4.html
事实上,这个地方须要提一下的是,快排有不少种版本。例如,咱们“基准数”的选择方法不一样就有不一样的版本,但重要的是快排的思想,咱们熟练掌握一种版本,在最后的笔试面试中也够用了,我这里罗列几种最有名的版本C代码。
咱们选取数组的第一个元素做为主元,每一轮都是和第一个元素比较大小,经过交换,分红大于和小于它的先后两部分,再递归处理。
代码以下
随机选基准数的快排
彻底考察快排算法自己的题目,多出如今选择填空,基本是关于时间空间复杂度的讨论,最好最坏的情形交换次数等等。却是快排的partition算法须要特别注意!频度极高地被使用在各类算法大题中!详见下小节列举的面试小题。
这里要重点强调的是快排的partition算法,博主当年面试的时候就遇到过数道用该思路的算法题,举几道以下:
最小的k个数,输入n个整数,找出其中最下的k个数,例如输入4、5、1、6、2、7、3、8、1、2,输出最下的4个数,则输出1、1、2、2。
固然,博主也知道这题能够建大小为k的大顶堆,而后用堆的方法解决。
可是这个题目可也以仿照快速排序,运用partition函数进行求解,不过咱们完整的快速排序分割后要递归地对先后两段继续进行分割,而这里咱们须要作的是断定分割的位置,而后再肯定对前段仍是后段进行分割,因此只对单侧分割便可。代码以下:
判断数组中出现超过一半的数字
固然,这道题不少人都见过,并且最通用的一种解法是数对对消的思路。这里只是再给你们提供一种思路,快排partition的方法在不少地方都能使用,好比这题。咱们也能够选择合适的断定条件进行递归。代码以下:
有一个由大小写组成的字符串,如今须要对他进行修改,将其中的全部小写字母排在大写字母的前面(不要求保持原顺序)
这题可能你们都能想到的方法是:设置首尾两个指针,首指针向后移动寻找大写字母,尾指针向前移动需找小写字母,找到后都停下,交换。以后继续移动,直至相遇。这种方法在这里我就不作讨论写代码了。
可是这题也能够采用相似快排的partition。这里使用从左日后扫描的方式。字符串在调整的过程当中能够分红两个部分:已排好的小写字母部分、待调整的剩余部分。用两个指针i和j,其中i指向待调整的剩余部分的第一个元素,用j指针遍历待调整的部分。当j指向一个小写字母时,交换i和j所指的元素。向前移动i、j,直到字符串末尾。代码以下:
不得不说,堆排序太容易出现了,选择填空问答算法大题都会出现。建堆的过程,堆调整的过程,这些过程的时间复杂度,空间复杂度,以及如何应用在海量数据Top K问题中等等,都是须要重点掌握的。
堆排序(Heapsort)是指利用堆这种数据结构所设计的一种排序算法。堆积是一个近似彻底二叉树的结构,并同时知足堆积的性质:即子结点的键值或索引老是小于(或者大于)它的父节点。
咱们这里介绍几个问题,一步步推到堆排序的算法。
咱们这里提到的堆通常都指的是二叉堆,它知足二个特性:
1---父结点的键值老是大于或等于(小于或等于)任何一个子节点的键值。
2---每一个结点的左子树和右子树都是一个二叉堆(都是最大堆或最小堆)。
以下为一个最小堆(父结点的键值老是小于任何一个子节点的键值)
这是为了保持堆的特性而作的一个操做。对某一个节点为根的子树作堆调整,其实就是将该根节点进行“下沉”操做(具体是经过和子节点交换完成的),一直下沉到合适的位置,使得刚才的子树知足堆的性质。
例如对最大堆的堆调整咱们会这么作:
一、在对应的数组元素A[i], 左孩子A[LEFT(i)], 和右孩子A[RIGHT(i)]中找到最大的那一个,将其下标存储在largest中。
二、若是A[i]已经就是最大的元素,则程序直接结束。
三、不然,i的某个子结点为最大的元素,将A[largest]与A[i]交换。
四、再从交换的子节点开始,重复1,2,3步,直至叶子节点,算完成一次堆调整。
这里须要提一下的是,通常作一次堆调整的时间复杂度为log(n)。
以下为咱们对4为根节点的子树作一次堆调整的示意图,可帮咱们理解。
建堆是一个经过不断的堆调整,使得整个二叉树中的数知足堆性质的操做。在数组中的话,咱们通常从下标为n/2的数开始作堆调整,一直到下标为0的数(由于下标大于n/2的数都是叶子节点,其子树已经知足堆的性质了)。下图为其一个图示:
很明显,对叶子结点来讲,能够认为它已是一个合法的堆了即20,60, 65, 4, 49都分别是一个合法的堆。只要从A[4]=50开始向下调整就能够了。而后再取A[3]=30,A[2] = 17,A[1] = 12,A[0] = 9分别做一次向下调整操做就能够了。
堆排序是在上述3中对数组建堆的操做以后完成的。
数组储存成堆的形式以后,第一次将A[0]与A[n - 1]交换,再对A[0…n-2]从新恢复堆。第二次将A[0]与A[n-2]交换,再对A[0…n-3]从新恢复堆,重复这样的操做直到A[0]与A[1]交换。因为每次都是将最小的数据并入到后面的有序区间,故操做完成后整个数组就有序了。
以下图所示:
最差时间复杂度 |
O(n log n) |
最优时间复杂度 |
O(n log n) |
平均时间复杂度 |
O(n log n) |
最差空间复杂度 |
O(n) |
略,见上一节。
可参见http://ds.fzu.edu.cn/fine/resources/FlashContent.asp?id=88中的flash动画,帮助理解
http://v.youku.com/v_show/id_XMzQzNzAwODQ=.html
直接上代码吧,重点注意HeapAdjust,BuildHeap和HeapSort的实现。
堆排序相关的考察太多了,选择填空问答算法大题都会出现。建堆的过程,堆调整的过程,这些过程的时间复杂度,空间复杂度,须要比较交换多少次,以及如何应用在海量数据Top K问题中等等。堆又是一种很好作调整的结构,在算法题里面使用频度很高。
编写算法,从10亿个浮点数当中,选出其中最大的10000个。
典型的Top K问题,用堆是最典型的思路。建10000个数的小顶堆,而后将10亿个数依次读取,大于堆顶,则替换堆顶,作一次堆调整。结束以后,小顶堆中存放的数即为所求。代码以下(为了方便,这里直接使用了STL容器):
设计一个数据结构,其中包含两个函数,1.插入一个数字,2.得到中数。并估计时间复杂度。
使用大顶堆和小顶堆存储。
使用大顶堆存储较小的一半数字,使用小顶堆存储较大的一半数字。
插入数字时,在O(logn)时间内将该数字插入到对应的堆当中,并适当移动根节点以保持两个堆数字相等(或相差1)。
获取中数时,在O(1)时间内找到中数。
归并排序是创建在归并操做上的一种有效的排序算法。该算法是采用分治法(Divide and Conquer)的一个很是典型的应用。归并排序是一种稳定的排序方法。
将已有序的子序列合并,获得彻底有序的序列;即先使每一个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为2-路归并。
2)算法描述
归并排序具体算法描述以下(递归版本):
一、Divide: 把长度为n的输入序列分红两个长度为n/2的子序列。
二、Conquer: 对这两个子序列分别采用归并排序。
三、Combine: 将两个排序好的子序列合并成一个最终的排序序列。
归并排序的效率是比较高的,设数列长为N,将数列分开成小数列一共要logN步,每步都是一个合并有序数列的过程,时间复杂度能够记为O(N),故一共为O(N*logN)。由于归并排序每次都是在相邻的数据中进行操做,因此归并排序在O(N*logN)的几种排序方法(快速排序,归并排序,希尔排序,堆排序)也是效率比较高的。
能够参考http://ds.fzu.edu.cn/fine/resources/FlashContent.asp?id=93中的过程
http://video.sina.com.cn/v/b/80012415-1642346981.html
归并排序自己做为一种高效的排序算法,也是常会被问到的。尤为是归并排序体现的递归思路很重要,在递归的过程当中能够完成不少事情,不少算法题也是使用的这个思路,可见下面7)部分的笔试面试算法题。
题目输入一个数组,数组元素的大小在0->999.999.999的范围内,元素个数为0-500000范围。题目要求经过相邻的元素的交换,使得输入的数组变为有序,要求输出交换的次数
这题求解的其实就是一个逆序对。咱们回想一下归并排序的过程:
归并排序是用分治思想,分治模式在每一层递归上有三个步骤:
分解:将n个元素分红个含n/2个元素的子序列。
解决:用合并排序法对两个子序列递归的排序。
合并:合并两个已排序的子序列已获得排序结果。
在归并排序算法中稍做修改,就能够在n log n的时间内求逆序对。
将数组A[1...size],划分为A[1...mid] 和 A[mid+1...size].那么逆序对数的个数为 f(1, size) = f(1, mid) + f(mid+1, size) + s(1, mid, size),这里s(1, mid, size)表明左值在[1---mid]中,右值在[mid+1, size]中的逆序对数。因为两个子序列自己都已经排序,因此查找起来很是方便。
代码以下:
有10个文件,每一个文件1G,每一个文件的每一行存放的都是用户的query,每一个文件的query均可能重复。要求你按照query的频度排序。
1、hash映射:顺序读取10个文件,按照hash(query)%10的结果将query写入到另外10个文件(记为)中。这样新生成的文件每一个的大小大约也1G(假设hash函数是随机的)。
2、hash统计:找一台内存在2G左右的机器,依次对用hash_map(query, query_count)来统计每一个query出现的次数。注:hash_map(query,query_count)是用来统计每一个query的出现次数,不是存储他们的值,出现一次,则count+1。
3、堆/快速/归并排序:利用快速/堆/归并排序按照出现次数进行排序。将排序好的query和对应的query_cout输出到文件中。这样获得了10个排好序的文件(记为)。对这10个文件进行归并排序(内排序与外排序相结合)。
归并一个左右两边分别排好序的数组,空间复杂度要求O(1)。
使用原地归并,可以让归并排序的空间复杂度降为O(1),可是速度上会有必定程度的降低。代码以下:
桶排序 (Bucket sort)或所谓的箱排序,是一个排序算法,工做的原理是将数组分到有限数量的桶子里。每一个桶子再个别排序(有可能再使用别的排序算法或是以递归方式继续使用桶排序进行排序)。
桶排序是稳定的,且在大多数状况下常见排序里最快的一种,比快排还要快,缺点是很是耗空间,基本上是最耗空间的一种排序算法,并且只能在某些情形下使用。
桶排序具体算法描述以下:
一、设置一个定量的数组看成空桶子。
二、寻访串行,而且把项目一个一个放到对应的桶子去。
三、对每一个不是空的桶子进行排序。
四、从不是空的桶子里把项目再放回原来的串行中。
桶排序最好状况下使用线性时间O(n),很显然桶排序的时间复杂度,取决与对各个桶之间数据进行排序的时间复杂度,由于 其它部分的时间复杂度都为O(n);很显然,桶划分的越小,各个桶之间的数据越少,排 序所用的时间也会越少。但相应的空间消耗就会增大。
能够证实,即便选用插入排序做为桶内排序的方法,桶排序的平均时间复杂度为线性。 具体证实,请参考算法导论。其空间复杂度也为线性。
能够参考http://ds.fzu.edu.cn/fine/resources/FlashContent.asp?id=90中的过程
这里就不给出桶排序的视频了,见上flash吧
桶排序是一种很巧妙的排序方法,在处理密集型数排序的时候有比较好的效果(主要是这种状况下空间复杂度不高),其思想也可用在不少算法题上,详见后续笔试面试算法例题。
一年的全国高考考生人数为500 万,分数使用标准分,最低100 ,最高900 ,没有小数,你把这500 万元素的数组排个序。
对500W数据排序,若是基于比较的先进排序,平均比较次数为O(5000000*log5000000)≈1.112亿。可是咱们发现,这些数据都有特殊的条件: 100=<score<=900。那么咱们就能够考虑桶排序这样一个“投机取巧”的办法、让其在毫秒级别就完成500万排序。
建立801(900-100)个桶。将每一个考生的分数丢进f(score)=score-100的桶中。这个过程从头至尾遍历一遍数据只须要500W次。而后根据桶号大小依次将桶中数值输出,便可以获得一个有序的序列。并且能够很容易的获得100分有***人,501分有***人。
实际上,桶排序对数据的条件有特殊要求,若是上面的分数不是从100-900,而是从0-2亿,那么分配2亿个桶显然是不可能的。因此桶排序有其局限性,适合元素值集合并不大的状况。
在一个文件中有 10G 个整数,乱序排列,要求找出中位数。内存限制为 2G。只写出思路便可(内存限制为 2G的意思就是,可使用2G的空间来运行程序,而不考虑这台机器上的其余软件的占用内存)。
分析: 既然要找中位数,很简单就是排序的想法。那么基于字节的桶排序是一个可行的方法。
思想:将整型的每1byte做为一个关键字,也就是说一个整形能够拆成4个keys,并且最高位的keys越大,整数越大。若是高位keys相同,则比较次高位的keys。整个比较过程相似于字符串的字典序。按如下步骤实施:
1、把10G整数每2G读入一次内存,而后一次遍历这536,870,912即(1024*1024*1024)*2 /4个数据。每一个数据用位运算">>"取出最高8位(31-24)。这8bits(0-255)最多表示255个桶,那么能够根据8bit的值来肯定丢入第几个桶。最后把每一个桶写入一个磁盘文件中,同时在内存中统计每一个桶内数据的数量,天然这个数量只须要255个整形空间便可。
2、继续之内存中的整数的次高8bit进行桶排序(23-16)。过程和第一步相同,也是255个桶。
3、一直下去,直到最低字节(7-0bit)的桶排序结束。我相信这个时候彻底能够在内存中使用一次快排就能够了。
给定n个实数x1,x2,...,xn,求这n个实数在实轴上相邻2个数之间的最大差值M,要求设计线性的时间算法
典型的最大间隙问题。
要求线性时间算法。须要使用桶排序。桶排序的平均时间复发度是O(N).若是桶排序的数据分布不均匀,假设都分配到同一个桶中,最坏状况下的时间复杂度将变为O(N^2).
桶排序: 最关键的建桶,若是桶设计得很差的话桶排序是几乎没有做用的。一般状况下,上下界有两种取法,第一种是取一个10^n或者是2^n的数,方便实现。另外一种是取数列的最大值和最小值而后均分做桶。
对于这个题,最关键的一步是:由抽屉原理知:最大差值M>= (Max(V[n])-Min(V[n]))/(n-1)!因此,假如以(Max(V[n])-Min(V[n]))/(n-1)为桶宽的话,答案必定不是属于同一个桶的两元素之差。所以,这样建桶,每次只保留桶里面的最大值和最小值便可。
代码以下:
计数排序(Counting sort)是一种稳定的排序算法。计数排序使用一个额外的数组C,其中第i个元素是待排序数组A中值等于i的元素的个数。而后根据数组C来将A中的元素排到正确的位置。它只能对整数进行排序。
算法的步骤以下:
一、找出待排序的数组中最大和最小的元素
二、统计数组中每一个值为i的元素出现的次数,存入数组C的第i项
三、对全部的计数累加(从C中的第一个元素开始,每一项和前一项相加)
四、反向填充目标数组:将每一个元素i放在新数组的第C(i)项,每放一个元素就将C(i)减去1
当输入的元素是n 个0到k之间的整数时,它的运行时间是 O(n + k)。计数排序不是比较排序,排序的速度快于任何比较排序算法。
因为用来计数的数组C的长度取决于待排序数组中数据的范围(等于待排序数组的最大值与最小值的差加上1),这使得计数排序对于数据范围很大的数组,须要大量时间和内存。例如:计数排序是用来排序0到100之间的数字的最好的算法,可是它不适合按字母顺序排序人名。可是,计数排序能够用在基数排序中的算法来排序数据范围很大的数组。
咱们使用计数排序对一个乱序的整数数组进行排序。
首先建立一个临时数组(长度为输入数据的最大间隔),对于每个输入数组的整数k,咱们在临时数组的第k位置"1"。以下图
上图中,第一行表示输入数据,第二行表示建立的临时数据,临时数组的下标表明输入数据的某一个值,临时数组的值表示输入数据中某一个值的数量。
若是输入数据中有重复的数值,那么咱们增长临时数组相应的值(好比上图中5有3个,因此小标为5的数组的值是3)。在“初始化”临时数组之后,咱们就获得了一个排序好的输入数据。
咱们顺序遍历这个数组,将下标解释成数据, 将该位置的值表示该数据的重复数量,记得获得一个排序好的数组。
可参见http://ds.fzu.edu.cn/fine/resources/FlashContent.asp?id=89中的flash过程
前面的flash已经可以清晰地表示出整个计数排序的过程了,这里就不推荐视频了
计数排序在处理密集整数排序的问题的时候很是有限,尤为是有时候题目对空间并不作太大限制,那使用计数排序可以达到O(n)的时间复杂度,远快于全部基于比较的其余排序方法。
某地区年龄排序问题
够典型的计数排序吧,年龄的区间也就那么大,代码就不上了,请参照上述参照计数排序算法。
基数排序是一种非比较型整数排序算法,其原理是将整数按位数切割成不一样的数字,而后按每一个位数分别比较。因为整数也能够表达字符串(好比名字或日期)和特定格式的浮点数,因此基数排序也不是只能使用于整数。基数排序的发明能够追溯到1887年赫尔曼·何乐礼在打孔卡片制表机(Tabulation Machine)上的贡献。
整个算法过程描述以下:
一、将全部待比较数值(正整数)统一为一样的数位长度,数位较短的数前面补零。
二、从最低位开始,依次进行一次排序。
三、这样从最低位排序一直到最高位排序完成之后, 数列就变成一个有序序列。
基数排序的时间复杂度是 O(k•n),其中n是排序元素个数,k是数字位数。
注意这不是说这个时间复杂度必定优于O(n·log(n)),由于k的大小通常会受到n的影响。 以排序n个不一样整数来举例,假定这些整数以B为底,这样每位数都有B个不一样的数字,k就必定不小于logB(n)。因为有B个不一样的数字,因此就须要B个不一样的桶,在每一轮比较的时候都须要平均n·log2(B) 次比较来把整数放到合适的桶中去,因此就有:
k 大于或等于 logB(n)
每一轮(平均)须要 n·log2(B) 次比较
因此,基数排序的平均时间T就是:
T ≥ logB(n)·n·log2(B) = log2(n)·logB(2)·n·log2(B) = log2(n)·n·logB(2)·log2(B) = n·log2(n)
因此和比较排序类似,基数排序须要的比较次数:T ≥ n·log2(n)。 故其时间复杂度为 Ω(n·log2(n)) = Ω(n·log n) 。
可参见http://ds.fzu.edu.cn/fine/resources/FlashContent.asp?id=91中的flash过程
http://www.tudou.com/programs/view/vfoUHC-tgi0
计数排序在处理密集整数排序的问题的时候很是有限,尤为是有时候题目对空间并不作太大限制,那使用计数排序可以达到O(n)的时间复杂度,远快于全部基于比较的其余排序方法。
总结一下各类排序算法以下:
名称 |
时间复杂度 |
额外空间 |
稳定性 |
考点 |
插入排序 |
平均O(n^2) 最优O(n) 最差O(n^2) |
O(1) |
稳定 |
选择填空 各类时间复杂度 移动元素个数 |
二分插入排序 |
平均 O(n^2) |
O(1) |
稳定 |
同上 |
希尔排序 |
最差O(n log n) 最优 O(n) |
O(n) |
不稳定 |
时间复杂度 比较次数 |
选择排序 |
O(n^2) |
O(1) |
不稳定 |
同插入排序 |
冒泡排序 |
O(n^2) 最优O(n) 最差O(n^2) |
O(1) |
稳定 |
时间复杂度 比较次数 单轮冒泡 |
鸡尾酒排序 |
O(n^2) |
O(1) |
稳定 |
同上 |
快速排序 |
O(n log n) |
O(1) |
不稳定 |
时间复杂度 快排partition算法 |
堆排序 |
O(n log n) |
O(n) |
不稳定 |
时间复杂度 堆调整,建堆,堆排序,Top K问题 |
归并排序 |
平均O(nlogn) 最差O(nlogn) 最优O(n) |
O(n) |
稳定 |
时间复杂度 递归思想 |