算法入门-快速选择算法

145 阅读3分钟

基于快速排序的算法选择算法

快速选择算法,其本质思想就是基于快速排序的分治思想
相信大家一定学习过快速排序,帮助大家回忆一下。
首先确定一个基准数,直接取 l + r >> 1 其实是比较方便的,因为不需要再交换基准数。
如下图:

image.png

image.png

image.png

以上是我的手写笔记,相信已经解释了什么是快排。

参考代码:

#include<iostream>

using namespace std;

const int N = 1000010;

int n ,m;
int a[N];

void  sort(int l , int r){
    if(l >= r) return ;

    int tem = a[l + r >> 1 ] , i = l -1 , j = r + 1;
    while(i < j ){
        do i++ ; while(tem > a[i]);
        do j-- ; while(tem < a[j]);
        if(i < j){
            int t = a[i];
            a[i] = a[j];
            a[j] = t;
        }
    }
    sort(l, j);
    sort(j + 1 , r);
}
int main(){
    scanf("%d",&n);
    for(int i=1; i<=n; i++)     scanf("%d", a + i);
    sort(1 , n);
    for(int i=1; i<=n; i++) printf("%d ", a[i]);
    return 0;
}

我们来学习快选,所谓快选就是基于快排做的。

快选的问题就是快速的选出,第k小的数,或者第k大的数。 利用快排选出这样的一个数,需要O(nlogn) 的时间复杂度,但是其实并不需要这么高的时间复杂度。
因为,当我们排好,划分好左边,右边之后。其实我们就可以选择去哪个序列里面找了。
假设 , 序列有10个元素 , 要找第7小的数 , 那么明显将整个序列划分成左右之后 , 答案只可能在右边。
照着这样的思路 , 我们同样可以递归的做这个过程。每次都统计这个子序列里面有多少元素 , 然后对比一下这个第k个数,是不是可以判断大于左边序列个数了。

image.png

就像这样,但是因为我们统计的这个左序列的元素个数 , 伴随着我们不断地划分一个序列。这个序列的也会不断的变化 , 所以我们的第K 小的数也要根据区间发生变化,如果我们选择的是右边序列。那么我们就需要 kSlk - S_l(其中Sl S_l 为左子序列的长度), 重新确定,这个第k小的数在这个局部(右边序列的位置对应的值)。

所以我们有如下代码:

#include <iostream>
#include <algorithm>

using namespace std;

const int N = 100010;


int n , k;
int q[N];

int quick_sort(int l , int r , int k){
	
	if(l == r) return q[l];
	
	int i = l - 1 , j = r + 1 , x = q[l + r >> 1];
	
	while(i < j){
		while(q[++i] < x);
		while(q[--j] > x);
		if(i < j) swap(q[i] , q[j]);
	}
	
	
	int sl = j - l + 1;
	
	if(k <= sl)  return quick_sort(l , j , k);
	
	return quick_sort(j + 1 , r , k - sl);
} 
int main(){
	
	scanf("%d%d" , &n , &k);
	
	for (int i = 1; i <= n; i ++ )
		scanf("%d", &q[i]);
	
	int t = quick_sort(1 , n , k);
	
	printf("%d" , t);
	
	return 0;
} 

该算法时间复杂度是O(n) 的 , 以下是证明:

假设提供的序列 s 长为 |s| 每次划分都将序列分成两份
那么我们由等差公式可以得到如下等式:

limss+s2+s4+=limsi=12s12s1s=2s \lim\limits_{|s|\to\infty} |s| + {\frac{|s|}{2}} + {\frac{|s|} {4}} + …… = \lim\limits_{|s|\to\infty} \sum_{i = 1}^\infty{2 * |s| - {\frac{1}{2}^{|s| - 1} * |s|} } = {2 |s|}