排序

 

名称最优时间复杂度平均时间复杂度最差时间复杂度空间复杂度稳定性
冒泡排序O(n)O(n^2)O(n^2)O(1)YES
选择排序O(n^2)O(n^2)O(n^2)O(1)NO
插入排序O(n)O(n^2)O(n^2)O(1)YES
归并排序O(nlgn)O(nlgn)O(nlgn)O(n)YES
快速排序O(nlgn)O(nlgn)O(n^2)O(lgn)~O(n)NO
堆排序O(nlgn)O(nlgn)O(nlgn)O(1)NO
希尔排序O(n^1.3)O(nlgn)O(n^2)O(1)NO

计数排序

基数排序

 O(n) O(m)YES

 

一、冒泡排序

对于一个int数组,请编写一个冒泡排序算法,对数组元素排序。
给定一个int数组A及数组的大小n,请返回排序后的数组。 
测试样例:
[1,2,3,5,2,3],6
[1,2,2,3,3,5]

冒泡排序:

时间复杂度 O(N^2)  空间复杂度O(1)

过程:

一开始交换的区间是0~N-1,即数组的整体。第1个数与第2个数比较,大的放在后面;第2个数与第3个数比较,大的放在后面;这样依次交换过去,最终最大的数会放在数组的后面。

然后把交换范围从0~N-1变为0~N-2,这样第二大的数在交换过后会放在倒数第二的位置。

依次进行这样的过程。直到交换范围只剩下一个数时,整个数组就变得有序了。

class BubbleSort {
public:
    int* bubbleSort(int* A, int n) {
        if (n<=1) 
            return A;
        for (int j=0; j<n; j++){
            for (int i=n-1; i>j; i--){
                if (A[i]<A[i-1])
                    swap(A[i],A[i-1]);
            }
        }
        return A;
    }
};

二、选择排序

对于一个int数组,请编写一个选择排序算法,对数组元素排序。
给定一个int数组A及数组的大小n,请返回排序后的数组。
测试样例:
[1,2,3,5,2,3],6

[1,2,2,3,3,5]

选择排序:

时间复杂度 O(N^2)  空间复杂度O(1)

过程:

一开始在0~N-1范围上选出最小值,把它放在位置0上,然后在1~N-1范围上选出最小值,把它放在位置1上。直到最后选择范围只包含一个数的时候,整个数组就变得有序了。

class SelectionSort {
public:
    int* selectionSort(int* A, int n) {
        if(n<=1) return A;
        for (int i=0; i<n; ++i){
            int smallest = i;
            for (int j=n-1; j>i; --j){
                if(A[j]<A[smallest]) smallest = j;
            }
            if(i!=smallest) swap(A[i],A[smallest]);
        }
        return A;
    }
};

三、插入排序

对于一个int数组,请编写一个插入排序算法,对数组元素排序。

给定一个int数组A及数组的大小n,请返回排序后的数组。

测试样例:
[1,2,3,5,2,3],6

[1,2,2,3,3,5]

插入排序:

时间复杂度 O(N^2)  空间复杂度O(1)

过程:

首先位置1上的数与位置0上的数进行比较,如果位置1上的数更小,则它与位置0上的数交换;接下来考察位置2上的数(记为a),如果a比位置1上的数更小,则a与位置1上的数交换,交换之后a再与位置0上的数比较,如果a更小,则a与之交换;对位置k上的数(记为b),b依次和前面位置上的数进行比较,如果b一直小于它前面的数,就一直进行这样的交换,直到b前面的数小于等于b,那么b就插入当前位置。

对1位置到N-1位置的数都进行这样的插入过程,最终整个数组变得有序。

class InsertionSort {
public:
    int* insertionSort(int* A, int n) {
        if(n<=1) return A;
        for(int i=1; i<n; i++){
            int key = A[i];
            int j = i-1;
            while(j>=0 && A[j]>key){
                A[j+1] = A[j];
                j--;
            }
            A[j+1] = key;
        }
        return A;
    }
};

四、归并排序

对于一个int数组,请编写一个归并排序算法,对数组元素排序。
给定一个int数组A及数组的大小n,请返回排序后的数组。

