分治算法的思想
分治算法的思想就是分而治之,比如规模为n的原问题的解无法直接求出,进行问题规模的缩减,划分子问题。如果子问题的规模仍然不够小,再进行子问题划分,如此递归的进行下去,直到问题规模足够小,很容易求出其解为止,最后将求出的小规模的问题的解合并为一个更大规模的问题的解,自底向上逐步求出原问题的解。
分治算法适用条件
1.原问题的规模缩小到一定的程度就可以容易地解决
⒉原问题可以分解为若干个规模较小的相同问题,即原问题具有最优子结构性质
3.利用原问题分解出的子问题的解可以合并为原问题的解
4.原问题所分解出的各个子问题是相互独立的,即子问题之间不包含公共的子问题(这条特征涉及到分治法的效率,如果各个子问题不独立,也就是子问题划分有重合的部分,则分治法要重复的求解公共子问题的解,此时虽然也可用分治法,但采用动态规划更好)
二分查找
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
// 分治算法思想 - 二分搜索
bool binarySearch(vector<int> &vec, int i, int j, int val) {
if (i > j) { // 元素不存在
return false;
}
int mid = (i + j) / 2;
if (vec[mid] == val) { // 子问题[i,j] 解是已知的了
return true;
}
else if (vec[mid] > val) { // 在[i, mid-1]子规模问题中
return binarySearch(vec, i, mid-1, val);
}
else { // 在[mid+1, j]子规模问题中
return binarySearch(vec, mid + 1, j, val);
}
}
int main1()
{
vector<int> vec;
for (int i = 0; i < 11; ++i) {
vec.push_back(rand() % 100);
}
sort(vec.begin(), vec.end());
for (int v : vec) {
cout << v << " ";
}
cout << endl;
bool result = binarySearch(vec, 0, vec.size() - 1, 34);
cout << "result:" << result << endl;
return 0;
}
快速排序
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
// 快排划分函数,调整基准数
int partation(vector<int> &vec, int i, int j) {
int val = vec[i]; // 作为基准数
int l = i;
int r = j;
while (l < r) {
while (l < r && vec[r] >= val) { // 右 - 左 找第一个比val小的
r--;
}
if (l < r) {
vec[l++] = vec[r];
}
while (l < r && vec[l] < val) { // 左 - 右 找第一个比val大的
l++;
}
if (l < r) {
vec[r--] = vec[l];
}
}
vec[l] = val; // 放置基准数
return l; // 返回基准数的下标
}
void quickSort(vector<int> &vec, int i, int j) {
if (i >= j) {
return;
}
int pos = partation(vec, i, j);
quickSort(vec, i, pos - 1);
quickSort(vec, pos + 1, j);
}
int main()
{
vector<int> vec;
for (int i = 0; i < 11; ++i) {
vec.push_back(rand() % 100);
}
for (int v : vec) {
cout << v << " ";
}
cout << endl;
quickSort(vec, 0, vec.size() - 1);
for (int v : vec) {
cout << v << " ";
}
cout << endl;
return 0;
}
求大数据的top K问题
十万个数字求前十大的数据:
- 大根堆小根堆 (优先级队列)。
- 快排的划分函数。
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
// 快排划分函数,调整基准数
static int partation(vector<int> &vec, int i, int j) {
int val = vec[i]; // 作为基准数
int l = i;
int r = j;
while (l < r) {
while (l < r && vec[r] >= val) { // 右 - 左 找第一个比val小的
r--;
}
if (l < r) {
vec[l++] = vec[r];
}
while (l < r && vec[l] < val) { // 左 - 右 找第一个比val大的
l++;
}
if (l < r) {
vec[r--] = vec[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 = partation(vec, i, j); // pos表示基准数的位置
if (pos == vec.size() - k) { // 基准数的位置和top k的k值相等了
return pos;
}
else if (pos < vec.size() - k) { // topk应该在基准数的右边
return max_select_topk(vec, pos + 1, j, k);
}
else { // topk应该落在基准数的左边
return max_select_topk(vec, i, pos - 1, k);
}
}
// 找第k小的, k-1小的 下标 3
int min_select_topk(vector<int> &vec, int i, int j, int k) {
int pos = partation(vec, i, j); // pos表示基准数的位置
if (pos == k-1) { // 基准数的位置和top k的k值相等了
return pos;
}
else if (pos < k-1) { // topk应该在基准数的右边
return min_select_topk(vec, pos + 1, j, k);
}
else { // topk应该落在基准数的左边
return min_select_topk(vec, i, pos - 1, k);
}
}
int main3()
{
vector<int> vec;
for (int i = 0; i < 20; ++i) {
vec.push_back(rand() % 100);
}
// 求第top 10大的元素
int pos = max_select_topk(vec, 0, vec.size() - 1, 4);
cout << "第topk大的:" << vec[pos] << endl;
cout << "前topk大的:";
for (int i = pos; i < vec.size(); ++i) {
cout << vec[i] << " ";
}
cout << endl;
// 找topl小的
pos = min_select_topk(vec, 0, vec.size() - 1, 4);
cout << "第topk小的:" << vec[pos] << endl;
cout << "前topk小的:";
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;
return 0;
}
归并排序
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
void merge(vector<int> &vec, int low, int high, int mid) {
// 定义额外的辅助空间,存储合并的子问题的有序数组
vector<int> tmp;
tmp.reserve(high - low + 1); // reserve resize
int i = low; // [low, mid]
int j = mid + 1; // [mid+1, high]
while (i <= mid && j <= high) {
if (vec[i] > vec[j]) {
tmp.push_back(vec[j++]);
}
else {
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 k = low; k <= high; ++k) {
vec[k] = tmp[k - low];
}
}
// 分治算法应用 - 归并排序
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, j, mid); // [i,mid] [mid+i, j]
}
int main4()
{
vector<int> vec;
for (int i = 0; i < 11; ++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;
return 0;
}
合并K个有序单链表
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
class Solution {
public:
ListNode* merge(ListNode* a,ListNode * b)
{
ListNode *head = nullptr;
if(a == nullptr){
return b;
}
if(b == nullptr){
return a;
}
if(a->val > b->val){
head = b;
b = b->next;
}
else{
head = a;
a = a->next;
}
ListNode *p = head;
while(a!=nullptr && b!=nullptr)
{
if(a->val > b->val){
p->next = b;
p = b;
b = b->next;
}
else{
p->next = a;
p = a;
a = a->next;
}
}
if(a!=nullptr){
p->next = a;
}
if(b!=nullptr){
p->next = b;
}
return head;
}
ListNode* mergeLink(vector <ListNode*> &lists, int l, int r) {
if (l == r) return lists[l];
if (l > r) return nullptr;
int mid = (l + r) >> 1;
ListNode *left = mergeLink(lists, l, mid);
ListNode *right = mergeLink(lists, mid + 1, r);
return merge(left,right);
}
ListNode* mergeKLists(vector<ListNode*>& lists)
{
int i = 0;
int j = lists.size()-1;
int mid = (i+j)/2;
return mergeLink(lists,i,j);
}
};
对数时间求解中位数
double middleValue(vector<int> &nums1, int length1, vector<int> &nums2, int length2) {
if (length1 > length2) { // 在短的数组中求解合适的i和j值
return middleValue(nums2, length2, nums1, length1);
}
if (length1 == 0) {
// 0 0 0 0 0 0 0 (6-1)/2=3
int k = (length2 - 1) / 2;
if (length2 % 2 == 0) {
return (nums2[k] + nums2[k + 1]) * 1.0 / 2;
}
else {
return nums2[k];
}
}
int i = 0;
int j = 0;
int begin = 0;
int end = length1;
int k = (length1 + length2 + 1) / 2; // 7 / 2 = 3 0 0 0 0 0 0 0
while (begin <= end) { // 二分搜索的算法思想,对数时间找到i+j = k
i = (begin + end) / 2;
j = k - i;
if (i > 0 && j < length2 && nums1[i - 1] > nums2[j]) {
end = i - 1;
}
else if (j > 0 && i < length1 && nums2[j - 1] > nums1[i]) {
begin = i + 1;
}
else {
break; // arr[i-1] < brr[j] && brr[j-1] < arr[i]
}
}
// nums1特别短,而且nums1数组的元素的值都特别大
int left = 0;
if (i == 0) { // 中位数肯定都在num2这个数组当中
left = nums2[j - 1];
}
else if (j == 0) { // nums2这个数组太短了 中位数肯定都在num1这个数组当中
left = nums1[i - 1];
}
else {
left = std::max(nums1[i - 1], nums2[j - 1]);
}
int right = 0;
if (i == length1) { // nums1数组元素太少,而且值都特别的小 中位数肯定都在num2这个数组当中
right = nums2[j];
}
else if (j == length2) { //中位数肯定都在num1这个数组当中
right = nums1[i];
}
else {
right = std::min(nums1[i], nums2[j]);
}
// 找到了合适的i和j的值
if ((length1 + length2) % 2 == 0) { // 偶数长度
return (left + right)*1.0 / 2;
}
else { // 奇数长度
return left;
}
}