面试常问的查找、排序

 一 查找 

        查找部分最简单和常见的应该是考察 二分查找了。

        例如 《剑指offer》中面试题8类型的考查方式:

        题目:把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。输入一个递增排序的数组的一个旋转,输出旋转数组的最小元素。例如数组{3,4,5,1,2}为{1,2,3,4,5}的一个旋转,该数组的最小值为1。

        使用二分查找应该是较好的解决办法。

        代码如下:

         

    int MinInOrder(int* numbers,int index1,int index2)
    {
        int result = numbers[index1];
        for(int i = index + 1;i <= index2;++i)
        {
            if(result > numbers[i])
               result = numbers[i];
        }
        
        return result;
    }
    int Min(int* numbers,int length)
    {
        if(numbers == NULL || length <= 0)
            throw new std::exception("Invalid parameters");
        
        int index1 = 0;
        int index2 = length - 1;
        int indexMid = index1;
        while(numbers[index1] >= numbers[index2])
        {
            if(index2 - index1 == 1)
            {
                indexMid = index2;
                break;
            }
            
            indexMid = (index1 + index2) / 2;
            
            if(numbers[index1] == numbers[index2] && numbers[indexMid] == numbers[index1])
                return MinInOrder(numbers,index1,index2);
            
            if(numbers[indexMid] >= numbers[index1])
                index1 = indexMid;
            else if(numbers[indexMid] <= numbers[index2])
                index2 = indexMid;
        }
        
        return numbers[indexMid];
    }


查找部分下次再补,主要是排序部分。

二 排序 

首先各种排序的 时间复杂度(最好情况,平均情况,最坏情况),空间复杂度,稳定性总结。


算法种类                                    时间复杂度                                           空间复杂度                                 稳定性

                                   最好               平均                最坏

直接插入排序           O(n)              O(n平方)        O(n平方)                        O(1)                                        稳定

冒泡排序                  O(n)              O(n平方)        O(n平方)                        O(1)                                        稳定

简单选择排序           O(n平方)       O(n平方)        O(n平方)                        O(1)                                        不稳定

希尔排序                                                                                                    O(1)                                        不稳定

快速排序                  O(nlogn)       O(nlogn)        O(n平方)                         O(logn)                                   不稳定

堆排序                      O(nlogn)       O(nlogn)        O(nlogn)                         O(1)                                        不稳定

2路归并排序             O(nlogn)       O(nlogn)        O(nlogn)                         O(n)                                        稳定

基数排序                   O(d(n+r))      O(d(n+r))       O(d(n+r))                        O(r)                                         稳定


各种排序算法思想和代码段:

1      直接插入排序

        思想:A[1..N]为待排序数组,将A[2]到A[N]依次插入前面已经排好序的子序列中,执行n-1次即可。每次查找到待插入位置,均需要把大于该插入元素的数据向后移动,因此当最坏情况下(原始数组逆序时),直接插入排序的时间复杂度为O(n方)。在最好情况下,原始数组已经有序,这时仅仅需要每次将待插入元素和有序子序列的最右端元素比较即可,不需移动元素,时间复杂度为O(n)。   

    void InsertSort(int A[],int length)
    {
        for(int i = 2;i <= length;++i )
        {
            if(A[i] < A[i - 1])
            {
                A[0] = A[i];
                for(int j = i - 1;A[j] > A[0];--j )
                    A[j + 1] = A[j];
                
                A[j + 1] = A[0];
            }
        }
    }

2   冒泡排序

      思想:A[1..N]为待排序数组,每次排序过程为:从后往前两两比较数组元素值的大小,若为逆序(A[i-1] > A[i]),则交换两个元素的值。则从后往前执行完比较过程,最小的元素被放在了A[0]位置(最轻的气泡冒出),则下次比较时第一个元素无需参加比较。这样最多执行 n-1 次比较过程,即可完成排序。 同时我们可以设置一个 flag 位,一旦发生数据交换,我们就更改flag位的值。如果一次比较过程中没有发生任何数据交换,即A[i-1] < A[i]且 A[i-2] < A[i-1]...,代表数组已经有序,此时flag的值未发生改变。一旦发现flag值未变,立即退出循环。因此最优时间复杂度为O(n),最坏为O(n方)。

    void BubbleSort(int A[],int length)
    {
        for (int i=0;i<n-1;++i)
        {
            bool flag = false;
            for(j=n-1;j>i;--j)
            {
                if(A[j] < A[j-1])
                {
                    swap(A[j-1],A[j]);
                    flag = true;
                }
            }
            
            if(flag == false)
               return;
        }
    }<span style="font-family: Arial, Helvetica, sans-serif; background-color: rgb(255, 255, 255);"> </span>