测试样例:

[1,2,3,5,2,3],6
[1,2,2,3,3,5]

归并排序:

时间复杂度 O(N logN)    空间复杂度O(N)

过程:

首先让数组中的每一个数单独成为长度为1的有序区间,然后把相邻的长度为1的有序区间进行合并,得到最大长度为2的有序区间,接下来再把相邻的有序区间合并,得到最大长度为4的有序区间,依次进行这样的合并过程,直至数组中的所有数都在一个有序区间,整个数组变得有序。

class MergeSort {
public:
    int* mergeSort(int *A,int n){
    mergeSort1(A,0,n-1);
    return A;
}
void mergeSort1(int *A,int l,int r){
    if(l>=r) return;
    int mid=(l+r)/2;
    mergeSort1(A,l,mid);
    mergeSort1(A,mid+1,r);
    merge(A,l,mid,r);
}
void merge(int *A,int l,int mid,int r){
    int L[mid-l+2];
    int R[r-mid+1];
    for(int i=0;i<mid-l+1;i++){
        L[i] = A[i+l];
    }
    for(int i=0;i<r-mid;i++){
        R[i] = A[i+mid+1];
    }
    L[mid-l+1] = INT_MAX;
    R[r-mid] = INT_MAX;
    for(int i=0,j=0,k=l;k<=r;k++){
        if(L[i]<R[j]) A[k]=L[i++];
        else A[k]=R[j++];
    }
}
};

五、快速排序

对于一个int数组,请编写一个快速排序算法,对数组元素排序。
给定一个int数组A及数组的大小n,请返回排序后的数组。
测试样例:
[1,2,3,5,2,3],6

[1,2,2,3,3,5]

快速排序:

时间复杂度 O(N logN)   空间复杂度O(logN)~O(N) (取决于划分情况)

过程:

随机在数组中选一个数,小于等于它的数统一放在这个数的左边,大于它的数统一放在这个数的右边,接下来对于左右两个部分分别递归的调用快速排序的过程,这样就使得整个数组变得有序。

快速排序的划分(Partition)过程:

首先令划分值放在整个数组的最后位置,然后设计一个小于等于区间,初始时,小于等于区间长度为0,放在整个数组的左边。接下来从左到右遍历所有元素,如果当前元素大于划分值,则继续遍历下一个元素,如果当前元素小于等于划分值,则把当前数和小于等于区间的下一个数进行交换,然后令小于等于区间向右扩一个位置,直到遍历到最后一个数(也就是划分值),将它与小于等于区间的下一个数进行交换。

划分过程的时间复杂度为O(N)

class QuickSort {
public:
    int* quickSort(int* A, int n) {
        return qSort(A,0,n-1);
    }
    int* qSort(int* A, int p, int r){
        if(p<r){
            int q = partition(A,p,r);
            qSort(A,p,q-1);
            qSort(A,q+1,r);
        }
        return A;
    }
    int partition(int* A, int p, int r){
        int x = A[r];
        int i = p-1;
        for(int j=p;j<r;j++){
            if(A[j]<x){
                i++;
                swap(A[i],A[j]); //A[p..i]<x, A[i+1..r-1]>=x
            }
        }
        swap(A[i+1],A[r]);
        return i+1;
    }
};

 

六、堆排序

 

对于一个int数组,请编写一个堆排序算法,对数组元素排序。
给定一个int数组A及数组的大小n,请返回排序后的数组。
测试样例:
[1,2,3,5,2,3],6

[1,2,2,3,3,5]

堆排序:

时间复杂度 O(N logN)   空间复杂度O(1)

过程:

首先把数组中的N个数建成大小为N的大根堆,堆顶是整个数组的最大值,把堆顶元素和堆的最后一个元素进行交换,然后把最大值脱离出整个堆结构,放在数组的最后位置,作为数组的有序部分存在下来;接下来把N-1大小的堆从堆顶位置开始进行大根堆的调整,调整出N-1个数中的最大值放在堆顶位置,把堆顶位置的值与整个堆最后位置的值交换,最后位置脱离堆结构,放在数组的有序部分……当堆的大小减为1时整个数组变得有序。

