赞
踩
规模为n的原问题的解无法直接求出,进行问题规模缩减,划分子问题(这里子问题相互独立而且和原问题解的性质是相同的,只是问题规模缩小了)。如果子问题的规模仍然不够小,再进行子问题划分,如此递归的进行下去,直到问题规模足够小,很容易求出其解为止,最后求出的小规模的问题的解合并为一个更大规模的问题的解,自底向上逐步求出原问题的解。
分治算法所能解决的问题一般具有以下几个特征:
二分查找算法
#include<iostream> #include<vector> using namespace std; bool binarySearch(int arr[], int i, int j, int val) { if (i > j) return false; int m = (i + j) >> 1; if (arr[m] == val) { return true; } else if (arr[m] < val) { return binarySearch(arr, m + 1, j, val); } else { return binarySearch(arr, i, m - 1, val); } } int main() { int arr[] = { 0,5,24,34,41,58,62,64,67,69,78 }; cout << binarySearch(arr, 0, sizeof(arr) / sizeof(arr[0]), 34) << endl; }
对一组数据{41,67,34,0,69,24,78,58,62,64,5}进行快排
#include<iostream> #include<vector> using namespace std; //快排划分函数,调整基准数 int partition(vector<int>& arr, int l, int r) { int val = arr[l];//作为基准数 while (l < r) { while (l < r) { if (arr[r] < val)//右 - 左,找第一个比val小的 { arr[l++] = arr[r]; break; } r--; } while (l < r) { if (arr[l] > val)//左 - 右,找第一个比val大的 { arr[r--] = arr[l]; break; } l++; } } arr[l] = val;//放置基准数 return l;//返回基准数的下标 } void quickSort(vector<int>& arr, int l, int r) { if (l >= r) { return; } int pos = partition(arr, l, r); quickSort(arr, l, pos - 1); quickSort(arr, pos + 1, r); } int main() { vector<int> arr = { 41,67,34,0,69,24,78,58,62,64,5 }; quickSort(arr, 0, arr.size() - 1); for (int v : arr) { cout << v << " "; } cout << endl; }
求topk问题:
两种解法各有优缺点,各自适用于不同的场合,因此在动手做题之前要先搞清楚题目要求,包括输入的数据量有多大、能否一次性载入内存、是否允许交换输入数据中数字的顺序
#include<iostream> #include<vector> #include<algorithm> using namespace std; //快排划分函数,调整基准数 int partition(vector<int>& vec, int l, int r) { int val = vec[l];//作为基准数 while (l < r) { while (l < r) { if (vec[r] < val)//右 - 左,找第一个比val小的 { vec[l++] = vec[r]; break; } r--; } while (l < r) { if (vec[l] > val)//左 - 右,找第一个比val大的 { vec[r--] = vec[l]; break; } l++; } } vec[l] = val;//放置基准数 return l;//返回基准数的下标 } //找第k大的,vec.size()-k小的下标 int max_select_topk(vector<int>& vec, int i, int j, int k) { int pos = partition(vec, i, j); if (pos == vec.size() - k)//基准数的位置和topk的k值相等 { return pos; } if (pos > vec.size() - k)//topk应该在基准数左边 { return max_select_topk(vec, i, pos - 1, k); } else//topk在基准数右边 { return max_select_topk(vec, pos + 1, j, k); } } //找第k小的,k-1小的下标 int min_select_topk(vector<int>& vec, int i, int j, int k) { int pos = partition(vec, i, j); if (pos == k - 1) { return pos; } if (pos > k - 1) { return min_select_topk(vec, i, pos - 1, k); } else { return min_select_topk(vec, pos + 1, j, k); } } int main() { vector<int> vec; for (int i = 0; i < 20; i++) { vec.push_back(rand() % 100); } //求第top10大的元素 int pos = max_select_topk(vec, 0, vec.size() - 1,10); cout << "第top10大的元素:" << vec[pos] << endl; cout << "前top10大的元素为:"; for (int i = pos; i < vec.size(); i++) { cout << vec[i] << " "; } cout << endl; pos = min_select_topk(vec, 0, vec.size() - 1, 4); cout << "第top4小的元素:" << vec[pos] << endl; cout << "前top4小的元素为:"; for (int i = 0; i <= pos; i++) { cout << vec[i] << " "; } cout << endl; sort(vec.begin(), vec.end()); for (int v : vec) { cout << v << " "; } cout << endl; }
快排的效率和基准数的选择有关,最差是数据已经趋于有序,基准数每次都在边上,二叉树退化为链表,时间复杂度为O(n*n),所以如果数据已经趋于有序,可以随机一个下标作为基准数
直到划分到子问题只剩一个元素了,认为已经是有序的,然后向上回溯,合并子问题的解
合并出原数组的解需要开辟额外的内存空间,写子问题合并后的解,再额外空间的这些数据找到原数组的范围,不能一边遍历一边写,要不然会把数据覆盖。
归并排序的空间复杂度是O(n),时间复杂度是O(nlogn)
#include<iostream> #include<vector> #include<algorithm> using namespace std; void merge(vector<int>& vec, int low, int mid, int high) { //定义额外的辅助空间,存储合并的子问题的有序数组 vector<int> tmp; //不能用resize //resize()是分配容器的内存大小,而capacity()只是设置容器容量大小,但并没有真正分配内存 tmp.reserve(high - low + 1); int i = low;//[low,mid] int j = mid+1;//[mid+1,high] while (i <= mid && j <= high) { vec[i] > vec[j] ? tmp.push_back(vec[j++]) : tmp.push_back(vec[i++]); } while (i <= mid) { tmp.push_back(vec[i++]); } while (j <= high) { tmp.push_back(vec[j++]); } //tmp里面的元素->vec当中 for (int t : tmp) { vec[low++] = t; } } void mergeSort(vector<int>& vec, int i, int j) { //子问题划分到一个元素的时候代表子问题的解已知 if (i == j) { return; } int mid = (i + j) / 2; //先划分子问题,降低问题规模 mergeSort(vec, i, mid); mergeSort(vec, mid + 1, j); //向上回溯,回溯过程中,合并子问题的解 merge(vec, i, mid, j); } int main() { vector<int> vec; for (int i = 0; i < 10; i++) { vec.push_back(rand() % 100); } for (int v : vec) { cout << v << " "; } cout << endl; mergeSort(vec, 0, vec.size()-1); for (int v : vec) { cout << v << " "; } cout << endl; }
代码中使用额外内存空间,如果使用vector.push_back的话不能用resize,因为resize会分配内存,并默认值为0,而应该用vector.reserve,设置容器容量大小,不分配内存。
#include<iostream> #include<algorithm> #include<vector> using namespace std; struct ListNode { int val; ListNode* next; ListNode(int x):val(x),next(nullptr){} }; ListNode* init_link(initializer_list<int> list) { ListNode* head = nullptr; ListNode* p = nullptr; for (int v : list) { if (head == nullptr) { head = new ListNode(v); p = head; } else { p->next = new ListNode(v); p = p->next; } } return head; } ListNode* mergeTwoLink(ListNode* p1, ListNode* p2) { ListNode* head = nullptr; if (p1 == nullptr) { return p2; } if (p2 == nullptr) { return p1; } if (p1->val > p2->val) { head = p2; p2 = p2->next; } else { head = p1; p1 = p1->next; } ListNode* p = head; while (p1 != nullptr && p2 != nullptr) { if (p1->val > p2->val) { p->next = p2; p = p2; p2 = p2->next; } else { p->next = p1; p = p1; p1 = p1->next; } } p->next = p1 ? p1 : p2; return head; } ListNode* mergeLink(vector<ListNode*>& vlink,int i,int j) { if (i >= j) { return vlink[i]; } int mid = (i + j) / 2; ListNode* left = mergeLink(vlink, i, mid); ListNode* right = mergeLink(vlink, mid + 1, j); return mergeTwoLink(left, right); } int main() { ListNode* p1 = init_link({ 1,3,5,7 }); ListNode* p2 = init_link({ 2,4,6,8 }); ListNode* p3 = init_link({ 0,9 }); ListNode* p4 = init_link({ 1,8 }); vector<ListNode*> vlink; vlink.push_back(p1); vlink.push_back(p2); vlink.push_back(p3); vlink.push_back(p4); ListNode* p = mergeLink(vlink, 0, vlink.size() - 1); for (ListNode* c = p; c != nullptr; c = c->next) { cout << c->val << " "; } cout << endl; }
偶数个数升序序列的中位数:(n/2+n/2+1)/2
奇数个数升序序列的中位数:n/2
问题:有两个升序的数组,长度分别是m和n,求两个数组所有元素的中位数是多少?要求在O(logn)时间内完成
分析:
在两个升序数组中找中位数的话,那么就是找第topk
个元素,第k个元素就是最中间的那个数字,第k
个元素的值就是max(arr[i-1],brr[j-1])
,中位数对应的下标应该是(m+n+1)/2
。如果找到k,若总长度是偶数,则中位数为(第k个+第k+1个元素)/2 (第k+1
个元素应该是min(arr[i],brr[j])
);若总长度为奇数,则中位数为第k个元素。
所以关键就是如何在对数时间内找到i和j合适的位置:
代码实现
#include<iostream> #include<vector> #include<cmath> #include<algorithm> using namespace std; double middleValue(vector<int>& nums1, int length1, vector<int>& nums2, int length2) { //在短的数组中求解合适的i和j值 if (length1 > length2) return middleValue(nums2, length2, nums1, length1); if (length1 == 0) { int k = (length2 - 1) / 2; if (length2 - 1 % 2 == 0) { return (nums2[k] + nums2[k + 1]) * 1.0 / 2; } else { return nums2[k]; } } int begin = 0; int end = length1; int k = (length1 + length2 + 1) / 2; int i; int j; //二分搜索的算法思想,在对数时间内找到i+j=k while (begin <= end) { i = (begin + end) / 2; j = k - i; if (j > 0 && i < length1 && nums2[j - 1] > nums1[i]) { begin = i + 1; } else if (i > 0 && j<length2 && nums1[i - 1]>nums2[j]) { end = i - 1; } else { break; } } int left = 0; //nums1特别短,而且nums1数组元素都特别大,中位数肯定在nums2数组中 if (i == 0) { left = nums2[j - 1]; } //nums2特别短,中位数肯定都在nums1数组中 else if (j == 0) { left = nums1[i - 1]; } else { left = max(nums1[i - 1], nums2[j - 1]); } int right = 0; //nums1数组元素太小了,而且值都特别小,中位数肯定在nums2数组中 if (i == length1) { right = nums2[j]; } //中位数肯定在nums1数组中 else if (j == length2) { right = nums1[i]; } else { right = min(nums1[i], nums2[j]); } //找到了合适的i和j值 if ((length1 + length2) % 2 == 0)//偶数长度 { return(left + right) * 1.0 / 2; } else//奇数长度 { return left; } } int main() { vector<int> vec1; vector<int> vec2; for (int i = 0; i < 10; i++) { vec1.push_back(rand() % 100); } for (int i = 0; i < 6; i++) { vec2.push_back(rand() % 100); } sort(vec1.begin(), vec1.end()); sort(vec2.begin(), vec2.end()); vector<int> vec = vec1; for (int v : vec2) { vec.push_back(v); } sort(vec.begin(), vec.end()); for (int v : vec) { cout << v << " "; } cout << endl; double midval = middleValue(vec1, vec1.size(), vec2, vec2.size()); cout << midval << endl; }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。