一、实验目的
1 掌握分治法的设计思想(划分、分解、合并)
2 掌握分治法的具体实现和时间复杂度分析
3 理解分治法的常见特性
- 实验内容
1 完成一维数组求和问题
2 求a的n次方
3 计算一位数组最大值的问题
4 快速排序
5 无序数组中查找数值k问题
6 大整数相乘问题
三、问题分析[按照实现内容的等级, 分开写,并体现每个同学的任务]
1、一维数组求和:
利用分治法将数组分成两个部分,然后利用递归对两个部分分别求和,最后再相加起来,就能求得总和;
2、求a的n次方
首先判断n是偶数还是奇数,如果n是偶数,就直接分成两个a的n/2相乘,利用递归求解;如果是奇数,就分成两个a的(n-1)/2次方和a相乘,然后再利用递归求解;
3、计算一维数组最大值的问题
先处理递归的出口,然后计算中点值,将数组分成两个部分,利用递归分别求出其最大值,最后再将这两个最大值进行比较,返回较大的值;
4、快速排序
首先对待排序记录序列进行划分,划分的轴值应该遵循平衡子问题的原则,使划分后两个子序列的长度尽量相等
找到轴值之后,以轴值为基准将待排序序列划分为两个子序列,对每一个子序列分别进行递归处理
5、无序数组查找数值k的问题
因为无序数组不方便对其直接查找,所以先对无序数组进行排序,利用快速排序的方法,将其排成有序数组之后,计算数组下标的中点,判断中点值是不是要查找的数,若是,则直接返回中点;然后再判断中点值和k的大小,分成两个区间利用递归去找,找到返回即可;
6、大整数相乘
现有两个大数x,y,首先先将x,y分别拆开成为两部分,可以的x1,x0,y1,y0,然后再分别去找x和y的位数,将大整数的乘法分解成若干个小整数相乘,最后再一起加起来。
四、问题解决[按照实现内容的等级, 分开写,并体现每个同学的任务]
1、一维数组求和
(1)、算法DivideSum()
输入:一维整形数组啊a[ ],数组左下标l,数组右下标r
输出:整形数组的和
过程:1、如l==r,则返回a[l];
2、计算中间值m=(l+r)/2;
3、调用DivideSum(a,l,m)得到前半部分和sum1
4、调用DivideSum(a,m+1,r)得到后半部分和sum2
5、返回sum1+sum2;
(2)、代码:
public class ch1 {
/*
一维数组求和
*/
int DivideSum(int []a,int l,int r)
{
if (l==r) {
return a[l];
}
int m=(l+r)/2;
int sum1=DivideSum(a,l,m); /* 求前一半 */
int sum2=DivideSum(a,m+1,r); /* 求后一半 */
int sum;
sum = sum1+sum2;
return sum;
}
}
该算法的时间复杂度为:O(log2n)
(3)、运行截图
2、求a的n次方
(1)、算法getSum(int a,int n)
输入:底数a,幂n
输出:a的n次方
过程:1、如n==1,则返回a;
2、如n%2==0,则返回调用getSum(a,n/2)*getSum(a,n/2)得的值
3、如n%2!=0,则返回调用getSum(a,(n-1)/2)*getSum(a,(n-1)/2)*a得的值
(2)、代码:
public class ch2 {
/*
求a的n次方
*/
int getSum(int a,int n){
if (n==1) {
return a;
}
else if (n%2==0)
{
return getSum(a,n/2)*getSum(a,n/2);/* n如果是偶数 */
}
else
{
return getSum(a,(n-1)/2)*getSum(a,(n-1)/2)*a; /* n如果是奇数 */
}
}
}
该算法时间复杂度为O(log2n)
(3)、运行截图:
3、计算一维数组的最大值
(1)、算法:getMax(int []a,int l,int r)
输入:一位整形数组a[],数组左下标l,数组右下标r
输出:数组最大值max
过程:1、如l==r,则返回a[l];
2、计算划分中点m=(l+r)/2;
3、调用getMax(a,l,m)得前半区间最大值lmax
4、调用getMax(a,m+1,r)得后半区间最大值rmax
5、如lmax>rmax,则返回lmax,否则返回rmax、
(2)、代码:
public class ch3 {
/*
求一维数组最大值
*/
int getMax(int []a,int l,int r){
if (l==r)
return a[l];
else
{
int m=(l+r)/2;
int lMax=getMax(a,l,m);
int rMax=getMax(a,m+1,r);
if (lMax>rMax)
return lMax;
else
return rMax;
}
}
}
该算法的时间复杂度为:O(log2n)
(3)、运行截图:
4、快速排序:
(1)、算法:设函数Partition实现对序列r[first]~r[end]进行划分,函数QuiSort实现快速排序;对于该算法的时间复杂度,最好的情况下,每次划分对一个记录定位后,该记录的左侧子序列与右侧子序列的长度相同。对于n个记录的序列中有:
T(n)=2T(n/2)+n=……= O(nlog2n)
而对于最坏情况下,待排序记录序列正序或者逆序,每次划分只得到一个比上依次划分少一个记录的子序列,此时的时间复杂度为O(n2)
平均下来时间复杂度为O(nlog2n)
(2)、代码:public class ch5 {
int Partition(int []r,int first,int end){
int temp,i=first,j=end;
while (i<j)
{
while (i<j && r[i]<=r[j] ) {
j--;
}
if(i<j)
{
temp = r[i];
r[i]=r[j];
r[j]=temp;
i++;
}
while (i<j && r[i]<=r[j]){
i++;
}
if (i<j)
{
temp = r[i];
r[i]=r[j];
r[j]=temp;
j--;
}
}
return i;
}
void QuickSort(int []r,int first,int end){
if (first<end)
{
int pivot =Partition(r,first,end);
QuickSort(r,first,pivot-1);
QuickSort(r,pivot+1,end);
}
}
}
(3)、运行截图:
5、无序数组查找元素k
(1)、算法:无序数组可以先用上述快速排序现将其变成有序数组,之后在对其查找
算法:SearchK(int a[],int l,int r,int k)
输入:一位整形数组a[],数组左下标l,数组右下标r
输出:数组中与元素k相同的值的下标
过程:1、计算中点m=(l+r)/2
2、如a[m]==k,则返回m;
3、如a[m]>k,则返回调用SearchK(a,l,m-1);
4、如a[m]<k,则返回调用SearchK(a,m+1,r);
(2)、代码:
public class ch4 {
/*
无序数组查找k
*/
int searchK(int []a,int l,int r,int k){
int m = (l+r)/2;
if (l<=r){
if (a[m]==k){
return m;
}
else if(a[m]>k)
{
return searchK(a,l,m-1,k);
}
else
{
return searchK(a,m+1,r,k);
}
}
else {
return -1;
}
}
}
该算法的时间复杂度为:O(log2n)
(3)、运行截图:
实验1~5的主函数:
public class Main {
public static void main(String[] args) {
ch1 cha1 = new ch1();
Scanner sc = new Scanner(System.in);
int n;
System.out.println("请输入数组长度:");
n=sc.nextInt();
int []m= new int[n];
System.out.println("请输入数组元素:");
for (int i=0;i<m.length;i++){
m[i]= sc.nextInt();
}
int k;
k=cha1.DivideSum(m,0,n-1);
System.out.println("一维数组之和为:"+k);
/*------------------------------------------*/
ch3 cha3 = new ch3();
int max=cha3.getMax(m,0,n-1);
System.out.println("该数组的最大值为:"+max);
/*------------------------------------------*/
ch5 cha5 = new ch5();
cha5.QuickSort(m,0,n-1);
System.out.println("数组快速排序后为(升序排序):");
System.out.println(Arrays.toString(m));
/*-------------------------------------------*/
ch4 cha4 = new ch4();
System.out.println("请输入你要查找的元素k:");
int kk=sc.nextInt();
System.out.println("k的下标为:"+cha4.searchK(m,0,n-1,kk));
/*------------------------------------------------*/
ch2 cha2 = new ch2();
int x,y;
System.out.println("请输入底数a");
x=sc.nextInt(); /*底数*/
System.out.println("请输入幂n"); /* 幂 */
y=sc.nextInt();
int sum=cha2.getSum(x,y);
System.out.println("a的n次方为"+sum);
/*---------------------------------------------------*/
}
}
6、大整数相乘:
(1)、对于x=1234,y=5678,首先先将x拆成a和b两部分,将y拆为c和d两部分,然后执行Karatsuba算法,计算过程如下:
步骤1:计算a*c
步骤2:计算b*d
步骤3:计算(a+b)*(c+d)
步骤4:步骤3的结果减去步骤1和步骤2的结果
步骤5:计算a*c*104 +步骤4的结果*102 +b*d
当整数很大时就一直拆分,用递归一直拆分,直到整数足够小之后进行求解
(2)、代码:
#include<iostream>
#include<cmath>
#include<stdio.h>
using namespace std;
//找到x的位数
int size(long x){
int num=0;
do{
num++;
x=x/10;
}while(x);
return num;
}
long Karatsuba(long x,long y)
{
if(x<10 || y<10) return x*y;
else{
long m , x1 , x0 ,y1 , y0 , z0 , z1 , z2 ;
m=max(size(x),size(y))/2;
x1 = x/(int)pow(10,m);
x0 = x-x1*(int)pow(10,m);
y1 = y/(int)pow(10,m);
y0 = y-y1*(int)pow(10,m);
z2 = Karatsuba(x1,y1);
z0 = Karatsuba(x0,y0);
z1 = Karatsuba((x1+x0),(y1+y0))-z2-z0;
return z2*(int)pow(10,2*m)+z1*(int)pow(10,m)+z0;
}
}
int main()
{
long a , b;
cin >> a >> b;
cout << Karatsuba(a,b) << endl;
return 0;
}
该算法的时间复杂度为:O(log3n)
(3)、运行截图:
五、实验结果总结
回答以下问题:
- 分治法实现的复杂度一定比蛮力法优吗?举例说明
分治法的时间复杂度一般情况下比蛮力法要好,但是也不是所有的分治法的时间复杂度都比蛮力法低,例如,蛮力法的选择排序时间复杂度只有O(n),而分治法的归并排序的时间复杂度确实O(nlog2n)
- 分治法实现一定要用递归吗?你能实现一个不用递归的分治吗?
分治法也不一定要使用递归的方法,例如:有序数组查找元素k
public static int k(int[]number,int k,int r) {
int low,high,mid;
low=0;
high=r-1;
while(low<=high) {
mid=(low+high)/2;
if(number[mid]==k)return mid;
if(number[mid]>k)high=mid-1;
if(number[mid]<k)low= mid+1;
}
return 0;
}
这也是分治法,但是它并没有应用到递归。
- 假定原问题的规模为n,分解的子问题的规模为n/b,分解的子问题的个数为a,a与b之间的大小关系如何,举例说明。
T(n)=a*T(n/b)
T(n)=a*blogbn-1
- 通过实验叙述你对分治法的理解及其优缺点。
分治法是一种将一个复杂的问题分解成若干个规模较小、相互独立,但类型相同的子问题求解;然后再将各种子问题的解组合成原始问题的一个完整答案。
它的优点有:结构清晰,可读性强,而且容易用数学归纳法来证明算法的正确性,因此它为设计算法、调试程序带来很大方便
它的缺点有:递归算法的运行效率较低,无论是耗费的计算时间还是占用的存储空间都比非递归算法要多。