软件工程(2018)第三次作业

最大子段和

令f[i]为从a的某一点开始到a[i]为止最大的子段和,则有以下转移方程:

\[f_i = \max(f_{i - 1} + a_i, a_i) \]

因为只需遍历一次数组就可求出,所以复杂度为\(O(n)\)

package org.sequix.homework3;

/**
 * 提供求最大子段和的工具类。
 *
 * @author sequix
 * @version 0.0.1
 * @since 2018/03/26
 */
public class MaxSubArray {

	/**
	 * 返回数组的最大子段和。
	 * f[i] 到a[i]为止最大的子段和。
	 * f[i] = max(f[i-1]+a[i], a[i])
	 * 
	 * @param arr 被求数组
	 * @return 最大子段和
	 */
	public static int msa(int[] arr) {
		if (arr.length == 0) {
			throw new IllegalArgumentException("expected a non-empty array");
		}
		
		int cur = arr[0];
		int ans = cur;
		
		for (int i = 1, len = arr.length; i < len; ++i) {
			cur += arr[i];
			if (arr[i] > cur) cur = arr[i];
			if (cur > ans) ans = cur;
		}
		return ans;
	}
}

测试

为了方便的测试,这里另写了一个工具类TestUtils。

在TestUtils.msa中,以另一种方式求最大子段和。其枚举所有的子段,选出最大的。此解法时间复杂度为\(O(n^2)\),但其正确性显而易见,所以用于对拍测试。

package org.sequix.homework3;

import java.util.Random;

/**
 * 提供测试用工具函数。
 * @author squix
 * @since 2018/03/19
 */
class TestUtils {
	private static Random random = new Random(); 
	
	/**
	 * 生成[min, max]范围内的随机数。
	 * @param min 最小值,最小可为Integer.MIN_VALUE
	 * @param max 最大值,最大可为Integer.MAX_VALUE
	 * @return 生成的随机数
	 */
	static int randomInteger(int min, int max) {
		long num = (long) max - min + 1;
		long kth = (long) (random.nextDouble() * num);
		long ret = (long) min + kth;
		return (int) ret;
	}
	
	/**
	 * 生成随机数数组。
	 * @param size 数组大小
	 * @param minElement 元素最小值
	 * @param maxElement 元素最大值
	 * @return 生成的数组
	 */
	static int[] generateRandomArray(int size, int minElement, int maxElement) {
		int[] arr = new int[size];
		for (int i = 0; i < size; ++i)
			arr[i] = TestUtils.randomInteger(minElement, maxElement);
		return arr;
	}
	
	/**
	 * 返回数组的最大子段和。用于和MaxSubArry.msa()对拍。
	 * @param arr 被求数组
	 * @return 最大子段和
	 */
	static int msa(int[] arr) {
		int length = arr.length;
		int[] sum = new int[arr.length];
		
		sum[0] = arr[0];
		for (int i = 1; i < length; ++i) {
			sum[i] = sum[i-1] + arr[i];
		}
		
		int ans = arr[0];
		for (int left = 0; left < length; ++left) {
			for (int right = left; right < length; ++right) {
				int tmp = sum[right];
				if (left > 0) tmp -= sum[left-1];
				if (ans < tmp) ans = tmp;
			}
		}
		return ans;
	}
}

具体的测试类如下:

package org.sequix.homework3;

import static org.junit.Assert.assertEquals;

import org.junit.Test;
import org.junit.jupiter.api.RepeatedTest;
import org.junit.platform.runner.JUnitPlatform;
import org.junit.runner.RunWith;

/**
 * MaxSubArray 的测试类。
 *
 * @author sequix
 * @version 0.0.1
 * @since 2018/03/26
 */
@RunWith(JUnitPlatform.class)
public class MaxSubArrayTest {
	@Test(expected=IllegalArgumentException.class)
	public void testEmptyArray() {
		int[] original = new int[0];
		MaxSubArray.msa(original);
	}
	
	@Test
	public void testGeneral() {
		int[] original = new int[] {-2, 11, -4, 13, -5, -2};
		int expected = TestUtils.msa(original);
		assertEquals(expected, MaxSubArray.msa(original));
	}
	
	@Test
	public void testOnlyNegatives() {
		int[] original = TestUtils.generateRandomArray(10000, -10000, -1);
		int expected = TestUtils.msa(original);
		assertEquals(expected, MaxSubArray.msa(original));
	}
	
	@Test
	public void testOnlyPositives() {
		int[] original = TestUtils.generateRandomArray(10000, 1, 10000);
		int expected = TestUtils.msa(original);
		assertEquals(expected, MaxSubArray.msa(original));
	}
	
	@RepeatedTest(10)
	public void testRandom() {
		int[] original = TestUtils.generateRandomArray(10000, -10000, 10000);
		int expected = TestUtils.msa(original);
		assertEquals(expected, MaxSubArray.msa(original));
	}
}

测试效果

测试效果

外链

代码:Github

posted @ 2018-03-26 13:56  sequix  阅读(176)  评论(0编辑  收藏  举报