基本思想:分治法
分治法(Divide-and-Conque Algorithm),基本思想是分而治之,把一个用暴力解决起来复杂度非常高的算法拆分成两个或者两个以上的相似的子问题,再把子问题进行同样的拆分得到更小的子问题,直到拆分得到的子问题能够通过很简单的方式解决为止,最后的解便是子问题的解的合并。
是不是超级像递归???!!!没错,就是用的递归的方法!!
最后附上经典分治法案例的分析:经典优化算法之分治法
归并排序
题目:
原理
首先先用递归分解数组,将数组分解到最小之后再合并数组,将两个无序的数组合并成一个有序的数组。
基本思路就是依次比较两个数组,谁小就取谁,最后如果任意一组没有比完就加在后面。
图例详解参考大佬:归并排序原理及实现
思路
为了能使主函数看起来更为简洁,且为了提高代码的可读性,将每一项功能的代码封装成一个函数。
分为三个函数:
1.数组打印函数
2.实现一次归并排序的函数
3.实现归并排序函数
!!!!注意:为了不在OJ上面提交报Runtime Error,尽量不要在递归过程中重复开数组,可以设置一个全局变量的数组,或者是作为函数的参数传递进去,反正不要重复开数组
同时,system(“pause”)也会造成Runtime Error,不要加。
代码
# include<stdio.h>
# include<stdlib.h>
// 为了在main函数里面的代码尽可能的简洁,所以尽量把所有功能都用函数先定义好,再在main函数里面调用
// 首先明确归并排序的思路,先是无限二分,也就是递归,再往回归并
// 打印数组的函数
void print_arr(int arr[],int len)
{
int i=0;
for(i=0;i<len;i++)
printf("%d ",arr[i]);
return;
}
//定义归并部分的函数
void merge(int arr[],int result[],int start,int mid,int end,int arr_length)
{
int i = start;
int j = mid+1;
int k = 0;
while (i <= mid && j <= end)
{
if (arr[i] < arr[j])
result[k++] = arr[i++];
else
result[k++] = arr[j++];
}
while (i <= mid)
{
result[k++] = arr[i++];
}
while (j <= end)
{
result[k++] = arr[j++];
}
for (j = 0, i = start ; j < k; i++, j++) {
arr[i] = result[j];
}
return;
}
//定义递归函数
void MergeSort(int arr[],int temp[],int start,int end)
{
if (start >= end)
return;
int mid = (start+end)/2;
MergeSort(arr,temp,start,mid);
MergeSort(arr,temp,mid+1,end);
merge(arr,temp,start,mid,end,(end-start+1));
return;
}
//main函数
int main()
{
int n;//第一行输入的数
scanf("%d",&n);
int a[n];
for(int i=0;i<n;i++)
scanf("%d",&a[i]);
int t[n];
MergeSort(a,t,0,n-1);
print_arr(a,n);
return 0;
}
可能遇到的报错及解决(附加OJ常见报错)
- Runtime Error
- 可能是在递归过程中重复开数组
- 有可能是加了system(“pause”)
- 有可能是无限递归了,有递无归
- 有可能是数组下标访问越界
- 有可能是malloc空间之后没有正确free,造成内存访问错误
…
- duplicated source code
就是前后两次提交的代码重复率太高,加一些空格或者改变一下变量名就能解决 - wrong answer
答案错误,再来吧!
更多的报错类型参考:OJ(vjudge)错误提示类型
逆序对数量
题目:
原理
用到的方法与归并排序相似
思路
这里的思路是借鉴了大佬的:逆序对的几种求法
最基本的做法就是暴力搜索,but脚指头都不动都知道肯定会超时。
最有效的方法就是在前面递归排序的基础上进行修改,因为在归并的过程中,有前后两个数组比较的过程,假设对于后面数组(称为B)的第k个元素(成为B[k]),在前面数组(称为A)找到第一个比B[k]大的数,那么在A数组中的这个数后面的数都能与B[k]组成逆序对,这样算法复杂度就从暴力搜索的O(
n
2
n^2
n2)降为了O(
n
n
n),再乘以拆分的复杂度O(
l
o
g
n
log^n
logn),所以最后的复杂度就是O(
n
l
o
g
n
nlog^n
nlogn),而我们需要做的就是在前面归并排序的基础上再加一个全局变量count(同样也是为了防止每次在递归过程中定义变量而造成超时),作为计数器
代码
# include<stdio.h>
# include<stdlib.h>
// 为了在main函数里面的代码尽可能的简洁,所以尽量把所有功能都用函数先定义好,再在main函数里面调用
// 首先明确归并排序的思路,先是无限二分,也就是递归,再往回归并
//首先定义一个全局变量
long long count=0;
//定义归并部分的函数
void merge(int arr[],int result[],int start,int mid,int end,int arr_length)
{
int i = start;
int j = mid+1;
int k = 0;
while (i <= mid && j <= end)
{
if (arr[i] <= arr[j])
result[k++] = arr[i++];
else
result[k++] = arr[j++],count +=mid-i+1;
}
while (i <= mid)
{
result[k++] = arr[i++];
}
while (j <= end)
{
result[k++] = arr[j++];
}
for (j = 0, i = start ; j < k; i++, j++) {
arr[i] = result[j];
}
return;
}
//定义递归函数
void MergeSort(int arr[],int temp[],int start,int end)
{
if (start >= end)
return;
int mid = (start+end)/2;
MergeSort(arr,temp,start,mid);
MergeSort(arr,temp,mid+1,end);
merge(arr,temp,start,mid,end,(end-start+1));
return;
}
//main函数
int main()
{
int n;//第一行输入的数
scanf("%d",&n);
int a[n];
for(int i=0;i<n;i++)
scanf("%d",&a[i]);
int t[n];
MergeSort(a,t,0,n-1);
printf("%lld",count);
return 0;
}
可能遇到的报错及解决
除了前一部分罗列的那些错误之外,可能遇到在某一个测试样例上出错的情况
- wrong answer on test 2
个人猜测test2就是一个等大的数列,比如2 2 2…,这个测试样例就是要让我们关注代码中取等时,该不该计数 - wrong answer on test 56
test 56这个样例是一个规模很大的数列,如果规模是 1 0 6 10^6 106,逆序排列,那最后的计数就是 1 0 11 10^{11} 1011,所以计数器count的数据类型就只能为long long