分治策略的基本思想

本文详细介绍了分治策略在算法设计中的应用,包括二分检索、二分归并排序、汉诺塔问题、快速排序以及幂乘算法。针对每个问题,阐述了其分治思想、递归边界和解的递推方程,并给出了Java代码实现。此外,还探讨了一个芯片测试问题,提出了一种分治解法以减少测试复杂度。
摘要由CSDN通过智能技术生成

P19 分治策略的基本思想

分治算法的算法设计

1、确定子问题

2、确定递归的边界(问题规模小于c是可以直接求解)

3、确定解的递推方程

相当于规模为p的问题

1、当问题规模小于c是可以直接求解

2、将问题p划分为k个子问题

3、子问题求解

4、将子问题的解合并
分治策略
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

以下是分治算法的实例运用
二分检索问题
问题输入:顺序数组T,下标从 l 到 r ;数x;
问题输出:j //若x在T中,j为下标;否则为0
分治求解:
1、确定子问题
首先确定原问题形式为 BinnarySearch(T,l,r, x);
子问题缩小问题的规模,也就是缩小T的规模,并且是原问题的解不受影响;
由于T是顺序数组,通过x与T的中位数比较,可以将原问题归结为规模减半的子问题;
2、递归的边界
当x与中位数相等时返回或者当T的规模等于1时返回;
复杂度分析
T(n) = T(n/2) + 1;
T(1) = 1;
递归树求解可知T(n) = logn
老师的分析
二分检索迭代方式伪代码
二分检索迭代方式伪代码
二分 检索设计在这里插入图片描述
复杂度分析
在这里插入图片描述
java代码实现

public int binarySearch(Integer[] arr, int target, int start, int end) {
		int mid = (end - start) / 2 + start;
		if (target == arr[mid]) {
			return mid;
		} 
		if(end - start == 0) {
			return -1;
		}
		if (target > mid) {
			return binarySearch(arr, target, mid + 1, end);
		} else {
			return binarySearch(arr, target, start, mid - 1);
		}
	}

二分归并排序
问题输入:数组T【p…r】;
问题输出:元素按从小到大的顺序排列
分治求解:
1、确定子问题
首先确定原问题形式为 MergeSort(T,start, end);
子问题缩小问题的规模,也就是缩小T的规模,并且使原问题的解不受影响;
把原问题均分为两个相等的规模子问题,然后两个子问题合并求得原始问题;
2、递归的边界
边界可以当规模为1时直接返回进行合并;
也可以当规模为2时,比较一次得到正确的顺序后返回进行合并;
3、解的递推方程
MergeSort(T,start, end) = MergeSort(T/2,start, (end-start)/2) + MergeSort(T/2,start, (end-start)/2 +1 ) + F(合并过程);
合并过程算法描述
两个排好序的数组,i,j分别指向两个数组的首元素,依次比较,较小的依次放入新数组同时指针加一;
复杂度分析
T(n) = 2T(n/2) + n-1;
T(1) = 0;
递归树求解可知T(n) = nlogn
老师的分析
在这里插入图片描述
二分设计思想
在这里插入图片描述
算法复杂度分析
在这里插入图片描述
java代码实现

	// 二分归并排序 nlogn
	/**
	 * 递归排序 递归参数 待排序数组arr,开始下标start,结束下标end 递归内容: 将待排序的数组 平均分成两组;
	 * 不断划分直到数组的长度小于等于2,当长度小于等于2时,将两个元素正确排序(小于2就不排序),返回; 递归返回后: 将两个数组合并 新建一个空数组
	 * 两个数组的首元素比较,如果是升序则谁小谁先放入数组; 返回 递归边界: 数组的长度小于等于2
	 */
	public Integer[] MergeSort(Integer[] array, int start, int end) {
		if (end - start <= 1) {
			if (array[start] > array[end]) {
				int tem = array[start];
				array[start] = array[end];
				array[end] = tem;
			}
			return array;
		} else {
			sort7(array, start, (end - start + 1) / 2 - 1 + start);
			sort7(array, (end - start + 1) / 2 + start, end);
		}

		Integer[] tempArr = new Integer[end - start + 1];
		int i = start;
		int j = (end - start + 1) / 2 + start;
		for (int k = 0; k < tempArr.length; k++) {
			if (j == end + 1) {
				tempArr[k] = array[i];
				i++;
				continue;
			}
			if (i == (end - start + 1) / 2 + start) {
				tempArr[k] = array[j];
				j++;
				continue;
			}

			if (array[i] < array[j]) {
				tempArr[k] = array[i];
				i++;
			} else {
				tempArr[k] = array[j];
				j++;
			}
		}
		for (int k = 0; k < tempArr.length; k++) {
			array[start + k] = tempArr[k];
		}
		return array;
	}

