📜  自动换行问题DP-19

📅  最后修改于: 2021-04-23 21:13:09             🧑  作者: Mango

给定一系列单词,并限制一行中可以输入的字符数(行宽)。按给定的顺序放置换行符,以使行被整齐地打印。假设每个单词的长度小于行宽。

像MS Word这样的字处理器可以完成换行符的任务。这个想法是要有平衡的线条。换句话说,没有几行具有大量额外空间,而有些行则具有少量额外空间。

The extra spaces includes spaces put at the end of every line except the last one.  
The problem is to minimize the following total cost.
 Cost of a line = (Number of extra spaces in the line)^3
 Total Cost = Sum of costs for all lines

For example, consider the following string and line width M = 15
 "Geeks for Geeks presents word wrap problem" 
     
Following is the optimized arrangement of words in 3 lines
Geeks for Geeks
presents word
wrap problem 

The total extra spaces in line 1, line 2 and line 3 are 0, 2 and 3 respectively. 
So optimal value of total cost is 0 + 2*2 + 3*3 = 13

请注意,总成本函数不是多余空间的总和,而是多余空间的立方体(或也使用正方形)的总和。此成本函数背后的想法是平衡行之间的空间。例如,考虑以下两个相同词组的排列:

1)有3行。一行有3个额外的空格,所有其他行有0个额外的空格。总多余空间= 3 + 0 + 0 =3。总成本= 3 * 3 * 3 + 0 * 0 * 0 + 0 * 0 * 0 = 27。

2)有3条线。 3行中的每一行都有一个额外的空间。总额外空间= 1 + 1 + 1 =3。总成本= 1 * 1 * 1 + 1 * 1 * 1 + 1 * 1 * 1 = 3。

在这两种情况下,总的额外空间均为3,但应首选第二种安排,因为所有三行中的额外空间都是平衡的。具有三次和的成本函数可以达到目的,因为第二种情况下的总成本值较小。

方法1(贪婪的解决方案)
贪婪的解决方案是在第一行中放置尽可能多的单词。然后对第二行执行相同的操作,依此类推,直到所有单词都放好为止。该解决方案在许多情况下都提供了最佳解决方案,但在所有情况下都未提供最佳解决方案。例如,考虑以下字符串“ aaa bb cc ddddd”,线宽为6。贪婪方法将产生以下输出。

aaa bb 
cc 
ddddd

以上3行中的多余空格分别为0、4和1。因此总费用为0 + 64 + 1 = 65。

但是上述解决方案不是最佳解决方案。后续安排具有更加平衡的空间。因此总成本函数的价值较小。

aaa
bb cc
ddddd

以上3行中的多余空格分别为3、1和1。因此总费用为27 + 1 + 1 = 29。

尽管在某些情况下不是最佳选择,但贪婪方法仍被许多文字处理器(例如MS Word和OpenOffice.org Writer)使用。

方法2(动态编程)
以下动态方法严格遵循Cormen书的解决方案中给出的算法。首先,我们计算二维表lc [] []中所有可能的线的成本。值lc [i] [j]表示将i到j的单词放在一行中的成本,其中i和j是输入序列中单词的索引。如果从i到j的单词序列不能排成一行,则lc [i] [j]被认为是无限的(以避免成为解决方案的一部分)。一旦构造了lc [] []表,就可以使用以下递归公式来计算总成本。在以下公式中,C [j]是从1到j排列单词的最佳总成本。

上述递归具有重叠的子问题属性。例如,子问题c(2)的解决方案由c(3),C(4)等使用。因此,动态编程用于存储子问题的结果。数组c []可以从左到右计算,因为每个值仅取决于较早的值。
为了打印输出,我们跟踪哪些单词在哪行,我们可以保留一个并行的p数组,该数组指向每个c值的来源。最后一行从单词p [n]开始,经过单词n。前一行从单词p [p [n]]开始,经过单词p [n] – 1,依此类推。函数printSolution()使用p []打印解决方案。
在下面的程序中,输入是一个数组l [],它表示序列中单词的长度。值l [i]表示输入序列中第i个字(i从1开始)的长度。

C++
// A Dynamic programming solution for Word Wrap Problem 
#include 
using namespace std;
#define INF INT_MAX 
  
// A utility function to print the solution 
int printSolution (int p[], int n); 
  