class HeapSort {
public:
    int* heapSort(int* A, int n) {
        int heapSize = n-1;
        buildMaxHeap(A,heapSize);
        for(int i=n-1;i>0;i--){
            swap(A[0],A[i]);
            heapSize --;
            maxHeapify(A,heapSize,0);
        }
        return A;
    }
    int* buildMaxHeap(int* A, int heapSize){
        for(int i=heapSize/2;i>=0;i--){
            maxHeapify(A,heapSize,i);
        }
        return A;
    }
    int* maxHeapify(int* A, int heapSize, int i){
        int l = 2*i+1;
        int r = 2*i+2;
        int largest = i;
        if(l<=heapSize && A[l]>A[i]) largest = l;
        if(r<=heapSize && A[r]>A[largest]) largest = r;
        if(i!=largest){
            swap(A[i],A[largest]);
            maxHeapify(A,heapSize,largest);
        }
        return A;
    }
};

七、希尔排序

对于一个int数组,请编写一个希尔排序算法,对数组元素排序。
给定一个int数组A及数组的大小n,请返回排序后的数组。保证元素小于等于2000。
测试样例:
[1,2,3,5,2,3],6

[1,2,2,3,3,5]

希尔排序:

时间复杂度 O(N logN)   空间复杂度O(1)

过程:

希尔排序是插入排序的改良算法。插入排序步长为1,希尔排序的步长是逐渐从大到小调整的。希尔排序的关键是步长的选择。

例: 6   5   3   1   8   7   2   4    步长为3

->  1   4   3   2   5   7   6   8   ->  步长为2的调整   ->  步长为1的调整(插入排序)  -> 1   2   3   4   5   6   7   8

 

class ShellSort {
public:
    int* shellSort(int* A, int n) {
        for(int s=n/2;s>=1;s/=2){
            insertionSort(A,n,s);
        }
        return A;
    }
    void insertionSort(int* A, int n, int stride){
        if(n<=1) return;
        for(int j = stride; j<n;j++){
            int key = A[j];
            while(j-stride>=0 && key<A[j-stride]){
                for(int i=0;i<=stride-1;i++){
                    A[j-i]=A[j-i-1];
                }
                j -= stride;
                A[j]=key;
            }
        }
    }
};

八、计数排序

对于一个int数组,请编写一个计数排序算法,对数组元素排序。
给定一个int数组A及数组的大小n,请返回排序后的数组。
测试样例:
[1,2,3,5,2,3],6

[1,2,2,3,3,5]

计数排序:

时间复杂度 O(N)  空间复杂度O(M)(M为桶的数量)

时间复杂度为 O(N) 的排序算法,不是基于比较的排序算法,思想来自于桶排序思想。

过程:

身高100~300,建立100~300号桶,身高依次进入相应的桶,100~300号桶依次倒出身高,从桶里倒出来的身高就是有序的了。

class CountingSort {
public:
    int* countingSort(int* A, int n) {
        if(n<=1) return A;
        int max = INT_MIN;
        int min = INT_MAX;
        for(int i=0;i<n;i++){
            if(A[i]>max) max=A[i];
            if(A[i]<min) min=A[i];
        }
        int len = max-min+1;
        vector<vector<int>> res(len);
        for(int i=0;i<n;i++){
            res[A[i]-min].push_back(A[i]);
        }
        int cnt = 0;
        for(int i=0;i<len;i++){
            if(!res[i].empty()){
                for(vector<int>::iterator itr=res[i].begin();itr!=res[i].end();itr++){
                    A[cnt++] = *itr;
                }
            }
        }
        return A;
    }
};

 

九、基数排序

 

对于一个int数组,请编写一个基数排序算法,对数组元素排序。
给定一个int数组A及数组的大小n,请返回排序后的数组。保证元素均小于等于2000。
测试样例:
[1,2,3,5,2,3],6

[1,2,2,3,3,5]

基数排序:

时间复杂度 O(N)   空间复杂度O(M)(M为桶的数量)

时间复杂度为 O(N) 的排序算法,不是基于比较的排序算法,思想来自于桶排序思想。

过程:

