分治策略:将原问题划分成n个规模较小而结构与原问题相似的子问题,然后递归地解决这些子问题,最后再合并其结果,就可以得到原问题的解。
它需要三个步骤:
- 分解:将原问题分解成一系列的子问题。
- 解决:递归地解决各个子问题。若子问题足够小,则直接求解。
- 合并:将子问题的结果合并成原问题的解。
通过分治策略和分治步骤,可以简单地默出归并算法。
- 分解:将n个元素分成各自包含n/2个元素的子序列
- 解决:用归并排序法递归地对两个子序列进行排序。
- 合并:合并两个以排序的子序列,得到排序结果:
void mergeSort(int* a, int left, int right) { if (left+1 < right) { int m = left + (right - left) / 2;//分解 mergeSort(a, left, m);//递归地对两个子序列进行排序 mergeSort(a, m, right); merge(a, left, m, right);//合并 } } void merge(int * a, int left, int m, int right) { int n1 = m - left; int n2 = right - m; int *L = new int[n1]; int *R = new int[n2]; //memcpy(L, a, n1 * sizeof(int)); //memcpy(R, a+m, n2 * sizeof(int)); for (int i = 0; i < m - left; i++) { L[i] = a[left+i]; } for (int i = 0; i < right - m; i++) { R[i] = a[m+i]; } int i = 0; int j =0; for (int k = left; k < right-left; k++) { if (L[i] <= R[j]) { a[k] = L[i++]; } else { a[k] = R[j++]; } } delete[]L; delete[]R; }
对于merge函数中的合并过程,有必要也用循环不变式来分析一下:
循环中不变的量是a[left...k-1] 中包含了L[0..n1], R[0..n2)中的k-left个最小元素,并且是排好序的。
在分解步骤里有个小坑:
取中通常算法是m = (left+(right-left)), 但因为编程语言的限制,如果left值非常大则m有可能会有溢出,所以改为
left + (right - left) / 2。 因为left + (right - left) / 2< right。所以只要right不溢出,m就不会溢出
归并算法的时间复杂度是O(nlgn). 因合并时使用了两个临时数组,因此空间复杂度是O(n)
同样的,二分查找也是分治法的应用。应用分治步骤,可以很容易地默出二分查找法:
int binSearch(int* a, int target,int left, int right) { if (right < left ) { return -1; } int m = 0; while (left < right) { m = left + (right - left) / 2; if (a[m] == target) { return m; } else if (a[m] < target) { left = m+1; } else { right = m; } } return -1; }