// l[] represents lengths of different words in input sequence. 
// For example, l[] = {3, 2, 2, 5} is for a sentence like 
// "aaa bb cc ddddd". n is size of l[] and M is line width 
// (maximum no. of characters that can fit in a line) 
void solveWordWrap (int l[], int n, int M) 
{ 
    // For simplicity, 1 extra space is used in all below arrays 
  
    // extras[i][j] will have number of extra spaces if words from i 
    // to j are put in a single line 
    int extras[n+1][n+1]; 
  
    // lc[i][j] will have cost of a line which has words from 
    // i to j 
    int lc[n+1][n+1]; 
  
    // c[i] will have total cost of optimal arrangement of words 
    // from 1 to i 
    int c[n+1]; 
  
    // p[] is used to print the solution. 
    int p[n+1]; 
  
    int i, j; 
  
    // calculate extra spaces in a single line. The value extra[i][j] 
    // indicates extra spaces if words from word number i to j are 
    // placed in a single line 
    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; 
    } 
  
    // Calculate line cost corresponding to the above calculated extra 
    // spaces. The value lc[i][j] indicates cost of putting words from 
    // word number i to j in a single line 
    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]; 
        } 
    } 
  
    // Calculate minimum cost and find minimum cost arrangement. 
    // The value c[j] indicates optimized cost to arrange words 
    // from word number 1 to 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; 
            } 
        } 
    } 
  
    printSolution(p, n); 
} 
  
int printSolution (int p[], int n) 
{ 
    int k; 
    if (p[n] == 1) 
        k = 1; 
    else
        k = printSolution (p, p[n]-1) + 1; 
    cout<<"Line number "<


C
// A Dynamic programming solution for Word Wrap Problem
#include 
#include 
#define INF INT_MAX
  
// A utility function to print the solution
int printSolution (int p[], int n);
  
// l[] represents lengths of different words in input sequence. 
// For example, l[] = {3, 2, 2, 5} is for a sentence like 
// "aaa bb cc ddddd". n is size of l[] and M is line width 
// (maximum no. of characters that can fit in a line) 
void solveWordWrap (int l[], int n, int M)
{
    // For simplicity, 1 extra space is used in all below arrays 
  
    // extras[i][j] will have number of extra spaces if words from i 
    // to j are put in a single line
    int extras[n+1][n+1];  
  
    // lc[i][j] will have cost of a line which has words from 
    // i to j
    int lc[n+1][n+1];
   
    // c[i] will have total cost of optimal arrangement of words 
    // from 1 to i
    int c[n+1];
  
    // p[] is used to print the solution.  
    int p[n+1];
  
    int i, j;
  
    // calculate extra spaces in a single line.  The value extra[i][j]
    // indicates extra spaces if words from word number i to j are
    // placed in a single line
    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;
    }
  
    // Calculate line cost corresponding to the above calculated extra
    // spaces. The value lc[i][j] indicates cost of putting words from
    // word number i to j in a single line
    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];
        }
    }
  
    // Calculate minimum cost and find minimum cost arrangement.
    //  The value c[j] indicates optimized cost to arrange words
    // from word number 1 to 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;
            }
        }
    }
  
    printSolution(p, n);
}
  
int printSolution (int p[], int n)
{
    int k;
    if (p[n] == 1)
        k = 1;
    else
        k = printSolution (p, p[n]-1) + 1;
    printf ("Line number %d: From word no. %d to %d \n", k, p[n], n);
    return k;
}
  
// Driver program to test above functions
int main()
{
    int l[] = {3, 2, 2, 5};
    int n = sizeof(l)/sizeof(l[0]);
    int M = 6;
    solveWordWrap (l, n, M);
    return 0;
}


Java
// A Dynamic programming solution for
// Word Wrap Problem in Java
public class WordWrap
{
  
    final int MAX = Integer.MAX_VALUE;
      
