《算法导论》思考题15-2 整齐打印

画外音:没想做到15-2题也是费了一番周折,看来《算法导论》里题都不是白给的

  整齐打印问题: 考虑在一个打印机上整齐地打印一段文章的问题。输入的正文是n个长度分别为L1、L2、……、Ln(以字符个数度量)的单词构成的序列。我们希望将这个段落在一些行上整齐地打印出来,每行至多M个字符。“整齐度”的标准如下:如果某一行包含从i到j的单词(i<j),且单词之间只留一个空格,则在行末多余的空格字符个数为 M - (j-i) - (Li+ …… + Lj),它必须是非负值才能让该行容纳这些单词。我们希望所有行(除最后一行)的行末多余空格字符个数的立方和最小。请给出一个动态规划的算法,来在打印机整齐地打印一段又n个单词的文章。分析所给算法的执行时间和空间需求。



  在网上非常认真地看了几篇关于这个整齐打印的博客,发现真的都是错误的!非常坑人!传送门我就不开了,虽说对思路会一些启发但是少一个准确的版本来解决这个问题。

这个问题的核心思想依然是动态规划,而动态规划的关键在于找到整个解决过程的最优子结构。

  我们声明一个数组来储存随着单词长度增加的动态最优解op[](在下面的代码中为了实现随机长度解决该问题使用了vector动态分配),在动态规划的过程之中的第n级最优子结构我们需要计算的是 1)第n-1到1级的最优化解  2)连续串接的 i 到 j 个单词在limit长度限制下的代价(weight)。

  公式表达为  op[n]=min{op[k-1]+cost(k,n)...} ////k为循环遍历的值 k从n到 1,整个计算过程为状态转移的过程

/*当 k==n的时候,表示前面n-1个单词构成的最优化结构不需要对齐,直接以新的一行添加入 */

  为了尽量减少算法的时间复杂度,我们可以使k从n向小遍历,这样以来可以检测当前 从k到n个单词所需要的代价大于一行的限制的时候跳出循环。

多次运行测试没有发现问题,欢迎帮我找一些BUG

#include<string>
#include<iostream>
#include<vector>
#include<cstdlib>

#define MAX_VAL 999999

using namespace std;

int g_iLast=0;
int g_iLimit=MAX_VAL;
int g_iLen=0;
vector<int> g_vecOp;  ////optimal amount for weight
vector<string> g_vecWord; ////////Storing the words
vector<int> g_vecIndex;

int space(int index){
	////////// index>=1
	int total=g_iLimit;
	for(int i=1;i<=g_iLen;i++){
		if(g_vecIndex[i]==index){
			total-=g_vecWord[i-1].size();
			total--;
		}
	}
	return total;////// A bug!
}

int space(int i,int j){
	/////// 1<=i<=j<=len
	int total=0;
	for(int x=i;x<=j;x++){
		total+=g_vecWord[x-1].size();
	}
	return g_iLimit-j+i-total;
}

void optimal(){
	int iCur=1;
	for(int i=1;i<=g_iLen;i++){
		////// Descend Order to Save More Time
		g_vecOp[i]=MAX_VAL;
		int iSpace=space(iCur);
		int iSize=g_vecWord[i-1].size();
		if(iSpace>=iSize){
			//////// The Space Enough to append
			g_vecIndex[i]=iCur;
			g_vecOp[i]=g_iLast+(iSpace-iSize)*(iSpace-iSize)*(iSpace-iSize);

		}else{
			/////// If Not
			iCur++;
			g_iLast=g_vecOp[i-1];
			int min=MAX_VAL;
			for(int j=i;j>1;j--){
				int s=space(j,i);
				if(s<0)break;
				int temp=g_vecOp[j-1]+s*s*s;
				if(min>temp){
					min=temp;
					for(int m=j;m<=i;m++){
						g_vecIndex[m]=iCur;
					}
				}
			}g_vecOp[i]=min;
			g_vecIndex[i]=iCur;
		}
	}
	cout<<"The cubic minimum of blank is "<<g_vecOp[g_iLen]<<endl<<endl;
}

int main(){
	cout<<"Input the total amount of your Word: "<<ends;
	cin>>g_iLen;
	///////////////////////
	string szTmp;
	for(int i=0;i<g_iLen;i++){
		cin>>szTmp;
		g_vecWord.push_back(szTmp);
	}
	///////////////////////
	cout<<"Input your maximun digits of letters allowed in a line :"<<ends;
	cin>>g_iLimit;
	for(int i=0;i<=g_iLen;i++){
		g_vecOp.push_back(0);
		g_vecIndex.push_back(0);
	}
	/////////////////////// Initializing Complete

	optimal();
	//////////////////////Outputting
	int now=1;
	for(int i=1;i<=g_iLen;i++){
		if(g_vecIndex[i]>now){
			now++;
			cout<<endl<<g_vecWord[i-1]<<‘ ‘<<ends;
		}else{
			cout<<g_vecWord[i-1]<<‘ ‘<<ends;
		}
	}
	return 0;
}
时间: 2024-10-05 05:05:46

《算法导论》思考题15-2 整齐打印的相关文章

算法导论动态规划 15.1-3 钢条切割问题

