文章目录
- 说明
- 约束条件
- 简单说下思路
- 解决方案
- 随机无序数组样本生成器
- 算法实现
- 验证代码
- 进行大样本随机测试验证算法正确性
说明
在算法中,局部最小值是指一个函数在一个局部范围内的最小值。
具体而言,如果一个函数在一个小区间内的取值都比该区间内的其他取值要小,那么该点就被称为局部最小值。
在算法中,寻找函数的局部最小值通常是一个重要的优化问题。
约束条件
- 无序数组,相邻位置数组元素的的值大小不相等
- 找到数组中某个局部最小值的下标
简单说下思路
-
数组长度为 1 :局部最小值就是它自己。
-
数组长度为 2 :较小的值是局部最小值。
-
数组长度为 3 及以上 :
假设数组长度为 N(N >= 3)
C1:那么首先对比第 0 个位置和第 1 个位置的值大小;如果第 0 个位置的值更小,那第 0 个位置就是数组中的一个局部最小值位置。
C2:如果情况 C1 没找到最小值,那就继续寻找局部最小值,这时候可以对比第 N-1 个位置和第 N-2 个位置的值大小。如果第 N-1 个位置的值更小,那第 N-1 个位置就是数组中的一个局部最小值位置。
C3:如果情况 C1 和 C2 都没找到最小值,这时候说明第 0 个位置到第 1 个位置的值是下降趋势,第 N-2 个位置到第 N-1 个位置的值是上升趋势。如下图示:
由上图例子可知,在数组中,第 0 个位置和 第 5 个位置之间必存在一个局部最小值。
这时候我们对数组存在局部最小值的区间(0, N-1)进行二分,找到中间位置((0 + (N-1) )/ 2
),然后对比中间位置跟相邻位置的值的大小:
1、如果此时中间位置的值最小,说明中间位置就是数组中的一个局部最小值位置;
2、如果中间位置的值不是最小值,那就可以知道中间位置跟相邻位置的值大小是上升还是下降趋势,然后进一步缩小存在局部最小值的区间范围。
3、再根据缩小后的区间继续进行二分判断,直至找到局部最小值,并且是肯定能找到的。
解决方案
有了上面的思路,我们下面开始来实现,并用对数器进行验证。
随机无序数组样本生成器
数组中相邻位置值的大小不相等。工具类如下:
package com.example.myapplication.util;
public class ArrayUtil {
private volatile static ArrayUtil arrayUtil;
private ArrayUtil() {
}
public static ArrayUtil instance(){
if (arrayUtil == null){
synchronized (ArrayUtil.class){
if (arrayUtil == null){
arrayUtil = new ArrayUtil();
}
}
}
return arrayUtil;
}
// 生成无序随机数组,长度和元素值都是随机,且相邻位置的值不相等
public static int[] getRandomArrayPosNotEqual(int maxLen, int maxValue){
int randomLen = (int)(Math.random() * maxLen);
int[] randomArr = new int[randomLen];
if (randomLen > 0){
randomArr[0] = (int)(Math.random() * maxValue);
for (int i = 1; i < randomLen;i++){
do {
randomArr[i] = (int)(Math.random() * maxValue);
}while (randomArr[i] == randomArr[i - 1]);
}
}
return randomArr;
}
// 生成随机数组,长度和元素值都是随机(相邻位置值可能相等)
public static int[] getRandomArray(int maxLen, int maxValue){
int randomLen = (int)(Math.random() * maxLen);
int[] randomArr = new int[randomLen];
if (randomLen > 0){
for (int i = 0; i < randomLen;i++){
randomArr[i] = (int)(Math.random() * maxValue);
}
}
return randomArr;
}
}
算法实现
算法问题解决方案,代码实现如下:
// 实现局部最小值算法 找到数组中某个局部最小值的下标并返回
// 数组无序,且相邻位置的元素值不相等
private int getLocalMinInArray(int[] arr){
if (arr == null || arr.length == 0){
return -1;
}
int len = arr.length;
if (len == 1){
return 0;
}
if (arr[0] < arr[1]){
return 0;
}
if (arr[len - 1] < arr[len - 2]){
return (len - 1);
}
// 示例: [1262, 134, 701, 749, 465, 1333]
int min = -1;
int L = 0, R = len - 1;
// 保证比较的数组中至少存在3个数组元素,防止边界条件异常:例如数组下标越界的异常
while (L < (R - 1)){
int mid = (L + R) / 2;
if (arr[mid] < arr[mid - 1] && arr[mid] < arr[mid + 1]){
return mid;
}
if (arr[mid] > arr[mid - 1]){
R = mid - 1;
continue;
}
if (arr[mid] > arr[mid + 1]){
L = mid + 1;
continue;
}
}
// 比较数组中还剩两个值的情况 哪个小就返回哪个对应的下标
min = L;
if (arr[L] > arr[R]){
min = R;
}
return min;
}
代码实现跟上面说的思路是一样的。
- 如果数组下标不存在,则返回 -1。
验证代码
获取到了数组中的局部最小值下标后,我们再来实现一个验证局部最小值是否正确的代码。如下:
// 验证数组的局部最小值下标是否正确
private boolean checkLocalMinIndex(int[] arr, int min){
if (arr.length == 0){
return (min == -1);
}
if (arr.length == 1){
return (min == 0);
}
int minLeft = min - 1;
int minRight = min + 1;
if (minLeft == -1 && arr[min] < arr[minRight]){
// 第一个是局部最小值
return true;
}
if (minRight == (arr.length) && arr[min] < arr[minLeft]){
// 最后一个是局部最小值
return true;
}
// 中间某个位置是局部最小值
if (arr[min] < arr[minLeft] && arr[min] < arr[minRight]){
return true;
}
return false;
}
进行大样本随机测试验证算法正确性
private void testLocalMinInArray(){
System.out.println(":> 测试开始");
int maxLen = 10;
int maxValue = 2000;
int testCount = 100000;
for (int i = 0;i < testCount;i++){
int[] randomArr = ArrayUtil.instance().getRandomArrayPosNotEqual(maxLen, maxValue);
int min = getLocalMinInArray(randomArr);
if (!checkLocalMinIndex(randomArr, min)){
System.out.println(":> randomArr = " + Arrays.toString(randomArr));
System.out.println(":> 获取数组局部最小值下标min不正确,min = " + min);
break;
}
if (i == testCount - 1){
System.out.println(":> 最后一个数组:" + Arrays.toString(randomArr));
System.out.println(":> 最后一个数组,局部最小值下标:" + min);
}
}
System.out.println(":> 测试结束");
}
- 多次测试均通过,如下:
故求取无序数组局部最小值下标算法正确。
“Peace Love Respect”