C/C++语言算法篇(二):分治算法

分治算法

首先呢,分治法就是把大问题分成若干小问题,接着就是解决小问题,大问题就可以顺手解决咯。分治法必须满足的条件是:
(1)原问题可分解为若干个规模较小的相同子问题。 (2)子问题相互独立。 (3)子问题的解可以合并为原问题的解。 一般的步骤有:
(1)分解:将要解决的问题分解为若干个规模较小、相互独立、与原问题形式相同的子问题。
(2)治理:求解各个子问题。由于各个子问题与原问题形式相同,只是规模较小而已,而当子问题划分得足够小时,就可以用较简单的方法解决。
(3)合并:按原问题的要求,将子问题的解逐层合并构成原问题的解。
一言以蔽之,分治法就是将一个难以直接解决的大问题,分割成一些规模较小的相同问题,以便各个击破,分而治之。
在分治算法中,各个子问题形式相同,解决的方法也一样,因此我们可以使用递归算法快速解决,递归是彰显分治法优势的利器。

实战训练

1、 二分查找法

问题描述:给定 n 个元素,这些元素是有序的(假定为升序),从中查找特定元素 x。
算法思想:将有序序列分成规模大致相等的两部分,然后取中间元素与特定查找元素 x进行比较,如果 x 等于中间元素,则查找成功,算法终止;如果
x
小于中间元素,则在序列的前半部分继续查找,即在序列的前半部分重复分解和治理操作;否则,在序列的后半部分继续查找,即在序列的后半部分重复分解和治理操作。
算法设计:用一维数组 S[]存储该有序序列,设变量 low 和 high 表示查找范围的下界和上界, middle 表示查找范围的中间位置,
x 为特定的查找元素。 (1)初始化。令 low=0,即指向有序数组 S[]的第一个元素;
high=n-1,即指向有序数组S[]的最后一个元素。 (2) middle=( low+high) /2,即指示查找范围的中间元素。
(3)判定 low≤high 是否成立,如果成立,转第 4 步,否则,算法结束。 (4)判断 x 与 S[middle]的关系。如果
x=S[middle],则搜索成功,算法结束;如果 x>S[middle],则令 low=middle+1;否则令
high=middle-1,转为第 2 步。 源代码(1)

源代码(1)

//已经知道数组是有序的排列,输入查找的数字,并输出它的所在位置
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstdlib>
using namespace std;

int main()
{
	int str[20];
	int i,n = 0,m,sum,temp=0;
	cout<<"请输入数组的大小:"<<endl;
	cin>>sum;
	cout<<"请输入数组的数字,以空格隔开每一个数字:"<<endl;
	for(i=0;i<sum;i++)
	cin>>str[i];
	sort(str,str+sum);
	cout<<"请输入你要查找的数字:"<<endl;
	cin>>m;
	int t = sum-1;
	while(t>=n)
	{
		int middle = (n+t)/2;
		if(str[middle]==m)
		{
			temp = 1;
			cout<<middle+1;
			break;
		}
		else if(str[middle]>m)
		{
			t = middle-1;
			continue;
		}
		else
		{
			t = middle+1;
			continue;
		}
	}
	if(temp == 0)
	cout<<"查找失败!"<<endl;
	return 0;
}

源代码(2)

#include <iostream>
#include <cstdlib>
#include <cstdio>
#include <algorithm>
using namespace std;
const int M=10000;
int x,n,i;
int s[M];
int BinarySearch(int n,int s[],int x)
{
int low=0,high=n-1; //low 指向数组的第一个元素, high 指向数组的最后一个元素
while(low<=high)
{
int middle=(low+high)/2; //middle 为查找范围的中间值
if(x==s[middle]) //x 等于查找范围的中间值,算法结束
return middle;
else if(x<s[middle]) //x 小于查找范围的中间元素,则从前半部分查找
high=middle-1;
else //x 大于查找范围的中间元素,则从后半部分查找
low=middle+1;
}
return -1;
}
int main()
{
cout<<"请输入数列中的元素个数 n 为: ";
while(cin>>n)
{
cout<<"请依次输入数列中的元素: ";
for(i=0;i<n;i++)
cin>>s[i];
sort(s,s+n);
cout<<"排序后的数组为: ";
for(i=0;i<n;i++)
{
cout<<s[i]<<" ";
}
cout<<endl;
cout<<"请输入要查找的元素: ";
cin>>x;
i=BinarySearch(n,s,x);
if(i==-1)
cout<<"该数列中没有要查找的元素"<<endl;
else
cout<<"要查找的元素在第"<<i+1<<"位"<<endl;
}
return 0;
}