首先假设被排序的数都是十进制的,然后准备0~9号桶,把每个数根据它个位数选择它进入几号桶,所有数进桶之后从0-9号桶依次倒出所有数,再依次按照十位数进桶,再依次倒出,。。。,再依次按照最高位数进桶,再倒出,倒出的序列就是排序之后的序列。

class RadixSort {
public:
    int* radixSort(int* A, int n) {
        if(n<=1) return A;
        for(int i=0;i<=3;i++){
            distribute(A,n,i);
        }
        return A;
    }
    void distribute(int* A, int n, int times){
        queue<int> que[10];
        for(int i=0;i<n;i++){
            int num=A[i]/pow(10,times);
            que[num%10].push(A[i]);
        }
        int idx = 0;
        for(int i=0;i<10;i++){
            while(!que[i].empty()){
                A[idx]=que[i].front();
                que[i].pop();
                idx++;
            }
        }
    }
};

十、小范围排序问题

 

已知一个几乎有序的数组,几乎有序是指,如果把数组排好顺序的话,每个元素移动的距离可以不超过k,并且k相对于数组来说比较小。请选择一个合适的排序算法针对这个数据进行排序。

给定一个int数组A,同时给定A的大小n和题意中的k,请返回排序后的数组。

测试样例:
[2,1,4,3,6,5,8,7,10,9],10,2

返回:[1,2,3,4,5,6,7,8,9,10]

【分析】

1、时间复杂度为O(n)的排序算法(计数排序、基数排序),不急于比较的排序算法的限制:不适用所有情况。不考虑。

2、时间复杂度为O(n^2)的排序算法,冒泡排序、选择排序:这两个排序算法与数组原始序列无关。插入排序的过程与原始顺序有关,每个元素移动距离不超过k,对本题来说,时间复杂度O(nk)。

3、时间复杂度为O(nlgn)的排序算法,快排、归并排序与数组原始序列无关。

答案:改进后的堆排序。最小值在a[0..k-1]中,将a[0..k-1]建立小根堆,堆顶是最小值,弹出堆顶,放在数组0位置,将a[k]放在小根堆堆顶,调整小根堆,弹出堆顶,放在数组1位置,……。每次调整的代价是O(lgk),总共n个数,整体时间复杂度O(nlgk)。

class ScaleSort {
public:
    vector<int> sortElement(vector<int> A, int n, int k) {
        // 边界检测
        if(A.size()==0 || n<1 || k<0|| k>n) return A;
        //初始化小顶堆
        vector<int> minHeap(k);
        for(int i=0;i<k;i++){
            minHeap[i] = A[i];
        }
        for(int i=k/2;i>=0;i--){
            minHeapify(minHeap,i,k);
        }
        //使用小顶堆,求出前n-k个值
        for(int i=0;i<n-k;i++){
            A[i] = minHeap[0];
            minHeap[0] = A[i+k];
            minHeapify(minHeap,0,k);
        }
        //对剩余的k个值,使用堆排序
        for(int i=n-k;i<n;i++){
            A[i] = minHeap[0];
            swap(minHeap[0],minHeap[k-1]);
            minHeapify(minHeap,0,--k);
        }
        return A;
    }

    void minHeapify(vector<int> &A, int i, int heapSize){
        int l = 2*i+1;
        int r = 2*i+2;
        int min=i;
        if(l<heapSize && A[l]<A[i]) min = l;
        if(r<heapSize && A[r]<A[min]) min = r;
        if(i!=min){
            swap(A[i],A[min]);
            minHeapify(A,min,heapSize);
        }
    }
};

 

 

十一、重复值判断问题

 

 

请设计一个高效算法,判断数组中是否有重复值。必须保证额外空间复杂度为O(1)。
给定一个int数组A及它的大小n,请返回它是否有重复值。
测试样例:
[1,2,3,4,5,5,6],7

返回:true

【分析】

如果没有空间复杂度的限制,用哈希表实现。在哈希表遍历数组的过程中,统计每个数出现的次数,时间复杂度O(n)、空间复杂度O(n)。

加上空间复杂度为O(1)的限制,要先排序、再判断,遍历一遍就知道有没有重复值。

