算法设计2-分治法
学了两周的分治法,来归纳一下。分治法其实就是将一个不好解决的大问题,分成性质一样规模小且容易解决的子问题分别解决之后再合并起来得到大问题的答案,难点在于合并的时候如何合并以及子问题的设置。
下面来看下分治法能够解决什么问题:
一、排序问题
1)快速排序
主要思路是,将一个数列中的第一个数作为基准,分别把小于和大于它的数排到数组中它的位置的前面和后面,再递归地求解前面和后面的无序数列,最后得到的就是有序数列。
#include<stdio.h>//快速排序算法 void disp(int a[],int n) { int i; for(i=0; i<n; i++) { printf("%d ",a[i]); } printf("\n"); } int Partition(int a[],int s,int t) { int i=s; int j=t; int tmp=a[s]; while(i!=j) { while(j>i&&a[j]>=tmp) j--; a[i]=a[j]; while(i<j&&a[i]<=tmp) i++; a[j]=a[i]; } a[i]=tmp; return i; } void QuickSort(int a[],int s,int t)//分治法核心,选一个元素作为基准,然后递归对 小于和大于该元素的序列排序 { if(s<t) { int i=Partition(a,s,t); QuickSort(a,s,i-1);//分别求解前面和后面的序列的排序 QuickSort(a,i+1,t); } } int main() { int n=10; int a[]= {2,5,1,7,10,6,9,4,3,8}; printf("排序前:"); disp(a,n); QuickSort(a,0,n-1); printf("排序后:"); disp(a,n); return 0; }
2)归并排序
归并排序是分治法的一个代表,主要思路为先将数列拆分(从中间),递归拆分每一个子数列,直到只剩一个元素(递归出口),然后进行合并,合并的规则是:将两个有序数列进行合并,每次选择两个数列中第一个元素比较大小,将较小的元素放入临时数列中,较小元素所在数列的指针加1,临时数列指针加1,继续比较第一个元素,知道有一个数列的所有元素都进入临时数列中,此时将还未填入数列的另一个数列余下部分复制到tmpa临时数列中,最后释放tmpa的临时空间。
#include<stdio.h>//归并排序算法 #include<malloc.h> void disp(int a[],int n) { int i; for(i=0; i<n; i++) { printf("%d ",a[i]); } printf("\n"); } void Merge(int a[],int low,int mid,int high) { int *tmpa; int i=low,j=mid+1,k=0; tmpa=(int*)malloc((high-low+1)*sizeof(int)); while(i<=mid&&j<=high) { if(a[i]<=a[j]) { tmpa[k]=a[i]; i++; k++; } else { tmpa[k]=a[j]; j++; k++; } } while(i<=mid) { tmpa[k]=a[i]; i++; k++; } while(j<=high) { tmpa[k]=a[j]; j++; k++; } for(k=0,i=low; i<=high; i++,k++) { a[i]=tmpa[k]; } free(tmpa); } void MergeSort(int a[],int low,int high) { int mid; if(low<high) { mid=(low+high)/2; MergeSort(a,0,mid); MergeSort(a,mid+1,high); Merge(a,low,mid,high); } } void main() { int n=10; int a[]= {2,5,1,7,10,6,9,4,3,8}; printf("排序前:"); disp(a,n); MergeSort(a,0,n-1); printf("排序后:"); disp(a,n); }
二、查找问题
分治法的典型查找算法便是二分查找,二分查找的核心思想是:对于一个有序序列,要找出其中值为x的元素在什么位置,需要找到数列的中间位置mid,查看mid是否等于x,若等于,返回mid,若大于,在(mid+1,high)的序列中继续二分查找,若小于,在(mid+1,high)的序列中继续二分查找,若low>high,返回-1,即未找到。
#include<stdio.h>//二分查找 int BinSerach(int a[],int low,int high,int x) { int mid; if(low<=high) { mid=(low+high)/2; if(a[mid]==x) return mid; if(a[mid]>x) return BinSerach(a,low,mid-1,x); else return BinSerach(a,mid+1,high,x); } else return -1; } void main() { int n=10,i; int k=6; int a[]= {1,2,3,4,5,6,7,8,9,10}; i=BinSerach(a,0,n-1,k); if(i>=0)printf("a[%d]=%d\n",i,k); else printf("未找到%d元素\n",k); }
上述折半查找是有缺陷的,如果存在相同元素,它返回的是随机一个元素,在有相同元素的情况下只需要查找左右等于x的元素在数组中的位置,如果要计算个数,加入控制变量count即可。
关于分治法还可以实现大整数乘法问题,在这里不介绍。用好这三种排序查找算法可以解决大部分能用分治法解决的问题。