基本思想:将有序序列等分为几乎相等的两部份,待查关键字和划分元比较。如果小于划分元,则递归处理左半部分;否则处理右半部分。
非递归算法:
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)