剑指offer之面试题:查找和排序

二分查找

基本思想:将有序序列等分为几乎相等的两部份,待查关键字和划分元比较。如果小于划分元,则递归处理左半部分;否则处理右半部分。

非递归算法:

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)

你可能感兴趣的:(剑指offer之面试题:查找和排序)