当前位置:   article > 正文

分 治 算 法_分治算法

分治算法

目录

一、什么是分治(what?)

二、为什么要分治(why? )

三、怎么使用分治(how?)

四、典型例题分析:

        例题1:猜数字游戏--二分搜索技术 

        例题2:合久必分,分久必合--合并排序 

        例题3:兵贵神速--快速排序

        例题4:效率至上--大整数乘法


一、什么是分治(what?)

        分治,即分而治之。这是一种将大规模问题分解为若干个规模较小的相同子问题,进而求得最终结果的一种策略思想。

二、为什么要分治(why? )

        当一个问题规模较大,且

        (1)原问题可分为若干个规模较小的子问题。

        (2)子问题相互独立。

        (3)子问题的解可以合并为原问题的解。

        这时使用分治算法能更好的解决问题。

三、怎么使用分治(how?)

分治的步骤一般如下:

        (1)分解:将要解决的问题分解为若干个规模较小、相互独立、与原问题形式相同的子问题。

        (2)治理:求解各个子问题。由于各个子问题与原问题形式相同,只是规模小而已,而当子问题划分得足够小时,就可以用较简单的方法解决。

        (3)合并:按原问题的要求,将子问题的解逐层合并成原问题的解。

        就是化整为零,在化零为整的过程。

        在分治中,递归是一把利器。

四、典型例题分析:

        例题1:猜数字游戏--二分搜索技术 

                

  

 问题分析:

        在最坏的情况,需要猜n次才能得出答案,但是由于0~10是一串有序的数,我们没必要一个一个去猜,可以从中间开始向两边搜索,这样可以大大提高寻找效率。所以这里要介绍一种新的查找方法——折半查找。当我们的面对的数据是有序序列时,就可以使用这种策略。

算法设计:

        问题简化:

        给定n个元素,这些元素是有序的(假设为升序),从中查找指定元素x。

        算法思想:

        将有序列分成两部分,将待查元素与中间值进行比较,如果相等,返回中间值;若果小于中间值,那么将原序列的右半部分切掉,将x和左边序列的中间值比较,再重复以上判定。

        算法设计:

        用一维数组s[]存储有序序列,设变量low和high表示查找范围的下界和上界,mid表示查找范围的中间位置,x为特定待查值。

        (1)初始化。令low=0,即指向有序数列的第一个元素,high = n - 1,指有序数列的最后一个元素。mid指向中间值 (high-low)/2+low 或者 ( high+low)/2

        (2)判定关系若low<=high,则判断x和s[mid]的大小关系,如果x>s[mid],low = mid+1,更新中间值mid = (low+high)/2;如果x<s[mid],high=mid-1,更新中间值mid = (low+high)/2。若果x==s[mid],返回mid就是x的位置。

        (3)如果low>high,说明数据错误,待查值不存在。

        图解算法:

            

         伪代码:

        二分搜索

  1. int BinarySearch(int n,int s[],int x)
  2. {
  3. int low=0,high=n-1;
  4. while(low<=high)
  5. {
  6. int mid=(high-low)/2+low;
  7. if(x==s[mid])
  8. {
  9. return mid;
  10. }
  11. else if(x<s[mid])
  12. {
  13. high=mid-1;
  14. }
  15. else if(x>s[mid])
  16. {
  17. low=mid+1;
  18. }
  19. }
  20. return -1;//返回-1没找到x
  21. }

实战代码: 

  1. #include<iostream>
  2. #include<algorithm>
  3. using namespace std;
  4. const int M=10000;
  5. int x,n,i;
  6. int s[M];
  7. int BinarySearch(int n,int s[],int x)
  8. {
  9. int low=0,high=n-1;
  10. while(low<=high)
  11. {
  12. int mid=(high-low)/2+low;
  13. if(x==s[mid])
  14. {
  15. return mid;
  16. }
  17. else if(x<s[mid])
  18. {
  19. high=mid-1;
  20. }
  21. else
  22. {
  23. low=mid+1;
  24. }
  25. }
  26. return -1;//返回-1没找到x
  27. }
  28. int main()
  29. {
  30. cout<<"请输入数列中的元素:"<<endl;
  31. while(cin>>n)
  32. {
  33. cout<<"请依次输入列表中元素:";
  34. for(int i=0; i < n; i++)
  35. {
  36. cin>>s[i];
  37. }
  38. sort(s,s+n);
  39. cout<<"排列后的数组为:";
  40. for(int i=0; i < n; i++)
  41. {
  42. cout<<s[i]<<' ';
  43. }
  44. cout<<endl;
  45. cout<<"请输入要查元素:";
  46. cin>>x;
  47. i = BinarySearch(n,s,x);
  48. if(i==-1) cout<<"该数列中没有待查值。"<<endl;
  49. else cout<<"已查到的元素在"<<i<<"位"<<endl;
  50. }
  51. return 0;
  52. }

 算法复杂度分析:

        例题2:合久必分,分久必合--合并排序 

