选择问题
一,普通排序:
算法1A:
将所有元素读入数组并且从大到小排序,然后直接返回第K个元素。如果使用简单的排序算法,运行时间就是O(N^2)。很不好。
//从大到小排序2
int choose_1A(VI& in, int k)3
{4
int count = in.size();5
for (int i = 0; i < count; ++i)6
{7
for (int j = i; j < count; ++j)8
if(in[i] < in[j])9
{10
swap(in[i], in[j]);11
}12
}13
return in[k-1];14
}
算法1B:
先将k个元素读入一个数组并且将其从大到小排序,这样的话这k个元素的最小者就在第k个位置上。然后我们依次的处理其余的元素。处理一个元素的时候,先将此元素跟数组中的第k个元素进行比较,如果此元素大就将第k个元素删除,而将这个新的元素放在其余k-1个元素间的正确的位置上。算法结束时,第k个元素上就是正确的答案。此方法的运行时间就是O(k*k+(N-k)*k)=O(N*k)。但是如果当K=[N/2]的时候(找中位数时),这种算法也是O(N^2)的。
//先排k个2
int choose_1B(VI& in, int k)3
{4
int count = in.size();5
FORR(i, 0, k)6
{7
FORR(j, i, k)8
{9
if (in[i] < in[j])10
{11
swap(in[i], in[j]);12
}13
}14
}15
list<int> lst_k(in.begin(), in.begin()+k);16

17
FORR(i, k, count)18
{19
if (in[i] > lst_k.back())20
{21
for (list<int>::iterator it = lst_k.begin(); it != lst_k.end(); ++it)22
{23
if (in[i] > *it)24
{25
lst_k.insert(it, in[i]);26
break;27
}28
}29
lst_k.pop_back();30
}31
}32
return lst_k.back();33
}34

二,使用优先队列(二叉堆)
算法2A:
简单起见现在我们把问题改一下:找出第k个最小的元素。算法是,先将N个元素读入一个数组,然后对此数组应用buildHeap算法。最后执行k次deleteMin操作。这时候从该堆中取出的就是正确答案。这个算法显然是正确的,如果使用buildHeap,构造堆的最坏情形就是O(N)的时间,而每次deleteMin的时间是O(logN)的时间。由于是k次deleteMin,则总时间是O(N+klogN)。当k=[N/2]的时候,运行时间就是O(NlogN)(找中位数时间)。其实注意的是,如果令k=N,那么实际上就是对输入文件以时间O(NlogN)进行了排序。这就是堆排序。
int choose_2A(VI& in, int k)
{
priority_queue<int> maxPQ(in.begin(), in.end());
FORR(i, 0, k-1)
{
maxPQ.pop();
}
return maxPQ.top();
}
算法2B:
使用算法1B的思路,在任意一个时刻,都将维持k个最大元素的集合S。在前k个元素读入以后,再读入一个新的元素时候,该元素将会和第k个最大最大元素进行比较,设其为Sk(是S中最小的)。如果新的元素大于Sk,那么就用新元素代替S中的Sk。这个时候S中就会有一个新的最小元素,可能是新添加的也可能不是。输入完成时,找到S中的最小元素,返回就是问题的答案。
上面看来基本和算法1B差不多,不过这里我们用一个堆来实现S了(上面我是用的链表)。通过buildHeap将前k个元素以总时间O(k)放到堆中。处理其余每个元素时间只要O(1)+O(logk)。总时间是O(Nlogk)。当k=[N/2]时候即找中位数时间也是O(NlogN)。
//1B算法加上二叉堆2
int choose_2B(const VI& in, int k)3
{4
int count = in.size();5
//下面定义的是最小堆(即最小的在最顶部)6
priority_queue<int, vector<int>, greater<int>> minPQ(in.begin(), in.begin()+k);7
FORR(i, k, count)8
{9
if (in[i] > minPQ.top())10
{11
minPQ.pop();12
minPQ.push(in[i]);13
}14
}15
return minPQ.top();16
}17

三,使用快速排序思想
算法3A:快速选择
这种算法称为快速选择,令|Si|是Si中的元素的个数,那么快速选择的步骤是:
(1)如果|S|=1,那么k=1并且将S中的元素作为答案返回。如果我们选用小数组的截止方法,则当|S|<=CUTOFF的时候将S排序并且返回第k个最小元。
(2)在S中选取一个枢纽元。
(3)将集合S-{v}分割成S1和S2,就像快速排序中所做的那样。
(4)如果k<=|S1|,那么第k个最小元必定在S1中。在这种情况下,返回quickselect(S1,k)。如果k=1+|S1|那么枢纽元就是第k个最小元,将它作为答案返回。否则第k个最小元就在S2中,它是S2中的第(k-|S1|-1) 个最小元。进行一次递归调用返回quickselect(S2,k-|S1|-1)。
跟快速排序相比,快速选择只是进行了一次递归调用不是两次。快速选择的最快情形也是O(N^2),不过平均时间是O(N)。
const int& median3(VI& a, int left, int right)2
{3
int center = (left+right)/2;4
if (a[center] < a[left])5
swap(a[left], a[center]);6
if (a[right] < a[left])7
swap(a[left], a[right]);8
if (a[right] < a[center])9
swap(a[center], a[right]);10

11
swap(a[center], a[right-1]);12
return a[right-1];13
}14

