查找和排序

查找和排序

查找和排序都是程序设计中经常用到的算法。查找相对而言较为简单,不外乎顺序查找、二分查找、哈希表查找和二叉排序树查找。二分查找一定要可以手写代码。

如果面试题是要求在排序的数组(或者部分排序的数组)中查找一个数字或者统计某个数字出现的次数,那么我们都可以尝试用二分查找法。

哈希表和二叉排序树查找的重点在于考察对应的数据结构而不是算法。哈希表最主要的优点是我们利用它能够在 O ( 1 ) O(1) O(1)的时间内查找某一元素,是效率最高的查找方式。但其缺点是需要额外的空间来实现哈希表。

排序比查找要复杂一些。面试官会经常要求面试者比较插入排序、冒泡排序、归并排序、快速排序等不同算法的优劣。在准备面试的时候一定要对各种排序算法的特点烂熟于心,能够从额外空间消耗、平均时间复杂度、最差时间复杂度等方面去比较他们的优缺点。

快速排序算法

实现快速排序算法的关键在于,先在数组中选择一个数字,接下来把数组中的数字分为两部分,比选择的数字小的数字移到数组的左边,比选择的数字大的数字移到数组的右边。

快速排序算法称为分治法。
第一版代码

#include<iostream>
using namespace std;

int AjustArray(int a[],int left,int right){
    int X = a[left];

    int i = left;
    int j=right;

    while(i<j){
        while (i < j && a[j] > X)
        {
            j--;
        }
        if (i < j)
        {
            a[i] = a[j];
            i++;
        }
        while (i < j && a[i] < X)
        {
            i++;
        }
        if (i < j)
        {
            a[j] = a[i];
            j--;
        }
    }
    a[i]=X;
    return i;
}
void quickSort(int a[],int left,int right){
    if(left<right){
        int m = AjustArray(a,left,right);
        quickSort(a,left,m-1);
        quickSort(a,m+1,right);
    }
    return;
}
void swap(int &a,int &b){
    int tmp=a;
    a=b;
    b=tmp;
}

int main(){
    int a[]={72,6,57,88,60,42,83,73,48,95};
    quickSort(a,0,9);
    for(int i=0;i<10;i++){
        cout<<a[i]<<" ";
    }
    cout<<endl;
    return 0;
}

更简洁代码

#include<iostream>
using namespace std;

void quickSort(int a[], int left, int right)
{
    if (left < right)
    {
        int X = a[left];

        int i = left;
        int j = right;

        while (i < j)
        {
            while (i < j && a[j] > X)
            {
                j--;
            }
            if (i < j)
            {
                a[i] = a[j];
                i++;
            }
            while (i < j && a[i] < X)
            {
                i++;
            }
            if (i < j)
            {
                a[j] = a[i];
                j--;
            }
        }
        a[i] = X;
        quickSort(a,left,i-1);
        quickSort(a,i+1,right);
    }
    
}
void swap(int &a,int &b){
    int tmp=a;
    a=b;
    b=tmp;
}

int main(){
    int a[]={72,6,57,88,60,42,83,73,48,95};
    quickSort(a,0,9);
    for(int i=0;i<10;i++){
        cout<<a[i]<<" ";
    }
    cout<<endl;
    return 0;
}

快速排序还有很多改进版本,如随机选择基准数,区间内数据较少时直接用另外的方法排序以减少递归深度。

剑指offer上的快排版本,其中Partion函数很重要!!!

#include<iostream>
#include<string.h>
#include<random>
using namespace std;

int Partition(int data[],int start,int end){
    srand(time(0));

    if(data==nullptr  || start<0){
        return -1;
    }

    int  index=(rand()%(end-start+1))+start;
    // cout<<index<<endl;
    swap(data[index],data[end]);

    int small = start -1;
    for (index=start;index<end;index++){
        if(data[index]<data[end]){
            ++small;
            if(small!=index){ //small和index不同步,说明有大于的情况出现,此时small指向第一个大于end端的值,交换
                swap(data[index],data[small]);//
            }
        }
    }
    ++small;
    swap(data[index],data[small]);
    return small;
}