问题分析:

        显然,合并排序采用的策略就是分治。将一个给定的无序数列,分解成两个规模大致相同的子序列,然后将两个子序列进行排序和合并。如果面对的问题不易解决就继续将子序列在分,直到分解成单个元素为一个子序列,这是每一个序列都可以当做是一个排好的子序列,然后在进行合并,得到一个完整的序列。

算法设计:

        合并排序采用分治策略实现对n个元素进行排序的算法,是分治法的一个典型应用和完美体现。他是一种平衡、简单的二分分治策略。过程大致如下:

        (1)分解——将待排序列元素分成大小大致相同的两个子序列。

        (2)治理——对两个子序列进行合并排序。

        (3)合并——将排好序的有序子序列进行合并,得到最终的有序序列。

算法图解:

        

伪代码:

        (1)合并算法:

  1. void Merge(int A[],int low,int mid,int high)
  2. {
  3. int *B = new int[high-low+1];
  4. int i = low,j = mid+1,k = 0;
  5. while(i<=mid&&j<=high)
  6. {
  7. if(A[i]<=A[j])
  8. B[k++]=A[i++];//这里是先对数组赋值,再移动指针
  9. else
  10. B[k++]=A[j++];
  11. }
  12. //如果前一半剩余
  13. while(i<=mid)
  14. {
  15. B[k++]=A[i++];
  16. }
  17. //如果后一半有剩余
  18. while(j<=high)
  19. {
  20. B[k++]=A[j++];
  21. }
  22. //将合并的序列赋值给A数组
  23. for(i = low,k=0; i <= high; i++)
  24. {
  25. A[i] = B[k++];
  26. }
  27. }

               (2)分治算法(递归)

  1. void MergeSort(int A[],int low, int high)
  2. {
  3. if(low<high)
  4. {
  5. int mid = (low+high)/2;
  6. MergeSort(A,low,mid);//左边拆分
  7. MergeSort(A,mid+1,high);//右边拆分
  8. Merge(A,low,mid,high);//分久必合
  9. }
  10. }

实战代码:

  1. #include<iostream>
  2. using namespace std;
  3. void Merge(int A[],int low,int mid,int high)
  4. {
  5. int *B = new int[high-low+1];
  6. int i = low,j = mid+1,k = 0;
  7. while(i<=mid&&j<=high)
  8. {
  9. if(A[i]<=A[j])
  10. B[k++]=A[i++];//这里是先对数组赋值,再移动指针
  11. else
  12. B[k++]=A[j++];
  13. }
  14. //如果前一半剩余
  15. while(i<=mid)
  16. {
  17. B[k++]=A[i++];
  18. }
  19. //如果后一半有剩余
  20. while(j<=high)
  21. {
  22. B[k++]=A[j++];
  23. }
  24. //将合并的序列赋值给A数组
  25. for(i = low,k=0; i <= high; i++)
  26. {
  27. A[i] = B[k++];
  28. }
  29. }
  30. void MergeSort(int A[],int low, int high)
  31. {
  32. if(low<high)
  33. {
  34. int mid = (low+high)/2;
  35. MergeSort(A,low,mid);//左边拆分
  36. MergeSort(A,mid+1,high);//右边拆分
  37. Merge(A,low,mid,high);//分久必合
  38. }
  39. }
  40. int main()
  41. {
  42. int n, A[100];
  43. cout<<"请输入数列中的元素个数n为:"<<endl;
  44. cin>>n;
  45. cout<<"请依次输入数列中的元素:"<<endl;
  46. for(int i=0; i<n; i++)
  47. cin>>A[i];
  48. MergeSort(A,0,n-1);
  49. cout<<"合并排序结果:"<<endl;
  50. for(int i=0;i<n;i++)
  51. cout<<A[i]<<" ";
  52. cout<<endl;
  53. return 0;
  54. }