    // A utility function to print the solution
    int printSolution (int p[], int n)
    {
        int k;
        if (p[n] == 1)
        k = 1;
        else
        k = printSolution (p, p[n]-1) + 1;
        System.out.println("Line number" + " " + k + ": " + 
                    "From word no." +" "+ p[n] + " " + "to" + " " + n);
        return k;
    }
  
// l[] represents lengths of different words in input sequence. 
// For example, l[] = {3, 2, 2, 5} is for a sentence like 
// "aaa bb cc ddddd". n is size of l[] and M is line width 
// (maximum no. of characters that can fit in a line) 
    void solveWordWrap (int l[], int n, int M)
    {
        // For simplicity, 1 extra space is used in all below arrays
      
        // extras[i][j] will have number of extra spaces if words from i
        // to j are put in a single line
        int extras[][] = new int[n+1][n+1];
      
        // lc[i][j] will have cost of a line which has words from
        // i to j
        int lc[][]= new int[n+1][n+1];
      
        // c[i] will have total cost of optimal arrangement of words
        // from 1 to i
        int c[] = new int[n+1];
      
        // p[] is used to print the solution.
        int p[] =new int[n+1];
      
        // calculate extra spaces in a single line. The value extra[i][j]
        // indicates extra spaces if words from word number i to j are
        // placed in a single line
        for (int i = 1; i <= n; i++)
        {
            extras[i][i] = M - l[i-1];
            for (int j = i+1; j <= n; j++)
            extras[i][j] = extras[i][j-1] - l[j-1] - 1;
        }
          
        // Calculate line cost corresponding to the above calculated extra
        // spaces. The value lc[i][j] indicates cost of putting words from
        // word number i to j in a single line
        for (int i = 1; i <= n; i++)
        {
            for (int j = i; j <= n; j++)
            {
                if (extras[i][j] < 0)
                    lc[i][j] = MAX;
                else if (j == n && extras[i][j] >= 0)
                    lc[i][j] = 0;
                else
                    lc[i][j] = extras[i][j]*extras[i][j];
            }
        }
          
        // Calculate minimum cost and find minimum cost arrangement.
        // The value c[j] indicates optimized cost to arrange words
        // from word number 1 to j.
        c[0] = 0;
        for (int j = 1; j <= n; j++)
        {
            c[j] = MAX;
            for (int i = 1; i <= j; i++)
            {
                if (c[i-1] != MAX && lc[i][j] != MAX && 
                   (c[i-1] + lc[i][j] < c[j]))
                {
                    c[j] = c[i-1] + lc[i][j];
                    p[j] = i;
                }
            }
        }
      
        printSolution(p, n);
    }
  
    public static void main(String args[])
    {
        WordWrap w = new WordWrap();
        int l[] = {3, 2, 2, 5};
        int n = l.length;
        int M = 6;
        w.solveWordWrap (l, n, M);
    }
}
  
// This code is contributed by Saket Kumar


Python3
# A Dynamic programming solution 
# for Word Wrap Problem 
  
# A utility function to print 
# the solution 
# l[] represents lengths of different 
# words in input sequence. For example, 
# l[] = {3, 2, 2, 5} is for a sentence
# like "aaa bb cc ddddd". n is size of 
# l[] and M is line width (maximum no. 
# of characters that can fit in a line) 
INF = 2147483647
def printSolution(p, n):
    k = 0
    if p[n] == 1:
        k = 1
    else:
        k = printSolution(p, p[n] - 1) + 1
    print('Line number ', k, ': From word no. ',
                                 p[n], 'to ', n)
    return k
  
def solveWordWrap (l, n, M):
      
    # For simplicity, 1 extra space is
    # used in all below arrays 
  
    # extras[i][j] will have number 
    # of extra spaces if words from i 
    # to j are put in a single line
    extras = [[0 for i in range(n + 1)]
                 for i in range(n + 1)]
                   
    # lc[i][j] will have cost of a line 
    # which has words from i to j
    lc = [[0 for i in range(n + 1)]
             for i in range(n + 1)]
               
    # c[i] will have total cost of 
    # optimal arrangement of words 
    # from 1 to i
    c = [0 for i in range(n + 1)]
      
    # p[] is used to print the solution.
    p = [0 for i in range(n + 1)]
      
    # calculate extra spaces in a single
    # line. The value extra[i][j] indicates
    # extra spaces if words from word number
    # i to j are placed in a single line 
    for i in range(n + 1):
        extras[i][i] = M - l[i - 1]
        for j in range(i + 1, n + 1):
            extras[i][j] = (extras[i][j - 1] - 
                                    l[j - 1] - 1)
                                      
    # Calculate line cost corresponding 
    # to the above calculated extra 
    # spaces. The value lc[i][j] indicates 
    # cost of putting words from word number
    # i to j in a single line 
    for i in range(n + 1):
        for j in range(i, n + 1):
            if extras[i][j] < 0:
                lc[i][j] = INF;
            elif j == n and extras[i][j] >= 0:
                lc[i][j] = 0
            else:
                lc[i][j] = (extras[i][j] * 
                            extras[i][j])
  
    # Calculate minimum cost and find 
    # minimum cost arrangement. The value
    # c[j] indicates optimized cost to 
    # arrange words from word number 1 to j.
    c[0] = 0
    for j in range(1, n + 1):
        c[j] = INF
        for i in range(1, j + 1):
            if (c[i - 1] != INF and 
                lc[i][j] != INF and 
                ((c[i - 1] + lc[i][j]) < c[j])):
                c[j] = c[i-1] + lc[i][j]
                p[j] = i
    printSolution(p, n)
      