Hanoi塔的递归算法
问题描述:有三根柱子A,B,C 在A柱子从下往上按照大小顺序摞着n片黄金圆盘
规定任何时候小圆盘上都不能放大圆盘,且在三根柱子之间一次只能移动一个圆盘。问应该如何操作,把n片圆盘从A移动到C
分治求解:
1、确定子问题
首先确定原问题形式为 Hanoi(A,C, n);
子问题缩小问题的规模,也就是缩小n的规模,并且使原问题的解不受影响;
可以想象得到,当将第n个圆盘从A移动到C时,此时n-1个圆盘在B上;
所以只要解决 Hanoi(A,B, n-1);

2、递归的边界
当规模为1时直接移动;
3、解的递推方程
Hanoi(A,C, n) = Hanoi(A,B, n-1) + Hanoi(A,C, 1) + Hanoi(B,C, n-1);

复杂度分析
T(n) = 2T(n-1) + 1;
T(1) = 1;
求解可知T(n) = 2^n
老师的分析
在这里插入图片描述
java代码实现

//Hanoi塔问题
	//问题描述:有三根柱子A,B,C 在A柱子从下往上按照大小顺序摞着n片黄金圆盘
	//规定任何时候小圆盘上都不能放大圆盘,且在三根柱子之间一次只能移动一个圆盘。问应该如何操作,把n片圆盘从A移动到C
	public void hanoi(char src, char desc, char mid, int n) {
		if(n == 1) {
			System.out.println(src + " 移动到  " + desc);
		}else {
			hanoi(src, mid, desc, n-1);
			System.out.println(src + " 移动到  " + desc);
			hanoi(mid, desc, src, n-1);
		}
	}

快速排序算法
问题输入:数组T【p…r】;
问题输出:元素按从小到大的顺序排列
分治求解:
1、确定子问题
首先确定原问题形式为 QuickSort(T,start, end);
2、划分子问题
以首元素为标准,将比首元素大的元素放到数组右边,比首元素小的元素放到数组左边;最后把首元素放入中间,也就是首元素左边是都比他小的元素数组,右边是都比它大的元素数组;左边数组和右边数组形成子问题;
划分算法描述:将小的放在右边大的放到左边的算法
i指向首元素后第一个元素,j指向最后一个元素,i从左至右移动,j从右到左移动,i找到比首元素大的数停止移动等待j找到一个比首元素小的数,找到后他们交换;
当i和j碰撞时算法结束;
3、递归边界
当n=1时,不再需要排序直接返回;
4、解的递推方程
QuickSort(T,start, end)
不需要合并子问题的解;
复杂度分析
最坏情况,刚好首元素就是第一个或者最后一个,那么子问题的规模只减1
T(n) = T(n-1)+ n-1; n-1是指需要n-1次的比较
T(1) = 0;
T(n)= n(n-1)/2
最好情况,刚好划到中间
T(n) = 2T(n/2)+ n-1
T(1) = 0;
T(n)= nlogn
老师分析
在这里插入图片描述
划分过程
在这里插入图片描述
划分实例
在这里插入图片描述
复杂度分析
在这里插入图片描述
在这里插入图片描述
递归树分析
在这里插入图片描述
平均复杂度分析
在这里插入图片描述
在这里插入图片描述
java代码实现

