数据结构:设要将序列(Q, H, C, Y, P, A, M, S, R, D, F, X)中的关键码按字母排序序的升序重新排列

设一组初始记录关键字序列为(Q,H,C,Y,P,A,M,S,R,D,F,X),则按字母升序的第一趟冒泡排序结束后的结果是_百度知道
设一组初始记录关键字序列为(Q,H,C,Y,P,A,M,S,R,D,F,X),则按字母升序的第一趟冒泡排序结束后的结果是
S.A,D,D.H,A,C,R,H,M,X,M,A,X,P,S,XB,C,F,A,F,P,R,Y,H,Q,R,YC,M,XD,D,Y,F,M,P,S.P,H,Q,R,Q,C.F,D,Q,C,SA
我有更好的答案
按默认排序
最后的元素应该会是最大的数D冒泡排序算法的运作如下:(从后往前)比较相邻的元素,就交换他们两个。在这一点。对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对,除了最后一个,直到没有任何一对数字需要比较。针对所有的元素重复以上的步骤。持续每次对越来越少的元素重复上面的步骤。如果第一个比第二个大
其他类似问题
冒泡排序的相关知识
等待您来回答
下载知道APP
随时随地咨询
出门在外也不愁数据结构:设要将序列(Q, H, C, Y, P, A, M, S, R, D, F, X)中的关键码按字母序的升序重新排列_百度知道
数据结构:设要将序列(Q, H, C, Y, P, A, M, S, R, D, F, X)中的关键码按字母序的升序重新排列
C, X)中的关键码按字母序的升序重新排列,则, Y,具体怎么排的, P, A,F, S, M, R, D:初始步长为4的希尔(shell)排序一趟的结果是 快速排序一趟扫描的结果是 求详细过程设要将序列(Q, H
提问者采纳
$a$2先在辅助列中写入下面的公式。(复制到比数据区域多一个单元格: =subtotal(3:a2) 下拉复制公式,即假设数据到第100行,数据从第二行开始,你就复制公式到第101行的单元格。) 这里假设第一行为表头,然后可以尽情筛选了
听不懂。。。
提问者评价
其他类似问题
数据结构的相关知识
等待您来回答
下载知道APP
随时随地咨询
出门在外也不愁哈工大2009秋《C++与数据结构》考试题-A_百度文库
两大类热门资源免费畅读
续费一年阅读会员,立省24元!
评价文档:
喜欢此文档的还喜欢
哈工大2009秋《C++与数据结构》考试题-A
阅读已结束,如果下载本文需要使用
想免费下载本文?
把文档贴到Blog、BBS或个人站等:
普通尺寸(450*500pix)
较大尺寸(630*500pix)
你可能喜欢13种排序算法详解(相当清楚,还附flash动画)
  作者:寒小阳转载:http://blog.csdn.net/han_xiaoyang/article/details/时间:2013年9月。出处:。声明:版权所有,转载请注明出处,谢谢。0、前言从这一部分开始直接切入我们计算机互联网笔试面试中的重头戏算法了,初始的想法是找一条主线,比如数据结构或者解题思路方法,将博主见过做过整理过的算法题逐个分析一遍(博主当年自己学算法就是用这种比较笨的刷题学的,囧),不过又想了想,算法这东西,博主自己学的过程中一直深感,基础还是非常重要的,很多难题是基础类数据结构和题目的思想综合发散而来。比如说作为最基本的排序算法就种类很多,而事实上笔试面试过程中发现掌握的程度很一般,有很多题目,包括很多算法难题,其母题或者基本思想就是基于这些经典算法的,比如说快排的partition算法,比如说归并排序中的思想,比如说桶排序中桶的思想。这里对笔试面试最常涉及到的12种排序算法(包括插入排序、二分插入排序、希尔排序、选择排序、冒泡排序、鸡尾酒排序、快速排序、堆排序、归并排序、桶排序、计数排序和基数排序)进行了详解。每一种算法都有基本介绍、算法原理分析、图解/flash演示/视频演示、算法代码、笔试面试重点分析、笔试面试题等板块,希望能帮助大家真正理解这些排序算法,并能使用这些算法的思想解决一些题。不多说了,下面就进入正题了。一、插入排序1)算法简介插入排序(Insertion&Sort)的算法描述是一种简单直观的排序算法。它的工作原理是通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。插入排序在实现上,通常采用in-place排序(即只需用到O(1)的额外空间的排序),因而在从后向前扫描过程中,需要反复把已排序元素逐步向后挪位,为最新元素提供插入空间。2)算法描述和分析一般来说,插入排序都采用in-place在数组上实现。具体算法描述如下:1、从第一个元素开始,该元素可以认为已经被排序2、取出下一个元素,在已经排序的元素序列中从后向前扫描3、如果该元素(已排序)大于新元素,将该元素移到下一位置4、重复步骤3,直到找到已排序的元素小于或者等于新元素的位置5、将新元素插入到该位置后6、重复步骤2~5如果目标是把n个元素的序列升序排列,那么采用插入排序存在最好情况和最坏情况。最好情况就是,序列已经是升序排列了,在这种情况下,需要进行的比较操作需(n-1)次即可。最坏情况就是,序列是降序排列,那么此时需要进行的比较共有n(n-1)/2次。插入排序的赋值操作是比较操作的次数减去(n-1)次。平均来说插入排序算法复杂度为O(n^2)。因而,插入排序不适合对于数据量比较大的排序应用。但是,如果需要排序的数据量很小,例如,量级小于千,那么插入排序还是一个不错的选择。&插入排序在工业级库中也有着广泛的应用,在STL的sort算法和stdlib的qsort算法中,都将插入排序作为快速排序的补充,用于少量元素的排序(通常为8个或以下)。3)算法图解、flash演示、视频演示图解:Flash:视频:插入排序舞蹈4)算法代码[cpp] void&insertion_sort(int&array[],&int&first,&int&last){int&i,j;int&for&(i&=&first+1;&i&=i++){temp&=&array[i];j=i-1;//与已排序的数逐一比较,大于temp时,该数后移while((j&=first)&&&&(array[j]&&&temp))&&//当first=0,j循环到-1时,由于[[短路求值]],不会运算array[-1]{array[j+1]&=&array[j];j--;}array[j+1]&=&&&&&&&//被排序数放到正确的位置}}&5)考察点,重点和频度分析把插入排序放在第一个的原因是因为其出现的频度不高,尤其是这里提到的直接排序算法,基本在笔试的选择填空问时间空间复杂度的时候才可能出现。毕竟排序速度比较慢,因此算法大题中考察的次数比较比较少。6)笔试面试例题例题1、请写出链表的插入排序程序[cpp] template&typename&T&struct&list_node{struct&list_node&T&&*T&};template&typename&T&struct&_list{struct&list_node&T&&*int&};template&typename&T&void&SortLink(struct&_list&T&&*&link)&{struct&list_node&T&&*pHead,*pRear,*p,*if&(!link)&for&(pHead=link-&head,pRear=0;pHpHead=pHead-&next)&{for&(tp=pHead,p=pHead-&p;tp=p,p=p-&next)if&(pHead-&value&=p-&value)tp-&next=p-&next,p-&next=pHead,pHead=p,p=if&(!pRear)&link-&head=pHelse&pRear-&next=pHpRear=pH}}例题2、下列排序算法中最坏复杂度不是n(n-1)/2的是 DA.快速排序&&&&&B.冒泡排序&&&C.直接插入排序&&&D.堆排序二、二分插入排序1)算法简介二分(折半)插入(Binary&insert&sort)排序是一种在直接插入排序算法上进行小改动的排序算法。其与直接排序算法最大的区别在于查找插入位置时使用的是二分查找的方式,在速度上有一定提升。2)算法描述和分析一般来说,插入排序都采用in-place在数组上实现。具体算法描述如下:1、从第一个元素开始,该元素可以认为已经被排序2、取出下一个元素,在已经排序的元素序列中二分查找到第一个比它大的数的位置3、将新元素插入到该位置后4、重复上述两步1)稳定2)空间代价:O(1)3)时间代价:插入每个记录需要O(log&i)比较,最多移动i+1次,最少2次。最佳情况O(n log&n),最差和平均情况O(n^2)。二分插入排序是一种稳定的排序。当n较大时,总排序码比较次数比直接插入排序的最差情况好得多,但比最好情况要差,所元素初始序列已经按排序码接近有序时,直接插入排序比二分插入排序比较次数少。二分插入排序元素移动次数与直接插入排序相同,依赖于元素初始序列。3)算法图解、flash演示、视频演示图解:视频:二分插入排序4)算法代码[cpp] void&BinInsertSort(int&a[],&int&n){int&key,&left,&right,&for&(int&i=1;&i&n;&i++){key&=&a[i];left&=&0;right&=&i-1;while&(left&=right){middle&=&(left+right)/2;if&(a[middle]&key)right&=&middle-1;elseleft&=&middle+1;}for(int&j=i-1;&j&=&j--){a[j+1]&=&a[j];}a[left]&=&}}&5)考察点,重点和频度分析这个排序算法在笔试面试中出现的频度也不高,但毕竟是直接排序算法的一个小改进算法,同时二分查找又是很好的思想,有可能会在面试的时候提到,算法不难,留心一下就会了。6)笔试面试例题例题1、下面的排序算法中,初始数据集的排列顺序对算法的性能无影响的是(B)A、二分插入排序&&&&&&&&&B、堆排序&&&&&&&&&C、冒泡排序&&&&&&&&&&&&D、快速排序例题2、写出下列算法的时间复杂度。(1)冒泡排序;(2)选择排序;(3)插入排序;(4)二分插入排序;(5)快速排序;(6)堆排序;(7)归并排序;三、希尔排序1)算法简介希尔排序,也称递减增量排序算法,因DL.Shell于1959年提出而得名,是插入排序的一种高速而稳定的改进版本。2)算法描述1、先取一个小于n的整数d1作为第一个增量,把文件的全部记录分成d1个组。2、所有距离为d1的倍数的记录放在同一个组中,在各组内进行直接插入排序。3、取第二个增量d2&d1重复上述的分组和排序,4、直至所取的增量dt=1(dt&dt-l&…&d2&d1),即所有记录放在同一组中进行直接插入排序为止。希尔排序的时间复杂度与增量序列的选取有关,例如希尔增量时间复杂度为O(n^2),而Hibbard增量的希尔排序的时间复杂度为O(N^(5/4)),但是现今仍然没有人能找出希尔排序的精确下界。3)算法图解、flash演示、视频演示图解:Flash:视频:希尔排序Shell&Sort&舞蹈4)算法代码[cpp] #include&&stdio.h&int&main(){const&int&n&=&5;int&i,&j,&int&gap&=&0;int&a[]&=&{5,&4,&3,&2,&1};while&(gap&=n){gap&=&gap&*&3&+&1;}while&(gap&&&0){for&(&i&=&&i&&&n;&i++&){j&=&i&-&temp&=&a[i];while&((&j&&=&0&)&&&&(&a[j]&&&temp&)){a[j&+&gap]&=&a[j];j&=&j&-&}a[j&+&gap]&=&}gap&=&(&gap&-&1&)&/&3;}}5)考察点,重点和频度分析事实上希尔排序算法在笔试面试中出现的频度也不比直接插入排序高,但它的时间复杂度并不是一个定值,所以偶尔会被面试官问到选择的步长和时间复杂度的关系,要稍微有点了解吧。算法大题中使用该方法或者其思想的题也不多。6)笔试面试例题例题1、写出希尔排序算法程序,并说明最坏的情况下需要进行多少次的比较和交换。程序略,需要O(n^2)次的比较例题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&&&。四、选择排序1)算法简介选择排序(Selection&sort)是一种简单直观的排序算法。它的工作原理如下。首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,然后,再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。以此类推,直到所有元素均排序完毕。2)算法描述和分析n个记录的文件的直接选择排序可经过n-1趟直接选择排序得到有序结果:1、初始状态:无序区为R[1..n],有序区为空。2、第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个的新无序区。3、前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值较小时,选择排序比冒泡排序快。最差时间复杂度О(n2)最优时间复杂度О(n2)平均时间复杂度О(n2)最差空间复杂度О(n)&total,&O(1)3)算法图解、flash演示、视频演示图解:Flash:视频:选择排序Select&Sort排序舞蹈4)算法代码[cpp] void&selection_sort(int&*a,&int&len){register&int&i,&j,&min,&t;for(i&=&0;&i&&&len&-&1;&i&++){min&=&i;//查找最小值for(j&=&i&+&1;&j&&&&j&++)if(a[min]&&&a[j])min&=&j;//交换if(min&!=&i){t&=&a[min];a[min]&=&a[i];a[i]&=&t;}}}5)考察点,重点和频度分析就博主看过的笔试面试题而言,选择算法也大多出现在选择填空中,要熟悉其时间和空间复杂度,最好最坏的情况分别是什么,以及在那种情况下,每一轮的比较次数等。6)笔试面试例题例题1、在插入和选择排序中,若初始数据基本正序,则选用&插入排序(到尾部)&&&;若初始数据基本反序,则选用&&&选择排序&&&&&。例题2、下述几种排序方法中,平均查找长度(ASL)最小的是A.&插入排序&&&&&&B.快速排序&&&&&&&&C.&归并排序&&&&&&&D.&选择排序五、冒泡排序1)算法简介冒泡排序是一种简单的排序算法。它重复地走访过要排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来。走访数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。这个算法的名字由来是因为越小的元素会经由交换慢慢“浮”到数列的顶端。2)算法描述1、比较相邻的元素。如果第一个比第二个大,就交换他们两个。2、对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。在这一点,最后的元素应该会是最大的数。3、针对所有的元素重复以上的步骤,除了最后一个。4、持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。冒泡排序是与插入排序拥有相等的执行时间,但是两种法在需要的交换次数却很大地不同。在最坏的情况,冒泡排序需要O(n^2)次交换,而插入排序只要最多O(n)交换。冒泡排序的实现(类似下面)通常会对已经排序好的数列拙劣地执行(O(n^2)),而插入排序在这个例子只需要O(n)个运算。因此很多现代的算法教科书避免使用冒泡排序,而用插入排序取代之。冒泡排序如果能在内部循环第一次执行时,使用一个旗标来表示有无需要交换的可能,也有可能把最好的复杂度降低到O(n)。在这个情况,在已经排序好的数列就无交换的需要。若在每次走访数列时,把走访顺序和比较大小反过来,也可以稍微地改进效率。有时候称为往返排序,因为算法会从数列的一端到另一端之间穿梭往返。最差时间复杂度O(n^2)最优时间复杂度O(n)平均时间复杂度O(n^2)最差空间复杂度总共O(n),需要辅助空间O(1)3)算法图解、flash演示、视频演示图解:Flash:视频:舞动的排序算法&冒泡排序4)算法代码[cpp] #include&&stdio.h&void&bubbleSort(int&arr[],&int&count){int&i&=&count,&j;int&while(i&&&0){for(j&=&0;&j&&&i&-&1;&j++){if(arr[j]&&&arr[j&+&1]){&&&temp&=&arr[j];arr[j]&=&arr[j&+&1];arr[j&+&1]&=&}}i--;}}int&main(){//测试数据int&arr[]&=&{5,&4,&1,&3,&6};//冒泡排序bubbleSort(arr,&5);//打印排序结果int&i;for(i&=&0;&i&&&5;&i++)printf("%4d",&arr[i]);}5)考察点,重点和频度分析一般我们学到的第一个排序算法就是冒泡排序,不得不说,这个还真是一个很常见的考点,平均时间空间复杂度,最好最坏情况下的时间空间复杂度,在不同情况下每一趟的比较次数,以及加标志位减少比较次数等,都是需要注意的地方。6)笔试面试例题例题1、对于整数序列100,99,98,…3,2,1,如果将它完全倒过来,分别用冒泡排序,它们的比较次数和交换次数各是多少?答:冒泡排序的比较和交换次数将最大,都是1+2+…+n-1=n(n-1)/2=50×99=4545次。例题2、把一个字符串的大写字母放到字符串的后面,各个字符的相对位置不变,不能申请额外的空间。事实上,这道题放到冒泡排序这里不知道是不是特别合适,只是有一种解法是类似冒泡的思想,如下解法一&&&&&&解法一、每次遇到大写字母就往后冒,最后结果即为所求[cpp] #include&&stdio.h&#include&&string.h&//题目以及要求:把一个字符串的大写字母放到字符串的后面,//各个字符的相对位置不变,不能申请额外的空间。//判断是不是大写字母int&isUpperAlpha(char&c){if(c&&=&'A'&&&&c&&=&'Z'){return&1;}return&0;}//交换两个字母void&swap(char&*a,&char&*b){char&temp&=&*a;*a&=&*b;*b&=&}char&*&mySort(char&*arr,&int&len){if(arr&==&NULL&||&len&&=&0){return&NULL;}int&i&=&0,&j&=&0,&k&=&0;for(i&=&0;&i&&&&i++){for(j&=&len&-&1&-&i;&j&&=&0;&j--){if(isUpperAlpha(arr[j])){for(k&=&j;&k&&&len&-&i&-&1;&k++){swap(&arr[k],&&arr[k&+&1]);}}//遍历完了字符数组,但是没发现大写字母,所以没必要再遍历下去if(j&==&0&&&&!isUpperAlpha(arr[j])){//结束;return&}}}//over:return&}int&main(){char&arr[]&=&"aaaaaaaaaaaaaaaaaaaaaaaAbcAdeBbDc";printf("%s\n",&mySort(arr,&strlen(arr)));return&0;}解法二。步骤如下1、两个指针p1和p2,从后往前扫描2、p1遇到一个小写字母时停下,&p2遇到大写字母时停下,两者所指向的char交换3、p1,&p2同时往前一格代码如下:[cpp] #include&&stdio.h&#include&&string.h&//判断是不是大写字母int&isUpperAlpha(char&c){if(c&&=&'A'&&&&c&&=&'Z'){return&1;}return&0;}//交换两个字母void&swap(char&*a,&char&*b){char&temp&=&*a;*a&=&*b;*b&=&}char&*&Reorder(char&*arr,&int&len){if(arr&==&NULL&||&len&&=&0){return&NULL;}int&*p1&=&int&*p2&=&While(p1&arr+len&&&&p2&arr+len){While(&isUpperAlpha(*p1)&){P1++;}While(&!isUpperAlpha(*p2)&){P2++;}swap(p1,&p2)}//结束return&}int&main(){char&arr[]&=&"aaaaaaaaaaaaaaaaaaaaaaaAbcAdeBbDc";printf("%s\n",&Reorder(arr,&strlen(arr)));return&0;}六、鸡尾酒排序/双向冒泡排序1)算法简介鸡尾酒排序等于是冒泡排序的轻微变形。不同的地方在于从低到高然后从高到低,而冒泡排序则仅从低到高去比较序列里的每个元素。他可以得到比冒泡排序稍微好一点的效能,原因是冒泡排序只从一个方向进行比对(由低到高),每次循环只移动一个项目。2)算法描述和分析1、依次比较相邻的两个数,将小数放在前面,大数放在后面;2、第一趟可得到:将最大数放到最后一位。3、第二趟可得到:将第二大的数放到倒数第二位。4、如此下去,重复以上过程,直至最终完成排序。鸡尾酒排序最糟或是平均所花费的次数都是O(n^2),但如果序列在一开始已经大部分排序过的话,会接近O(n)。最差时间复杂度O(n^2)最优时间复杂度O(n)平均时间复杂度O(n^2)3)算法图解、flash演示、视频演示图解:Flash:参见右侧flash动画4)算法代码[cpp] void&CocktailSort(int&*a,int&nsize){int&tail=nsize-1;for&(int&i=0;i&){for&(int&j=j&i;--j)&//第一轮,先将最小的数据排到前面{if&(a[j]&a[j-1]){int&temp=a[j];a[j]=a[j-1];a[j-1]=}}++i;&&&&&&&&&&&&&&&&&&&&//原来i处数据已排好序,加1for&(j=i;j&++j)&&&&//第二轮,将最大的数据排到后面{if&(a[j]&a[j+1]){int&temp=a[j];a[j]=a[j+1];a[j+1]=}}tail--;&&&&&&&&&&&&&&&&&//原tail处数据也已排好序,将其减1}}5)考察点,重点和频度分析鸡尾酒排序在博主印象中出现的频度也不高,用到它的算法题大题很少,选择填空出现的话多以双向冒泡排序的名称出现,注意注意时间空间复杂度,理解理解算法应该问题就不大了。6)笔试面试例题考点基本类似冒泡排序,请参考上一节七、快速排序恩,重头戏开始了,快速排序是各种笔试面试最爱考的排序算法之一,且排序思想在很多算法题里面被广泛使用。是需要重点掌握的排序算法。1)算法简介快速排序是由东尼·霍尔所发展的一种排序算法。其基本思想是基本思想是,通过一趟排序将待排记录分隔成独立的两部分,其中一部分记录的关键字均比另一部分的关键字小,则可分别对这两部分记录继续进行排序,以达到整个序列有序。2)算法描述和分析快速排序使用分治法来把一个串(list)分为两个子串行(sub-lists)。步骤为:1、从数列中挑出一个元素,称为&"基准"(pivot),2、重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区(partition)操作。3、递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序。递归的最底部情形,是数列的大小是零或一,也就是永远都已经被排序好了。虽然一直递归下去,但是这个算法总会退出,因为在每次的迭代(iteration)中,它至少会把一个元素摆到它最后的位置去。算法伪代码描述:function&quicksort(q)var&list&less,&pivotList,&greaterif&length(q)&≤&1&{return&q}&else&{select&a&pivot&value&pivot&from&qfor&each&x&in&q&except&the&pivot&elementif&x&&&pivot&then&add&x&to&lessif&x&≥&pivot&then&add&x&to&greateradd&pivot&to&pivotListreturn&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)最差空间复杂度根据实现的方式不同而不同3)算法图解、flash演示、视频演示图解:快速排序会递归地进行很多轮,其中每一轮称之为快排的partition算法,即上述算法描述中的第2步,非常重要,且在各种笔试面试中用到该思想的算法题层出不穷,下图为第一轮的partition算法的一个示例。Flash:可一步步参见中的快排过程视频&舞动的排序算法4)算法代码事实上,这个地方需要提一下的是,快排有很多种版本。例如,我们“基准数”的选择方法不同就有不同的版本,但重要的是快排的思想,我们熟练掌握一种版本,在最后的笔试面试中也够用了,我这里罗列几种最有名的版本C代码。1、版本一我们选取数组的第一个元素作为主元,每一轮都是和第一个元素比较大小,通过交换,分成大于和小于它的前后两部分,再递归处理。代码如下[cpp] /**************************************************函数功能:对数组快速排序函数参数:指向整型数组arr的首指针arr;整型变量left和right左右边界的下标函数返回值:空/**************************************************/void&QuickSort(int&*arr,&int&left,&int&right){int&i,j;if(left&right){i=j=arr[0]=arr[i];&//准备以本次最左边的元素值为标准进行划分,先保存其值do{while(arr[j]&arr[0]&&&&i&j)j--;&&&&&&&&//从右向左找第1个小于标准值的位置jif(i&j)&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&//找到了,位置为j{arr[i]&=&arr[j];i++;}&&&&&&&&&&&//将第j个元素置于左端并重置iwhile(arr[i]&arr[0]&&&&i&j)i++;&&&&&&//从左向右找第1个大于标准值的位置iif(i&j)&&&&&&&&&&&&&&&&&&&&&&&//找到了,位置为i{arr[j]&=&arr[i];j--;}&&&&&&&&&&&//将第i个元素置于右端并重置j}while(i!=j);arr[i]&=&arr[0];&&&&&&&&&//将标准值放入它的最终位置,本次划分结束quicksort(arr,&left,&i-1);&&&&&//对标准值左半部递归调用本函数quicksort(arr,&i+1,&right);&&&&//对标准值右半部递归调用本函数}}2、版本二随机选基准数的快排[cpp] //使用引用,完成两数交换void&Swap(int&&a&,&int&&b){int&temp&=&a;a&=&b;b&=&}//取区间内随机数的函数int&Rand(int&low,&int&high){int&size&=&hgh&-&low&+&1;return&&low&+&rand()%}//快排的partition算法,这里的基准数是随机选取的int&RandPartition(int*&data,&int&low&,&int&high){swap(data[rand(low,high)],&data[low]);//int&key&=&data[low];int&i&=&for(int&j=low+1;&j&=&j++){if(data[j]&=key){i&=&i+1;swap(data[i],&data[j]);}}swap(data[i],data[low]);return&i;}//递归完成快速排序void&QuickSort(int*&data,&int&low,&int&high){if(low&high){int&k&=&RandPartition(data,low,high);QuickSort(data,low,k-1);QuickSort(data,k+1,high);}}5)考察点,重点和频度分析完全考察快排算法本身的题目,多出现在选择填空,基本是关于时间空间复杂度的讨论,最好最坏的情形交换次数等等。倒是快排的partition算法需要特别注意!频度极高地被使用在各种算法大题中!详见下小节列举的面试小题。6)笔试面试例题这里要重点强调的是快排的partition算法,博主当年面试的时候就遇到过数道用该思路的算法题,举几道如下:例题1、最小的k个数,输入n个整数,找出其中最下的k个数,例如输入4、5、1、6、2、7、3、8、1、2,输出最下的4个数,则输出1、1、2、2。当然,博主也知道这题可以建大小为k的大顶堆,然后用堆的方法解决。但是这个题目可也以仿照快速排序,运用partition函数进行求解,不过我们完整的快速排序分割后要递归地对前后两段继续进行分割,而这里我们需要做的是判定分割的位置,然后再确定对前段还是后段进行分割,所以只对单侧分割即可。代码如下:[cpp] void&GetLeastNumbers_by_partition(int*&input,&int&n,&int*&output,&int&k){if(input&==&NULL&||&output&==&NULL&||&k&&&n&||&n&&=&0&||&k&&=&0)int&start&=&0;int&end&=&n&-&1;int&index&=&Partition(input,&n,&start,&end);while(index&!=&k&-&1){if(index&&&k&-&1){end&=&index&-&1;index&=&Partition(input,&n,&start,&end);}else{start&=&index&+&1;index&=&Partition(input,&n,&start,&end);}}for(int&i&=&0;&i&&&k;&++i)output[i]&=&input[i];}例题2、判断数组中出现超过一半的数字当然,这道题很多人都见过,而且最通用的一种解法是数对对消的思路。这里只是再给大家提供一种思路,快排partition的方法在很多地方都能使用,比如这题。我们也可以选择合适的判定条件进行递归。代码如下:[cpp] bool&g_bInputInvalid&=&bool&CheckInvalidArray(int*&numbers,&int&length){g_bInputInvalid&=&if(numbers&==&NULL&&&&length&&=&0)g_bInputInvalid&=&return&g_bInputI}bool&CheckMoreThanHalf(int*&numbers,&int&length,&int&number){int&times&=&0;for(int&i&=&0;&i&&&&++i){if(numbers[i]&==&number)times++;}bool&isMoreThanHalf&=&if(times&*&2&&=&length){g_bInputInvalid&=&isMoreThanHalf&=&}return&isMoreThanH}int&MoreThanHalfNum_Solution1(int*&numbers,&int&length){if(CheckInvalidArray(numbers,&length))return&0;int&middle&=&length&&&&1;int&start&=&0;int&end&=&length&-&1;int&index&=&Partition(numbers,&length,&start,&end);while(index&!=&middle){if(index&&&middle){end&=&index&-&1;index&=&Partition(numbers,&length,&start,&end);}else{start&=&index&+&1;index&=&Partition(numbers,&length,&start,&end);}}int&result&=&numbers[middle];if(!CheckMoreThanHalf(numbers,&length,&result))result&=&0;return&}例题3、有一个由大小写组成的字符串,现在需要对他进行修改,将其中的所有小写字母排在大写字母的前面(不要求保持原顺序)这题可能大家都能想到的方法是:设置首尾两个指针,首指针向后移动寻找大写字母,尾指针向前移动需找小写字母,找到后都停下,交换。之后继续移动,直至相遇。这种方法在这里我就不做讨论写代码了。但是这题也可以采用类似快排的partition。这里使用从左往后扫描的方式。字符串在调整的过程中可以分成两个部分:已排好的小写字母部分、待调整的剩余部分。用两个指针i和j,其中i指向待调整的剩余部分的第一个元素,用j指针遍历待调整的部分。当j指向一个小写字母时,交换i和j所指的元素。向前移动i、j,直到字符串末尾。代码如下:[cpp] #include&&iostream&using&namespace&void&Proc(&char&*str&){int&i&=&0;int&j&=&0;//移动指针i,&使其指向第一个大写字母while(&str[i]&!=&'\0'&&&&str[i]&&=&'a'&&&&str[i]&&=&'z'&)&i++;if(&str[i]&!=&'\0'&){//指针j遍历未处理的部分,找到第一个小写字母for(&j=i;&str[j]&!=&'\0';&j++&){if(&str[j]&&=&'a'&&&&str[j]&&=&'z'&){char&tmp&=&str[i];str[i]&=&str[j];str[j]&=&i++;}}}}int&main(){char&data[]&=&"SONGjianGoodBest";Proc(&data&);return&0;}八、堆排序不得不说,堆排序太容易出现了,选择填空问答算法大题都会出现。建堆的过程,堆调整的过程,这些过程的时间复杂度,空间复杂度,以及如何应用在海量数据Top&K问题中等等,都是需要重点掌握的。1)算法简介堆排序(Heapsort)是指利用堆这种数据结构所设计的一种排序算法。堆积是一个近似完全二叉树的结构,并同时满足堆积的性质:即子结点的键值或索引总是小于(或者大于)它的父节点。2)算法描述我们这里介绍几个问题,一步步推到堆排序的算法。1、什么是堆?我们这里提到的堆一般都指的是二叉堆,它满足二个特性:1---父结点的键值总是大于或等于(小于或等于)任何一个子节点的键值。2---每个结点的左子树和右子树都是一个二叉堆(都是最大堆或最小堆)。如下为一个最小堆(父结点的键值总是小于任何一个子节点的键值)2、什么是堆调整(Heap&Adjust)?这是为了保持堆的特性而做的一个操作。对某一个节点为根的子树做堆调整,其实就是将该根节点进行“下沉”操作(具体是通过和子节点交换完成的),一直下沉到合适的位置,使得刚才的子树满足堆的性质。例如对最大堆的堆调整我们会这么做:1、在对应的数组元素A[i],&左孩子A[LEFT(i)],&和右孩子A[RIGHT(i)]中找到最大的那一个,将其下标存储在largest中。2、如果A[i]已经就是最大的元素,则程序直接结束。3、否则,i的某个子结点为最大的元素,将A[largest]与A[i]交换。4、再从交换的子节点开始,重复1,2,3步,直至叶子节点,算完成一次堆调整。这里需要提一下的是,一般做一次堆调整的时间复杂度为log(n)。如下为我们对4为根节点的子树做一次堆调整的示意图,可帮我们理解。3、如何建堆建堆是一个通过不断的堆调整,使得整个二叉树中的数满足堆性质的操作。在数组中的话,我们一般从下标为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分别作一次向下调整操作就可以了。4、如何进行堆排序堆排序是在上述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)3)算法图解、flash演示、视频演示图解:略,见上一节。Flash:可参见中的flash动画,帮助理解视频&堆排序4)算法代码直接上代码吧,重点注意HeapAdjust,BuildHeap和HeapSort的实现。[cpp] #include&&cstdio&#include&&cstdlib&#include&&cmath&using&namespace&int&parent(int);int&left(int);int&right(int);void&HeapAdjust(int&[],&int,&int);void&BuildHeap(int&[],&int);void&print(int&[],&int);void&HeapSort(int&[],&int);/*返回父节点*/int&parent(int&i){return&(int)floor((i&-&1)&/&2);}/*返回左孩子节点*/int&left(int&i){return&(2&*&i&+&1);}/*返回右孩子节点*/int&right(int&i){return&(2&*&i&+&2);}/*对以某一节点为根的子树做堆调整(保证最大堆性质)*/void&HeapAdjust(int&A[],&int&i,&int&heap_size){int&l&=&left(i);int&r&=&right(i);int&int&if(l&&&heap_size&&&&A[l]&&&A[i]){largest&=&l;}else{largest&=&i;}if(r&&&heap_size&&&&A[r]&&&A[largest]){largest&=&r;}if(largest&!=&i){temp&=&A[i];A[i]&=&A[largest];A[largest]&=&HeapAdjust(A,&largest,&heap_size);}}/*建立最大堆*/void&BuildHeap(int&A[],int&heap_size){for(int&i&=&(heap_size-2)/2;&i&&=&0;&i--){HeapAdjust(A,&i,&heap_size);}}/*输出结果*/void&print(int&A[],&int&heap_size){for(int&i&=&0;&i&&&heap_i++){printf("%d&",&A[i]);}printf("\n");}/*堆排序*/void&HeapSort(int&A[],&int&heap_size){BuildHeap(A,&heap_size);int&for(int&i&=&heap_size&-&1;&i&&=&0;&i--){temp&=&A[0];A[0]&=&A[i];A[i]&=&HeapAdjust(A,&0,&i);}print(A,&heap_size);}/*测试,对给定数组做堆排序*/int&main(int&argc,&char*&argv[]){const&int&heap_size&=&13;int&A[]&=&{19,&1,&10,&14,&16,&4,&7,&9,&3,&2,&8,&5,&11};HeapSort(A,&heap_size);system("pause");return&0;}5)考察点,重点和频度分析堆排序相关的考察太多了,选择填空问答算法大题都会出现。建堆的过程,堆调整的过程,这些过程的时间复杂度,空间复杂度,需要比较交换多少次,以及如何应用在海量数据Top&K问题中等等。堆又是一种很好做调整的结构,在算法题里面使用频度很高。6)笔试面试题例题1、编写算法,从10亿个浮点数当中,选出其中最大的10000个。典型的Top&K问题,用堆是最典型的思路。建10000个数的小顶堆,然后将10亿个数依次读取,大于堆顶,则替换堆顶,做一次堆调整。结束之后,小顶堆中存放的数即为所求。代码如下(为了方便,这里直接使用了STL容器):[cpp] #include&"stdafx.h"#include&&vector&#include&&iostream&#include&&algorithm&#include&&functional&&//&for&greater&&using&namespace&int&_tmain(int&argc,&_TCHAR*&argv[]){vector&float&&bigs(10000,0);vector&float&::iterator&//&Init&vector&datafor&(it&=&bigs.begin();&it&!=&bigs.end();&it++){*it&=&(float)rand()/7;&//&random&}cout&&&&bigs.size()&&&&make_heap(bigs.begin(),bigs.end(),&greater&float&());&//&The&first&one&is&the&smallest&one!float&for&(int&i&=&0;&i&&&;&i++){ff&=&(float)&rand()&/&7;if&(ff&&&bigs.front())&//&replace&the&first&one&?{//&set&the&smallest&one&to&the&end!pop_heap(bigs.begin(),&bigs.end(),&greater&float&());//&remove&the&last/smallest&onebigs.pop_back();//&add&to&the&last&onebigs.push_back(ff);//&mask&heap&again,&the&first&one&is&still&the&smallest&onepush_heap(bigs.begin(),bigs.end(),greater&float&());}}//&sort&by&ascentsort_heap(bigs.begin(),&bigs.end(),&greater&float&());return&0;}例题2、设计一个数据结构,其中包含两个函数,1.插入一个数字,2.获得中数。并估计时间复杂度。使用大顶堆和小顶堆存储。使用大顶堆存储较小的一半数字,使用小顶堆存储较大的一半数字。插入数字时,在O(logn)时间内将该数字插入到对应的堆当中,并适当移动根节点以保持两个堆数字相等(或相差1)。获取中数时,在O(1)时间内找到中数。九、归并排序1)算法简介归并排序是建立在归并操作上的一种有效的排序算法。该算法是采用分治法(Divide&and&Conquer)的一个非常典型的应用。归并排序是一种稳定的排序方法。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为2-路归并。2)算法描述归并排序具体算法描述如下(递归版本):1、Divide:&把长度为n的输入序列分成两个长度为n/2的子序列。2、Conquer:&对这两个子序列分别采用归并排序。3、Combine:&将两个排序好的子序列合并成一个最终的排序序列。归并排序的效率是比较高的,设数列长为N,将数列分开成小数列一共要logN步,每步都是一个合并有序数列的过程,时间复杂度可以记为O(N),故一共为O(N*logN)。因为归并排序每次都是在相邻的数据中进行操作,所以归并排序在O(N*logN)的几种排序方法(快速排序,归并排序,希尔排序,堆排序)也是效率比较高的。3)算法图解、flash演示、视频演示图解:Flash:可以参考中的过程视频:舞动的排序算法&归并排序4)算法代码[cpp] //将有二个有序数列a[first...mid]和a[mid...last]合并。void&MergeArray(int&a[],&int&first,&int&mid,&int&last,&int&temp[]){int&i&=&first,&j&=&mid&+&1;int&m&=&mid,&&&n&=&int&k&=&0;while&(i&&=&m&&&&j&&=&n){if&(a[i]&&=&a[j])temp[k++]&=&a[i++];elsetemp[k++]&=&a[j++];}while&(i&&=&m)temp[k++]&=&a[i++];while&(j&&=&n)temp[k++]&=&a[j++];for&(i&=&0;&i&&&k;&i++)a[first&+&i]&=&temp[i];}//递归地完成归并排序void&MergeSort(int&a[],&int&first,&int&last,&int&temp[]){if&(first&&&last){int&mid&=&(first&+&last)&/&2;mergesort(a,&first,&mid,&temp);&&&&//左边有序mergesort(a,&mid&+&1,&last,&temp);&//右边有序mergearray(a,&first,&mid,&last,&temp);&//再将二个有序数列合并}}5)考察点、重点和频度分析归并排序本身作为一种高效的排序算法,也是常会被问到的。尤其是归并排序体现的递归思路很重要,在递归的过程中可以完成很多事情,很多算法题也是使用的这个思路,可见下面7)部分的笔试面试算法题。6)笔试面试题例题1、题目输入一个数组,数组元素的大小在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]中的逆序对数。由于两个子序列本身都已经排序,所以查找起来非常方便。代码如下:[cpp] #include&iostream&#include&stdlib.h&using&namespace&void&printArray(int&arry[],int&len){for(int&i=0;i&i++)cout&&arry[i]&&"&";cout&&}int&MergeArray(int&arry[],int&start,int&mid,int&end,int&temp[])//数组的归并操作{//int&leftLen=mid-start+1;//arry[start...mid]左半段长度//int&rightLlen=end-//arry[mid+1...end]右半段长度int&i=int&j=int&k=0;//临时数组末尾坐标int&count=0;//设定两个指针ij分别指向两段有序数组的头元素,将小的那一个放入到临时数组中去。while(i&=start&&j&mid){if(arry[i]&arry[j]){temp[k++]=arry[i--];//从临时数组的最后一个位置开始排序count+=j-//因为arry[mid+1...j...end]是有序的,如果arry[i]&arry[j],那么也大于arry[j]之前的元素,从a[mid+1...j]一共有j-(mid+1)+1=j-mid}else{temp[k++]=arry[j--];}}cout&&"调用MergeArray时的count:"&&count&&while(i&=start)//表示前半段数组中还有元素未放入临时数组{temp[k++]=arry[i--];}while(j&mid){temp[k++]=arry[j--];}//将临时数组中的元素写回到原数组当中去。for(i=0;i&k;i++)arry[end-i]=temp[i];printArray(arry,8);//输出进过一次归并以后的数组,用于理解整体过程return&}int&InversePairsCore(int&arry[],int&start,int&end,int&temp[]){int&inversions&=&0;if(start&end){int&mid=(start+end)/2;inversions+=InversePairsCore(arry,start,mid,temp);//找左半段的逆序对数目inversions+=InversePairsCore(arry,mid+1,end,temp);//找右半段的逆序对数目inversions+=MergeArray(arry,start,mid,end,temp);//在找完左右半段逆序对以后两段数组有序,然后找两段之间的逆序对。最小的逆序段只有一个元素。}return&}int&InversePairs(int&arry[],int&len){int&*temp=new&int[len];int&count=InversePairsCore(arry,0,len-1,temp);delete[]&return&}void&main(){//int&arry[]={7,5,6,4};int&arry[]={1,3,7,8,2,4,6,5};int&len=sizeof(arry)/sizeof(int);//printArray(arry,len);int&count=InversePairs(arry,len);//printArray(arry,len);//cout&&count&&system("pause");}例题2、有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个文件进行归并排序(内排序与外排序相结合)。例题3、归并一个左右两边分别排好序的数组,空间复杂度要求O(1)。使用原地归并,能够让归并排序的空间复杂度降为O(1),但是速度上会有一定程度的下降。代码如下:[cpp] #include&iostream&#include&cmath&#include&cstdlib&#include&Windows.h&using&namespace&void&insert_sort(int&arr[],int&n){for(int&i=1;i&n;++i){int&val=arr[i];int&j=i-1;while(arr[j]&val&&j&=0){arr[j+1]=arr[j];--j;}arr[j+1]=}}void&aux_merge(int&arr[],int&n,int&m,int&aux[]){for(int&i=0;i&m;++i)swap(aux[i],arr[n+i]);int&p=n-1,q=m-1;int&dst=n+m-1;for(int&i=0;i&n+m;++i){if(p&=0){if(q&=0){if(arr[p]&aux[q]){swap(arr[p],arr[dst]);p--;}else{swap(aux[q],arr[dst]);q--;}}else}else{swap(aux[q],arr[dst]);q--;}dst--;}}void&local_merge(int&arr[],int&n){int&m=sqrt((float)n);int&k=n/m;for(int&i=0;i&m;++i)swap(arr[k*m-m+i],arr[n/2/m*m+i]);for(int&i=0;i&k-2;++i){int&index=i;for(int&j=i+1;j&k-1;++j)if(arr[j*m]&arr[index*m])index=j;if(index!=i)for(int&j=0;j&m;++j)swap(arr[i*m+j],arr[index*m+j]);}for(int&i=0;i&k-2;++i)aux_merge(arr+i*m,m,m,arr+(k-1)*m);int&s=n%m+m;insert_sort(arr+(n-2*s),2*s);aux_merge(arr,n-2*s,s,arr+(k-1)*m);insert_sort(arr+(k-1)*m,s);}void&local_merge_sort(int&arr[],int&n){if(n&=1)if(n&=10){insert_sort(arr,n);}local_merge_sort(arr,n/2);local_merge_sort(arr+n/2,n-n/2);local_merge(arr,n);}void&merge_sort(int&arr[],int&temp[],int&n){if(n&=1)if(n&=10){insert_sort(arr,n);}merge_sort(arr,temp,n/2);merge_sort(arr+n/2,temp,n-n/2);for(int&i=0;i&n/2;++i)temp[i]=arr[i];for(int&i=n/2;i&n;++i)temp[n+n/2-i-1]=arr[i];int&left=0,right=n-1;for(int&i=0;i&n;++i)if(temp[left]&temp[right])arr[i]=temp[left++];elsearr[i]=temp[right--];}const&int&n=2000000;int&arr1[n],arr2[n];int&temp[n];int&main(){for(int&i=0;i&n;++i)arr1[i]=arr2[i]=rand();int&begin=GetTickCount();merge_sort(arr1,temp,n);cout&&GetTickCount()-begin&&begin=GetTickCount();local_merge_sort(arr2,n);cout&&GetTickCount()-begin&&for(int&i=0;i&n;++i)if(arr1[i]!=arr2[i])cout&&"ERROR"&&system("pause");}十、桶排序1)算法简介桶排序&(Bucket&sort)或所谓的箱排序,是一个排序算法,工作的原理是将数组分到有限数量的桶子里。每个桶子再个别排序(有可能再使用别的排序算法或是以递归方式继续使用桶排序进行排序)。桶排序是稳定的,且在大多数情况下常见排序里最快的一种,比快排还要快,缺点是非常耗空间,基本上是最耗空间的一种排序算法,而且只能在某些情形下使用。2)算法描述和分析桶排序具体算法描述如下:1、设置一个定量的数组当作空桶子。2、寻访串行,并且把项目一个一个放到对应的桶子去。3、对每个不是空的桶子进行排序。4、从不是空的桶子里把项目再放回原来的串行中。桶排序最好情况下使用线性时间O(n),很显然桶排序的时间复杂度,取决与对各个桶之间数据进行排序的时间复杂度,因为&其它部分的时间复杂度都为O(n);很显然,桶划分的越小,各个桶之间的数据越少,排&序所用的时间也会越少。但相应的空间消耗就会增大。可以证明,即使选用插入排序作为桶内排序的方法,桶排序的平均时间复杂度为线性。&具体证明,请参考算法导论。其空间复杂度也为线性。3)算法图解、flash演示、视频演示图解Flash:可以参考中的过程视频:这里就不给出桶排序的视频了,见上flash吧4)算法代码[cpp] #include&&time.h&#include&&iostream&#include&&iomanip&using&namespace&&/*initial&arr*/void&InitialArr(double&*arr,int&n){srand((unsigned)time(NULL));for&(int&i&=&0;&i&n;i++){arr[i]&=&rand()/double(RAND_MAX+1);&&&//(0.1)}}/*&print&arr*/void&PrintArr(double&*arr,int&n){for&(int&i&=&0;i&&&n;&i++){cout&&setw(15)&&arr[i];if&((i+1)%5&==&0&||&i&==&&n-1){cout&&}}}void&BucketSort(double&*&arr,int&n){double&**bucket&=&new&double*[10];for&(int&i&=&0;i&10;i++){bucket[i]&=&new&double[n];}int&count[10]&=&{0};for&(int&i&=&0&;&i&&&n&;&i++){double&temp&=&arr[i];int&flag&=&(int)(arr[i]*10);&//flag标识小树的第一位bucket[flag][count[flag]]&=&&//用二维数组的每个向量来存放小树第一位相同的数据int&j&=&count[flag]++;/*&利用插入排序对每一行进行排序&*/for(;j&&&0&&&&temp&&&bucket[flag][j&-&1];&--j){bucket[flag][j]&=&bucket[flag][j-1];}bucket[flag][j]&=}/*&所有数据重新链接&*/int&k=0;for&(int&i&=&0&;&i&&&10&;&i++){for&(int&j&=&0&;&j&&count[i];j++){arr[k]&=&bucket[i][j];k++;}}for&(int&i&=&0&;&i&10&;i++){delete&bucket[i];bucket[i]&=NULL;}delete&[]bucket&=&NULL;}void&main(){double&*arr=new&double[10];InitialArr(arr,&10);BucketSort(arr,&10);PrintArr(arr,10);delete&[]&}5)考察点、重点和频度分析桶排序是一种很巧妙的排序方法,在处理密集型数排序的时候有比较好的效果(主要是这种情况下空间复杂度不高),其思想也可用在很多算法题上,详见后续笔试面试算法例题。6)笔试面试题例题1、一年的全国高考考生人数为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亿个桶显然是不可能的。所以桶排序有其局限性,适合元素值集合并不大的情况。例题2、在一个文件中有&10G&个整数,乱序排列,要求找出中位数。内存限制为&2G。只写出思路即可(内存限制为&2G的意思就是,可以使用2G的空间来运行程序,而不考虑这台机器上的其他软件的占用内存)。分析:&既然要找中位数,很简单就是排序的想法。那么基于字节的桶排序是一个可行的方法。思想:将整型的每1byte作为一个关键字,也就是说一个整形可以拆成4个keys,而且最高位的keys越大,整数越大。如果高位keys相同,则比较次高位的keys。整个比较过程类似于字符串的字典序。按以下步骤实施:1、把10G整数每2G读入一次内存,然后一次遍历这536,870,912即(24)*2&/4个数据。每个数据用位运算"&&"取出最高8位(31-24)。这8bits(0-255)最多表示255个桶,那么可以根据8bit的值来确定丢入第几个桶。最后把每个桶写入一个磁盘文件中,同时在内存中统计每个桶内数据的数量,自然这个数量只需要255个整形空间即可。2、继续以内存中的整数的次高8bit进行桶排序(23-16)。过程和第一步相同,也是255个桶。3、一直下去,直到最低字节(7-0bit)的桶排序结束。我相信这个时候完全可以在内存中使用一次快排就可以了。例题3、给定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)为桶宽的话,答案一定不是属于同一个桶的两元素之差。因此,这样建桶,每次只保留桶里面的最大值和最小值即可。代码如下:[cpp] //距离平均值为offset&=&(arrayMax&-&arrayMin)&/&(n&-&1),&则距离最大的数必然大于这个值//每个桶只要记住桶中的最大值和最小值,依次比较上一个桶的最大值与下一个桶的最小值的差值,找最大的即可.#include&&iostream&#define&MAXSIZE&100&&&&//实数的个数#define&MAXNUM&32767using&namespace&struct&Barrel{double&&&&//桶中最小的数double&&&&//桶中最大的数bool&&&&//标记桶中有数};int&BarrelOperation(double*&array,&int&n){Barrel&barrel[MAXSIZE];&&//实际使用的桶int&nBarrel&=&0;&&//实际使用桶的个数Barrel&tmp[MAXSIZE];&&&//临时桶,用于暂存数据double&arrayMax&=&-MAXNUM,&arrayMin&=&MAXNUM;for(int&i&=&0;&i&&&n;&i++)&{if(array[i]&&&arrayMax)arrayMax&=&array[i];if(array[i]&&&arrayMin)arrayMin&=&array[i];}double&offset&=&(arrayMax&-&arrayMin)&/&(n&-&1);&&//所有数的平均间隔//对桶进行初始化for(i&=&0;&i&&&n;&i++)&{tmp[i].flag&=&tmp[i].max&=&arrayMtmp[i].min&=&arrayM}//对数据进行分桶for(i&=&0;&i&&&n;&i++)&{int&pos&=&(int)((array[i]&-&arrayMin)&/&offset);if(!tmp[pos].flag)&{tmp[pos].max&=&tmp[pos].min&=&array[i];tmp[pos].flag&=&}&else&{if(array[i]&&&tmp[pos].max)tmp[pos].max&=&array[i];if(array[i]&&&tmp[pos].min)tmp[pos].min&=&array[i];}}for(i&=&0;&i&&=&n;&i++)&{if(tmp[i].flag)barrel[nBarrel++]&=&tmp[i];}int&maxOffset&=&0.0;for(i&=&0;&i&&&nBarrel&-&1;&i++)&{if((barrel[i+1].min&-&barrel[i].max)&&&maxOffset)maxOffset&=&barrel[i+1].min&-&barrel[i].}return&maxO}int&main(){double&array[MAXSIZE]&=&{1,&8,&6,&11,&7,&13,&16,&5};&&//所需处理的数据int&n&=&8;&//数的个数//double&array[MAXSIZE]&=&{8,&6,&11};//int&n&=&3;int&maxOffset&=&BarrelOperation(array,&n);cout&&&&maxOffset&&&&return&0;}十一、计数排序1)算法简介计数排序(Counting&sort)是一种稳定的排序算法。计数排序使用一个额外的数组C,其中第i个元素是待排序数组A中值等于i的元素的个数。然后根据数组C来将A中的元素排到正确的位置。它只能对整数进行排序。2)算法描述和分析算法的步骤如下:1、找出待排序的数组中最大和最小的元素2、统计数组中每个值为i的元素出现的次数,存入数组C的第i项3、对所有的计数累加(从C中的第一个元素开始,每一项和前一项相加)4、反向填充目标数组:将每个元素i放在新数组的第C(i)项,每放一个元素就将C(i)减去1当输入的元素是n&个0到k之间的整数时,它的运行时间是&O(n&+&k)。计数排序不是比较排序,排序的速度快于任何比较排序算法。由于用来计数的数组C的长度取决于待排序数组中数据的范围(等于待排序数组的最大值与最小值的差加上1),这使得计数排序对于数据范围很大的数组,需要大量时间和内存。例如:计数排序是用来排序0到100之间的数字的最好的算法,但是它不适合按字母顺序排序人名。但是,计数排序可以用在基数排序中的算法来排序数据范围很大的数组。3)算法图解、flash演示、视频演示图解:我们使用计数排序对一个乱序的整数数组进行排序。首先创建一个临时数组(长度为输入数据的最大间隔),对于每一个输入数组的整数k,我们在临时数组的第k位置"1"。如下图上图中,第一行表示输入数据,第二行表示创建的临时数据,临时数组的下标代表输入数据的某一个值,临时数组的值表示输入数据中某一个值的数量。如果输入数据中有重复的数值,那么我们增加临时数组相应的值(比如上图中5有3个,所以小标为5的数组的值是3)。在“初始化”临时数组以后,我们就得到了一个排序好的输入数据。我们顺序遍历这个数组,将下标解释成数据,&将该位置的值表示该数据的重复数量,记得得到一个排序好的数组。Flash:可参见中的flash过程视频:前面的flash已经能够清晰地表示出整个计数排序的过程了,这里就不推荐视频了4)算法代码[cpp] #include&&stdlib.h&#include&&string.h&#include&&stdio.h&/**************************************************************功能:计数排序。参数:&data&:&要排序的数组size&:数组元素的个数k&&&:数组中元素数组最大值&+1&(这个需要+1)返回值:&成功0;失败-1.*************************************************************/int&ctsort(int&*data,&int&size,&int&k){int&*&counts&=&NULL,/*计数数组*/*&temp&=&NULL;/*保存排序后的数组*/int&i&=&0;/*申请数组空间*/if&((counts&=&(int&*)&malloc(&k&*&sizeof(int)))&==&NULL)return&-1;if&((temp&=&(int&*)&malloc(&k&*&sizeof(int)))&==&NULL)return&-1;/*初始化计数数组*/for&(i&=&0;&i&&&k;&i&++)counts[i]&=&0;/*数组中出现的元素,及出现次数记录*/for(i&=&0;&i&&&&i++)counts[data[i]]&+=&1;/*调整元素计数中,加上前一个数*/for&(i&=&1;&i&&&k;&i++)counts[i]&+=&counts[i&-&1];/*使用计数数组中的记录数值,来进行排序,排序后保存的temp*/for&(i&=&size&-1;&i&&=&0;&i&--){temp[counts[data[i]]&-&1]&=&data[i];counts[data[i]]&-=&1;}memcpy(data,temp,size&*&sizeof(int));free(counts);free(temp);return&0;}int&main(){int&a[8]&=&{2,0,2,1,4,6,7,4};int&max&=&a[0],i&=&0;/*获得数组中中的数值*/for&(&i&=&1;&i&&&8;&i++){if&(a[i]&&&max)max&=&a[i];}ctsort(a,8,max+1);for&(i&=&0;i&&&8;i&++)printf("%d\n",a[i]);}5)考察点、重点和频度分析计数排序在处理密集整数排序的问题的时候非常有限,尤其是有时候题目对空间并不做太大限制,那使用计数排序能够达到O(n)的时间复杂度,远快于所有基于比较的其他排序方法。6)笔试面试题例题1、某地区年龄排序问题够典型的计数排序吧,年龄的区间也就那么大,代码就不上了,请参照上述参照计数排序算法。十二、基数排序1)算法简介基数排序是一种非比较型整数排序算法,其原理是将整数按位数切割成不同的数字,然后按每个位数分别比较。由于整数也可以表达字符串(比如名字或日期)和特定格式的浮点数,所以基数排序也不是只能使用于整数。基数排序的发明可以追溯到1887年赫尔曼·何乐礼在打孔卡片制表机(Tabulation&Machine)上的贡献。2)算法描述和分析整个算法过程描述如下:1、将所有待比较数值(正整数)统一为同样的数位长度,数位较短的数前面补零。2、从最低位开始,依次进行一次排序。3、这样从最低位排序一直到最高位排序完成以后,&数列就变成一个有序序列。基数排序的时间复杂度是&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)&。3)算法图解、flash演示、视频演示图解:Flash:可参见中的flash过程视频:4)算法代码[cpp] #include&&stdio.h&#include&&stdlib.h&void&radixSort(int&data[])&{int&temp[10][10]&=&{0};int&order[10]&=&{0};int&n&=&1;while(n&&=&10)&{int&i;for(i&=&0;&i&&&10;&i++)&{int&lsd&=&((data[i]&/&n)&%&10);temp[lsd][order[lsd]]&=&data[i];order[lsd]++;}//&重新排列int&k&=&0;for(i&=&0;&i&&&10;&i++)&{if(order[i]&!=&0)&&{int&j;for(j&=&0;&j&&&order[i];&j++,&k++)&{data[k]&=&temp[i][j];}}order[i]&=&0;}n&*=&10;}}int&main(void)&{int&data[10]&=&{73,&22,&93,&43,&55,&14,&28,&65,&39,&81};printf("\n排序前:&");int&i;for(i&=&0;&i&&&10;&i++)printf("%d&",&data[i]);putchar('\n');radixSort(data);printf("\n排序後:&");for(i&=&0;&i&&&10;&i++)printf("%d&",&data[i]);return&0;}5)考察点、重点和频度分析计数排序在处理密集整数排序的问题的时候非常有限,尤其是有时候题目对空间并不做太大限制,那使用计数排序能够达到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)稳定时间复杂度递归思想即
来自:&&&《》
更多精彩,关注微信号:360doc
馆友评论(0)
您好,请&&或者&&后再进行评论
合作登录:
(window.slotbydup = window.slotbydup || []).push({
container: s,
id: 'u1442766',
scale: '20.3',
display: 'inlay-fix'}

我要回帖

更多关于 中码是什么字母 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信