算法复杂度:

        例题3:兵贵神速--快速排序

 问题分析:

 算法设计:

快速排序的基本思想也是分治。

(1)分解:先从数列中选取一个元素作为基准。以基准元素为标准。比基准元素小的值放在基准元素左边,比基准元素大的值放在基准元素右边。

(2)治理:对两个子序列进行快速排序。

(3)合并:将两个排好的序列合在一起,得到原问题的解。

 图解算法:

伪代码:

(1)划分函数

  1. int Partition(int r[],int low,int high) //划分函数
  2. {
  3. int i=low,j=high,pivot=r[low]; //基准元素
  4. while(i<j)
  5. {
  6. while(i<j&&r[j]>pivot)
  7. j--; //向左扫描
  8. if(i<j)
  9. {
  10. swap(r[i++],r[j]); //r[i]和r[j]交换后i右移一位
  11. }
  12. while(i<j&&r[i]<=pivot)
  13. i++; //向右扫描
  14. if(i<j)
  15. {
  16. swap(r[i],r[j--]); //r[i]和r[j]交换后j左移一位
  17. }
  18. }
  19. return i; //返回最终划分完成后基准元素所在的位置
  20. }

 (2)快速排序递归 

  1. void QuickSort(int R[],int low,int high){
  2. int mid;
  3. if(low<high)
  4. {
  5. mid=Partition(R,low,high); //返回基准元素位置
  6. QuickSort(R,low,mid-1); //左区间递归快速排序
  7. QuickSort(R,mid+1,high); //右区间递归快速排序
  8. }
  9. }

 实战代码:

  1. #include <iostream>
  2. using namespace std;
  3. int Partition(int r[],int low,int high) //划分函数
  4. {
  5. int i=low,j=high,pivot=r[low]; //基准元素
  6. while(i<j)
  7. {
  8. while(i<j&&r[j]>pivot) j--; //向左扫描
  9. if(i<j)
  10. {
  11. swap(r[i++],r[j]); //r[i]和r[j]交换后i右移一位
  12. }
  13. while(i<j&&r[i]<=pivot) i++; //向右扫描
  14. if(i<j)
  15. {
  16. swap(r[i],r[j--]); //r[i]和r[j]交换后j左移一位
  17. }
  18. }
  19. return i; //返回最终划分完成后基准元素所在的位置
  20. }
  21. void QuickSort(int R[],int low,int high)//快速排序递归算法
  22. {
  23. int mid;
  24. if(low<high)
  25. {
  26. mid=Partition(R,low,high); //基准位置
  27. QuickSort(R,low,mid-1); //左区间递归快速排序
  28. QuickSort(R,mid+1,high); //右区间递归快速排序
  29. }
  30. }
  31. int main()
  32. {
  33. int a[1000];
  34. int i,N;
  35. cout<<"请先输入要排序的数据的个数:";
  36. cin>>N;
  37. cout<<"请输入要排序的数据:";
  38. for(i=0;i<N;i++)
  39. cin>>a[i];
  40. cout<<endl;
  41. QuickSort(a,0,N-1);
  42. cout<<"排序后的序列为:"<<endl;
  43. for(i=0;i<N;i++)
  44. cout<<a[i]<<" " ;
  45. cout<<endl;
  46. return 0;
  47. }

        例题4:效率至上--大整数乘法

问题分析:

算法设计:

 图解算法:

道理都懂,那么具体应该如何操作?

首先将两个大整数以字符串形式输入,

转换成数字后,倒序存储在s[]中,

l 表示数的长度,

c 表示数的次幂。

两个大数的初始次幂是0.

(1)初始化

(2)分解

(3)求解子问题

(4)继续求解子问题

(5)合并

伪代码:

(1)数据结构

        将两个大数以字符串的形式输入,然后定义结构体Node,其中:

        s[]存储大数倒序,l 表示长度,c 表示次幂。两个大数的初始次幂为0.

  1. char sa[10000];//接收大数的字符串
  2. char sb[10000];//接收大数的字符串
  3. typedef struct _Node{
  4. int s[M];//数组,倒序存储大数
  5. int l;//代表数的长度
  6. int c;//代表数的次幂
  7. }Node,*pNode;