2、 合并排序法

合并排序就是采用分治的策略,将一个大的问题分成很多个小问题,先解决小问题,再通过小问题解决大问题。由于排序问题给定的是一个无序的序列,可以把待排序元素分解成两个规模大致相等的子序列。如果不易解决,再将得到的子序列继续分解,直到子序列中包含的元素个数为
1。因为单个元素的序列本身是有序的,此时便可以进行合并,从而得到一个完整的有序序列。 合并排序是采用分治策略实现对 n
个元素进行排序的算法, 是分治法的一个典型应用和完美体现。它是一种平衡、简单的二分分治策略,过程大致分为:
(1)分解—将待排序元素分成大小大致相同的两个子序列。 (2)治理—对两个子序列进行合并排序。
(3)合并—将排好序的有序子序列进行合并,得到最终的有序序列。

源代码(1)

#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstdlib>
using namespace std;

void sort(int A[],int low,int mid,int high)
{
	int *B = new int[high-low+1]; //申请一个辅助数组
	int i = low, j = mid+1, k = 0;
	while(i <= mid && j <= high)
	{//按从小到大存放到辅助数组 B[]中
	if(A[i] <= A[j])
	B[k++] = A[i++];
	else
	B[k++] = A[j++];
	}
	while(i <= mid) B[k++] = A[i++]; //将数组中剩下的元素复制到数组 B 中
	while(j <= high) B[k++] = A[j++];
	for(i = low, k = 0; i <= high; i ++)
	A[i] = B[k++];
	delete B//这一步必须的,否则以后就知道事情大条了。
}

int main()
{
	int i,n,m,sum;
	int str[100];
	cout<<"请输入数列中元素的个数:"<<endl;
	cin>>sum;
	cout<<"请依次输入数列中的元素大小:"<<endl;
	for(i=0;i<sum;i++)
	cin>>str[i];
	m = (sum-1)/2;
	sort(str,0,m,sum-1);
	cout<<"合并排序的结果为:"<<endl;
	for(i = 0;i<sum;i++)
	cout<<str[i]<<" ";
	cout<<endl;
	return 0;
}

源代码(2)

#include <iostream>
#include <cstdlib>
#include <cstdio>
using namespace std;
void MergeSort(int A[], int low, int high)
{
if(low < high)
{
int mid = (low+high) /2; //取中点
MergeSort(A, low, mid); //对 A[low:mid]中的元素合并排序
MergeSort(A, mid+1, high); //对 A[mid+1:high]中的元素合并排序
Merge(A, low, mid, high); //合并
}
}
int main()
{
int n, A[100];
cout<<"请输入数列中的元素个数 n 为: "<<endl;
cin>>n;
cout<<"请依次输入数列中的元素: "<<endl;
for(int i=0; i<n; i++)
cin>>A[i];
MergeSort(A,0,n-1);
cout<<"合并排序结果: "<<endl;
for(int i=0;i<n;i++)
cout<<A[i]<<" ";
cout<<endl;
return 0;
}

3、 快速排序法