考察经典排序算法的空间复杂度限制——选择堆排序。

堆排序的经典实现使用了递归的方式(需要用调用栈实现递归过程,栈的大小为堆的层数),这样空间复杂度不是O(1)而是O(lgn)。因此本题需要非递归版本的堆排序。

class Checker {
public:
    bool checkDuplicate(vector<int> a, int n) {
        // write code here
        heapSort(a,n);
        for(int i=1;i<=n-1;i++){
            if(a[i]==a[i-1]) return true;
        }
        return false;
    }
    void heapSort(vector<int>& a, int n){
        //build maxHeap from the last parent node
        for(int p=(n-1)/2;p>=0;p--){
            maxHeapify(a,n-1,p);
        }
        //heap sort
        for(int end=n-1;end>=1;end--){
            swap(a[0],a[end]);
            maxHeapify(a,end-1,0);
        }
    }
    void maxHeapify(vector<int>& a, int heapSize, int i){
        int p=i;
        int key=a[i];
        int l=2*p+1;
        int r=2*p+2;
        while(l<=heapSize){
            if(key>=a[l] && (r<=heapSize && key>=a[r])) break; //have right child, parent >= left child, donot need change
            else if(key>=a[l] && r>heapSize) break;            //no right child, parent > left child, donot need change
            else if(r<=heapSize && a[r]>=a[l] && a[r]>key){    //have right child, right child is the largest, swap
                swap(a[r],a[p]);
                p=r;
                l=2*p+1;
                r=2*p+2;
            }
            else if(r<=heapSize && a[l]>a[r] && a[l]>key){    //have right child, left child is the largest, swap
                swap(a[l],a[p]);
                p=l;
                l=2*p+1;
                r=2*p+2;
            }
            else if(r>heapSize && a[l]>key){                 //no right child, parent < left child, swap
                swap(a[l],a[p]);
                p=l;
                l=2*p+1;
                r=2*p+2;
            }
        }
    }
};

 

十二、有序数组合并问题

 

有两个从小到大排序以后的数组A和B,其中A的末端有足够的缓冲空容纳B。请编写一个方法,将B合并入A并排序。

给定两个有序int数组A和B,A中的缓冲空用0填充,同时给定A和B的真实大小int n和int m,请返回合并后的数组。

要求合并后的数组是从小到大排序的。

【分析】要点在于,AB有效部分要从后至前比较(直至B完全拷贝进A),以免A的有用部分不会因合并过程而被覆盖掉。

class Merge {
public:
    int* mergeAB(int* A, int* B, int n, int m) {
        // write code here
        int i = n-1;
        int j = m-1;
        for(int k=n+m-1;k>=0;k--){
            A[k] = A[i]>=B[j]? A[i--] : B[j--];
        }
        return A;
    }
};

 

十三、三色排序问题(荷兰国旗问题)

 

有一个只由0,1,2三种元素构成的整数数组,请使用交换、原地排序而不是使用计数进行排序。
给定一个只含0,1,2的整数数组A及它的大小,请返回排序后的数组。保证数组大小小于等于500。
测试样例:
[0,1,1,0,2,2],6
返回:[0,0,1,1,2,2]

【分析】本题主要过程与快排划分过程类似(0区、2区)。时间复杂度O(n),额外空间复杂度O(1)。

class ThreeColor {
public:
    vector<int> sortThreeColor(vector<int> A, int n) {
        // write code here
        if(n<=1) return A;
        int left=-1;
        int right=n;
        int idx=0;
        //A[0..left]: 0
        //A[right..n-1]: 2
        while(idx<right){
            if(A[idx]==0) swap(A[idx++],A[++left]);
            else if(A[idx]==2) swap(A[idx],A[--right]); //note: idx not add 1
            else idx++;
        }
        return A;
    }
};

 

十四、有序矩阵查找问题

 

现在有一个行和列都排好序的矩阵,请设计一个高效算法,快速查找矩阵中是否含有值x。
给定一个int矩阵mat,同时给定矩阵大小nxm及待查找的数x,请返回一个bool值,代表矩阵中是否存在x。所有矩阵中数字及x均为int范围内整数。保证n和m均小于等于1000。