// 快速排序1
	/**
	 * 递归排序 递归参数 待排序数组,开始下标,结束下标
	 * 
	 * 递归内部: 数组第一个数字 作为“排序目标” 用来比较 i指针从小到大遍历,j指针从大到小遍历
	 * 假定是升序,则较小的数字应在坐标,较大的数字应在右边。 那么i指针找到比“排序目标”大的数,找到后 等待j指针
	 * j指针找到比“排序目标”小的数,找到后i 和 j 所指的数字进行交换 (也就是大的数往右边放,小的数往左边放)
	 * i不能与j交叉或者相等,即i++和j--的限制条件是相邻,当ij相碰则结束(j>i); 假如i未找到比“排序目标”大的数 或者
	 * 假如j未找到比“排序目标”小的数则交换结束
	 * 
	 * 结束后划分数组 假如arr[i] < “排序目标”,则 “排序目标”与i下标交换 反之与i-1下标交换
	 * 这样“排序目标”的位置已经排好,且“排序目标”左边都是比它小的数,排序目标右边都是比它大的数; 排序目标左边,排序目标右边继续进入新的递归
	 * 
	 * 递归边界: end = start 意味着只有一个数 end < start
	 */
	public Integer[] sort5(Integer[] array, int start, int end) {
		// 递归边界 end = start 意味着只有一个数
		if (end <= start) {
			return array;
		}
		// 也可以对 只有两个数的数组单独处理
		// if(end - start == 1) {
		// //比较这两个数然后交换
		// }
		// 定义i指针 i指针指向第二个数 第一个数单独拿出来作比较
		int i = start + 1;
		// j指针从后面开始
		int j = end;
		//
		while (j > i) {
			boolean iFlag = false;
			while (array[i] < array[start]) {
				if (i < j - 1) {
					i++;
				} else {
					iFlag = true;
					break;
				}
			}
			if (iFlag) {
				break;
			}

			boolean jFlag = false;
			while (array[j] > array[start]) {
				if (j > i + 1) {
					j--;
				} else {
					jFlag = true;
					break;
				}
			}
			if (jFlag) {
				break;
			}
			int tem = array[i];
			array[i] = array[j];
			array[j] = tem;
		}

		// 递归
		if (array[start] > array[i]) {
			int tem = array[i];
			array[i] = array[start];
			array[start] = tem;
			sort5(array, start, i - 1);
			sort5(array, i + 1, end);
		} else {
			int tem = array[i - 1];
			array[i - 1] = array[start];
			array[start] = tem;
			sort5(array, start, i - 2);
			sort5(array, i, end);
		}
		return array;
	}

	// 快速排序2 最坏n^2 平均nlogn
	/**
	 * 递归排序 递归参数 待排序数组arr,开始下标start,结束下标end
	 * 
	 * 递归内部: 数组第一个数字 作为“排序目标”arr[start]用来比较 暂存arr[start]的值为first swapindex为交换的下标
	 * 初始值为start 假定升序排序:i指针从start+1起从左至右寻找比first大的值,找到后与swapindex交换,新的swapindex
	 * = i; j指针从end起从右至左寻找比first小的值,找到后与swapindex交换,新的swapindex = j; 在寻找的过程中
	 * j不能小于i,否则寻找结束; 结束后把first放入arr[swapindex]
	 * 
	 * 递归结束后,swapindex左边都是比first小的数,右边都是比first大的数; 划分数组 递归边界: end <= start
	 */
	public Integer[] sort6(Integer[] array, int start, int end) {
		// 递归边界
		if (end <= start) {
			return array;
		}

		int i = start + 1;
		int j = end;
		// 记录初始start的值 和swapindex
		int first = array[start];
		int sawpIndex = start;

		// j不能小于i,存在等于是因为可能存在{8,7}这样长度为2的未排序数组
		while (j >= i) {
			boolean jFlag = false;
			while (array[j] > first) {
				if (j > i + 1) {
					j--;
				} else {
					jFlag = true;
					break;
				}
			}
			if (jFlag) {
				break;
			}
			array[sawpIndex] = array[j];
			sawpIndex = j;

			boolean iFlag = false;
			while (array[i] < first) {
				if (i < j - 1) {
					i++;
				} else {
					iFlag = true;
					break;
				}
			}
			if (iFlag) {
				break;
			}
			array[sawpIndex] = array[i];
			sawpIndex = i;
		}
		array[sawpIndex] = first;
		sort6(array, start, sawpIndex - 1);
		sort6(array, sawpIndex + 1, end);
		return array;
	}

幂乘算法
问题描述:a为给定实数,n为自然数,求a的n次方
传统算法,temp=a 循环temp*=a 复杂度为T(n)=n;
分治求解:
1、确定子问题
确定原问题形式为F(a,n)
那么子问题可为F(a,n/2);
2、划分子问题
直接对半划分子问题
3、递归边界
当n=1时可以直接返回n
或当n=2时直接返回n*n
4、递推方程
当n的偶数时,
F(a,n) = F(a,n/2) 乘 F(a,n/2);
当n为奇数时,
F(a,n) = F(a,(n-1)/2) 乘 (a,(n-1)/2)乘 F(a, 1);
算法复杂度分析
T(n)=T(n/2) + 1;
T(n) = logn
老师分析
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
java实现

//幂乘算法
	//问题描述:a为给定实数,n为自然数,求a的n次方
	public long powerMultiplyAlgorithm(int a, int n) {
		if(n <= 2) {
			if(n == 1) {
				return a;
			}else {
				return a * a;
			}
		}
		if(n % 2 == 0) {
			long temp = powerMultiplyAlgorithm(a, n/2);
			return temp * temp;
		}else {
			long temp = powerMultiplyAlgorithm(a, (n-1)/2);
			return temp * temp * a;
		}
	}