快速排序的基本思想是通过一组排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此使所有数据变成有序序列
基本步骤
(1)分解:先从数列中取出一个元素作为基准元素。以基准元素为标准,将问题分解为两个子序列,使小于或等于基准元素的子序列在左侧,使大于基准元素的子序列在右侧。
(2)治理:对两个子序列进行快速排序。 (3)合并:将排好序的两个子序列合并在一起,得到原问题的解。 设当前待排序的序列为
R[low:high],其中 low≤high,如果序列的规模足够小,则直接 进行排序,否则分 3 步处理。 步骤实现 (1)分解:在
R[low: high]中选定一个元素 R[pivot],以此为标准将要排序的序列划分为两个序列 R[low:pivot-1]和
R[pivot+1:high],并使用序列 R[low:pivot-1]中所有元素的值小于等于 R[pivot],序列
R[pivot+1:high]中所有元素均大于 R[pivot],此时基准元素已经位于正确的位置,它无需参加后面的排序,
(2)治理:对于两个子序列 R[low:pivot-1]和 R[pivot+1:high],分别通过递归调用快速排序算法来进行排序。
(3)合并:由于对 R[low:pivot-1]和 R[pivot+1:high]的排序是原地进行的,所以在R[low:pivot-1]和
R[pivot+1:high]都已经排好序后,合并步骤无需做什么,序列 R[low:high]就已经排好序了
另外基准元素的取舍也很有讲究的(任意一种都可以) 1、 取第一个元素。 2、 取最后一个元素。 3、 取中间位置元素。
4、 取第一个、最后一个、中间位置元素三者之中位数。 5、 取第一个和最后一个之间位置的随机数 k( low≤k≤high),选
R[k]做基准元素。

源代码(1)

#include <stdio.h> 
int a[101],n;//定义全局变量,这两个变量需要在子函数中使用 
void quicksort(int left,int right) 
{ 
 int i,j,t,temp; 
 if(left>right) 
 return; 
 temp=a[left]; //temp中存的就是基准数 
 i=left; 
 j=right; 
 while(i!=j) 
 { 
 //顺序很重要,要先从右往左找 
 while(a[j]>=temp && i<j) 
 j--; 
 //再从左往右找 
 while(a[i]<=temp && i<j) 
 i++; 
 //交换两个数在数组中的位置 
 if(i<j)//当哨兵i和哨兵j没有相遇时
 { 
 t=a[i]; 
 a[i]=a[j]; 
 a[j]=t; 
 } 
 } 
 //最终将基准数归位 
 a[left]=a[i]; 
 a[i]=temp; 
 
 quicksort(left,i-1);//继续处理左边的,这里是一个递归的过程 
 quicksort(i+1,right);//继续处理右边的,这里是一个递归的过程 
} 
int main() 
{ 
 int i,j,t; 
 //读入数据 
 scanf("%d",&n); 
 for(i=1;i<=n;i++) 
 scanf("%d",&a[i]); 
 quicksort(1,n); //快速排序调用 
 
 //输出排序后的结果 
 for(i=1;i<=n;i++) 
 printf("%d ",a[i]); 
 getchar(); 
 return 0; 
}

源代码(2)

#include <iostream>
using namespace std;
int Partition(int r[],int low,int high) //划分函数
{
int i=low,j=high,pivot=r[low]; //基准元素
while(i<j)
{
while(i<j&&r[j]>pivot) j--; //向左扫描
if(i<j)
{
swap(r[i++],r[j]); //r[i]和 r[j]交换后 i 右移一位
}
while(i<j&&r[i]<=pivot) i++; //向右扫描
if(i<j)
{
swap(r[i],r[j--]); //r[i]和 r[j]交换后 j 左移一位
}
}
return i; //返回最终划分完成后基准元素所在的位置
}
void QuickSort(int R[],int low,int high)//快速排序递归算法
{
int mid;
if(low<high)
{
mid=Partition(R,low,high); //基准位置
QuickSort(R,low,mid-1); //左区间递归快速排序
QuickSort(R,mid+1,high); //右区间递归快速排序
}
}
int main()
{
int a[1000];
int i,N;
cout<<"请先输入要排序的数据的个数: ";
cin>>N;
cout<<"请输入要排序的数据: ";
for(i=0;i<N;i++)
cin>>a[i];
cout<<endl;
QuickSort(a,0,N-1);
cout<<"排序后的序列为: "<<endl;
for(i=0;i<N;i++)
cout<<a[i]<<" " ;
cout<<endl;
return 0;
}

优化后交换算法

int Partition2(int r[],int low,int high)//划分函数
{
int i=low,j=high,pivot=r[low];//基准元素
while(i<j)
{
while(i<j&&r[j]>pivot) j--;//向左扫描
while(i<j&&r[i]<=pivot) i++;//向右扫描
if(i<j)
{
swap(r[i++],r[j--]);//r[i]和 r[j]交换,交换后 i++, j--
}
}
if(r[i]>pivot)
{
swap(r[i-1],r[low]);//r[i-1]和 r[low]交换
return i-1;//返回最终划分完成后基准元素所在的位置
}
swap(r[i],r[low]);//r[i]和 r[low]交换
return i;//返回最终划分完成后基准元素所在的位置
}

