算法逻辑思维(2)分治

2.分治:如何利用分治法完成数据查找
从定性的角度看,分治法的核心思想就是“分而治之”
可以把一个大规模、高难度的问题,分解为若干个小规模、低难度的小问题
很多高效率的算法都是以分治法作为其基础思想,例如排序算法中的快速排序和归并排序
2.1分治法是什么?
计算机求解问题所需的计算时间与其涉及的数据规模强相关。
分治法的核心思想就是分而治之,可以采用同一种解法,递归地去解决这些子问题,再将每个子问题的解合并,得到的就是原问题的解。
误区:当计算机性能还不错时,采用分治法与全部遍历一遍没有区别。
2.2分治法的价值是什么呢?
在小数据规模上,分治法没有什么特殊的价值。只有在大数据集上,分治法的价值才能显现出来。
假如厚度为1毫米且足够柔软的纸,将它对折多少次之后,厚度能达到地球到月球的距离?
答案是39次
2的39次方等于55万千米大于39万千米。
如果使用二分查找,只需要39次判断,相比暴力搜索的方法,性能优势高的不是一星半点。这也意味着复杂度O(logn)相比复杂度为O(n)的算法,在大数据集合中性能有着爆发式的提高。
2.3分治法的使用方法
采用分治法时,一般原问题需要具备以下几个特征:
1.难度在降低,即原问题的解决难度,随着数据的规模的缩小而降低。这个特征绝大多数问题都是满足的。
2.问题可分,原问题可以分解为若干个规模较小的同类型问题。这是应用分治法的前提
3.解可合并,利用所有子问题的解,可合并出原问题的解。这个特征很关键,能否利用分治法完全取决于这个特征。
4.相互独立,各个子问题之间相互独立,某个子问题的求解不会影响到另一个子问题。如果子问题之间不独立,则分治法需要重复地解决公共的子问题,造成效率低下的结果。
分治法在每轮递归上,都包含了分解问题、解决问题和合并结果这三个步骤。
二分查找的思路比较简单,步骤如下:
1.选择一个标志i将集合L分为二个子集合,一般可以使用中位数;
2.判断标志L(i)是否能与要查找的值des相等,相等则直接返回结果;
3.如果不相等,需要判断L(i)与des的大小;
4.基于判断的结果决定下步是向左查找还是向右查找。如果向某个方向查找的空间为0,则返回结果为查到;
5.回到步骤1.
2.4分治法的案例
在数组 { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 } 中,查找 8 是否出现过。
首先判断 8 和中位数 5 的大小关系。因为 8 更大,所以在更小的范围 6, 7, 8, 9, 10 中继续查找。此时更小的范围的中位数是 8。由于 8 等于中位数 8,所以查找到并打印查找到的 8 对应在数组中的 index 值。
从代码实现的角度来看,我们可以采用两个索引 low 和 high,确定查找范围。最初 low 为 0,high 为数组长度减 1。在一个循环体内,判断 low 到 high 的中位数与目标变量 targetNumb 的大小关系。根据结果确定向左走(high = middle - 1)或者向右走(low = middle + 1),来调整 low 和 high 的值。直到 low 反而比 high 更大时,说明查找不到并跳出循环。我们给出代码如下:

public static void main(String[] args) {

	// 需要查找的数字

	int targetNumb = 8;

	// 目标有序数组

	int[] arr = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };

	int middle = 0;

	int low = 0;

	int high = arr.length - 1;

    int isfind = 0;

	while (low <= high) {

		middle = (high + low) / 2;

		if (arr[middle] == targetNumb) {

			System.out.println(targetNumb + " 在数组中,下标值为: " + middle);

            isfind = 1;

			break;

		} else if (arr[middle] > targetNumb) {

			// 说明该数在low~middle之间

			high = middle - 1;

		} else {

			// 说明该数在middle~high之间

			low = middle + 1;

		}

    }

    if (isfind == 0) {

			System.out.println("数组不含 " + targetNumb);

	}

}

我们基于这个例子,可以对它进行一些经验和规律的总结,这些经验会辅助大家在面试时找到解题思路。
二分查找的时间复杂度是 O(logn),这也是分治法普遍具备的特性。当你面对某个代码题,而且约束了时间复杂度是 O(logn) 或者是 O(nlogn) 时,可以想一下分治法是否可行。
二分查找的循环次数并不确定。一般是达到某个条件就跳出循环。因此,编码的时候,多数会采用 while 循环加 break 跳出的代码结构。
二分查找处理的原问题必须是有序的。因此,当你在一个有序数据环境中处理问题时,可以考虑分治法。相反,如果原问题中的数据并不是有序的,则使用分治法的可能性就会很低了。
在一个有序数组中,查找出第一个大于 9 的数字,假设一定存在。例如,arr = { -1, 3, 3, 7, 10, 14, 14 }; 则返回 10。

public static void main(String[] args) {

	int targetNumb = 9;

	// 目标有序数组

	int[] arr = { -1, 3, 3, 7, 10, 14, 14 };

	int middle = 0;

	int low = 0;

	int high = arr.length - 1;

	while (low <= high) {

		middle = (high + low) / 2;

		if (arr[middle] > targetNumb && (middle == 0 || arr[middle - 1] <= targetNumb)) {

			System.out.println("第一个比 " + targetNumb + " 大的数字是 " + arr[middle]);

			break;

		} else if (arr[middle] > targetNumb) {

			// 说明该数在low~middle之间

			high = middle - 1;

		} else {

			// 说明该数在middle~high之间

			low = middle + 1;

		}

	}	

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值