首页 > 代码库 > 动态规划之整齐打印

动态规划之整齐打印

解题思路:既然是用动态规划方法,那么想办法把第i个单词到第j个单词制作成一个表格,这个表格用于存放剩余空格数,代价以及代价和,其中代价剩余空格数的立方。

                  于是有如下定义:

                  定义extra[i][j]=M - j + i - ∑lk ,其中k = i, ..., j,表示剩余的空格数

                  定义lc[i][j]表示每行空格数的立方值,INF表示无穷大. 当数组extra[i][j]<0时,lc[i][j]=INF;当j==n且extra[i][j]≥0时,表示已经到最后一行,lc[i][j]=0;其他情况lc[i][j]=(lc[i][j])3

                  定义c[j]表示从1到i的空格数立方和。有递归式:c[j]=c[i-1] + lc[i][ j]。c[0]=0。当j>0,(1≤i≤j)c[j]=min(c[i - 1] + lc[i - 1][ j]  。  

 代码如下:(注意本代码是将《教师手册》伪代码转换成了C/C++程序,但是其中对伪代码做了修改,为了能使程序正确运行,某些地方是伪代码没有的。)

//15-4对于自动换行问题的动态规划解决方案
#include <limits.h>
#include <stdio.h>
#include <iostream>
using namespace std;
#define INF INT_MAX
//一种实用功能打印解决方案
int GIVE_LINES (int p[], int j,char *str[]);

// l[]表示不同单词的输入序列的长度。例如,
// l[] = {3, 2, 2, 5} 对于像 "aaa bb cc ddddd"一句.  n 是大小尺寸。
// l[] M 是一行宽度 (没有最大字符串能填满一行)
void PRINT_NEATLY (int l[],char *str[],int n,int M)
{
	//为简单起见,extra数组空间用于存放每行剩余空格数
	//如果从单词i到单词j可以放在一行,那么extras[i][j]表示剩余空格数
    //int extras[n+1][n+1]={0};  
	int **extras,i,j;
	extras=new int*[n+1];
	for ( i=0;i<=n;i++)
	{
		extras[i]=new int[n+1];
	}
	//lc[i][j]表示从单词i到单词j的一行的代价(空格数的立方)
	int **lc;
	lc=new int*[n+1];
	for ( i=0;i<=n;i++)
	{
		lc[i]=new int[n+1];
	}
	//c[i]表示从单词i到单词j最优代价和(空格数最小立方和)
    int *c=new int[n+1];
	
    // p[] 用于打印解决方案 
    int *p=new int[n+1];
	
	//计算每行的剩余空格数。
	//若单词i到单词j可以放到一行,那么extra[i][j]值意味着是此行的剩余空格数。
    for (i = 1; i <= n; i++)
    {
        extras[i][i] = M - l[i-1];//
        for (j = i+1; j <= n; j++)
		{
            extras[i][j] = extras[i][j-1] - l[j-1] - 1;
		}
    }
	//用上述剩余空格数(数组extra)来计算行代价(数组lc)。
	//若单词i到单词j可以放到一行,lc[i][j]值表示此行的代价。
    for (i = 1; i <= n; i++)
    {
        for (j = i; j <= n; j++)
        {
            if (extras[i][j] < 0)
                lc[i][j] =INF;
            else if (j == n && extras[i][j] >= 0)
                lc[i][j] = 0;
            else
                lc[i][j] = extras[i][j]*extras[i][j]*extras[i][j];
        }
    }
	//计算最小代价并且找到最小代价的每行换行处。
	//c[j]意味着从单词1到单词j的最优总代价(代价=立方和)
    c[0] = 0;
    for (j = 1; j <= n; j++)
    {
        c[j] = INF;
        for (i = 1; i <= j; i++)
        {
            if (c[i-1] != INF&&lc[i][j] != INF && (c[i-1] + lc[i][j] < c[j]))
            {
                c[j] = c[i-1] + lc[i][j];
                p[j] = i;
            }
        }
    }
    GIVE_LINES(p, n,str);
}

int GIVE_LINES(int p[], int j,char *str[])
{
    int k,i=p[j];
    if (i == 1)
        k = 1;
    else
        k = GIVE_LINES (p, i-1,str) + 1;
	cout<<"Line number:"<<k<<" ";
	for (int t=i;t<=j;t++)
	{
		cout<<str[t-1]<<" ";
	}
	cout<<endl;
    return k;
}

//主程序测试上述功能
int main()
{
    const int n=10;//一共有10个单词
	const int M=8;//每行最多容纳8个字符
    char*str[n]={"abc","def","gh","polq","cs","opaqe","klfgh","t","asd","th"};
	int l[n]={0};
	for (int i=0;i<n;i++)
	{
		l[i]=strlen(str[i]);
	}
    PRINT_NEATLY (l,str,n,M);
    return 0;
}

样例输出

最后总结:程序运行时间是O(n2),《教师手册》给的思路是正确的。但是答案不完整。如果全部按照书上的伪代码转换成对应变成语言的话,你会发现程序不能正确运行进而怀疑程序的正确性,但是实际上程序大致没问题,就是有些判断和边界条件没有写清楚,这些需要对题目有所理解后,才能添加正确的代码。具体哪里有修改请看上面代码。