最大子数组问题算法比较


title: 最大子数组问题算法比较
date: 2019-02-08 17:23:34
tags: Algorithms

《算导》4.1-3。将暴力法和分治法进行比较。

首先定义结构体:

typedef struct {
	int left;
	int right;
	int max;
}ans;

产生随机数组以供测试

#include 
#define random(x) (rand()%x)

srand((int)time(0));
int A[NUM];
for (int i = 0; i < NUM; i++) {
	A[i] = random(100) - 50;	
}

暴力求解法

代码

ans MaxSubarray_violent(int A[], int low, int high) {
	ans a {low, high, A[low]};
	if (low == high) return a;//假设low<=high
	for (int i = low; i <= high; i++) {
		int sum = 0;
		for (int j = i; j <= high; j++) {
			sum += A[j];
			if (sum > a.max) {
				a.max = sum;
				a.left = i;
				a.right = j;
			}
		}
	}
	return a;
}

分析

运行时间f(n) = c1n²+c2n+c3

时间复杂度 O(n²)

分治法

思路

分治法首先将问题分成三种情况:最大子数组在左半部分、在右半部分、跨越中间点。前两种称为递归情况,需要继续递归下去;最后一种情况称为基本情况,可以直接求解。

而求解跨越中点的情况,只需要从中点向左、向右找最大子数组,合并即可。

代码

ans MaxSubarray_divide(int A[], int low, int high) {
	ans a = { low,high,A[low] };
	if (low == high) return a;
	ans left, right, cross;
	int mid = (low + high) / 2;
	left = MaxSubarray_divide(A, low, mid);
	right = MaxSubarray_divide(A, mid + 1, high);
	cross = MaxSubarray_divide_crossing(A, low, mid, high);
	if (left.max > right.max&&left.max > cross.max) return left;
	else if (right.max > left.max&&right.max > cross.max) return right;
	else return cross;
}
ans MaxSubarray_divide_crossing(int A[], int low, int mid, int high) {
	int sum = 0, left_max = INT_MIN, right_max = INT_MIN;
	ans a;
	for (int i = mid; i >= low; i--) {
		sum += A[i];
		if (sum > left_max) {
			left_max = sum;
			a.left = i;
		}
	}
	sum = 0;
	for (int i = mid + 1; i <= high; i++) {
		sum += A[i];
		if (sum > right_max) {
			right_max = sum;
			a.right = i;
		}
	}
	a.max = left_max + right_max;
	return a;
}

分析

分治法时间复杂度为nlgn。

交叉点

题目要求求出这两种算法的性能交叉点。用数学的角度思考也就是解方程:c1n+c2n+c3 = nlgn。由函数图像知n很小时,左边<右边;n很大时,左边>右边。在VS2017,x64平台上,我通过clock函数计时来确定交叉点:

问题规模(N) 运行次数 暴力法运行时长(ms) 分治法运行时长(ms)
120 10000 208 174
110 10000 178 157
100 10000 147 141
95 10000 133 131
94 10000 138 142
90 10000 124 142
80 10000 101 136
70 10000 79 109

观察发现,N = 94 时暴力法运行时长开始大于分治法,也就是说该方程的解n0——性能交叉点为94。
(这里我其实有担心VS做编译优化,导致运行时间不准确)

混合法

题目提到,当问题规模小于n0时采用暴力算法,大于n0时采用分治算法,再来看性能交叉点。

ans MaxSubarray_mix(int A[], int low, int high) {
	if (high < CROSS) {
		return MaxSubarray_violent(A, low, high);
	}
	return MaxSubarray_divide(A, low, high);
}

我的分析:问题规模小于n0时,混合法运行时间等于暴力法;大于n0时,运行时间等于分治法。从函数图像上看只不过是两函数的最小值,所以性能交叉点不会变化。这种方法我觉得是比较好的。

实际:发现混合法的运行时长没有规律,推测是因为我把混合法放在最后一个来测试,编译器对重复运行的函数会进行优化。需要将随机数组改成固定数组再来测试。

你可能感兴趣的:(算法)