void QuickSort(int a[],int start,int end){
    if(start==end){
        return;
    }
    int index=Partition(a,start,end);
    if(index<end){
        QuickSort(a,index+1,end);
    }
    if(index>start){
        QuickSort(a,start,index-1);
    }
}

int main(){
    int a[]={2,3,1,6,9,4,5,7};
    
    QuickSort(a,0,7);
    for(int i=0;i<8;i++){
        cout<<a[i]<<" ";
    }
    cout<<endl;
    return 0;
}

函数Partion除了可以用在快速排序中,还可以用来实现长度为 n n n的数组中查找第 k k k大数字。

如果面试官要求实现一个排序算法,那么应聘者一定要问清楚这个排序应用的环境是什么、有哪些约束条件,在得到足够的信息之后再选择合适的排序算法。

11 旋转数组的最小数字

在这里插入图片描述
思路:既然是旋转数组,那就从数组末尾开始找,找到该元素的前一个元素大于该元素,则该元素是答案。

class Solution {
public:
    int minArray(vector<int>& numbers) {
        int len = numbers.size();
        if(len<=0){
            return -1;
        }
        int res=numbers[len-1];
        for(int i=len-2;i>=0;i--){
            if(numbers[i]<res){
                res=numbers[i];
            }
        }
        return res;

    }
};

在这里插入图片描述
时间复杂度 O ( n ) O(n) O(n),空间复杂度 O ( 1 ) O(1) O(1)

优化的解法
在排序的数组中使用二分查找法实现 O ( l o g n ) O(logn) O(logn)的查找。

和二分查找法一样,用两个指针分别指向数组的第一个元素和最后一个元素。按照题目中旋转的规则,第一个元素应该大于等于最后一个元素(这其实不完全对,还有特例,比如数组没有进行旋转)。

接着我们可以找到数组的中间元素。如果中间元素位于前面的递增数组,那么它就大于等于第一个元素(如果中间元素位于后面的递增数组,那么它就小于等于最后一个元素),此时,数组中最小的元素应该位于该中间元素的后面。就可以把第一个指针指向中间元素,这样可以减少寻找的范围,移动之后的第一个指针仍然位于前面的递增数组。

按照上述思路,第一个指针总是指向前面递增数组的元素,最后一个指针总是指向后面递增数组的元素。最终,第一个指针将指向前面子数组的最后一个元素,而第二个指针将指向后面子数组的第一个元素。也就是他们最终会指向两个相邻的元素,而第二个指针指向的刚好是最小的元素。这就是循环结束的条件。

按照定义,还有一个特例:如果把排序数组的前面的0个元素搬到后面,也就是排序树组本身,这仍然是数组的一个旋转。此时,数组中第一个数字就是最小的数字,可以直接返回。

class Solution {
public:
    int minArray(vector<int>& numbers) {
        int len = numbers.size();
        if(len<=0){
            return -1;
        }
        int p1=0;
        int p2=len-1;
        int p_m=0;
        while(numbers[p1]>=numbers[p2]){
            
            if(p2-p1==1){
                p_m=p2;
                break;
            }

            p_m=(p1+p2)/2;
            if(numbers[p1]==numbers[p_m]
            &&numbers[p_m]==numbers[p2]){
                return minInorder(numbers,p1,p2);
                
            }
            if(numbers[p_m]>=numbers[p1]){//p_m在前面递增数组
                p1=p_m;
            }
            if(numbers[p_m]<=numbers[p2]){//p_m在后面递增数组
                p2=p_m;
            }
        }
        return numbers[p_m];
    }

    int minInorder(vector<int>& numbers,int p1,int p2){
        int res=numbers[p1];
        for(int i=p1+1;i<=p2;i++){
            if(res>numbers[i]){
                res=numbers[i];
            }
        }
        
        return res;
    }
};

在这里插入图片描述
时间复杂度是 O ( l o g n ) O(logn) O(logn)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值