第二章分治法
一、分治算法找第k小元素:在n(1<=n<=1000)个无序的整数中找出第k小的数。
1、选取一个基准值p,对数组区间进行划分:小于等于p的放在左边,大于p的放在右边,最后得到p的位置pos。
2、计算p是当前区间里的第cnt = pos - left + 1小。
3、如果cnt == k,p就是第k小,直接返回p位置对应的值;如果cnt > k,第k小在左半边,递归左区间查找第k小;如果cnt < k,第k小在右半边,递归右区间查找第k - cnt小。
4、递归直到区间只有一个元素,返回结果。
以下为代码实现
const int MAX = 1005;
int partition(int a[], int left, int right){
int i = left, j = right + 1;
int x = a[left];
while(true){
while(a[++i] < x && i < right);
while(a[--j] > x);
if(i >= j)
break;
int temp;
temp = a[i];
a[i] = a[j];
a[j] = temp;
}
a[left] = a[j];
a[j] = x;
return j;
}
int find(int b[], int left, int right, int k){
int temp = partition(b, left, right);
int pos = temp - left + 1;
if(pos == k)
return b[temp];
else if(pos < k)
return find(b, temp + 1, right, k - pos);
else
return find(b, left, temp - 1, k);
}
int main(){
int i, n, k,a[MAX];
cin >> n >> k;
for(i = 0; i < n; i++){
cin >> a[i];
}
int result;
result = find(a, 0, n-1, k);
cout << result << endl;
return 0;
}
二、时间复杂度分析
1、最好时间复杂度为 O(n)
这种情况为每次划分的基准刚好把数组切得比较均匀,每次递归只处理一半规模。
递归代价:n + n/2 + n/4 + ... = O(n)
2、最坏时间复杂度为 O(n^2)
这种情况为每次选的基准是当前区间的最大值 / 最小值,划分后一边只有 1 个元素,另一边是剩下全部元素。
递归代价:n + (n-1) + (n-2) + ... = O(n^2)
三、对分治法的体会与思考
核心思路:分、治、合
1、分:把一个大规模原问题,拆成若干规模更小、结构和原问题相同的子问题;
2、治:递归求解子问题,子问题足够小时直接求解;
3、合:把子问题的解合并,得到原问题的解
体会与思考:通过第 k 小元素这个例子,可以认识到分治有很强的灵活性,并不是所有分治算法都需要求解全部子问题。归并排序需要同时处理左右两个子区间并合并结果;而快速选择只需要求解包含答案的那一个子区间,舍弃无用子问题,减少大量计算。
分治算法效率很大程度取决于划分策略。划分均衡,算法效率高;划分失衡,就会出现最坏时间复杂度。分治的思想是化繁为简,将复杂大问题不断拆分为简单小问题,递归到基线条件直接求解,逻辑清晰,适合计算机实现。但递归也存在潜在缺陷,递归深度过大会引发栈溢出。
对比暴力排序后取第 k 小(O(nlog n)),快速选择利用分治避免了全数组排序,减少不必要运算,提升算法效率。这让我理解:设计分治算法时,不仅要考虑如何拆分问题,还要思考哪些子问题是不必要计算的,尽可能减少计算量。分治是一种重要的算法设计思想,很多经典算法基于它实现,学习分治锻炼了我拆解问题、分析复杂度的思维。

浙公网安备 33010602011771号