分治策略
1.算法总体思想
1.1分治法的设计思想
1. 当要求解一个输入规模n相当大的问题时,直接求解往往是非常困难的,甚至没法求出。正确的方法是, 首先应仔细分析问题本身所具有的特性,然后根据这些特性选择适当的设计策略来求解。
2. 在将这n个输入分成k个不同子集合的情况下,如果能得到k个不同的可独立求解的子问题,而且在求解之后,还可找到适当的方法把它们合并成整个问题的解,那么,可考虑使用分治法来求解。
1.2分支法的使用条件
分治法所能解决的问题一般具有以下几个特征:
- **该问题的****规模缩小到一定的程度就可以容易地解决;
- 该问题可以分解为若干个规模较小的相同问题,即该问题具有最优子结构性质;
- 利用该问题分解出的子问题的解可以合并为该问题的解;
- 该问题所分解出的各个子问题是相互独立的,即子问题之间不包含公共的子问题。**
通常,由分治法所得到的子问题与原问题具有相同的类型。如果得到的子问题相对来说还太大,则可反复使用分治策略将这些子问题分成更小的同类型子问题,直至产生出不用进一步细分就可求解的子问题。由此可知,分治法求解很自然地可用一个递归过程来表示。
总而言之:分治法作为一种算法设计策略,要求分解所得的子问题是同类问题,并要求原问题的解可以通过组合子问题的解来获取。
1.3分治法的基本步骤
**
divide-and-conquer**(P)—分而治之
{
**if** ( | P | <= n0) **adhoc**(P); //解决小规模的问题
**divide** P into smaller subinstances P1,P2,...,Pk;//分解问题
**for** (i=1,i<=k,i++)
yi=**divide-and-conquer**(Pi); //递归的解各子问题
**return** merge(y1,...,yk); //将各子问题的解合并为原问题的解
}
2.常见使用场景
2.1二分搜索技术
给定已按升序排好序的n个元素a[0:n-1],现要在这n个元素中找出一特定元素x。
代码如下 使用语言java
//返回单个数据
public static int binary(int[] arr,int left,int right,int num){
if (left > right){
return -1;
}
int mid = (left + right) / 2;
if (arr[mid] < num){
return binary(arr, mid + 1, right, num);
}else if (arr[mid] > num){
return binary(arr, left, mid - 1, num);
}else{
return mid;
}
}
//返回所有数据
public static ArrayList binaryAll(int[] arr,int left,int right,int num){
if (left > right){
return null;
}
int mid = (left + right) / 2;
if (arr[mid] < num){
return binaryAll(arr, mid + 1, right, num);
}else if (arr[mid] > num){
return binaryAll(arr, left, mid - 1, num);
}else{
ArrayList list = new ArrayList();
int i = mid;
int j = mid;
//向左扫描
while (--i >=0 && arr[i] == num){
list.add(i);
}
list.add(mid);
//向右扫描
while (++j <= (arr.length - 1) && arr[j] == num){
list.add(j);
}
return list;
}
}
2.2合并排序
合并排序算直观地操作如下:
分解:将 n 个元素分成各含 n/2 个元素的子序列;
解决:用合并排序法对两个子序列递归地排序;
合并:合并两个已排序的子序列以得到排序结果。
在对子序列排序时,其长度为 1 时递归结束。单个元素被视为是已排好序的。
代码如下 使用语言java
public class MergeSort {
public static void main(String[] args) {
int[] arr = new int[100000000];
for (int i = 0; i <100000000; i++) {
arr[i] = (int)(Math.random() * 800000000);
}
Date date1 = new Date();
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy--mm-dd HH:mm:ss");
String d1 = simpleDateFormat.format(date1);
System.out.println(d1);
int[] temp = new int[arr.length];
mergeSort(arr, 0, arr.length - 1, temp);
Date date2 = new Date();
String d2 = simpleDateFormat.format(date2);
System.out.println(d2);
}
//分治
public static void mergeSort(int[] arr,int left,int right,int[] temp){
if (left < right){
int mid = (left + right) / 2;
mergeSort(arr, left, mid, temp);//左边递归
mergeSort(arr,mid + 1,right,temp);//右边递归
merge(arr, left, mid, right, temp);
}
}
//合并
public static void merge(int[] arr,int left,int mid,int right,int[] temp){
//第一步 排序到temp数组
int i = left;//左指针
int j = mid + 1;//左二指针
int t = 0;//右指针
while (i <= mid && j <= right) {
if (arr[i] <= arr[j]) {
temp[t] = arr[i];
t++;
i++;
} else {
temp[t] = arr[j];
t++;
j++;
}
}
//第二步将剩余组数据 填充到temp
while (i <= mid){
temp[t] = arr[i];
t++;
i++;
}
while (j <= right){
temp[t] = arr[j];
t++;
j++;
}
//将temp中元素拷贝到原数组
t = 0;
int tempLeft = left;
while (tempLeft <= right){
arr[tempLeft] = temp[t];
t++;
tempLeft++;
}
}
}
程序运行结果
一亿条数据排序使用时间为21秒,综合性能较好,
**最坏时间复杂度:**O(nlogn)
**平均时间复杂度:**O(nlogn)
**辅助空间:**O(n)
2.3快速排序
快速排序算法基本思想是:对输入的子数组a[p:r],按以下三个步骤进行排序。
(1) 分解(Divide):以a[p]为基准元素将a[p:r]划分成3段a[p:q-1],a[q]和a[q+1:r],使得a[p:q-1]中任何一个元素小于等于a[q],而a[q+1:r]中任何一个元素大于等于a[q]。下标q在划分过程中确定。
(2) 递归求解(Conquer):通过递归调用快速排序算法分别对a[p:q-1]和a[q+1:r]进行排序。
(3) 合并(Merge):由于对a[p:q-1]和a[q+1:r]的排序是就地进行,所以在a[p:q-1]和a[q+1:r]都已排好的序后,不需要执行任何计算,a[p:r]就已排好序。
代码如下 使用语言java
public class QuickSort{
public static void main(String[] args) {
int[] arr = new int[100000000];
for (int i = 0; i <100000000; i++) {
arr[i] = (int)(Math.random() * 80000000);
}
Date date1 = new Date();
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy--mm-dd HH:mm:ss");
String d1 = simpleDateFormat.format(date1);
System.out.println(d1);
quickSort(arr);
Date date2 = new Date();
String d2 = simpleDateFormat.format(date2);
System.out.println(d2);
}
public static void quickSort(int[] arr){
if (arr == null || arr.length == 0 || arr.length == 1){
return;
}
sort(arr, 0, arr.length - 1);
}
//核心算法
public static void sort(int[] arr,int left,int right){
//退出判断
if (left >= right){
return;
}
int base = arr[left];//基数
int temp;
int i = left;
int j = right;
while (i < j){
//寻找右指针位置
while (j > i && arr[j] >= base){
j--;
}
//寻找左指针位置
while (i < j && arr[i] <= base){
i++;
}
if (i < j){
temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
//交换base位置
arr[left] = arr[i];
arr[i] = base;
sort(arr, left, i - 1);
sort(arr, i + 1, right);
}
}
程序运行结果
一亿条数据使用时间为17秒
&最坏时间复杂度:O(n2**)**
&**平均时间复杂度:**O(nlogn)
&**辅助空间:**O(n)或O(logn)