(2)划分函数

        其中,cp()函数用于将一个n位的数分成两个n/2的数并储存,纪录它的次幂。

  1. void cp(pNode*src,pNode*des,int st,int l)
  2. {
  3. //src表示分解的数结点,des表示分解后得到的数结点
  4. //st表示从src结点数组中取数的开始位置,1表示取数的长度
  5. int i,j;
  6. for(i = st,j=0;i<st+l;i++,j++)
  7. {
  8. des->s[j] = src->s[i];
  9. }
  10. des->l = l;//长度等于取数的长度
  11. des->c = st+src->c;//des次幂等于开始取数的位置加上src
  12. }

举例:如果有大数43579,我们应该把数放在结点a中

ma = a.l/2;                           //ma表示a长度的一半,此例中a.l=5,ma=2

 这样两次调用cp()函数,我们就把一个大的整数分解成了两个长度约为原来一半的整数。

(3)乘法运算

定义mul函数用于将两数相乘,不断分解,直到有一个乘数为1时停止,让这两个数相乘,并记录结果回溯。

  1. ma = pa->l/2; //ma表示a长度的一半
  2. mb = pb->l/2; //mb表示b长度的一半
  3. if(!ma || !mb) //如果!ma说明ma=0,即a的长度为1,该乘数为1位数
  4. //如果!mb说明mb=0,即b的长度为1,该乘数为1位数
  5. {
  6. if(!ma) //!ma说明a为1位数,a、b交换,保证a的长度大于等于b的长度
  7. {
  8. temp =pa;
  9. pa = pb;
  10. pb = temp;
  11. } //交换后b的长度为1
  12. ans->c = pa->c + pb->c; //结果的次幂等于两乘数次幂之和
  13. w = pb->s[0];//因为交换后b的长度为1,用变量 w记录即可
  14. cc= 0; //初始化进位cc为0
  15. for(i=0; i <pa->l; i++) //把a中的数依次取出与w相乘,记录结果和进位
  16. {
  17. ans->s[i] = (w*pa->s[i] + cc)%10;//存储相乘结果的个位,十位做进位处理
  18. cc = (w*pa->s[i] + cc)/10; //处理进位
  19. }

(4)合并函数

add()函数将分解得到的数进行相加合并。

  1. void add(pNode pa, pNode pb, pNode ans)
  2. { //程序调用时把 a、b地址传递给pa、pb参数,表示待合并的两个数
  3. //ans记录它们相加的结果
  4. int i, cc, k,alen,blen,len;
  5. int ta, tb; //ta、tb分别记录a、b相加时对应位上的数
  6. pNode temp;
  7. if(pa->c <pb->c) //交换以保证a的次幂大
  8. {
  9. temp = pa;
  10. pa = b;
  11. pb =temp;
  12. }
  13. ans->c = pb->c; //结果的次幂为两个数中小的次幂
  14. cc = 0; //初始化进位cc为0
  15. k=pa->c - pb->c //k为a左侧需要补零的个数

        

  1. alen=pa->l + pa->c; //a数加上次幂的总长度,上例中alen=6
  2. blen=pb->l + pb->c; //b数加上次幂的总长度,上例中alen= 5
  3. if(alen>blen)
  4. len=alen; //取a、b总长度的最大值
  5. else
  6. len=blen;
  7. len=len-pb->c; //结果的长度为a,b之中的最大值减去最低次幂,上例中len= 4
  8. //最低次幂是不进行加法运算的位数)
  9. for(i=0; i<len; i++)
  10. {
  11. if(i <k) //k为a左侧需要补零的个数
  12. ta = 0; //a左侧补零
  13. else
  14. ta =pa->s[i-k];//i=k时,补0结束,从a数组中第0位开始取数字
  15. if(i <b->l)
  16. tb = pb->s[i]; //从b数组中第0位开始取数字
  17. else
  18. tb = 0; //b数字先取完,b右侧补0
  19. if(i>=pa->l+k) //a数字先取完,a右侧补0
  20. ta = 0;
  21. ans->s[i] = (ta + tb + cc)%10; //记录两位之和的个位数,十位做进位处理
  22. cc = (ta + tb + cc)/10;
  23. }

 

实战代码:

  1. //program 3-4
  2. #include <stdlib.h>
  3. #include <cstring>
  4. #include <iostream>
  5. using namespace std;
  6. #define M 100
  7. char sa[1000];
  8. char sb[1000];
  9. typedef struct _Node
  10. {
  11. int s[M];
  12. int l; //代表字符串的长度
  13. int c;
  14. } Node,*pNode;
  15. void cp(pNode src, pNode des, int st, int l)
  16. {
  17. int i, j;
  18. for(i=st, j=0; i<st+l; i++, j++)
  19. {
  20. des->s[j] = src->s[i];
  21. }
  22. des->l = l;
  23. des->c = st + src->c; //次幂
  24. }
  25. void add(pNode pa, pNode pb, pNode ans)
  26. {
  27. int i,cc,k,palen,pblen,len;
  28. int ta, tb;
  29. pNode temp;
  30. if((pa->c<pb->c)) //保证Pa的次幂大
  31. {
  32. temp = pa;
  33. pa = pb;
  34. pb = temp;
  35. }
  36. ans->c = pb->c;
  37. cc = 0;
  38. palen=pa->l + pa->c;
  39. pblen=pb->l + pb->c;
  40. if(palen>pblen)
  41. len=palen;
  42. else
  43. len=pblen;
  44. k=pa->c - pb->c;
  45. for(i=0; i<len-ans->c; i++) //结果的长度最长为pa,pb之中的最大长度减去最低次幂
  46. {
  47. if(i<k)
  48. ta = 0;
  49. else
  50. ta = pa->s[i-k]; //次幂高的补0,大于低的长度后与0进行计算
  51. if(i<pb->l)
  52. tb = pb->s[i];
  53. else
  54. tb = 0;
  55. if(i>=pa->l+k)
  56. ta = 0;
  57. ans->s[i] = (ta + tb + cc)%10;
  58. cc = (ta + tb + cc)/10;
  59. }
  60. if(cc)
  61. ans->s[i++] = cc;
  62. ans->l = i;
  63. }
  64. void mul(pNode pa, pNode pb, pNode ans)
  65. {
  66. int i, cc, w;
  67. int ma = pa->l>>1, mb = pb->l>>1; //长度除2
  68. Node ah, al, bh, bl;
  69. Node t1, t2, t3, t4, z;
  70. pNode temp;
  71. if(!ma || !mb) //如果其中个数为1
  72. {
  73. if(!ma) //如果a串的长度为1,pa,pb交换,pa的长度大于等于pb的长度
  74. {
  75. temp = pa;
  76. pa = pb;
  77. pb = temp;
  78. }
  79. ans->c = pa->c + pb->c;
  80. w = pb->s[0];
  81. cc = 0; //此时的进位为c
  82. for(i=0; i < pa->l; i++)
  83. {
  84. ans->s[i] = (w*pa->s[i] + cc)%10;
  85. cc= (w*pa->s[i] + cc)/10;
  86. }
  87. if(cc)
  88. ans->s[i++] = cc; //如果到最后还有进位,则存入结果
  89. ans->l = i; //记录结果的长度
  90. return;
  91. }
  92. //分治的核心
  93. cp(pa, &ah, ma, pa->l-ma); //先分成4部分al,ah,bl,bh
  94. cp(pa, &al, 0, ma);
  95. cp(pb, &bh, mb, pb->l-mb);
  96. cp(pb, &bl, 0, mb);
  97. mul(&ah, &bh, &t1); //分成4部分相乘
  98. mul(&ah, &bl, &t2);
  99. mul(&al, &bh, &t3);
  100. mul(&al, &bl, &t4);
  101. add(&t3, &t4, ans);
  102. add(&t2, ans, &z);
  103. add(&t1, &z, ans);
  104. }
  105. int main()
  106. {
  107. Node ans,a,b;
  108. cout << "输入大整数 a:"<<endl;
  109. cin >> sa;
  110. cout << "输入大整数 b:"<<endl;
  111. cin >> sb;
  112. a.l=strlen(sa); //sa,sb以字符串进行处理
  113. b.l=strlen(sb);
  114. int z=0,i;
  115. for(i = a.l-1; i >= 0; i--)
  116. a.s[z++]=sa[i]-'0'; //倒向存储
  117. a.c=0;
  118. z=0;
  119. for(i = b.l-1; i >= 0; i--)
  120. b.s[z++] = sb[i]-'0';
  121. b.c = 0;
  122. mul(&a, &b, &ans);
  123. cout << "最终结果为:";
  124. for(i = ans.l-1; i >= 0; i--)
  125. cout << ans.s[i]; //ans用来存储结果,倒向存储
  126. cout << endl;
  127. return 0;
  128. }

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小蓝xlanll/article/detail/716376
推荐阅读
相关标签
  

闽ICP备14008679号