折行演算法(word warp)

2021-05-28 04:59:53 字數 4125 閱讀 3279

在英文本處理程式中,由於單詞都是由字母序列構成,所以當輸入到一行的末尾的時候,就會遇到想要輸入的單詞長度大於所剩餘的空白長度的情況,這就是折行問題。對於手寫文字,我們可以用連字元『-』把單詞

分割到兩行上,但是對於字處理程式而言,其擁有更強的處理能力,可以通過運算來避免單詞被分割到兩行上。

目前對於文字的折行,比較流行的是貪心演算法,也就是嘗試在當前行中放下盡可能多的單詞,當前行不能再容納更多單詞時,就放到下一行。這樣進行折行,對於輸入的段落,其折行後的結果,行數最少。對於行寬為6,輸入文字"aaa bb cc ddddd",使用貪心演算法生成的折行後結果為:

------

aaa bb cc

ddddd

在第二行的輸入中,發現輸入單詞'cc'之後,後面只有3個空位(單詞之間有乙個空格作為分隔符),而下乙個單詞'ddddd'的長度為5,所以只能將'ddddd'輸出到第3行,對於行寬比較小的情況下,貪心演算法會造成個別行右側的空白較大,顯得不是很協調。但是對於常用的字處理程式來說,行寬一般為幾十,且這些程式在執行折行的同時還會動態調整行內各單詞的間距,所以美觀問題不是很大。

要實現折行演算法,首先需要把輸入的字串分割成一系列單詞,getwords()函式負責實現此功能,將文字中的單詞逐個分離出來,新增到單詞陣列中。該函式實現的功能比較簡單,他將空白視為單詞的分隔符,如果兩個單詞之間包含多個空白符號,函式會將其忽略。

void getwords(const string& text, vector& words_array)

if (isgraph(*word_end))

else}}

貪心折行演算法以原始文字作為輸入,通過getwords()函式將文字分割為一系列單詞,再利用貪心演算法對其進行折行處理,在需要折行的地方,函式會加入'\n'(實際的折行演算法會在兩行之間插入『軟換行』而不是段落標記),最後將執行折行後的文字返回。這裡假設所有單詞的長度都小於行寬。

string wordwarpgreedy(const string& text, int line_size)

else

if (line_space)

}return outstr.str();

}

這個演算法在openoffice,word等字處理軟體中被廣泛使用。優點就是高效,簡單。但是貪心演算法也存在不足之處,就是對於乙個指定的段落執行折行處理時,貪心演算法只會利用當前行的資訊,來安排單詞,而不會考慮段落內其他行的資訊,所以對於某些比較畸形的文字輸入,貪心演算法產生的某些行,會由於單詞安排過少,而出現大量的空白,使文字顯得不太協調。

knuth和plass提出了一種,更加協調的折行處理演算法,這種演算法在latex中被使用。

該演算法的核心思想是,對於折行後文字的每一行末尾,對由於無法繼續安插單詞而剩餘的空白,計算乙個代價值,演算法保證,處理後的折行文字的所有行的代價之和最小。以上面的輸入文字"aaa bb cc ddddd"為例,第一行剩餘的空白為0,第二行剩餘的空白為4,第三行剩餘的空白為1,如果以空白數的平方作為代價計算公式的話,折行後的總代價和是0^2 + 4^2 + 1^2 = 17。利用knuth-plass提出的演算法,折行後的輸出文字是

------

aaabb cc

ddddd

這樣,第一行剩餘空白是3,第二行的剩餘空白是1,第三行的剩餘空白是1,折行後總代價值是3^2 + 1^2 + 1^2 = 11,總代價值小於貪心演算法的折行結果。

但是對於輸入段落,要想求得最小的代價值並不容易,在對段落內容的逐漸掃瞄中,之前已經安排的單詞的位置可能會不斷發生變化。如上例,當我們輸入完bb時,演算法會將bb安排到第1行,此時總代價值為0,當我們輸入"cc"之後,如果"bb"還在第一行,則總代價值為4^2 = 16,不是目前的最小代價值,所以演算法會將bb移動到第二行,此時的代價總和為3^2 + 1^2 = 10,是當前的最小值。對於行數更多的文字,由於追求最小代價值而導致的級聯單詞位置調整,可能會很頻繁。

knuth-plass折行演算法,使用了動態規劃的思想,當輸入單詞j的時候,前j個單詞所構成的段落的代價最小值f(j),的計算公式為:

語言描述這個遞推式的意思是,當輸入單詞j時,向前搜尋詞表中的所有單詞,找到滿足條件的單詞k,將單詞k~j安排在最後一行,其他單詞位置不動(如果之前有某幾個單詞在倒數第2行,則將這幾個單詞移動到最後一行,如前例的'bb'),最後計算出這種安排的總代價和,這個總代價在所有的安排方案中最小。其中的c(i, j),是代價計算公式,其意義是,將第i到第j個單詞安排在一行時,其末尾的空白數的平方,公式如下:

這兩個公式均摘自wiki百科。代價計算公式的p值可以自行決定,一般使用2,3.這個公式有一點需要注意,因為每行的寬度有限,所以如果單詞i到j再加上他們之間的分割空白的總長度之和超過行寬時,實際上是無法將這些單詞安排在一行的。此時需要返回一組特定的值來表示這種情況。示例為了簡單,對於無法將單詞i~j安排在一行的情況c(i,j)返回-1,下面是代價計算函式:

inline int costfunc(const vectorwords_array, int line_size,

unsigned int begin, unsigned int end)

cost = line_size - (end - begin) - t;

if (cost < 0)

return cost * cost;

}

knuth-plass折行演算法的實現**如下:

string wordwarpknuth(const string& text, int line_size)

for(;i < words_array.size(); ++i)

}else

}cost[i] = min;

//這裡lines[i]儲存的是當前單詞所處行的上一行的最後乙個單詞的索引

lines[i] = p + 1;

} i = words_array.size() - 1;

//根據計算結果倒推各單詞所處的行的情況

while(i > 0)

if(i == 0)

else

}outstr << words_array[0] << ' ';

for(i = 1; i < words_array.size(); ++i)

outstr << words_array[i] << ' ';

} return outstr.str();

}

**的空間複雜度為o(j),如果提前將段落中所有子單詞串的長度和計算出來備用,時間複雜度為o(j^2)。

對於行寬為25,輸入文字"i'm a good guy, and i

know what i should not to

do!"為例,貪心演算法的折行結果為:

-------------------------

i'm a good guy, and i

know what i should not to

do!

knuth-plass演算法的折行結果為:

-------------------------

i'm a good guy,

and i know what i

should not to do!

knuth-plass演算法的折行結果,比貪心演算法更加整齊,對於末行的大片空白,可以通過修正代價值計算方法解決。latex號稱最強悍的文字處理程式,絕對不是浪得虛名,而是由很多強悍的技術來支撐的。

當然獲得更優美的折行結果是需要代價的,knuth-plass演算法無論是時間複雜度還是空間複雜度都明顯要比貪心演算法大很多。而且更重要的是,要實現優美的折行處理,knuth-plass演算法需要獲取整個段落的資訊,而在像word這樣的所見即所得字處理程式中,在使用者輸入段落標記之前,你是無法確切知道這個段落究竟是什麼情況的,實時呼叫knuth-plass演算法進行折行,代價就比較可觀了。knuth-plass演算法在執行過程中,隨著使用者的輸入,會動態調整各行單詞的安排,貪心演算法則只會影響當前行的內容,如果使用者在輸入文字的過程中,發現之前輸入的單詞「上竄下跳」,估計也是一種比較怪異的使用體驗。

所以這個演算法只有在像latex這樣的編譯型字處理程式中,才能大展身手。而且為了簡單起見,本文的示例程式,是以字元為單位安排折行的,而在實際的應用中,字母'w'和字母'i'的顯示寬度顯然是不一樣的,所以如果使用畫素作為折行的依據,無疑knuth-plass演算法能夠達到更高的境界,這是貪心演算法所無法比擬的。

div折行 table折行問題

css控制自動換行,防止 或div等被撐開 折行 style table layout fixed word break break all word wrap break word fixed 固定布局的演算法。在這演算法中,水平布局是僅僅基於 的寬度,邊框的寬度,單元格間距,列的寬度,而和 內容...

css 文字折行

word wrap break word overflow hidden 而不是word wrap break word word break break all 也不是word wrap break word overflow auto 這種最好的方式,在 ie 下沒有任何問題,在 ff 下,長串...

關於折行顯示的問題

pb折行顯示 自動折行資料視窗中實現自動折行 有時為了介面的考慮需要將一些長欄位折行顯示,但又不能確保在所有情況下都會出現長欄位。比如,我們要求bookname 超過50個字元時將它分兩行顯示,對於沒滿50個字元的仍然按照正常格式顯示,具體如下 1 在資料視窗中選擇要自動折行的列address。2 ...