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

来源:互联网 发布:管道安装设计软件 编辑:程序博客网 时间:2024/05/23 20:40

二分查找

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

非递归算法:

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=dd=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)
0 0