《算法導論》第4章4.1使用分治策略求最大子數組(數組包含負數,不然整個數組即使最大子數組,求解沒意義)。
思路:數組頭爲low,尾爲high,mid=(low+high)/2,這樣將數組分爲了兩段。首先肯定存在這個最大子數組。那麼子數組的位置要麼處於mid左邊,要麼處於mid右邊,要麼包含mid。假設最大子數組出現在mid左邊,對mid左邊子數組再進行(low+high)/2切分,那麼最大子數組可能在切分出的子數組中的位置又存在三種情況中的一種,這樣遞歸地切分下去,最終切分到整個數組都變爲1-2個元素的子數組,這時候的情況就像一顆二叉樹,例如數組[53,-4,-73,-16,88,91,-50,-15,-15,52,-19],切分之後的二叉樹:
然後用後續遍歷二叉樹的方式計算、查找最大子數組。如圖,節點8的子數組有53、-4、53,-4,則找出來最大子數組爲[53],後序遍歷的方式遍歷完左子樹,返回根,到右子樹,有一個節點9,則最大子數組爲9,對於節點4的左右子樹的最大子數組都找到,再對節點8、4、9合併的數組找位於mid的最大子數組(此時low爲0,mid爲1,high爲2,從mid出發往左走找最大子數組,再從mid+1出發往右走找最大子數組,再將找到的兩個數組合並起來,爲經過mid的最大子數組),找到爲[53,-4,-73],將[53]、[-73],[53,-4,-73]比較,得出根節點爲4的樹最大數組爲[53],則再返回根節點2,再後續到節點5,對5再求最大子數組爲[88,91],再對節點2求經過mid的最大子數組爲[53,-4,-73,-16,88,91],和爲139,與[53]、[88,91]比較,選[88,91];接着返回根節點1,對右子樹節點2求最大子數組,爲[52],再對節點1組成的數組求經過mid的最大子數組,爲[88,91,-50,-15,-15,52],和爲151,與[88,91]、[52]比較,選[88,91]。
c代碼:
#include <stdio.h>
#include <unistd.h>
#include <time.h>
#include <string.h>
#include <stdlib.h>
int crossLow = 0, crossHigh = 0, crossSum = 0;
int finalLeftIndex = -1;
int finalRightIndex = -1;
int finalSubArraySum = 0;
//求經過mid的最長子數組,範圍low-high
void find_max_crossing_subarray( int *arr,
int low, int mid, int high )
{
if ( low == mid && mid == high ) {
crossLow = low;
crossHigh = high;
crossSum = arr[mid];
return ;
}
int lMax = -10000000, rMax = -10000000;
int lTmpSum = 0, rTmpSum = 0;
int i = 0;
for ( i = mid; i >= low; i-- ) {
lTmpSum += arr[i];
if ( lTmpSum > lMax ) {
lMax = lTmpSum;
crossLow = i;
}
}
for ( i = mid + 1; i <= high; i++ ) {
rTmpSum += arr[i];
if ( rTmpSum > rMax ) {
rMax = rTmpSum;
crossHigh = i;
}
}
crossSum = lMax + rMax;
}
//分治,將數組切分子規模的待求數組
void find_maximum_subarray( int *arr,
int low, int high )
{
if ( high <= low + 1 ) {
find_max_crossing_subarray( arr, low, low, high );
if ( arr[low] >= arr[high] && arr[low] >= crossSum ) {
finalLeftIndex = low;
finalRightIndex = low;
finalSubArraySum = arr[low];
}
else if ( arr[high] >= arr[low] && arr[high] >= crossSum ) {
finalLeftIndex = high;
finalRightIndex = high;
finalSubArraySum = arr[high];
}
else {
finalLeftIndex = low;
finalRightIndex = high;
finalSubArraySum = crossSum;
}
}
else {
int lLow = 0, lHigh = 0, lSum = 0;
int rLow = 0, rHigh = 0, rSum = 0;
//其實這裏的遞歸就像二叉樹的後續遍歷,直到遍歷完左子樹,再開始右子樹,
//這樣的好處假如數組有上億的元素,不會造成棧空間不足,
//解決了一個左子樹就返回了遞歸棧,再進行右子樹的展開工作
int mid = ( low + high ) / 2;
find_maximum_subarray( arr, low, mid );
lLow = finalLeftIndex;
lHigh = finalRightIndex;
lSum = finalSubArraySum;
find_maximum_subarray( arr, mid + 1, high );
rLow = finalLeftIndex;
rHigh = finalRightIndex;
rSum = finalSubArraySum;
find_max_crossing_subarray( arr, low, mid, high );
// printf("%d %d %d/%d %d %d/%d %d %d\n",
// lLow, lHigh, lSum,
// rLow, rHigh, rSum,
// crossLow, crossHigh, crossSum);
if ( lSum >= rSum && lSum >= crossSum ) {
finalLeftIndex = lLow;
finalRightIndex = lHigh;
finalSubArraySum = lSum;
}
else if ( rSum >= lSum && rSum >= crossSum ) {
finalLeftIndex = rLow;
finalRightIndex = rHigh;
finalSubArraySum = rSum;
}
else {
finalLeftIndex = crossLow;
finalRightIndex = crossHigh;
finalSubArraySum = crossSum;
}
// printf("------%d %d %d\n", finalLeftIndex, finalRightIndex, finalSubArraySum);
}
}
//暴力破解法求最長子數組,不過這裏用來測試我寫的分治求法結果是否正確
int check_result( int *arr, int len, int lIndex, int rIndex, int Sum )
{
int maxSum = -100000000;
int lIndex1;
int rIndex1;
int i = 0, j = 0;
for ( i = 0; i < len; i++ ) {
int tmpSum = 0;
for ( j = i; j < len; j++ ) {
tmpSum += arr[j];
if ( tmpSum > maxSum ) {
maxSum = tmpSum;
lIndex1 = i;
rIndex1 = j;
}
}
}
if ( Sum == maxSum ) {
if ( lIndex == lIndex1 && rIndex == rIndex1 ) {
return 0;
}
printf("equal!!\n");
printf("exhaustivly_find_result:lIndex->%d,rIndex->%d,Sum->%d\n",
lIndex1, rIndex1, maxSum);
printf("divide_and_conquer_result:lIndex->%d,rIndex->%d,Sum->%d\n",
lIndex, rIndex, Sum);
return 1;
}
// else {
// printf("not equal!!!!!!!!!");
// }
return -1;
}
void mainLoop( int *arr, int len )
{
find_maximum_subarray( arr, 0, len - 1 );
printf("----------------------------------------------------\n");
printf("lIndex:%d,rIndex:%d,subArrSum:%d\n",
finalLeftIndex, finalRightIndex, finalSubArraySum);
printf("----------------------------------------------------\n");
}
void stepLoop( int *arr, int len )
{
time_t tt0 = time( NULL );
printf("before:%s", ctime(&tt0));
mainLoop( arr, len );
time_t tt1 = time( NULL );
printf("after:%s", ctime(&tt1));
printf("cost %d(sec),%d(min)\n",
(int)(tt1 - tt0), (int)((tt1 - tt0) / 60));
}
//初始化隨機數組
void initArr( int *arr, int lowV,
int upV, int len )
{
int i = 0;
int size = upV - lowV;
for ( ; i < len; i++ )
{
arr[i] = rand() % size + lowV;
}
}
//打印數組
void printArr( int *arr, int len )
{
int i = 0;
for ( ; i < len; i++ )
printf("%d ", arr[i]);
printf("\n");
}
int main( int argc, char **argv )
{
srand( (int)time(NULL) );
if ( argc != 4 )
{
printf("usage: ./execfile lowV upV len\n");
return 0;
}
int lowV = atoi( argv[1] );
int upV = atoi( argv[2] );
unsigned int len = atoi( argv[3] );
int *arr = NULL;
arr = ( int *) malloc( len * sizeof(int) );
// int len = 10;
// int arr[10] = {7, 0, -86, 61, -72, 50, -38, -25, -70, -76};
int i = 0;
//隨機10000個數組求最大子數組,然後檢測結果是否正確
for ( i = 0; i < 10000; i++ ) {
initArr( arr, lowV, upV, len );
printArr( arr, len );
// stepLoop( arr, len );
usleep(1000);
find_maximum_subarray( arr, 0, len - 1 );
int ret = check_result( arr, len,
finalLeftIndex, finalRightIndex, finalSubArraySum );
if ( ret < 0 ) {
printf("failed!!\n");
return 0;
}
printf("-------------equal:%d\n", i);
}
free( arr );
arr = NULL;
return 0;
}
以上代碼定義了幾個全局變量,與《算法導論》的代碼有點出入,書上的函數塊返回3個值,例如:(left-low,left-high,left-sum) = FIND-MAXIMUM-SUBARRAY(A, low, mid),這裏可能用其它編程語言更好描述算法,erlang的函數就可以返回一個元組並接收{LeftLow, LeftHigh, LeftSum} = FIND….()。