# Driver Code
l = [3, 2, 2, 5]
n = len(l)
M = 6
solveWordWrap(l, n, M)
  
# This code is contributed by sahil shelangia


C#
// A Dynamic programming solution for Word Wrap
// Problem in Java
using System;
  
public class GFG {
  
    static int MAX = int.MaxValue;
      
    // A utility function to print the solution
    static int printSolution (int []p, int n)
    {
        int k;
          
        if (p[n] == 1)
            k = 1;
        else
            k = printSolution (p, p[n]-1) + 1;
              
        Console.WriteLine("Line number" + " "
                + k + ": From word no." + " "
                + p[n] + " " + "to" + " " + n);
        return k;
    }
  
    // l[] represents lengths of different 
    // words in input sequence. For example,
    // l[] = {3, 2, 2, 5} is for a sentence
    // like "aaa bb cc ddddd". n is size of
    // l[] and M is line width (maximum no. 
    // of characters that can fit in a line)
    static void solveWordWrap (int []l, int n, 
                                        int M)
    {
          
        // For simplicity, 1 extra space
        // is used in all below arrays
      
        // extras[i][j] will have number of
        // extra spaces if words from i
        // to j are put in a single line
        int [,]extras = new int[n+1,n+1];
      
        // lc[i][j] will have cost of a line 
        // which has words from i to j
        int [,]lc = new int[n+1,n+1];
      
        // c[i] will have total cost of
        // optimal arrangement of words 
        // from 1 to i
        int []c = new int[n+1];
      
        // p[] is used to print the solution.
        int []p = new int[n+1];
      
        // calculate extra spaces in a single 
        // line. The value extra[i][j] indicates
        // extra spaces if words from word number
        // i to j are placed in a single line
        for (int i = 1; i <= n; i++)
        {
            extras[i,i] = M - l[i-1];
              
            for (int j = i+1; j <= n; j++)
                extras[i,j] = extras[i,j-1]
                                 - l[j-1] - 1;
        }
          
        // Calculate line cost corresponding to 
        // the above calculated extra spaces. The
        // value lc[i][j] indicates cost of 
        // putting words from word number i to 
        // j in a single line
        for (int i = 1; i <= n; i++)
        {
            for (int j = i; j <= n; j++)
            {
                if (extras[i,j] < 0)
                    lc[i,j] = MAX;
                else if (j == n && 
                              extras[i,j] >= 0)
                    lc[i,j] = 0;
                else
                    lc[i,j] = extras[i,j]
                                 * extras[i,j];
            }
        }
          
        // Calculate minimum cost and find 
        // minimum cost arrangement. The value
        // c[j] indicates optimized cost to
        // arrange words from word number
        // 1 to j.
        c[0] = 0;
        for (int j = 1; j <= n; j++)
        {
            c[j] = MAX;
            for (int i = 1; i <= j; i++)
            {
                if (c[i-1] != MAX && lc[i,j] 
                    != MAX && (c[i-1] + lc[i,j] 
                                       < c[j]))
                {
                    c[j] = c[i-1] + lc[i,j];
                    p[j] = i;
                }
            }
        }
      
        printSolution(p, n);
    }
  
    // Driver code
    public static void Main()
    {
        int []l = {3, 2, 2, 5};
        int n = l.Length;
        int M = 6;
        solveWordWrap (l, n, M);
    }
}
  
// This code is contributed by nitin mittal.


输出:

Line number 1: From word no. 1 to 1
Line number 2: From word no. 2 to 3
Line number 3: From word no. 4 to 4 

时间复杂度:O(n ^ 2)
辅助空间:O(n ^ 2)可以将上述程序中使用的辅助空间优化为O(n)(有关详细信息,请参见参考文献2)。

自动换行问题(优化空间的解决方案)

参考:
http://en.wikipedia.org/wiki/Word_wrap