3 简单选择排序

    思想:A[0..N-1]为待排序数组,第 i 趟排序为从A[i]到A[N-1]中选出最小的元素与A[i]的值进行交换。因此选择N-1次后即可使数组有序。 

    void SelectSort(int A[],int length)
    {
        for(int i = 0;i < length - 1;++i)
        {
            int min = i;
            for(int j = i + 1;j < length;++j)
                if (A[j] < A[min])  min = j;
            if(min != i) swap(A[i],A[min])
        }
    }

4      希尔排序

        由于直接插入排序适用于基本有序和数据量不大的排序表,希尔排序就是针对以上问题提出,又称为缩小增量排序。

        思想:待排序数组为A[0...N-1],先对待排序表分割成若干个A[i]、A[i+d]、A[i+2d]...A[i+kd]的子表,对子表进行直接插入排序。然后依次降低步长d,直到数组基本有序时,再对整个表进行一次直接插入排序。希尔提出的方法是 d1=n/2,d2=d1/2...,最后的增量为1。        

    void ShellSort(int A[],int n)
    {
        for(int dk= len/2;dk >= 1;dk = dk/2)
        {
            for(int i = dk + 1;i <= n;++i)
            {
                if(A[i-dk] > A[i])
                {
                    A[0] = A[i];
                    for(j = i - dk;j >0&&A[j]>A[0];j -= dk)
                        A[j+dk] = A[j];
                    A[j+dk] = A[0];
                }
            }
        }
    }
      希尔排序的空间复杂度为 O(1),当n在某个特定范围时,时间复杂度为O(n1.3),最坏为O(n2)。

5      快速排序(重要)

        思想:快速排序是基于分治法的,A[0..N-1]为待排序数组,在待排序数组中选择一个元素作为基准,通过一趟排序过程将待排序数组分为两部分,比基准小的元素均位于基准元素左边,比基准元素大的元素均位于基准元素右边,基准元素已放置在数组整体有序时其所在的位置。然后分别递归的对两个字表重复上述过程,直至每部分只有一个元素或者为空为止,则可以实现数组整体有序。

       快速排序均是递归实现,但其划分函数的版本有多种。

       首先是严版数据结构上的版本:

       每次选第一个元素作为基准,然后从右端查找比基准小的元素,将其复制到左端A[low]处(A[low]已保存为pivot,或被复制到了A[high]),然后从左端寻找比基准大的元素,将其复制到A[high],最后将基准元素复制到 low 指针所指位置,说明原来比基准小的元素均被复制到了左端,原来比基准大的元素均被复制到了右端。

    int Partition(int A[],int low,int high)
    {
        int pivot = A[low];
        while(low < high)
        {
            while(low < high && A[high] >= pivot)  
                --high;
            A[low] = A[high];
            while(low < high && A[low] <= pivot)
                ++low;
            A[high] = A[low];
        }
        
        A[low] = pivot;
        return low;
    }
    
    void QuickSort(int A[],int low,int high)
    {
        if(low < high)
        {
            int pivotpos = Partition(A,low,high);
            QuickSort(A,low,pivotpos - 1);
            QuickSort(A,pivotpos + 1,high);
        }
    }
         其次是 算法导论和剑指offer 上的划分版本:  

    int Partition(int data[],int length,int start,int end)
    {
        if(data == NULL || length <=0 || start<0 ||end >= length)
            throw new std::exception("Invalid Parameters");
            
        int index = RandomInRange(start,end);
        Swap(&data[index],&data[end]);
        
        int small = start - 1;
        for(index = start;index < end;++index)
        {
            if(data[index] < data[end])
            {
                ++small;
                if(small != index)
                   Swap(&data[index],&data[small]);
            }
        }
        
        ++small;
        Swap(&data[small],&data[end]);
        
        return small;
    }
    
    void QuickSort(int data[],int length,int start,int end)
    {
        if(start == end)
             return;
        
        int index = Partition(data,length,start,end);
        
        if(index > start)
             QuickSort(data,length,start,index - 1);
        if(index < end)
             QucckSort(data,length,index + 1,end);
    }