测试样例:

[[1,2,3],[4,5,6],[7,8,9]],3,3,10

返回:false

【分析】最优解时间复杂度O(n+m),额外空间复杂度O(1)。从右上开始找,如果当前数>目标数,向左移动;如果当前数<目标数,向下移动。

class Finder {
public:
    bool findX(vector<vector<int> > mat, int n, int m, int x) {
        int cx=m-1;
        int cy=0;
        while(cx>=0 && cy<=n-1){
            if(mat[cy][cx]==x) return true;
            else if(mat[cy][cx]<x) cy++;
            else cx--;
        }
        return false;
    }
};

 

十五、最短子数组问题

 

对于一个数组,请设计一个高效算法计算需要排序的最短子数组的长度。
给定一个int数组A和数组的大小n,请返回一个二元组,代表所求序列的长度。(原序列位置从0开始标号,若原序列有序,返回0)。保证A中元素均为正整数。
测试样例:
[1,4,6,5,9,10],6

返回:2

【分析】

最优解时间复杂度O(n),额外空间复杂度O(1)。

从左到右遍历,记录遍历过的部分的最大值,考察该最大值大于当前数的情况,只记录发生这种情况的最右位置。

从右到左遍历,记录遍历过的部分的最小值,考察该最小值小于当前数的情况,只记录发生这种情况的最左位置。

最左位置~最右位置,这个范围就是要排序的最短子数组。

class Subsequence {
public:
    int shortestSubsequence(vector<int> A, int n) {
        // write code here
        if(n<=1) return 0;
        int left_max=A[0];
        int left_idx=0;
        int right_min=A[n-1];
        int right_idx=n-1;
        for(int i=0;i<=n-1;i++){
            if(A[i]>=left_max) left_max=A[i];
            else left_idx=i;
        }
        for(int i=n-1;i>=0;i--){
            if(A[i]<=right_min) right_min=A[i];
            else right_idx=i;
        }
        return left_idx>right_idx ? left_idx-right_idx+1 : 0;
    }
};

 

十六、相邻两数最大差值问题

 

有一个整形数组A,请设计一个复杂度为O(n)的算法,算出排序后相邻两数的最大差值。
给定一个int数组A和A的大小n,请返回最大的差值。保证数组元素多于1个。
测试样例:
[1,2,5,4,6],5

返回:2

【分析】最优解时间复杂度O(n),额外空间复杂度O(n)。思想来自桶排序。遍历数组,找到最大值与最小值,把[min,max)等量分成n个区间(n为数组长度),每个区间对应一个桶,最大值单独放在第n+1个桶中。n个数、n+1个桶,必然出现空桶。同一桶中元素的差值不会大于桶区间,空桶两侧相邻数的差值,必大于桶区间。所以不用考虑同一个桶的相邻数,只用考虑桶间的相邻数。

class Gap {
public:
    int maxGap(vector<int> A, int n) {
        if(n<=1) return 0;
        int max=INT_MIN;
        int min=INT_MAX;
        for(int i=0;i<=n-1;i++){
            max = max<A[i]? A[i] : max;
            min = min>A[i]? A[i] : min;
        }
        if(max-min<=1) return max-min;
        vector<bool> hasNum(n+1,false);
        vector<int> maxs(n+1,INT_MIN);
        vector<int> mins(n+1,INT_MAX);
        int bid=0;
        for(int i=0;i<=n-1;i++){
            bid = int(double(A[i]-min)/double(max-min)*n);
            maxs[bid] = maxs[bid]<A[i] ? A[i] : maxs[bid];
            mins[bid] = mins[bid]>A[i] ? A[i] : mins[bid];
            hasNum[bid] = true;
        }
        int firstbid=0;
        int ans=0;
        int lastMax=0;
        for(int i=0;i<=n;i++){
            if(hasNum[i]){
                lastMax=maxs[i];
                firstbid=i;
                break;
            }
        }
        for(int i=firstbid+1;i<=n;i++){
            if(hasNum[i]){
                if(mins[i]-lastMax > ans) ans = mins[i]-lastMax;
                lastMax=maxs[i];
            }
        }
        return ans;
    }
};

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值