4、大数算法

其实大数算法有很多种,那就是加减乘除,即大数加法,大数减法,大数乘法,大数除法,可以利用分治法解决大数算法问题。在解决两个大的整数相乘时,我们可以将一个大的整数乘法分而治之,将大问题变成小问题,变成简单的小数乘法再进行合并,从而解决上述问题。
分治法下的大数算法,例如大数乘法312356乘与345621,这个样子可以划分为312乘以10的3次方加356,同理345621可以划分为345乘以10的3次方加621,依次递归下去,最终化为单个整数的乘法就OK了。
实现步骤 1、 首先将两个大数以字符串的形式输入,转换成数字后, 倒序存储在数组 s[]中, l 用来表示数的长度, c
表示次幂。两个大数的初始次幂为 0。 2、 cp()函数:用于将一个 n 位的数分成两个 n/2 的数并存储,记录它的长度和次幂。
3、 mul()函数:用于将两个数进行相乘,不断地进行分解,直到有一个乘数为 1 位数时停止分解,进行乘法运算并记录结果。
4、 add()函数:将分解得到的数进行相加合并。

源代码(1)

#include <stdlib.h>
#include <cstring>
#include <iostream>
using namespace std;
#define M 100
char sa[1000];
char sb[1000];
typedef struct _Node
{
int s[M];
int l; //代表字符串的长度
int c;
} Node,*pNode;
void cp(pNode src, pNode des, int st, int l)
{
int i, j;
for(i=st, j=0; i<st+l; i++, j++)
{
des->s[j] = src->s[i];
}
des->l = l;
des->c = st + src->c; //次幂
}
void add(pNode pa, pNode pb, pNode ans)
{
int i,cc,k,palen,pblen,len;
int ta, tb;
pNode temp;
if((pa->c<pb->c)) //保证 Pa 的次幂大
{
temp = pa;
pa = pb;
pb = temp;
}
ans->c = pb->c;
cc = 0;
palen=pa->l + pa->c;
pblen=pb->l + pb->c;
if(palen>pblen)
len=palen;
else
len=pblen;
k=pa->c - pb->c;
for(i=0; i<len-ans->c; i++) //结果的长度最长为 pa, pb 之中的最大长度减去最低次幂
{
if(i<k)
ta = 0;
else
ta = pa->s[i-k]; //次幂高的补 0,大于低的长度后与 0 进行计算
if(i<pb->l)
tb = pb->s[i];
else
tb = 0;
if(i>=pa->l+k)
ta = 0;
ans->s[i] = (ta + tb + cc)%10;
cc = (ta + tb + cc)/10;
}
if(cc)
ans->s[i++] = cc;
ans->l = i;
}
void mul(pNode pa, pNode pb, pNode ans)
{
int i, cc, w;
int ma = pa->l>>1, mb = pb->l>>1; //长度除 2
Node ah, al, bh, bl;
Node t1, t2, t3, t4, z;
pNode temp;
if(!ma || !mb) //如果其中个数为 1
{
if(!ma) //如果 a 串的长度为 1, pa,pb 交换, pa 的长度大于等于 pb 的长度
{
temp = pa;
pa = pb;
pb = temp;
}
ans->c = pa->c + pb->c;
w = pb->s[0];
cc = 0; //此时的进位为 c
for(i=0; i < pa->l; i++)
{
ans->s[i] = (w*pa->s[i] + cc)%10;
cc= (w*pa->s[i] + cc)/10;
}
if(cc)
ans->s[i++] = cc; //如果到最后还有进位,则存入结果
ans->l = i; //记录结果的长度
return;
}
//分治的核心
cp(pa, &ah, ma, pa->l-ma); //先分成 4 部分 al,ah,bl,bh
cp(pa, &al, 0, ma);
cp(pb, &bh, mb, pb->l-mb);
cp(pb, &bl, 0, mb);
mul(&ah, &bh, &t1); //分成 4 部分相乘
mul(&ah, &bl, &t2);
mul(&al, &bh, &t3);
mul(&al, &bl, &t4);
add(&t3, &t4, ans);
add(&t2, ans, &z);
add(&t1, &z, ans);
}
int main()
{
Node ans,a,b;
cout << "输入大整数 a: "<<endl;
cin >> sa;
cout << "输入大整数 b: "<<endl;
cin >> sb;
a.l=strlen(sa); //sa,sb 以字符串进行处理
b.l=strlen(sb);
int z=0,i;
for(i = a.l-1; i >= 0; i--)
a.s[z++]=sa[i]-'0'; //倒向存储
a.c=0;
z=0;
for(i = b.l-1; i >= 0; i--)
b.s[z++] = sb[i]-'0';
b.c = 0;
mul(&a, &b, &ans);
cout << "最终结果为: ";
for(i = ans.l-1; i >= 0; i--)
cout << ans.s[i]; //ans 用来存储结果,倒向存储
cout << endl;
return 0;
}

