给定两个数组X和Y,元素都是正数。请找出满足如下条件的数对的数目: x^y > y^x,即x的y次方>y的x次方 x来自X数组,y来自Y数组 |
假设数组X的长度为m,数组Y的长度为n,最直接的暴力法,时间复杂度为O(m*n),但这样的话,并不需要都是正数这个条件的。那么,我们该如何优化呢?
x^y>y^x,对于x和y来讲,有什么规律呢?该如何发现呢?这里其实有规律的,大多数的条件下,当y>x的时 候,x^y>y^x,但是有一些例外,1,2,3,4几个数,需要特殊的考虑,比如2^4=4^2。这个大家可以通过在纸上写写画画来得到,相对繁琐,我们就不进一步分析了。
我们可否对于原式做一些数学变换呢?使得式子变化简单。如何去做呢?这个式子的复杂体现在两边都是指数的形式,如何变化一下呢?我们很自然的就想到,逆运算对数运算,则,两边取对数可得:ylog(x)>xlog(y)。这里同学们可能要问,可以直接取对数么?取对数之后,大小关系仍旧满足 么?这里是有两点保证的:
对数函数的性质,单调递增
题目中的说明:元素都是正数
对于式子:ylog(x)>xlog(y),x和y都是正数,则进一步有:两边同时除以xy,则:log(x)/x >log(y)/y。这个式子,看起来也复杂,但是,x和y都在各自的一边,要简单的多。
对于log(x)/x >log(y)/y,
数组X和Y分别计算log(x)/x,log(y)/y
然后对Y进行排序O(nlogn)
遍历X数组,对于每一个x,在Y中,进行二分查找,即可。
总的时间复杂度为O(nlogn + mlogn).
考点 - 算法 |
Dijikstra算法
SPFA算法
数据结构的各种排序(常 用的冒泡、快速、插入、拓扑等)、查找(二分),包括效率方面
? 二叉查找树
? 顺序表的特点
? 邻接图(表)的深度遍历
? 递归(计算函数的调用次数)
? 单循环链表求长度和判断是否为空链
? 二路归并排序
? 佛洛依德算法。
? 目前效率最好的内部排序是什么?
? 如何判断一棵树是平衡二叉树?
? 现场手写代码,实现一个双向循环链表的增删查操作。
? 求最大子串和,说思路
? 哈夫曼树的构造与带权路径长度的计算。
? 给出一个表达式(如:a*(b+c)-d ),求它的后序遍历结果。
? 森林与二叉树的转换 。
? 连通图-最短路径的计算。
? 递归算法- 斐波拉契函数中F(5)共执行了多少次加法?
? 快排–给一组数,问执行一次快排以后的结果是多少?
? 给一组数的3次排序的结果,问是哪种排序算法得到的?
? 堆排序,内部是怎么存储的,时间复杂度是什么。各种排序算法。
? 描述:求单源最短路的SPFA算法的全称是:ShortestPathFasterAlgorithm。
? 重建二叉树
? 链表反转
? Catalan数的计算
? 排序算法适用场合(插入排序,快速排序)
? LIS问题的时空复杂度。
? 知道多少种排序算法,希尔排序的时间复杂度
? 关于二叉树的问题:
1、常用的平衡二叉树有哪些?
2、二叉树不平衡怎么办?
? 关于排序算法的问题:
1、你熟悉的排序算法有哪些?
2、归并排序的原理是什么?它的时间复杂度怎么算?
树的序列化。
KMP算法(字符串匹配算法)
海量数据处理
图算法:SPFA算法
回溯算法
动态规划
分治法递归式求解
重建二叉树
链表反转
Catalan数的计算
排序算法试用场合(插入排序快速排序)
2 关键码序列(Q,H,C,Y,Q,A,M,S,R,D,F,X),要按照关键码值递增的次序进行排序,若采用初始步长为4的Shell的排序法,则一趟扫描的结果是QACSQDFXRHMY;若采用以第一个元素为分界元素的快速排序法,则扫描一趟的结果是FHCDQAMQRSYX。
AVL树 - 数据结构 |
排序算法总结 |
各种排序算法的特点,时间复杂度,稳定性等http://blog.csdn.net/kuzuozhou/article/details/7989542
选择排序、快速排序、希尔排序、堆排序不是稳定的排序算法,
冒泡排序、插入排序、归并排序和基数排序是稳定的排序算法。
冒泡法: 这是最原始,也是众所周知的最慢的算法了。他的名字的由来因为它的工作看来象是冒泡: 复杂度为O(n*n)。当数据为正序,将不会有交换。复杂度为O(0)。
直接插入排序:O(n*n)
选择排序:O(n*n)
快速排序:平均时间复杂度log2(n)*n,所有内部排序方法中最高好的,大多数情况下总是最好的。
归并排序:log2(n)*n
堆排序:log2(n)*n
希尔排序:算法的复杂度为n的1.2次幂
这里我没有给出行为的分析,因为这个很简单,我们直接来分析算法:
首先我们考虑最理想的情况 1.数组的大小是2的幂,这样分下去始终可以被2整除。假设为2的k次方,即k=log2(n)。 2.每次我们选择的值刚好是中间值,这样,数组才可以被等分。 第一层递归,循环n次,第二层循环2*(n/2)...... 所以共有n+2(n/2)+4(n/4)+...+n*(n/n)= n+n+n+...+n=k*n=log2(n)*n 所以算法复杂度为O(log2(n)*n) 其他的情况只会比这种情况差,最差的情况是每次选择到的middle都是最小值或最大值,那么他将变成交换法(由于使用了递归,情况更糟)。但是你认为这种情况发生的几率有多大??呵呵,你完全不必担心这个问题。实践证明,大多数的情况,快速排序总是最好的。如果你担心这个问题,你可以使用堆排序,这是一种稳定的O(log2(n)*n)算法,但是通常情况下速度要慢 于快速排序(因为要重组堆)。
这几天笔试了好几次了,连续碰到一个关于常见排序算法稳定性判别的问题,往往还是多选,对于我以及和我一样拿不准的同学可不是一个能轻易下结论的题目,当然如果你笔试之前已经记住了数据结构书上哪些是稳定的,哪些不是稳定的,做起来应该可以轻松搞定。
本文是针对老是记不住这个或者想真正明白到底为什么是稳定或者不稳定的人准备的。
首先,排序算法的稳定性大家应该都知道,通俗地讲就是能保证排序前2个相等的数其在序列的前后位置顺序和排序后它们两个的前后位置顺序相同。在简单形式化一下,如果Ai = Aj, Ai原来在位置前,排序后Ai还是要在Aj位置前。
其次,说一下稳定性的好处。排序算法如果是稳定的,那么从一个键上排序,然后再从另一个键上排序,第一个键排序的结果可以为第二个键排序所用。基数排序就是这样,先按低位排序,逐次按高位排序,低位相同的元素其顺序再高位也相同时是不会改变的。另外,如果排序算法稳定,对基于比较的排序算法而言,元素交换的次数可能会少一些(个人感觉,没有证实)。
回到主题,现在分析一下常见的排序算法的稳定性,每个都给出简单的理由。
(1)冒泡排序
冒泡排序就是把小的元素往前调或者把大的元素往后调。比较是相邻的两个元素比较,交换也发生在这两个元素之间。所以,如果两个元素相等,我想你是不会再无聊地把他们俩交换一下的;如果两个相等的元素没有相邻,那么即使通过前面的两两交换把两个相邻起来,这时候也不会交换,所以相同元素的前后顺序并没有改变,所以冒泡排序是一种稳定排序算法。
(2)选择排序
选择排序是给每个位置选择当前元素最小的,比如给第一个位置选择最小的,在剩余元素里面给第二个元素选择第二小的,依次类推,直到第n-1个元素,第n个元素不用选择了,因为只剩下它一个最大的元素了。那么,在一趟选择,如果当前元素比一个元素小,而该小的元素又出现在一个和当前元素相等的元素后面,那么交换后稳定性就被破坏了。比较拗口,举个例子,序列5 8 5 2 9, 我们知道第一遍选择第1个元素5会和2交换,那么原序列中2个5的相对前后顺序就被破坏了,所以选择排序不是一个稳定的排序算法。
(3)插入排序 插入排序是在一个已经有序的小序列的基础上,一次插入一个元素。当然,刚开始这个有序的小序列只有1个元素,就是第一个元素。比较是从有序序列的末尾开始,也就是想要插入的元素和已经有序的最大者开始比起,如果比它大则直接插入在其后面,否则一直往前找直到找到它该插入的位置。如果碰见一个和插入元素相等的,那么插入元素把想插入的元素放在相等元素的后面。所以,相等元素的前后顺序没有改变,从原无序序列出去的顺序就是排好序后的顺序,所以插入排序是稳定的。
(4)快速排序 快速排序有两个方向,左边的i下标一直往右走,当a[i] <= a[center_index],其中center_index是中枢元素的数组下标,一般取为数组第0个元素。而右边的j下标一直往左走,当a[j] > a[center_index]。如果i和j都走不动了,i <=j, 交换a[i]和a[j],重复上面的过程,直到i>j。 交换a[j]和a[center_index],完成一趟快速排序。在中枢元素和a[j]交换的时候,很有可能把前面的元素的稳定性打乱,比如序列为 5 3 34 3 8 9 10 11, 现在中枢元素5和3(第5个元素,下标从1开始计)交换就会把元素3的稳定性打乱,所以快速排序是一个不稳定的排序算法,不稳定发生在中枢元素和a[j]交换的时刻。
(5)归并排序 归并排序是把序列递归地分成短序列,递归出口是短序列只有1个元素(认为直接有序)或者2个序列(1次比较和交换),然后把各个有序的段序列合并成一个有序的长序列,不断合并直到原序列全部排好序。可以发现,在1个或2个元素时,1个元素不会交换,2个元素如果大小相等也没有人故意交换,这不会破坏稳定性。那么,在短的有序序列合并的过程中,稳定是是否受到破坏?没有,合并过程中我们可以保证如果两个当前元素相等时,我们把处在前面的序列的元素保存在结果序列的前面,这样就保证了稳定性。所以,归并排序也是稳定的排序算法。
(6)基数排序 基数排序是按照低位先排序,然后收集;再按照高位排序,然后再收集;依次类推,直到最高位。有时候有些属性是有优先级顺序的,先按低优先级排序,再按高优先级排序,最后的次序就是高优先级高的在前,高优先级相同的低优先级高的在前。基数排序基于分别排序,分别收集,所以其是稳定的排序算法。
(7)希尔排序(shell) 希尔排序是按照不同步长对元素进行插入排序,当刚开始元素很无序的时候,步长最大,所以插入排序的元素个数很少,速度很快;当元素基本有序了,步长很小,插入排序对于有序的序列效率很高。所以,希尔排序的时间复杂度会比o(n^2)好一些。由于多次插入排序,我们知道一次插入排序是稳定的,不会改变相同元素的相对顺序,但在不同的插入排序过程中,相同的元素可能在各自的插入排序中移动,最后其稳定性就会被打乱,所以shell排序是不稳定的。
(8)堆排序 我们知道堆的结构是节点i的孩子为2*i和2*i+1节点,大顶堆要求父节点大于等于其2个子节点,小顶堆要求父节点小于等于其2个子节点。在一个长为n的序列,堆排序的过程是从第n/2开始和其子节点共3个值选择最大(大顶堆)或者最小(小顶堆),这3个元素之间的选择当然不会破坏稳定性。但当为n/2-1, n/2-2, ...1这些个父节点选择元素时,就会破坏稳定性。有可能第n/2个父节点交换把后面一个元素交换过去了,而第n/2-1个父节点把后面一个相同的元素没有交换,那么这2个相同的元素之间的稳定性就被破坏了。所以,堆排序不是稳定的排序算法
1 快速排序(QuickSort)快速排序是一个就地排序,分而治之,大规模递归的算法。从本质上来说,它是归并排序的就地版本。快速排序可以由下面四步组成。(1) 如果不多于1个数据,直接返回。(2)一般选择序列最左边的值作为支点数据。(3) 将序列分成2部分,一部分都大于支点数据,另外一部分都小于支点数据。(4) 对两边利用递归排序数列。快速排序比大部分排序算法都要快。尽管我们可以在某些特殊的情况下写出比快速排序快的算法,但是就通常情况而言,没有比它更快的了。快速排序是递归的,对于内存非常有限的机器来说,它不是一个好的选择。 2 归并排序(MergeSort)归并排序先分解要排序的序列,从1分成2,2分成4,依次分解,当分解到只有1个一组的时候,就可以排序这些分组,然后依次合并回原来的序列中,这样就可以排序所有数据。合并排序比堆排序稍微快一点,但是需要比堆排序多一倍的内存空间,因为它需要一个额外的数组。3 堆排序(HeapSort)堆排序适合于数据量非常大的场合(百万数据)。堆排序不需要大量的递归或者多维的暂存数组。这对于数据量非常巨大的序列是合适的。比如超过数百万条记录,因为快速排序,归并排序都使用递归来设计算法,在数据量非常大的时候,可能会发生堆栈溢出错误。堆排序会将所有的数据建成一个堆,最大的数据在堆顶,然后将堆顶数据和序列的最后一个数据交换。接下来再次重建堆,交换数据,依次下去,就可以排序所有的数据。4 Shell排序(ShellSort)Shell排序通过将数据分成不同的组,先对每一组进行排序,然后再对所有的元素进行一次插入排序,以减少数据交换和移动的次数。平均效率是O(nlogn)。其中分组的合理性会对算法产生重要的影响。现在多用D.E.Knuth的分组方法。Shell排序比冒泡排序快5倍,比插入排序大致快2倍。Shell排序比起QuickSort,MergeSort,HeapSort慢很多。但是它相对比较简单,它适合于数据量在5000以下并且速度并不是特别重要的场合。它对于数据量较小的数列重复排序是非常好的。5 插入排序(InsertSort)插入排序通过把序列中的值插入一个已经排序好的序列中,直到该序列的结束。插入排序是对冒泡排序的改进。它比冒泡排序快2倍。一般不用在数据大于1000的场合下使用插入排序,或者重复排序超过200数据项的序列。6 冒泡排序(BubbleSort)冒泡排序是最慢的排序算法。在实际运用中它是效率最低的算法。它通过一趟又一趟地比较数组中的每一个元素,使较大的数据下沉,较小的数据上升。它是O(n^2)的算法。7 交换排序(ExchangeSort)和选择排序(SelectSort)这两种排序方法都是交换方法的排序算法,效率都是 O(n2)。在实际应用中处于和冒泡排序基本相同的地位。它们只是排序算法发展的初级阶段,在实际中使用较少。8 基数排序(RadixSort)基数排序和通常的排序算法并不走同样的路线。它是一种比较新颖的算法,但是它只能用于整数的排序,如果我们要把同样的办法运用到浮点数上,我们必须了解浮点数的存储格式,并通过特殊的方式将浮点数映射到整数上,然后再映射回去,这是非常麻烦的事情,因此,它的使用同样也不多。而且,最重要的是,这样算法也需要较多的存储空间。9 总结下面是一个总的表格,大致总结了我们常见的所有的排序算法的特点。
排序法 |
平均时间 |
最差情形 |
稳定度 |
额外空间 |
备注 |
冒泡 |
O(n2) |
O(n2) |
稳定 |
O(1) |
n小时较好 |
交换 |
O(n2) |
O(n2) |
不稳定 |
O(1) |
n小时较好 |
选择 |
O(n2) |
O(n2) |
不稳定 |
O(1) |
n小时较好 |
插入 |
O(n2) |
O(n2) |
稳定 |
O(1) |
大部分已排序时较好 |
基数 |
O(logRB) |
O(logRB) |
稳定 |
O(n) |
B是真数(0-9), R是基数(个十百) |
Shell |
O(nlogn) |
O(ns) 1 |
不稳定 |
O(1) |
s是所选分组 |
快速 |
O(nlogn) |
O(n2) |
不稳定 |
O(nlogn) |
n大时较好 |
归并 |
O(nlogn) |
O(nlogn) |
稳定 |
O(1) |
n大时较好 |
堆 |
O(nlogn) |
O(nlogn) |
不稳定 |
O(1) |
n大时较好 |
——————————————————————————————————————
一、插入排序
1 排序思想
将待排序的记录Ri,插入到已排好序的记录表R1, R2 ,…., Ri-1中,得到一个新的、记录数增加1的有序表。 直到所有的记录都插入完为止。复杂度为O(n2) 。
设待排序的记录顺序存放在数组R[1…n]中,在排序的某一时刻,将记录序列分成两部分:
◆ R[1…i-1]:已排好序的有序部分;
◆ R[i…n]:未排好序的无序部分。
显然,在刚开始排序时,R[1]是已经排好序的。
例:设有关键字序列为:7, 4, -2, 19, 13, 6,直接插入排序的过程如下图所示:
代码如下:
//直接插入排序
[cpp] view plaincopy
1 void straight_insert_sort(int *L,intlength)
2 {
3 inti,j,temp ;
4 for(i=1; i<=length; i++)
5 {
6 temp=L[i];
7 j=i-1; /* 设置哨兵 */
8 while(temp 9 { L[j+1]=L[j]; 10 j--; 11 } /* 查找插入位置 */ 12 L[j+1]=temp; /* 插入到相应位置 */ 13 } 14 } 二、 选择排序 最简单的排序算法,过程如下:选出数组中最小的元素,将它与数组中第一个元素交换。然后找出次小的元素,将它与数组中第二个元素交换,一直持续下去,直到整个数组排序结束。之所叫选择排序,是因为它不断选出剩余元素中的最小元素来实现。 实现代码如下: [cpp] view plaincopy 15 void selection(Item a[],int l,int r) 16 { 17 int i,j; 18 for(i=l;i 19 { 20 intmin=i;//最小元素索引 21 for(j=i+1;j<=r;j++) 22 { 23 if(less(a[j]),a[min]) 24 { 25 min=j;//更新最小元素的索引值 26 } 27 } 28 exch(a[i],a[min]);//交换 29 } 30 31 } 选择排序的执行时间由比较操作的数目决定,所使用的次数大概是 :比较操作次数N*N/2,交换操作次数是 N次。而且选择排序的时间消耗与当前序列的排序状态无关。 三、 希尔排序 希尔排序时插入排序的扩展,他通过允许非相邻的元素进行交换来提高执行效率。希尔排序的本质:h-排序,是每第h个元素产生一个排序好的文件。h-排序的文件时h个独立的已排序好的文件,相互交叉在一起。对h值较大的h排序文件,可以移动相距较远的元素,比较容易的使h值较小时进行h排序,通过直到1的h值的排序,产生有序文件。 [cpp] view plaincopy 32 void hell(int *data,int left,int right) 33 { 34 intlen=right-left+1; 35 intd=len; 36 while(d>1) 37 { 38 d=(d+1)/2; 39 for(int i=left;i 40 { 41 if(data[i+d]
42 { 43 inttmp=data[i+d]; 44 data[i+d]=data[i]; 45 data[i]=tmp; 46 } 47 } 48 } 49 } 希尔排序要选择一个比较好的步长序列。 四、 冒泡排序 冒泡排序很简单:遍历文件,如果紧邻的两个元素顺序不对,就将两者交换,重复这个操作,直到整个序列排好序。对于l~r-1的i值,内部循环j通过从右到左遍历元素,对连续的元素执行比较—交换操作,实现将a[i],…a[r]中最小的元素放大a[i]中。在所有的比较操作中,最小的元素都要移动,冒到最前端。 [cpp] view plaincopy 50 void maopao(Item a[],int l,int r) 51 { 52 inti,j; 53 for(i=l;i 54 { 55 for(j=r;j>i;j--) 56 { 57 if(less(a[j],a[j-1])) 58 { 59 Itemtemp=a[j]; 60 a[j]=a[j-1]; 61 a[j-1]=temp; 62 } 63 } 64 } 65 } 在最坏情况和平均情况下,冒泡排序执行大约N*N/2次比较操作和N*N/2次交换操作。 五、快速排序 快速排序算法是一种分治排序算法,它将数组划分为两部分,然后分别对这两部分进行排序。它将重排序数组,使之满足一下三个条件: 1,对于某个i,a[i]在最终的位置上 2,a[l]…a[i-1]中的元素都比a[i]小 3,a[i+1] …a[r]中的元素都比a[i]大 通过划分后完成本轮排序,然后递归地处理子文件。 如果数组中有一个或者0个元素,就什么都不做。否则,调用以下算法实现快速排序: [cpp] view plaincopy 66 int partition(Itema[],int l,int r) 67 { 68 int i=l-1,j=r; 69 Itemv=a[r]; 70 for (;;) 71 { 72 while (less(a[++i],v)) 73 { 74 ; 75 } 76 77 while (less(v,a[--j])) 78 { 79 if (j==1) 80 { 81 break; 82 } 83 } 84 if (i>=j) 85 { 86 break; 87 } 88 exch(a[i],a[j]); 89 } 90 exch(a[i],a[r]); 91 92 return i; 93 } 变量v保存了划分元素a[r],i和j分别是左扫描指针和右扫描指针。划分循环使得i增加j减小,while循环保持一个不变的性质---------i左侧没有元素比v大,j右侧没有元素比v小。一旦两个指针相遇,我们就交换啊a[i]和a[r],,这样v左侧的元素都小于v,v右侧的元素都大于等于v,结束划分过程。 划分是一个不确定的过程,当两个指针相遇,就通过break语句结束。测试j==1用来防止划分元素是文件总的最小元素。 [cpp] view plaincopy 94 voidquichsort(Itema[],int l,int r) 95 { 96 int i; 97 if (r<=l) 98 { 99 return; 100 } 101 102 i=partition(a,l,r); 103 quichsort(a,l,i-1); 104 quichsort(a,i+1,r); 105 } 注:快速排序是一个递归划分过程,我们对一个文件进行划分,划分原则是将一些元素放在它最终的位置上,对数组进行排序使比划分元素小的元素都在划分元素的左边,比划分元素大的元素都在划分元素的右边,然后分别对左右两部分进行递归处理。然后,从数组的左端进行扫描,直到找到一个大于划分元素的元素,同时从数据的右端开始扫描,直到找到一个小于划分元素的元素。使扫描停止的这个两个元素,显然在最终的划分数组中的位置相反,于是交换二者。继续这一过程,我们就可以保证比划分元素小的元素都在划分元素的左边,比划分元素大的元素都在划分元素的右边。 堆排序算法 http://blog.csdn.net/v_JULY_v/article/details/6198644 精通八大排序算法系列:二、堆排序算法 原理熟悉 二路归并 - 排序算法 (磁盘文件的归并排序) 1 设有字母序列{Q,D,F,X,A,P,N,B,Y,M,C,W},请写出按二路归并方法对该序列进行一趟扫描后的结果为DQFXAPBNMYCW。 选择题 - 算法 判断有向图是否存在回路,利用(A)方法最佳 A、拓扑排序 B、求最短路径 C、求关键路径 D、广度优先遍历 下列有关图的遍历说法中,不正确的是(C) A、有向图和无向图都可以进行遍历操作 B、基本遍历算法两种:深度遍历和广度遍历 C、图的遍历必须用递归实现 D、图的遍历算法可以执行在有回路的图中 设计一个最优算法来查找一n个元素数组中的最大值和最小值,已知一种需要比较2n次的方法,请给一个更优的算法。请特别注意优化时间复杂度的常数。 给出该算法最坏情况下的比较次数和该算法的步骤描述。(不用写代码,不给出比较次数的不得分) 分析:已知的比较2n次的方法,显然是将每个元素和最大值、最小值各比一次,要减少比较次数,可以有多种优化方法: 方法一:一个元素先和最大值比较,如果比最大值大,就不用再和最小值比较(或者先和最小值比较,如果比最小值小,就不用再和最大值比较),一般情况下,这种优化后的比较次数一定会少于2n 方法二:将数组元素按两个,两个分组,组内两元素有序存放,之后最小值跟组内较小的值比较,最大值只需跟组内较大的值比较,这样每组的比较次数是3,共n/2组,总的时间复杂度是3n/2次。 详情请参考http://blog.csdn.net/thebestdavid/article/details/11975809的第3题 二叉查找树 转化为 排序双向链表