算法导论的第一个动态规划问题--钢条切割 我们知道钢条的长度和价格为: 长度i 1 2 3 4 5 6 7 8 9 10 价格pi 1 5 8 9 10 17 17 20 24 30 书上的两种方法已经很清楚,这里主要说一下课后练习里面15-3钢条成本加上切割成本,即要切割的次数最少.15-4返回切割方案 #include<fstream> #include<iostream> using namespace std; int main() { int const N = 11;

[算法导论]4.1-5最大连续子数组问题

在线性时间内非递归的求数组的最大连续子数组(连续和最大的子数组). 题目给出思路为数组A[1...j+1]的最大和子数组,有两种情况:a) A[1...j]的最大和子数组; b) 某个A[i...j+1]的最大和子数组,但思考很久没有理解如何用这个思路设计线性时间算法,希望有人能给予指点. (i点是使A[1]+..+A[i]为负的一个值?) 目前的思路是,最大子数组一定位于从某个正数开始,全部求和<=0的一段数组中 从其实点i到目标点j,从第一个正数开始截取尽量长的一段数组,从第一个正数起的最大

算法导论思考题 - 瓶颈生成树

列思路,以后填坑. a. 证明:最小生成树是瓶颈生成树 证略 b. 给定图G和整数b,线性时间内判断瓶颈生成树T值是否不超过b 解:DFS或BFS遍历图G,跳过所有权值大于b的边,最后若有节点未遍历到,则T值大于b,否则不超过b c. 求瓶颈生成树T值 1. 求出边权值的中位数(类似于求nth element一类问题)M,以此将图G的边按权值分成两部分,一部分小于等于M,另一部分大于M 2. 利用b提出的方法判断图G瓶颈生成树的T值是否不超过M,也就是看这个T值位于大小哪半边 3. 若位于小半边

双端队列C实现代码 算法导论10.1-5 10.1-6 10.1-7

数组实现双端队列的时候注意区别判断上溢和下溢. 用两个栈实现队列,就相当于把两个栈底靠在一起(背靠背),一个栈用来出队列,一个栈用来进队列.这个队列的操作时间大部分时候是常数时间,除了出列的栈为空,需要把进列的栈全部转移过去,再出列.Back()操作和Pop()操作类似,也是这样. 而两个队列实现栈,队列轮流充当入栈和出栈的角色,而什么时候会改变角色呢,就是Pop()操作.Pop()操作先把一个队列中的所有元素全部出列并加入另外一个空队列中去,然后再出列(第二个队列). 实现代码为C #incl

[算法导论 Ch9 中位数和顺序统计量] Selection in O(n)

1. 寻找第k大(小)的数 假设数据存储在数组a[1..n]中 首先,寻找一个数组中最大或者最小的数,因为最大(小)的数一定要比其他所有的数大(小),因此至少要比较完所有的pair才能确定,所以时间复杂度在O(n).那么寻找第k大(小)呢? 比较直观的,就是对数组中国所有的数据先进行排序,在我们这种渣渣的计算机入门选手而言,可选的有QuickSort,MergeSort和HeapSort,甚至是ShellSort等一些比较高级的方法啊...一般的代价都在O(n*logn)上,然后直接取出即可.

算法导论—动态规划

华电北风吹 天津大学认知计算与应用重点实验室 日期:2015/8/27 首先区分动态规划和分治策略. 这两者有很相似的地方,都是通过组合子问题的解来求解原问题.不同的是,分治策略将原问题划分为互不相交的子问题,递归的求解子问题,再将它们的解组合起来,求出原问题的解.与之相反,动态规划应用于子问题重叠的情况,即不同的子问题具有公共的子子问题(子问题的求解是递归进行的,将其划分为更小的子问题).在这种情况下,分治法会做许多不必要的工作,他会反复求解那些公共的子子问题.而动态规划算法对每个子子问题只求

print neatly 整齐打印 算法导论

作者:jostree 转载请注明出处 http://www.cnblogs.com/jostree/p/4098562.html 题目链接:print neatly 整齐打印 算法导论 考虑在一个打印机上整齐地打印一段文章的问题.输入的正文是$n$个长度分别为$L_1,L_2,\dots ,L_n$(以字符个数度量)的单词构成的序列.我们希望将这个段落在一些行上整齐地打印出来,每行至多$M$个字符.“整齐度”的标准如下:如果某一行包含从i到j的单词$(i<j)$,且单词之间只留一个空格,则在行末

算法导论之八(10.1-5单数组实现双端队列)

算法导论第三版P131 题目: 10.1-5 栈插入和删除元素只能在同一端进行,队列的插入操作和删除操作分别在两端进行,与它们不同的,有一种双端队列(deque),其插入和删除操作都可以在两端进行.写出4个时间均为O(1)的过程,分别实现在双端队列插入和删除元素的操作,该队列使用一个数组实现的. 注意点: 1.左右端点指向的位置是类似于队列中的tail端点,是下一个插入操作的位置. 2.然后注意遍历的时候,左端点和右端点的位置关系,有两种可能,所以遍历的方式不一样. 代码: /* * 使用单数组

算法导论读书笔记(15) - 红黑树的具体实现

算法导论读书笔记(15) - 红黑树的具体实现 目录 红黑树的简单Java实现 红黑树的简单Java实现 /** * 红黑树 * * 部分代码参考自TreeMap源码 */ public class RedBlackTree<T> { protected TreeNode<T> root = null; private final Comparator<? super T> comparator; private int size = 0; private static