15
//快速选择,注意这里我们找到的是第k小的元素16
void quickSelect(VI& in, int left, int right, int k)17
{18
if (left < right)19
{20
int pivot = median3(in, left, right); 21
int i = left, j = right-1;22
23
while(true)24
{25
while(in[++i] < pivot) {}26
while(pivot < in[--j]) {}27
28
if (i < j)29
swap(in[i], in[j]);30
else31
break;32
}33

34
if(i < right-1)35
swap(in[i], in[right-1]);36

37
if (k <= i)38
quickSelect(in, left, i-1, k);39
else if(k > i+1)40
quickSelect(in, i+1, right, k);41
}42
else43
return;44
}45

46
int choose_3A(VI& in, int k)47
{48
int count = in.size();49
quickSelect(in, 0, count-1, k);50
return in[k-1];51
}52

算法3B:
是一个最坏为O(n)的算法,要准备考研了,没时间研究了,以后再说吧~~
全部源代码:
#include <iostream>2
#include <sstream>3
#include <algorithm>4
#include <string>5
#include <map>6
#include <set>7
#include <list>8
#include <stack>9
#include <queue>10
#include <cctype>11
#include <vector>12
#include <bitset>13
#include <cmath>14
//#include <hash_map>15

16
#define FORR(i, a, b) for(int i = a; i < b; ++i)17
#define BE(x) x.begin(),x.end()18
#define MP(a, b) make_pair(a, b)19

20
using namespace std;21
//using namespace stdex;22

23
typedef vector<string> VS;24
typedef vector<int> VI;25

26
//从大到小排序O(N^2)27
int choose_1A(VI& in, int k)28
{29
int count = in.size();30
for (int i = 0; i < count; ++i)31
{32
for (int j = i; j < count; ++j)33
if(in[i] < in[j])34
{35
swap(in[i], in[j]);36
}37
}38
return in[k-1];39
}40

41
//先排k个,O(K*N)42
int choose_1B(VI& in, int k)43
{44
int count = in.size();45
FORR(i, 0, k)46
{47
FORR(j, i, k)48
{49
if (in[i] < in[j])50
{51
swap(in[i], in[j]);52
}53
}54
}55
list<int> lst_k(in.begin(), in.begin()+k);56

57
FORR(i, k, count)58
{59
if (in[i] > lst_k.back())60
{61
for (list<int>::iterator it = lst_k.begin(); it != lst_k.end(); ++it)62
{63
if (in[i] > *it)64
{65
lst_k.insert(it, in[i]);66
break;67
}68
}69
lst_k.pop_back();70
}71
}72
return lst_k.back();73
}74

75
//优先队列O(N + k*logN)76
int choose_2A(const VI& in, int k)77
{78
priority_queue<int> maxPQ(in.begin(), in.end());79

80
int count = maxPQ.size();81

82
FORR(i, 0, k-1)83
{84
maxPQ.pop();85
}86
return maxPQ.top();87
}88

89
//1B算法加上二叉堆90
int choose_2B(const VI& in, int k)91
{92
int count = in.size();93
//下面定义的是最小堆(即最小的在最顶部)94
priority_queue<int, vector<int>, greater<int>> minPQ(in.begin(), in.begin()+k);95
FORR(i, k, count)96
{97
if (in[i] > minPQ.top())98
{99
minPQ.pop();100
minPQ.push(in[i]);101
}102
}103
return minPQ.top();104
}105

106
const int& median3(VI& a, int left, int right)107
{108
int center = (left+right)/2;109
if (a[center] < a[left])110
swap(a[left], a[center]);111
if (a[right] < a[left])112
swap(a[left], a[right]);113
if (a[right] < a[center])114
swap(a[center], a[right]);115

116
swap(a[center], a[right-1]);117
return a[right-1];118
}119

120
//快速选择,注意这里我们找到的是第k小的元素121
void quickSelect(VI& in, int left, int right, int k)122
{123
if (left < right)124
{125
int pivot = median3(in, left, right); 126
int i = left, j = right-1;127
128
while(true)129
{130
while(in[++i] < pivot) {}131
while(pivot < in[--j]) {}132
133
if (i < j)134
swap(in[i], in[j]);135
else136
break;137
}138

139
if(i < right-1)140
swap(in[i], in[right-1]);141

142
if (k <= i)143
quickSelect(in, left, i-1, k);144
else if(k > i+1)145
quickSelect(in, i+1, right, k);146
}147
else148
return;149
}150

151
int choose_3A(VI& in, int k)152
{153
int count = in.size();154
quickSelect(in, 0, count-1, k);155
return in[k-1];156
}157

158

159

160
int main()161
{162
int a[] = {45, 34, 12, 9 ,5, 23, 34, 32, 5, 10, 56, 29, 14, 2};163
VI input(a, a+14);164

165
int k = 8;166

167
//cout << choose_1A(input, k) << endl;168
//print(input);169
//cout << endl;170
//cout << choose_1B(input, k) << endl;171
//cout << choose_2A(input, k) << endl;172
//cout << choose_2B(input, k) << endl;173

174
//cout << choose_3A(input, k) << endl;175

176
return 0;177
}



浙公网安备 33010602011771号