芯片测试问题
问题描述:n片芯片,其中好芯片至少比坏芯片多一片;好芯片能正确测试出芯片好坏,坏芯片测试结果可能是好也可能是坏;设计一种测试方法,通过测试从n片芯片中跳出一块好的芯片;
蛮力算法:
任取一块芯片,作为被测试芯片;其他芯片依次对该芯片进行测试,若报告是好芯片的次数大于等于n/2则说明是好芯片,停止测试;若报告是坏芯片的次数大于等于n/2+1,则说明是该芯片是坏芯片;
最好的情况第一块就是好芯片复杂度为n
最坏的情况下,前面测试的全是换芯片,
复杂度=n-2 + n-3 + …+n-n/2-1
总计复杂度=O(n^2)
分治解法:

1、确定子问题
确定原问题形式为F(n)
如何将n的规模减小
2、划分子问题
把n个芯片两两分组
两两分组的芯片互相测试
结果为两个好的任意保留一块
其他情况的全部抛弃
保留的芯片进入子问题(如此需要证明子问题与原问题性质一样,保持好的芯片比坏芯片多)
证明:
假如两两分组的芯片,结果为两个好的有 i 组,一个好一个坏的有 j 组,两个坏的 k 组;
那么有如下关系,2(i+j+k)= n;好的芯片数树比坏的芯片数多,那么有 2i+j > 2k+j
而通过如上的分组两个好的有i组,将留下有i个,两个坏的k组,假设都是测出来两个好也全部留下,有k个,那么最终子问题有i+k个,i>k,故好的比坏的多,任保留了原问题的性质;
当n是奇数时,单独出来的芯片,采用暴力算法单独测试,是好的直接返回结果;
3、递归的边界
当n=3时,只需要任意比较一次就可以确定好的芯片
n<3时,都是好的,不需要测试,直接返回
4、子问题合并
子问题不需要合并
5、算法复杂度分析
T(n)= T(n/2) + O(n)
T(3) = 1
T(2)=0
T(1)=0
T(n) = O(n) 为什么不是nlogn
老师分析
问题
在这里插入图片描述

在这里插入图片描述
测试结果分析
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
java实现

// 芯片测试
	// 题目:n片芯片,其中好芯片至少比坏芯片多一片;好芯片能正确测试出芯片好坏,坏芯片测试结果可能是好也可能是坏;
	// 设计一种测试方法,通过测试从n片芯片中跳出一块好的芯片
	class Chip {
		public boolean test;
		public int id;

		//芯片测试规则
		public boolean testChip(Chip b) {
			Random random = new Random();
			if (test) {
				return b.test;
			} else {
				return random.nextInt() % 2 == 0 ? false : true;
			}
		}
	}

	// 构造一堆随机芯片
	public List<Chip> getChipArr(int n) {
		List<Chip> chipList = new ArrayList<Chip>(n);
		Random random = new Random();
		int j = n / 2 + 1;
		for (int i = 1; i <= n; i++) {
			Chip chip = new Chip();
			chip.id = i;
			if (i + j >= n) {
				chip.test = true;
			} else {
				if (random.nextInt() % 2 == 0) {
					chip.test = true;
					j--;
				} else {
					chip.test = false;
				}
			}
			chipList.add(chip);
		}
		return chipList;
	}

	//找出一片正确的芯片
	public Chip ChipTest(List<Chip> chipList) {
		if (chipList.size() % 2 != 0) {
			if (singerChipTest(chipList)) {
				return chipList.get(chipList.size() - 1);
			} else {
				chipList.remove(chipList.size() - 1);
			}
			;
		}
		if (chipList.size() == 3) {
			if (chipList.get(0).testChip(chipList.get(0))) {
				return chipList.get(1);
			} else {
				return chipList.get(2);
			}
		} else if (chipList.size() < 3) {
			return chipList.get(0);
		}
		List<Chip> newList = new ArrayList<Chip>();
		for (int i = 0; i < chipList.size(); i += 2) {
			if (chipList.get(i).testChip(chipList.get(i + 1)) && chipList.get(i + 1).testChip(chipList.get(i))) {
				newList.add(chipList.get(i));
			}
			;
		}

		return ChipTest(newList);
	}

	// 单片芯片测试
	public boolean singerChipTest(List<Chip> chipList) {
		Chip singerChip = chipList.get(chipList.size() - 1);
		int trueCount = (chipList.size() - 1) / 2;
		int falseCount = trueCount + 1;
		for (int i = 0; i < chipList.size() - 1; i++) {
			if (chipList.get(i).testChip(singerChip)) {
				trueCount--;
			} else {
				falseCount--;
			}
			if (trueCount == 0) {
				return true;
			}
			if (falseCount == 0) {
				return false;
			}
		}
		return false;
	}

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值