6      堆排序(重要)

        根节点均小于左右孩子结点的为小根堆,根节点均大于左右结点的为大根堆。堆排序的特点是将待排序数据看成一棵完全二叉树的顺序存储结构,利用完全二叉树的双亲结点和孩子结点之间的内在关系,在当前无序区中选择关键字最大或最小的元素。

         首先构造堆时从下到上对堆进行调整,以大根堆为例。n个结点的完全二叉树,首先调整以 n/2 为根节点的子树,若该子树中左右结点的值大于根节点,将左右结点值的较大者与根节点交换。然后依次对各结点(n/2 - 1)为根的子树进行筛选,筛选完毕后根节点即为最大值。

         建立大根堆算法:

    void BuildMaxHeap(int A[],int len)
    {
        for(int i = len/2;i>0;i--)
            AdjustDown(A,i,len);
    }
    
    void AdjustDown(int A[],int k,int len)
    {
        A[0] = A[k];
        for(int i = 2*k;i<=len;i *= 2)
        {
            if(i < len && A[i] < A[i+1])
                i++;
            
            if(A[0] >= A[i])  break;
            else 
            {
                A[k] = A[i];
                k = i;
            }
        }
        A[k] = A[0];
    }
        堆排序算法:   

    void HeapSort(int A[],int len)
    {
        BuildMaxHeap(A,len);
        for(int i =len;i>1;i--)
        {
            swap(A[i],A[1]);
            AdjustDown(A,1,i-1);
        }
    }
       对支持删除和插入操作。堆顶元素为最大值或最小值,删除堆顶元素后,将堆的最后一个元素和堆顶元素交换,然后从根节点进行向下调整操作。对堆进行插入操作时,将新节点放到堆末端,再对新节点执行向上调整操作。

        向上调整堆的算法:     

    void AdjustUp(int A[],int k)
    {
        A[0] = A[k];
        int i = k / 2;
        while(i > 0 && A[i]< A[0])
        {
            A[k] = A[i];
            k = i;
            i = k / 2;
        }
        A[k] = A[0];
    }
       建堆时间为O(n),之后有 n-1 次向下调整操作,每次调整的时间复杂度为O(logn),因此最好最坏平均时间复杂度均为O(nlogn),空间复杂度为O(1)。

7      2路归并排序(重要)

        归并排序中Merge() 函数的功能是将 前后两个有序表归并为一个有序表的算法。设两段有序表 A[low...mid],A[mid+1,high]存放在同一顺序表相邻的位置上,先将他们复制到辅助数组 B 中。每次从对应B中的两个段取出一个记录进行关键字的比较,将较小者放入A中,当数组B中有一段超出其表长时,将另一端中剩余的部分直接复制到A中。     

    int* B = (int*)malloc((n+1)*sizeof(int));
    void Merge(int A[],int low,int mid,int high)
    {
        for(int k = low;k <= high;++k)
            B[k] = A[k];
        
        for(i = low,j=mid+1,k=i;i <= mid&&j<=high;++k)
        {
            if(B[i] <= B[j])
                A[k] = B[i++];
            else
                A[k] = B[j++];
        }
        
        while(i <= mid)  A[k++]=B[i++];
        while(j <= high) A[k++]=B[j++];
    }
       归并排序时间复杂度为 O(nlogn),空间复杂度为 O(n)。

8    基数排序

      基数排序也叫桶排序,是采用多关键字排序思想,借助分配和收集两种操作来对单逻辑关键字进行排序。

      例如待排序的均为整数数字,数字均为 d 位,每位的大小范围为r。则可以设置r个空队列,依次考察每个数字。例如从数字的最右端的位开始考察,若该位为i,则放入对应的 r(i) 队列中。然后收集一次,得到新的结点序列。接下来对右端倒数第二位开始考察。

一共需要d趟分配和收集,需要存储空间为r(r个队列)。因此空间复杂度为O(r),时间复杂度为 O(d(n+r))。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值