查找并输出数组中第i小的元素,这样的题目我们可以先对数组进行排序,然后输出相对应的第i小的元素;还有另外一种方法,一种解决选择问题的分治算法,该算法是以快速排序算法为模型的,与快速排序一样,我们仍然将输入数组进行划分,但与快速排序不同的是,快速排序会递归处理划分的两边,而该选择方法select只处理划分的一边。这一差异会在性能分析中体现出来:快速排序的期望运行时间为O(nlog(n)),而select的选择期望运行时间是O(n)。
1、对数组进行排序,然后输入第i小的元素,这里排序算法用的是插入排序。
#include <iostream> using namespace std; int select(int a[], int n, int i); int main(void) { int a[5] = {4, 67, 8, 2, 3}; cout<<select(a, 5, 4)<<endl; return 0; } static void InsertSort(int a[], int n) { int i, j; for(i = 1; i < n; i++) { int x = a[i]; for(j = i; j >= 0 && a[j-1] > x; j--) a[j] = a[j-1]; a[j] = x; } } int select(int a[], int n, int i) { InsertSort(a, n); return a[i-1]; }
2、递归进行选择数组中第i小的元素,select函数运行过程如下:刚开始检查递归的基本情况,当a[]包含只有一个元素时,i必须等于1,直接返回。其他情况下,就会调用Partition函数,将数组划分为两个子数组(可能为空的),a[left..j-1]和a[j+1..right],使得a[left..j-1]中每个元素都小于等于a[j],而a[j]小于a[j+1..right]中的每个元素。与快速排序一样,我们称a[j]为主元,接着计算子数组内a[left..j]中的元素个数k,即处于划分的低区的元素个数加1。然后检查a[j]是否是第i小的元素,如果是接下来直接返回,否则的话确定第i小的元素落在a[left..j-1]和a[j+1..right]两个子数组中的哪一个。如果i<k,则要查找的元素落在了划分的低区,则接下来在低区的子数组中继续递归查找;如果i>k,则要查找的元素落在了高区。我们已经知道了有k个值小于aleft..right]中的第i小的元素,即a[left..j]内的元素,所以我们要找的元素必然是a[j+1..right]中的第I-k小的元素,再进行递归查找。
#include <iostream> using namespace std; int select(int a[], int left, int right, int i); int main(void) { int a[5] = {4, 67, 8, 2, 3}; cout<<select(a, 0, 4, 3)<<endl; return 0; } static void Swap(int &a, int &b) { int t = a; a = b; b = t; } static int Partition(int a[], int left, int right) { int t, i, j; t = a[right]; i = left; for(j = left; j < right; j++) { if(a[j] < t) Swap(a[i++], a[j]); } Swap(a[i], a[right]); return i; } int select(int a[], int left, int right, int i) { if(left == right) return a[left]; int j = Partition(a, left, right); int k = j - left + 1; if(k == i) return a[j]; else if(i < k) return select(a, left, j-1, i); else return select(a, j+1, right, i-k); }
参考资料:
1、《算法导论》(第三版)第9章-中位数和顺序统计量