剑指offer之面试题:查找和排序
2016-04-16 12:15
781 查看
二分查找
基本思想:将有序序列等分为几乎相等的两部份,待查关键字和划分元比较。如果小于划分元,则递归处理左半部分;否则处理右半部分。非递归算法:
BinarySearch1(L[],n,x){ left=1; right=n; flag=0; while(left<=right&&flag==0){ mid=(left+right)/2; if(x==L[mid]){ flag=1; } else if(x>L[mid]){ left=mid+1; } else{ right=mid-1; } } if(flag==1)return mid; else return -1; }
递归实现
BinarySearch2(L[] ,x,i,j){ if(i>j)return -1; if(i==j){ if(L[i]==x)return i; else return -1; } else{ mid=(i+j)/2; if(x==L[mid)return mid; else if(x<L[mid])return BinarySearch2(L,x,i,mid-1); else return BinarySearch2(L,x,mid+1,j); } }
java实现
/** * */ package com.su.biancheng; /** * @title BinarySearch.java * @author Shuai * @date 2016-4-16上午11:34:58 */ public class BinarySearch { public static int BinarySearch(int[] array,int x){ /*if(array==null||array.length<=0) return -1;*/ int left=0; int right=array.length-1; int flag=0; int mid=0; while(left<=right&&flag==0){ mid=(left+right)/2; if(x==array[mid]) flag=1; else if(x<array[mid]) right=mid-1; else left=mid+1; } if(flag==1) return mid; return -1; } public static int BinarySearch2(int[] array,int x,int left,int right){ if(left>right) return -1; if(left==right){ if(x==array[left]) return left; else return -1; } else{ int mid=(left+right)/2; if(x==array[mid]) return mid; else if(x<array[mid]) return BinarySearch2(array,x,left,mid-1); else return BinarySearch2(array,x,mid+1,right); } } public static void main(String[] args){ int[] array={1,2,3,3,5,5,7,8,9}; int x=5; System.out.println(BinarySearch(array,x)); System.out.println(BinarySearch2(array,x,0,array.length-1)); } }
排序
快排序期望时间为O(nlogn)
基于比较的排序时间下界为logn!=nlogn-1.44n+O(logn)
快排序平均为1.39nlogn+O(n)
1 算法描述
(1) 方法:分治法
分解:
A[p..r]==>A[p..q-1]<=A[q]<A[q+1..r]
递归:递归对A[p..q-1]和A[q+1..r]进行快速排序,
临界条件:区间长度为1,空操作
合并:空操作,子数组原址排序,不需要合并
(2) 算法
QuickSort(A,p,r){ if(p<r){ q=partition(A,p,r); QuickSort(A,p,q-1); QuickSort(A,q+1,r); } } partition(A,p,r){ x=A[r]; i=p-1; for(j=p;j<r;j++){ if(A[j]<=x){ i++; swap(A[i],A[j]); } } swap(A[i+1],A[r]); return i+1; }
2 性能分析
(1)最坏的划分:A[p..q-1],A[q+1..r]中有一个区间是空的
T(n)=T(n-1)+T(0)+O(n) =T(n-1)+O(n) =O(n^2)
(2)最好的划分:一分为二,每个区间长度大致相等
T(n)=2T(n/2)+O(n) =O(nlogn)
(3)平衡划分:每次划分产生的区间为9:1(固定比例)
可用递归数得到
T(n)<=cn*h=cn*log(10/9)n=O(nlogn)
3 随机化版本:利用随机数发生器,随机产生划分元
将QuickSort算法中的partition改为
RandomizedPartition(A,p,r){ i=random(p,r); Swap(A[i],A[r]); return pratition(A,p,r); }
4 期望时间O(nlogn),证明看算法导论P101,7.4快速排序分析
快排序平均性能最优,但不是任何时候都是最优。如数组本身有序,而每一轮的排序都是以最后一个元素作为划分元,此时时间为O(n^2)。面试的时候,如果面试官要我们实现一个排序算法,首先要清楚这个排序的应用环境是什么、有哪些约束条件,然后在选择合适的排序算法。举个例子:
面试官:请实现一个排序算法,要求时间效率是O(n) 应聘者:对什么数字进行排序,有多少数字? 面试官:我们想对公司的员工的年龄排序,我们公司总共有几万名员工 应聘者:也就是数字的大小在一个较小的范围内,对吧? 面试官:恩,是的 应聘者:可以使用辅助空间吗? 面试官:看你用多少辅助内存,只允许使用常量大小的辅助空间,不得超过O(n)
根据以上交谈,不难想到时间O(n)的排序,常见的三种:
计数排序:n个输入元素的每一个都是在到k区间内的一个整数,k是某个整数,排序时间为O(n+k),k=O(n)时,O(n)。
基数排序:计数排序的一个扩展,非负整数,k进制表示不超过d位数。k为基,d为位数,时间为O(d(n+k)),k=O(n)且d为常数时,O(n)。
桶排序:输入是均匀分布在[0,1)上的实数。
符合题意的计数排序。
计数排序
1 基本思想
统计<=A[i]的元素数目,将A[i]置入相应位置,即A[i]–>B[<=A[i]的元素数目],主要解决的问题:
q1:计数,统计小于或等于A[i]的元素数目
q2:值相同元素的处理
2 特殊情形的计数排序
问题描述:n个互补相同的整数A[1..n],1<=A[i]<=n
算法:
SpecialCountingSort(A,B){ //B[1..n]为排序结果 for i=1 to n do{ B[A[i]]=A[i]; } }
3 一般情形的计数排序
问题描述:n个可以相同的整数A[1..n],1<=A[i]<=k
基本思想:A[1..n]–>计数器C[1..k]–>B[1..n]
s1:值相同元素计数:将A中值为i的元素个数计入C[i]中
s2:累计计数:对C[1..k]进行修改,使得C[i]的值表示为<=i的元素个数
s3:放置:将A[i]依据C[A[i]],放入正确的位置B[C[A[i]]],并修改C[A[i]]=C[A[i]]-1
算法:
CountingSort(A,B,k){ //let c[1..k] to be a new array for i=0 to k{ C[i]=0; } for j=1 to A.length{ C[A[j]]=C[A[j]]+1; } //C[i] now contains the number of elements equal to i for i=1 to k{ C[i]=C[i]+C[i-1]; } //C[i] now contains the number of elements less than or equal to i for j=A.length downto 1{ B[C[A[j]]]=A[j]; C[A[j]]=C[A[j]]-1; } }
演示例子可以参考算法导论P109图8-2
根据对技术排序的分析,可以写出上面面试官的年龄排序的代码
void SortAges(int[] ages,int length){ if(ages==null||ages.length<=0) return; int oldestAge=99; int timesOfAge[oldestAgs+1]; for(int i=0;i<=oldestAge;i++){ timesOfAge=0; } for(int i=0;i<length;i++){ int age=ages[i]; if(age<0||age>oldestAge) System.out.println("age out of range"); ++timesOfAge[age]; } int index=0; for(int i=0;i<=oldestAge;i++){ for(int j=0;j<timesOfAge[i];j++){ ages[index]=i; index++; } } }
基数排序
假定A[1..n]是非负整数,用k进制表示不超过d位
算法:
RadixSort(A,d){ for i=1 to d do{ 使用稳定的排序算法对A的第i位排序,如计数排序 } } 时间T(n)=O(d(n+k))//k为基,d为位数 =O(n)//如果k=O(n)且d为常数
演示例子见算法导论P110图8-3
问题?若d不为常数,基数排序算法还是线性时间吗?
设n个整数的取值范围是0-n^c,c是常数,c>=1
对于十进制数,n^c需要的位数d=log(10)n^c+1==log(10)n
T(n)=O(d(n+k))=O(nlogn)//k=10 不是线性时间
算法何时为线性时间?
Idea:只要是d变为常数,k变大到与n同阶 how to do: 选基k=n,则n^c的位数Log(n)n^c=c=d d=c,k=n,T(n)=O(n)
桶排序
基本思想:
假定:输入是均匀分布在[0,1)上的实数
s1:[0,1)划分为[0,1/n),[1/n,2/n),..,[k/n,(k+1)/n),[(n-1)/n,1)n个大小相等的子区间,每个子区间看做一个桶
s2:将n个元素分配到桶中
s3:对每个桶里的元素进行排序,依次连接桶
算法思想:
输入0<=A[1..n]<1
辅助数组B[0..n-1]是一个指针数组,指向每个桶
关键字映射:由于0<=A[1..n]<1,必须将A[i]映射到0,1,..,n-1上
因为[0,1)-->[0,n) //nA[i] 即k<=nA[i]<k+1 //存在k 所以桶号k=nA[i]
算法:
BucketSort(A){ time n=A.length; for i=1 to n do{ O(n) 将A[i]插入到链表B[nA[i]]中; } for i=0 to n-1 do{ O(n)* 用插入排序将B[i]排序; } 将B[0],B[1]..B[n-1]连接起来 O(n) } O(n)*因为n个数量是均匀分布在[0,1)中 所以每个桶中大约只有一个数,故时间为O(n)
相关文章推荐
- 个人总结面试题
- 从一次谷歌面试趣事中想到问题的更好的解决办法
- 面试总结 —— 高级JAVA工程师
- 新手感言!
- 查询出一张表中某个字段具有相同值的记录
- 【笔试/面试】—— 不使用大于、小于、if 语句,实现 max 宏
- Morgan IT 笔试面试汇总
- 程序员到底该如何学习?——初级
- 机器学习常见的算法面试题总结
- 【面试题】小数转换为人民币大写形式,PHP实现。
- JAVA面试经验宝典
- 高级工程师面试经验宝典
- Android 面试总结~~~
- 剑指offer 面试题16:反转链表(Leetcode 206: Reverse Linked List) 题解
- 程序员第45天
- 老码农的技术理想
- JAVA用双栈实现队列,纪念我挂掉的第一次腾讯面试题
- 面试路之算法总结(1)
- 剑指offer 面试题5:从尾到头打印链表 题解
- 面试路之书单(0)