源代码(2):大数乘法

#include<stdio.h>
#include<string.h>
#include<math.h>
#define N 1005//定义全局变量 
char a[N],b[N];//字符数组 
int s1[N],s2[N],s3[N*N];
int main()
{
int len1,len2,max,i,j;
while(scanf("%s%s",a,b)!=EOF)//输入字符数组 
{
memset(s1,0,sizeof(s1));//数组归零 
memset(s2,0,sizeof(s2));
memset(s3,0,sizeof(s3));
len1=strlen(a);//获取字符串的长度 
len2=strlen(b);
max=0;
max=len1+len2;//定义长度 
for(i=0,j=len1-1;i<len1;i++,j--)//字符数组整形化 
s1[i]=a[j]-'0';
for(i=0,j=len2-1;i<len2;i++,j--)
s2[i]=b[j]-'0';
for(i=0;i<len1;i++)
for(j=0;j<len2;j++)
s3[i+j]+=s1[i]*s2[j];//按位相乘 
for(i=0;i<max;i++) 
{
if(s3[i]>=10)//判断是否产生进位 
{
s3[i+1]+=s3[i]/10;
s3[i]%=10;
}
}
while(s3[max-1]==0)//去除前导零
{
if(s3[max-1]==0)
max--;
}
for(i=max-1;i>=0;i--)
printf("%d",s3[i]);
printf("\n");
}
return 0;
}

源代码(3):大数加法

#include<stdio.h>
#include<string.h>
#define max 2000
int max1(int n,int m)
{
    if(n>m)
    return n;
    else
    return m;
}
int main(void)
{
      int i,n,m,j,t;
      char arr[max],iarr[max];
        int sum[10000];
    scanf("%d",&t);
    getchar();
    for(j=1;j<=t;j++)
    {
       if(j>1&&j<=t)
        printf("\n");
        int count=0;
         int num1[1000]={0},num2[1000]={0};
        n=0;m=0;
      memset(arr,0,sizeof(arr));
      memset(iarr,0,sizeof(iarr));
      memset(sum,0,sizeof(sum));
     while(arr[n]=getchar())
     {
         if(arr[n]==' ')
         {
           arr[n]='\0'; 
            break;
         }
         else
         n++;
     }
      gets(iarr);
      m=strlen(iarr);
      printf("Case %d:\n",j);
        printf("%s + %s = ",arr,iarr);
      for(i=0;i<n;i++)
      {
          num1[i]=arr[n-i-1]-0;
      }    
      for(i=0;i<m;i++)
      {
         num2[i]=iarr[m-i-1]-0;
      }
        if(n>m)
        {
            for(i=n;i>m;i--)
            iarr[i]==0;
        }
        else if(n<m)
        {
            for(i=m;i>n;i--)
            arr[i]==0;
        }
        for(i=0;i<max1(n,m);i++)
        {
            count++;
            sum[i]=sum[i]+num1[i]+num2[i];
            if(sum[i]>=10)
            {
                sum[i]=sum[i]-10;
                sum[i+1]++;
                 if(i==max1(n,m)-1)
                count++;
            }    
        }
        for(i=count-1;i>=0;i--)
           {
                printf("%d",sum[i]);
           }
            printf("\n");
    }
    return 0;
}
  • 3
    点赞
  • 40
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值