Wewe带你看代码 --二分法与分治法
来源:互联网 发布:太原市安全教育网络 编辑:程序博客网 时间:2024/04/29 08:35
题目1:
设X[0:n-1]和Y[0:n-1]为两个数组,每个数组中含有n个已排好序的数。设计一个O(logn)时间的算法,找出X和Y的2n个数的中位数。
分析:
中位数:位于某一序列中间的某个数或两个数的平均数。
由此可知,中位数是在一序列的中间的数,利用此特性可以使用二分法对序列进行运算。
由题已知,该两个数组都是已经排好序的,所以中位数必然是由该两个序列去掉头尾部分得到。
我们设:
Size = N;low1 = low2 = 0;high1 = high2 = size – 1;mid1 = (low1 + high1)/2;mid2 = (low2 + high2)/2;
1.奇数个元素:
if (s2[mid] >s1[mid]){ high2 = mid2; low1 = mid1;}else if(s2[mid] <s1[mid]){ high1 = mid1; low2 = mid2;} else{ return s1[mid]; // 此时两个数组的中位数相等即为合并数组的中位数}
2.偶数个元素:
if ((high1 - low1) % 2 != 0){if (s1[mid1 + 1] > s2[mid2 + 1]) { high1 = mid1 + 1; low2 = mid2; //此处与奇数时有所不同 } else if (s1[mid1 + 1] < s2[mid2 + 1]) { high2 = mid2 + 1; low1 = mid1; } else { if (s1[mid1 + 2] >= s2[mid2 + 2]) { high1 = mid1 + 1; low2 = mid2; } else { high2 = mid2 + 1; low1 = mid1; }}else{ 调用奇数处理的方法(处理如下图所示情况)}
代码:
#include <iostream>#include <fstream>#include <string>using namespace std;const int N = 5; // 数组的元素个数double oddMidNum(int s1[N], int s2[N]);double evenMidNum(int s1[N], int s2[N]);int main(){ ifstream fin("midnumber.in"); ofstream fout("midnumber.out"); int s1[N]; int s2[N]; //cout << "输入数组1: "; for (int i = 0; i < N; i++) fin >> s1[i]; //cout << "输入数组2: " ; for (int i = 0; i < N; i++) fin >> s2[i]; if (N % 2 != 0) { fout << oddMidNum(s1, s2) << endl; } else { fout << evenMidNum(s1, s2) << endl; } return 0;}double oddMidNum(int s1[N], int s2[N]){ int mid1, mid2; int low1 = 0; int low2 = 0; int high1 = N - 1; int high2 = N - 1; while ((high1 - low1 != 1) && (high2 - low2 != 1)) { mid1 = (high1 + low1) / 2; mid2 = (high2 + low2) / 2; if (s1[mid1] > s2[mid2]) { high1 = mid1; low2 = mid2; } else if (s1[mid1] < s2[mid2]) { high2 = mid2; low1 = mid1; } else { return s1[mid1]; } } if (s1[low1] >= s2[low2]) { if (s1[high1] > s2[high2]) { return double(s1[low1] + s2[high2]) / 2.0; } else { return double(s1[low1] + s1[high1])/2.0; } } else { if (s2[high2] > s1[high1]) { return double(s2[low2] + s1[high1])/2.0; } else { return double(s2[low2] + s2[high2])/2.0; } }}double evenMidNum(int s1[N], int s2[N]){ int mid1, mid2; int low1 = 0; int low2 = 0; int high1 = N - 1; int high2 = N - 1; while ((high1 - low1 != 1) && (high2 - low2 != 1)) { if ((high1 - low1) % 2 != 0) { // 处理偶数个子字符串 mid1 = (high1 + low1) / 2; mid2 = (high2 + low2) / 2; if (s1[mid1 + 1] > s2[mid2 + 1]) { high1 = mid1 + 1; low2 = mid2; //此处与奇数时有所不同 } else if (s1[mid1 + 1] < s2[mid2 + 1]) { high2 = mid2 + 1; low1 = mid1; } else { if (s1[mid1 + 2] >= s2[mid2 + 2]) { high1 = mid1 + 1; low2 = mid2; } else { high2 = mid2 + 1; low1 = mid1; } } } else { // 处理奇数个子字符串 mid1 = (high1 + low1) / 2; mid2 = (high2 + low2) / 2; if (s1[mid1] > s2[mid2]) { high1 = mid1; low2 = mid2; } else if (s1[mid1] < s2[mid2]) { high2 = mid2; low1 = mid1; } else { return s1[mid1]; } } } if (s1[low1] >= s2[low2]) { if (s1[high1] > s2[high2]) { return double(s1[low1] + s2[high2]) / 2.0; } else { return double(s1[low1] + s1[high1]) / 2.0; } } else { if (s2[high2] > s1[high1]) { return double(s2[low2] + s1[high1]) / 2.0; } else { return double(s2[low2] + s2[high2]) / 2.0; } }}
复杂度分析:
此处注意二分法与分治法的不同之处,二分法是将原问题化简为更小的子问题,子问题的解即是原问题的解;而分治法是将原问题分解为多个子问题,然后根据子问题的解逐步得出原问题解。
T(n) = T(n/2) + O(1)
->T(n) = T(n/4) + 2O(1)
->T(n) = T(n/8) + 3O(1)
…
->T(n) = T(1) + logN * O(1)
->T(n) = O(logN)
题目2:
有一个实数序列a1, a2, a3, … ,aN;若i < j 且 ai > aj,则(ai, aj)构成了一个逆序对,请使用分治方法求整个序列中逆序对个数,并分析算法的时间复杂性。
分析:
考虑更小的逆序对个数(count)问题。
首先我们只考虑两个数的逆序对,如果有2,1两个数,则count = 1;
考虑三个数的逆序对,如果有4 , 2, 1三个数,我们可以先考虑4, 2,是逆序,则将4,2反序得到2, 4, 1,count += 1;
2, 4, 1拆分:
2 4
1
由于2 > 1, 则count += 2(因为反序后2后面的那个数4必然大于2);
再比较4 > 1, 则count += 1;
由此我们可以得出多个数的逆序对求法,详见代码部分。
代码:
#include <iostream>#include <fstream>#include <vector>using namespace std;int merge(int* arr1, int* arr2, int begin, int mid, int end){ int i = begin, j = mid + 1, k = begin, c = 0; // 数组的划分比较 while (i != mid + 1 && j != end + 1) { if (arr2[i] < arr2[j]) { arr1[k++] = arr2[i++]; } else { arr1[k++] = arr2[j++]; c += (mid + 1 - i); } } return c;}int separate(int* arr, int* temp, int begin, int end, bool inArr){ int count = 0; if (begin < end) { if (end - begin == 1) { if (arr[end] < arr[begin]) { // 对子数组的两个数从大到小排序,原始数组arr子数组微小改变 arr[begin] = temp[end]; arr[end] = temp[begin]; count++; } } else { int mid = (begin + end) / 2; int c1 = separate(arr, temp, begin, mid, !inArr); int c2 = separate(arr, temp, mid + 1, end, !inArr); int c3 = 0; if (inArr) { c3 = merge(arr, temp, begin, mid, end); // 最终使用排好序的两个子数组 } else { c3 = merge(temp, arr, begin, mid, end); // 子数组的子数组比较还是使用微小改变的原始数组arr } count = c1 + c2 + c3; } return count; } else { return 0; }}int main(){ ifstream fin("reversednum.in"); ofstream fout("reversednum.out"); int size; // 数组中数据个数 fin >> size; int *arr, *temp; arr = new int[size]; for (int i = 0; i < size; i++) fin >> arr[i]; // 原始数组的备份 temp = new int[size]; memcpy(temp, arr, size*4); fout << separate(arr, temp, 0, size - 1, true) << endl; return 0;}
复杂度分析:
T(n) = 2T(n/2) + n
f(n) = n,由master定理, n^(logb^a ) = n^1 = f(n),则为同阶情况,T(n) = O(nlogn)。
- Wewe带你看代码 --二分法与分治法
- Wewe带你看代码0 --序言
- Wewe带你看代码 --USACO Section1.1
- D3D初始化三带你一起看代码
- 分治法之二分法搜索最大值最小值
- 分治法--二分法求方程近似解
- JAVA分治法之二分法实现
- 大佬带你看DevOps
- 二分法与牛顿法
- java实现的二分法查找(分治法)
- 分治法和二分法的时间复杂度简明分析
- 分治法和二分法的时间复杂度简明分析
- 带---有测试代码----的二分法查找(折半查找)
- java 排序与二分法查找代码
- 二分法代码
- 【ShawnZhang】带你看数据结构——第九课:二叉树的存储与遍历
- Handler消息机制与AsyncTask深度解析-从源码带你看handler
- 【Albert带你1小时看遍美国前沿科技与商业运作】微访谈精选
- P1312 Mayan游戏
- css基础图形绘制
- Java实现数据结构之深度优先搜索DFS和广度优先搜索BFS
- HBase 权限控制
- 总结6
- Wewe带你看代码 --二分法与分治法
- Configuration(Android 6.0)
- JZOJ3256 【TJOI2013】松鼠聚会 切比雪夫距离转曼哈顿距离
- Python.matplotlib -- Python二维图表绘制
- MySQL索引背后的数据结构及算法原理
- Android Studio 配置
- verdi windows版本[使用debussy 5.4]
- Qt中的QTimer的应用
- 2. Add Two Numbers 大数运算