分治法 思想:分+治+分(将一个大规模的问题分成若干块小问题去求解,如果小问题也不好直接解决,则继续分成更小的问题)
分治法:
(1)分治法产生的子问题是原问题的较小模式. (2)反复应用分治手段,可以使子问题规模不断缩小, (3)最终使子问题缩小到很容易直接求出其解。 (4)将规模较小问题的答案逐级向上合并,可得大问题答案
分治法解决问题通常使用递归算法 直接或间接地调用自身的算法称为递归算法,
例题1:Fibonnacci数列 斐波那契数列Q(Fibonacclsequence),又称“黄金分割”数列,比如这样一个数列:1,1,2,3,5,8,13,21,34,55,89….数列从第3项开始,每一项都等于前两项之和。递归定义为如下:
1 2 3 4 5 6 int Fibonacci (int n) { if (n<=1 ) return 1 ; else return Fibonacci (n-1 ) + Fibonacci (n-2 ); }
例题2:Ackerman函数 一个函数及它的一个变量是由函数自身定义时当称这个函数是双递归函数。 Ackerman函数A(n,m)定义如下:
1 2 3 4 5 6 7 8 int Ackerman (int m,int n) { if (m == 0 ) return n + 1 ; else if (m > 0 && n == 0 ) return ackermann (m - 1 , 1 ); else return ackermann (m - 1 , ackermann (m, n - 1 )); }
例题3:排列问题 设计一个递归算法生成n个元素{r,r.…,rn}的全排列。设R={r,r.…rn}是要进行排列的n个元素,R;=R-{ri}。集合X中元素的全排列记为perm(X)。(r)perm(X)表示在全排列perm(X)的每一个排列前加上前缀得到的排列。R的全排列归纳定义如下:
当n=1时,perm(R)=(r),其中r是集合R中唯一的元素 当n>1时,perm(R)由(r1)perm(R1),(r2)perm(R2),.,(rn)perm(Rn)构成。
例如:1 2 3全排列先把1放在首位,然后剩下的有两种情况,2或3放在第二位,剩下的那个数放在第三位,这样就遍历了所有1在首位的情况;然后同第一步,依次把2、3放在首位,求出剩余元素的排列情况;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 void Perm (Type list[],int a,int b) { if (a==b) { for (int i=0 ;i<=b;i++) cout<<List[i]; } else { for (int i=a;i<=b;i++){ swap (list[a],list[i]); Perm (list,a+1 ,b); swap (list[a],list[i]); } } }
例题4:Hanoi塔问题 设a,b,c是3个塔座。要求由a移动到b。移动圆盘时遵守以下移动规则 规则1:每次只能移动1个圆盘; 规则2:任何时刻都不允许较大的圆盘压在较小的圆盘之上: 规则3:在满足移动规则1和2的前提下,可将圆盘移至a,b,c中任一塔座上,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 void Move (int a,int b) { std::cout<<a"->" <<b<<std::endl; } void Hanoi (int n ,int a,int b,int c) { if (n==1 ) Move (a,b); else { Hanoi (n-1 ,a,c,b); Move (a,b); Hanoi (n-1 ,c,a,b); } }
例题5:二分法查找 非递减序的n个元素a[0:n-1],现要在这n个元素中找出一特定元素x。
分析:设在a[l:r]中找x,mid=(l+r)/2 (0)如果 x==a[mid],则找到; (1)如果x<a[mid],则继续在a[i,mid-1]中找x即可 (2)如果x>a[mid],则继续在a[mid+1,j]中找x即可子问题答案就是大问题的答案
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 void binarySearch (Type List[],int n,int target) { int left = 0 ; int right = n-1 ; int mid = (left + right)/2 ; while (left<=right){ if (target==List[mid]){ return mid; }; else if (target<List[mid]){ right = mid -1 ; } else { left = mid + 1 ; } } return -1 ; }
例题6:快速幂算法
1、当指数是偶数时,我们可以让指数除以2,底数乘以底数 2、当指数是奇数时,我们可以将指数变为偶数
1 2 3 4 5 6 7 8 9 10 11 12 int fastPower (int a, int b) { if (b == 0 ) { return 1 ; } if (b % 2 == 1 ) { return a * fastPower (a, b - 1 ); } int temp = fastPower (a, b / 2 ); return temp * temp; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 int fastPower (int a, int b) { int result = 1 ; int base = a; while (b > 0 ) { if (b % 2 == 1 ) { result = (result * base) ; } base = (base * base) ; b /= 2 ; } return result; }
例题7:棋盘覆盖 给定一个大小为(为正整数)的棋盘,其中有一个方格被损坏(或特殊标记),要求用形的骨牌(由三个方格组成)覆盖剩下的所有方格。例如n = 2,当时,棋盘是一个的方阵。
解决思路:
对于的棋盘,我们可以将其划分为四个大小为的子棋盘。
那个特殊方格(被损坏的方格)必定落在这四个子棋盘中的一个。我们可以在没有特殊方格的子棋盘中放置一个形骨牌,使得这三个子棋盘都有一个 “特殊方格”(这个特殊方格是我们放置形骨牌后新产生的)。
然后,我们可以递归地对这四个子棋盘进行棋盘覆盖操作。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 const int MAX_SIZE = 8 ;int board[MAX_SIZE][MAX_SIZE];int tile = 0 ; void chessBoard (int tr, int tc, int dr, int dc, int size) { if (size == 1 ) return ; int t = ++tile; int s = size / 2 ; if (dr < tr + s && dc < tc + s) { chessBoard (tr, tc, dr, dc, s); } else { board[tr + s - 1 ][tc + s - 1 ] = t; chessBoard (tr, tc, tr + s - 1 , tc + s - 1 , s); } if (dr < tr + s && dc >= tc + s) { chessBoard (tr, tc + s, dr, dc, s); } else { board[tr + s - 1 ][tc + s] = t; chessBoard (tr, tc + s, tr + s - 1 , tc + s, s); } if (dr >= tr + s && dc < tc + s) { chessBoard (tr + s, tc, dr, dc, s); } else { board[tr + s][tc + s - 1 ] = t; chessBoard (tr + s, tc, tr + s, tc + s - 1 , s); } if (dr >= tr + s && dc >= tc + s) { chessBoard (tr + s, tc + s, dr, dc, s); } else { board[tr + s][tc + s] = t; chessBoard (tr + s, tc + s, tr + s, tc + s, s); } } 时间复杂度 O(n*n)
例题8:排序算法
归并排序的核心是分治策略。它将一个数组分成两个子数组,然后递归地对这两个子数组进行排序,最后将排好序的子数组合并成一个完整的排序数组。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 void merge (int arr[], int left, int mid, int right) { int n1 = mid - left + 1 ; int n2 = right - mid; int L[n1], R[n2]; for (int i = 0 ; i < n1; i++) L[i] = arr[left + i]; for (int j = 0 ; j < n2; j++) R[j] = arr[mid + 1 + j]; int i = 0 , j = 0 , k = left; while (i < n1 && j < n2) { if (L[i] <= R[j]) { arr[k++] = L[i++]; } else { arr[k++] = R[j++]; } } while (i < n1) arr[k++] = L[i++]; while (j < n2) arr[k++] = R[j++]; } void mergeSort (int arr[], int left, int right) { if (left < right) { int mid = (left + right) / 2 ; mergeSort (arr, left, mid); mergeSort (arr, mid + 1 , right); merge (arr, left, mid, right); } } c
任取待排序元素序列中的某元素作为基准值,按照该排序码将待排序集合分割成两子序列,左子序列中所有元素均小于基准值,右子序列中所有元素均大于基准值,然后最左右子序列重复该过程,直到所有元素都排列在相应位置上为止。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 void QuickSort (int a[], int left, int right) { if (left >= right) { return ; } int pivot = a[left]; int i = left, j = right; while (i < j) { while (i < j && a[j] >= pivot) { j--; } a[i] = a[j]; while (i < j && a[i] <= pivot) { i++; } a[j] = a[i]; } a[i] = pivot; int pivotIndex = i; QuickSort (a, left, pivotIndex - 1 ); QuickSort (a, pivotIndex + 1 , right); }