选择算法
2015-09-15 09:54
423 查看
1. 概念
顺序统计量:在一个由n个元素组成的集合中,第i个顺序统计量是指该集合中第i小的元素。例如最小值是第1个顺序统计量,最大值是第n个顺序统计量。
中位数:一般来说,中位数是指它所在集合的“中间元素”,当n为奇数时,中位数是唯一的,出现位置为n/2;当n为偶数时候,存在两个中位数,位置分别为n/2(上中位数)和n/2+1(下中位数)。
选择问题:
输入:一个包含n个(不同的)数的集合A和一个数i,1≤i≤n。
输出:元素x∈A,它恰大于A中其他的i-1个元素。
2.最小值和最大值
要在集合中选择最大值和最小值,可以通过两两元素比较,并记录最大值和最小值,n元素的集合需要比较n-1次,这样运行时间为O(n)。
伪代码:
同时找出集合的最大值和最小值:
方法一:按照上面讲到的方法,分别独立的找出集合的最大值和最小值,各用n-1次比较,共有2n-2次比较。
方法二:输入元素与当前的最大值和最小值进行比较,而是成对的处理元素,先将一对输入元素进行比较,然后把较大者与当前最大值比较,较小者与当前最小者比较,因此每两个元素需要3次比较。
初始设置最大值和最小值方法:
如何n为奇数,就将最大值和最小值都设置为第一个元素的值,然后成对的处理后续的元素。
如果n为偶数,那么先比较前面两个元素的值,较大的设置为最大值,较小的设置为最小值,然后成对处理后续的元素。实际上只需要最多3⌊n/2⌋次比较。
3. 以期望线性时间的选择算法
以期望线性时间选择顺序统计量的方法是以快速排序为模型。如同在快速排序中一样,此算法的思想也是对输入数组进行递归划分。
但和快速排序不同的是,快速排序会递归处理划分的两边,而randomized-select只处理划分的一边。并由此将期望的运行时间由O(nlgn)下降到了O(n)。
这就是顺序统计量算法能够如此高效的核心原因所在!
伪代码:
RANDOMIZED_SELECT通过对输入数组的递归划分来找出所求元素,该算法要保证对数组的划分是个好划分才更加高效。
RANDOMIZED_SELECT的最坏情况运行时间为θ(n^2),即使是选择最小元素也是如此。因为在每次划分过程中,导致划分后两边不对称,总好是按照剩下元素中最大的划分进行。
以期望线性时间的选择算法
同时求数组的最大值和最小值
完整代码:
4. 最坏线性时间选择顺序统计量的方法
核心在于:要保证对数组的划分是一个好的划分。
对PARTITION过程进行了修改。现在通过SELECT算法来确定n个元素的输入数组中的第i小的元素,具体操作步骤如下:
(1)将输入数组的n个元素划分为n/5(上取整)组,每组5个元素,且至多只有一个组有剩下的n%5个元素组成。(为何是5,而不是其他数,有点不明白。)
(2)寻找每个组织中中位数。首先对每组中的元素(至多为5个)进行插入排序,然后从排序后的序列中选择出中位数。
(3)对第2步中找出的n/5(上取整)个中位数,递归调用SELECT以找出其中位数x。(如果是偶数去下中位数)
(4)调用PARTITION过程,按照中位数x对输入数组进行划分。确定中位数x的位置k。
(5)如果i=k,则返回x。否则,如果i小于k,则在地区间递归调用SELECT以找出第i小的元素,若干i>k,则在高区找第(i-k)个最小元素。
SELECT算法通过中位数进行划分,可以保证每次划分是对称的,这样就能保证最坏情况下运行时间为θ(n)。
举个例子说明此过程,求集合A={32,23,12,67,45,78,10,39,9,58,125,84}的第5小的元素,操作过程如下图所示:
最坏线性时间选择顺序统计量的方法
完整代码:
顺序统计量:在一个由n个元素组成的集合中,第i个顺序统计量是指该集合中第i小的元素。例如最小值是第1个顺序统计量,最大值是第n个顺序统计量。
中位数:一般来说,中位数是指它所在集合的“中间元素”,当n为奇数时,中位数是唯一的,出现位置为n/2;当n为偶数时候,存在两个中位数,位置分别为n/2(上中位数)和n/2+1(下中位数)。
选择问题:
输入:一个包含n个(不同的)数的集合A和一个数i,1≤i≤n。
输出:元素x∈A,它恰大于A中其他的i-1个元素。
2.最小值和最大值
要在集合中选择最大值和最小值,可以通过两两元素比较,并记录最大值和最小值,n元素的集合需要比较n-1次,这样运行时间为O(n)。
伪代码:
MINMUN(A) min = A[1] for i=1 to length(A) do if min > A[i] min >= A[i] return min
同时找出集合的最大值和最小值:
方法一:按照上面讲到的方法,分别独立的找出集合的最大值和最小值,各用n-1次比较,共有2n-2次比较。
方法二:输入元素与当前的最大值和最小值进行比较,而是成对的处理元素,先将一对输入元素进行比较,然后把较大者与当前最大值比较,较小者与当前最小者比较,因此每两个元素需要3次比较。
初始设置最大值和最小值方法:
如何n为奇数,就将最大值和最小值都设置为第一个元素的值,然后成对的处理后续的元素。
如果n为偶数,那么先比较前面两个元素的值,较大的设置为最大值,较小的设置为最小值,然后成对处理后续的元素。实际上只需要最多3⌊n/2⌋次比较。
3. 以期望线性时间的选择算法
以期望线性时间选择顺序统计量的方法是以快速排序为模型。如同在快速排序中一样,此算法的思想也是对输入数组进行递归划分。
但和快速排序不同的是,快速排序会递归处理划分的两边,而randomized-select只处理划分的一边。并由此将期望的运行时间由O(nlgn)下降到了O(n)。
这就是顺序统计量算法能够如此高效的核心原因所在!
伪代码:
RANDOMIZED_SELECT(A,p,r,i) if p==r return A[p] q = RANDOMIZED_PARTITION(A,p,r) k = q-p+1; if i==k return A[q] else if i<k return RANDOMIZED_SELECT(A,p,q-1,i) else RANDOMIZED_SELECT(A,q+1,r,i-k)
RANDOMIZED_SELECT通过对输入数组的递归划分来找出所求元素,该算法要保证对数组的划分是个好划分才更加高效。
RANDOMIZED_SELECT的最坏情况运行时间为θ(n^2),即使是选择最小元素也是如此。因为在每次划分过程中,导致划分后两边不对称,总好是按照剩下元素中最大的划分进行。
以期望线性时间的选择算法
同时求数组的最大值和最小值
完整代码:
#include <iostream> #include<cstdlib> #include<ctime> using namespace std; /* * 包含结果的结构体,里面含有最大值和最小值 */ struct result { public: int max; int min; result() : max(0), min(0) {} }; result* getMinMax(int a[], int len) { result* re = new result(); if (len == 0) { return 0; } if (len == 1) { re->max = a[0]; re->min = a[0]; return re; } if (len == 2) { re->max = a[0] > a[1] ? a[0] : a[1]; re->min = a[0] < a[1] ? a[0] : a[1]; return re; } int max, min; int i = 0; if (len % 2 == 0) { //元素个数为偶数的情况 re->max = a[i] > a[i + 1] ? a[i] : a[i + 1]; re->min = a[i] < a[i + 1] ? a[i] : a[i + 1]; i += 2; } else { //元素个数为奇数的情况 re->max = a[i]; re->min = a[i]; i++; } while (i < len) { //在成对的数中比较取值,然后再分别与max和min进行比较 max = a[i] > a[i + 1] ? a[i] : a[i + 1]; min = a[i] < a[i + 1] ? a[i] : a[i + 1]; i += 2; re->max = re->max > max ? re->max : max; re->min = re->min < min ? re->min : min; } return re; } void Swap(int &a, int &b) { int temp = a; a = b; b = temp; } int Partition(int arr[], int begin, int end) { int pivot = arr[begin]; int i = begin; for (int j = begin + 1; j <= end; ++j) if (arr[j] <= pivot) { ++i; Swap(arr[i], arr[j]); } Swap(arr[i], arr[begin]); return i; } int Rand_Partition(int arr[], int begin, int end) { srand(time(NULL)); //根据系统时间设置随机数种子 int i = rand() % (end - begin + 1) + begin; //取得区间[begin,end+1)的整数 Swap(arr[i], arr[begin]); return Partition(arr, begin, end); } void Rand_QuickSort(int arr[], int begin, int end) { if (begin < end) { int q = Rand_Partition(arr, begin, end); Rand_QuickSort(arr, begin, q - 1); Rand_QuickSort(arr, q + 1, end); } } int Rand_Select(int arr[], int begin, int end, int i) { if (begin == end) return arr[begin]; int q = Rand_Partition(arr, begin, end); int k = q - begin + 1; if (i == k) return arr[q]; else if (i < k) return Rand_Select(arr, begin, q - 1, i); else return Rand_Select(arr, q + 1, end, i - k); } int main() { int a[9] = { 5, 8, 0, -89, 9, 22, -1, -31, 98 }; result* r1 = getMinMax(a, 9); cout << "最大值为:" << r1->max << ",最小值为:" << r1->min << endl; int b[10] = { 5, 8, 0, -89, 9, 22, -1, -31, 98, 2222 }; result* r2 = getMinMax(b, 10); cout << "最大值为:" << r2->max << ",最小值为:" << r2->min << endl; delete r1; delete r2; int c[] = { 12, 23, 34, 45, 11, 32, 9, 22, 55 }; cout << "第5小的数是:"; cout << Rand_Select(c, 0, 8, 5) << endl; Rand_QuickSort(c, 0, 8); cout << "随机化的快速排序:"; for (int i = 0; i <= 8; i++) cout << c[i] << " "; cout << endl; system("pause"); return 0; }
4. 最坏线性时间选择顺序统计量的方法
核心在于:要保证对数组的划分是一个好的划分。
对PARTITION过程进行了修改。现在通过SELECT算法来确定n个元素的输入数组中的第i小的元素,具体操作步骤如下:
(1)将输入数组的n个元素划分为n/5(上取整)组,每组5个元素,且至多只有一个组有剩下的n%5个元素组成。(为何是5,而不是其他数,有点不明白。)
(2)寻找每个组织中中位数。首先对每组中的元素(至多为5个)进行插入排序,然后从排序后的序列中选择出中位数。
(3)对第2步中找出的n/5(上取整)个中位数,递归调用SELECT以找出其中位数x。(如果是偶数去下中位数)
(4)调用PARTITION过程,按照中位数x对输入数组进行划分。确定中位数x的位置k。
(5)如果i=k,则返回x。否则,如果i小于k,则在地区间递归调用SELECT以找出第i小的元素,若干i>k,则在高区找第(i-k)个最小元素。
SELECT算法通过中位数进行划分,可以保证每次划分是对称的,这样就能保证最坏情况下运行时间为θ(n)。
举个例子说明此过程,求集合A={32,23,12,67,45,78,10,39,9,58,125,84}的第5小的元素,操作过程如下图所示:
最坏线性时间选择顺序统计量的方法
完整代码:
#include <iostream> #include <vector> #include <algorithm> #include <cmath> #include <random> using namespace std; int find_mid(int M[], int size = 5) { vector<int> v{}; for (int i = 0; i < size; ++i) v.push_back(M[i]); sort(v.begin(), v.end()); return v[(size - 1) / 2]; } void bult_the_mids(int A[], int mids_array[], int begin, int end, int sub_array_num, int last_array_size) { for (int i = 0; i < sub_array_num; ++i) { if (i != sub_array_num - 1) { int* M = new int[5] {}; for (int j = 0; j < 5; ++j) { M[j] = A[begin + i * 5 + j]; } mids_array[i] = find_mid(M); delete[]M; } else { int* M = new int[last_array_size] {}; for (int j = 0; j < last_array_size; ++j) { M[j] = A[begin + i * 5 + j]; } mids_array[i] = find_mid(M, last_array_size); delete[]M; } } } int find_index_by_value(int A[], int begin, int end, int x) { int i{}; for (i = begin; i <= end; ++i) { if (A[i] == x) break; } return i; } int partition(int A[], int begin, int end, int choice) { int i{ begin - 1 }; int j{ begin }; swap(A[end], A[choice]); for (; j <= end - 1; ++j) { if (A[j] <= A[end]) { i++; swap(A[i], A[j]); } } swap(A[i + 1], A[end]); return i + 1; } int select(int A[], int begin, int end, int xth) { if (end <= begin) { return A[begin]; } else { int size_of_A = end - begin + 1; int sub_array_num = ceil(size_of_A / 5.0f); int last_array_size = (size_of_A % 5) == 0 ? 5 : size_of_A % 5; int* mids_array = new int[sub_array_num] {}; bult_the_mids(A, mids_array, begin, end, sub_array_num, last_array_size); int the_mid_of_mids = select(mids_array, 0, sub_array_num - 1, (sub_array_num - 1) / 2); delete[]mids_array; int index_of_final_mid{}; index_of_final_mid = find_index_by_value(A, begin, end, the_mid_of_mids); int pivot{}; pivot = partition(A, begin, end, index_of_final_mid); if (pivot == xth) { return A[pivot]; } if (pivot > xth){ return select(A, begin, pivot - 1, xth); } else{ return select(A, pivot + 1, end, xth); } } } bool check_exist(vector<int> v, int x) { for (auto i : v) { if (i == x) return false; } return true; } int main(int argc, const char * argv[]) { int n; cout << "please enter the sum of elements you want to produce :" << endl; cin >> n; random_device rd{}; default_random_engine e{ rd() }; uniform_int_distribution<int> u{ -100, 500 }; vector<int> v{}; while (v.size() < n) { int element{ static_cast<int>(round(u((e)))) }; if (check_exist(v, element)) v.push_back(element); } cout << "the array is :" << endl; for (auto i : v) { cout << i << " "; } cout << endl; int* A = new int[v.size()]{}; int index{}; for_each(v.begin(), v.end(), [=](int x)mutable{ A[index++] = x; }); int xth{}; cout << "please choose the index you want to select:" << endl; cin >> xth; cout << "the result is :" << endl; int result{}; result = select(A, 0, static_cast<int>(v.size() - 1), xth); cout << result << endl; delete[]A; cout << "(cheat here) sorted array :" << endl; sort(v.begin(), v.end()); for (auto i : v) { cout << i << " "; } cout << endl; system("pause"); return 0; }
相关文章推荐
- 期望为线性时间的选择算法randomizedSelect
- Python实现返回数组第i小的元素
- 查找算法、选择算法——LeetCode
- 第 9 章 中位数和顺序统计量
- 中位数和顺序统计量(第9章)
- 算法导论之中位数与顺序统计量(2)
- 初识遗传算法(一): 基本概念
- Spring 3 MVC ContentNegotiatingViewResolver example
- log4net发布之后无法写入日志原因汇总
- 图片加载Fresco
- 执行力,到底是什么鬼?
- unix top命令
- spring cache 详解
- xStream完美转换XML、JSON
- angular 自定义指令参数详解
- IOS-通信录
- IOS网络第六天 ASI (略)
- ANGULARJS 出错解决
- iOS开发工具Xcode 8.4版本 添加包的操作
- C++从零开始