求解最大连续子序列和问题
问题描述
给定一个有n(n≥1)个整数的序列,要求求出其中最大连续子序列的和。
例如:
序列(-2,11,-4,13,-5,-2)的最大子序列和为20
序列(-6,2,4,-7,5,3,2,-1,6,-9,10,-2)的最大子序列和为16。
规定一个序列最大连续子序列和至少是0(长度为0的子序列),如果小于0,其结果为0。
问题求解
对于含有n个整数的序列a[0…n-1],若n=1,表示该序列仅含一个元素,如果该元素大于0,则返回该元素;否则返回0。
若n>1,采用分治法求解最大连续子序列时,取其中间位置mid=(n-1)/2,该子序列只可能出现3个地方。
(1)该子序列完全落在左半部即a[0…mid]中。采用递归求出其最大连续子序列和maxLeftSum。
(2)该子序列完全落在右半部即a[mid+1…n-1]中。采用递归求出其最大连续子序列和maxRightSum。
(3)该子序列跨越序列a的中部而占据左右两部分。
结果:max3( maxLeftSum,maxRightSum,maxLeftBorderSum+maxRightBorderSum )
代码
long maxSubSum(int a[], int left, int right)
{
int i, j;
long maxLeftSum, maxRightSum;
long maxLeftBorderSum, leftBoerderSum;
long maxRightBorderSum, rightBorderSum;
if (left == right)//序列中只有一个元素
{
if (a[left] > 0)
return a[left];
else
return 0;
}
int mid = (left + right) / 2;
maxLeftSum = maxSubSum(a, left, mid);//左区间最大连续子序列和
maxRightSum = maxSubSum(a, mid + 1, right);//右区间最大连续子序列和
maxLeftBorderSum = 0, leftBoerderSum = 0;
for (i = mid; i >= left; i--)//求解左边界最大连续子序列和
{
leftBoerderSum += a[i];
if (leftBoerderSum > maxLeftBorderSum)
maxLeftBorderSum = leftBoerderSum;
}
maxRightBorderSum = 0, rightBorderSum = 0;
for (j = mid + 1; j <= right; j++)//求解右边界最大连续子序列和
{
rightBorderSum += a[j];
if (rightBorderSum > maxRightBorderSum)
maxRightBorderSum = rightBorderSum;
}
return (maxLeftBorderSum + maxRightBorderSum) > max(maxLeftSum, maxRightSum) ? (maxLeftBorderSum + maxRightBorderSum) : max(maxLeftSum, maxRightSum);//返回最大值
}
算法分析
设求解序列a[0…n-1]最大连续子序列和的执行时间为T(n),第(1)、(2)两种情况的执行时间为T(n/2),第(3)种情况的执行时间为O(n),所以得到以下递推式:
T(n)=1 当n=1
T(n)=2T(n/2)+n 当n>1
容易推出,T(n)=O(nlog2n)。
求解棋盘覆盖问题
问题描述
有一个2k×2k(k>0)的棋盘,恰好有一个方格与其他方格不同,称之为特殊方格。现在要用如下的L型骨牌覆盖除了特殊方格外的其他全部方格,骨牌可以任意旋转,并且任何两个骨牌不能重叠。请给出一种覆盖方法。
问题求解
代码
int k;//棋盘大小
int x, y;//特殊方格的位置
int board[MAX][MAX];
int tile = 1;
void ChessBoard(int tr, int tc, int dr, int dc, int size)
{
if (size == 1)
return;
int t = tile++;
int s = size / 2;
//考虑左上角象限
if (dr < tr + s && dc < tc + s)//特殊方格位于左上角
ChessBoard(tr, tc, dr, dc, s);
else//不位于左上角
{
board[tr + s - 1][tc + s - 1] = t;
ChessBoard(tr, tc, tr + s - 1, tc + s - 1, s);//将右下角方格作为特殊方格
}
//考虑右上角象限
if (dr < tr + s && dc >= tc + s)//特殊方格位于右上角
ChessBoard(tr, tc + s, dr, dc, s);
else//不位于右上角
{
board[tr + s - 1][tc + s] = t;
ChessBoard(tr, tc + s, tr + s - 1, tc + s, s);//将左下角方格作为特殊方格
}
//考虑左下角象限
if (dr >= tr + s && dc < tc + s)//特殊方格位于左下角
ChessBoard(tr + s, tc, dr, dc, s);
else//不位于左下角
{
board[tr + s][tc + s - 1] = t;
ChessBoard(tr + s, tc, tr + s, tc + s - 1, s);//将右上角方格作为特殊方格
}
//处理右下角象限
if (dr >= tr + s && dc >= tc + s)//特殊方格位于右下角
ChessBoard(tr + s, tc + s, dr, dc, s);
else//不位于右下角
{
board[tr + s][tc + s] = t;
ChessBoard(tr + s, tc + s, tr + s, tc + s, s);//将右上角方格作为特殊方格
}
}
求解循环日程安排问题
问题描述
设有n=2k个选手要进行网球循环赛,要求设计一个满足以下要求的比赛日程表:
(1)每个选手必须与其他n-1个选手各赛一次。
(2)每个选手一天只能赛一次。
(3)循环赛在n-1天之内结束。
问题求解
按问题要求可将比赛日程表设计成一个n行n-1列的二维表,其中第i行、第j列表示和第i个选手在第j天比赛的选手。
假设n位选手被顺序编号为1、2、…、n(2k)。
将n=2k问题划分为4部分:
(1)左上角:左上角为2k-1个选手在前半程的比赛日程(k=1时直接给出,否则,上一轮求出的就是2k-1个选手的比赛日程)。
(2)左下角:左下角为另2k-1个选手在前半程的比赛日程,由左上角加2k-1得到,例如22个选手比赛,左下角由左上角直接加2(2k-1)得到,23个选手比赛,左下角由左上角直接加4(2k-1)得到。
(3)右上角:将左下角直接复制到右上角得到另2k-1个选手在后半程的比赛日程。
(4)右下角:将左上角直接复制到右下角得到2k-1个选手在后半程的比赛日程。
代码
int k;//求解结果标识
int a[MAX][MAX];//存放比赛日程表
void Plan(int k)
{
int i, j, n, t, temp;
n = 2;
a[1][1] = 1; a[1][2] = 2;
a[2][1] = 2; a[2][2] = 1;
for (t = 1; t < k; t++)
{
temp = n;
n = n * 2;
for (i = temp + 1; i <= n; i++)//填写左下角元素
for (j = 1; j <= temp; j++)
a[i][j] = a[i - temp][j] + temp;
for (i = 1; i <= temp; i++)//填写右上角元素
for (j = temp + 1; j <= n; j++)
a[i][j] = a[i + temp][(j + temp) % n];
for (i = temp + 1; i <= n; i++)//填写右下角元素
for (j = temp + 1; j <= n; j++)
a[i][j] = a[i - temp][j - temp];
}
}