![](/img/trans.png)
[英]Abort Called in program for printing the next lexicographical permutation of a string
[英]String lexicographical permutation and inversion
考慮對字符串的以下函數:
int F(string S)
{
int N = S.size();
int T = 0;
for (int i = 0; i < N; i++)
for (int j = i + 1; j < N; j++)
if (S[i] > S[j])
T++;
return T;
}
長度為N且所有成對的不同字符的字符串S0的總數為N! 獨特的排列。
例如,“ bac”具有以下6個排列:
bac
abc
cba
bca
acb
cab
考慮這些N! 按字典順序的字符串:
abc
acb
bac
bca
cab
cba
現在考慮將F應用於以下每個字符串:
F("abc") = 0
F("acb") = 1
F("bac") = 1
F("bca") = 2
F("cab") = 2
F("cba") = 3
給定此排列集合的某個字符串S1,我們希望找到該集合中的下一個字符串S2,它與S1具有以下關系:
F(S2) == F(S1) + 1
例如,如果S1 ==“ acb”(F = 1)比S2 ==“ bca”(F = 1 + 1 = 2)
一種方法是從S1以后開始,並遍歷排列列表以尋找F(S)= F(S1)+1。 不幸的是,這是O(N!)。
通過S1上的O(N)函數,我們可以直接計算S2嗎?
假設S1的長度為n,則F(S1)
最大值為n(n-1)/2
,如果F(S1) = n(n-1)/2
,則表示它是最后一個函數,並且沒有任何函數下一步,但如果F(S1) < n(n-1)/2
,是指存在至少一個字符x
比炭更大y
和x
毗鄰y
,找到了這樣的x
與最低索引,和更改x和y位置。 讓我們看一下它:
S1 ==“ACB”(F = 1),1 <3,以便有一個char x
比另一個更大的炭y
和其指數大於更大y
,這里最小索引x
是c
,並且通過第一嘗試在將取代它帶有a
(小於x
因此算法在此處完成)==> S2 =“ cab”,F(S2)= 2。
現在讓我們用S2駕駛室進行測試:x = b,y = a,==> S3 =“ cba”。\\
查找x
並不困難,重復輸入,並將其變量名為min
,而當前訪問的字符小於min
,將min
設置為新訪問的char,然后訪問下一個字符,第一次訪問的字符大於min
停止迭代,這是x
:
這是c#中的偽代碼(但我對邊界不小心,例如在input.Substring中):
string NextString(string input)
{
var min = input[0];
int i=1;
while (i < input.Length && input[i] < min)
{
min = input[i];
i++;
}
if (i == input.Length) return "There isn't next item";
var x = input[i], y=input[i-1];
return input.Substring(0,i-2) + x + y + input.Substring(i,input.Length - 1 - i);
}
這是用於解決問題的算法的概述。
我假設您有一個函數直接返回第n
個置換(給定n
)及其反函數,即,一個函數返回給定置換的n
。 讓它們分別是perm(n)
和perm'(n)
。
如果我沒有弄錯的話,當您有一個由4個字母組成的字符串來置換函數F時,如下所示:
F("abcd") = 0
F("abdc") = 1
F(perm(3)) = 1
F(...) = 2
F(...) = 2
F(...) = 3
F(perm(7)) = 1
F(...) = 2
F(...) = 2
F(...) = 3
F(...) = 3
F(...) = 4
F(perm(13)) = 2
F(...) = 3
F(...) = 3
F(...) = 4
F(...) = 4
F(...) = 5
F(perm(19)) = 3
F(...) = 4
F(...) = 4
F(...) = 5
F(...) = 5
F(perm(24)) = 6
換句話說,當您從3個字母變為4個字母時,您將獲得F值表的4個副本,分別將(0,1,2,3)添加到(1st,2nd,3rd,4th)副本中。 例如,在第二種情況下,您已經通過將第二個字母放在第一位來進行一次排列; 只需以與原始3個字母的字符串相同的模式將其添加到其他排列中即可。
從這個輪廓出發,編寫函數F應該不是很困難(但我現在還沒有時間)。嚴格來說,F的逆不是函數,因為它將是多值的,但是給定n
,和F(n)
只有少數情況可以找到m
st F(m)==F(n)+1
。 這些情況是:
n == N!
其中N
是字符串中字母的數目,沒有下一個排列; F(n+1) < F(n)
,尋求的解決方案是perm(n+(N-1)!)
; F(n+1) == F(n)
,解是perm(n+2)
; F(n+1) > F(n)
,解為perm(n+1)
。 我懷疑其中某些可能僅適用於4個字母的字符串,其中某些術語將必須針對K字母排列進行調整。
這不是O(n)
,但至少是O(n²)
(其中n是排列中元素的數量,在您的示例3中)。
首先,請注意,每當您在字符串中放置一個字符時,您就已經知道F的增加意味着多少-然而,比尚未添加到字符串中的字符要小很多。
這為我們提供了另一種計算F(n)的算法:
used = set()
def get_inversions(S1):
inv = 0
for index, ch in enumerate(S1):
character = ord(ch)-ord('a')
cnt = sum(1 for x in range(character) if x not in used)
inv += cnt
used.add(character)
return inv
這並不比原始版本好多少,但是在反轉F時很有用。您想知道按字典順序較小的第一個字符串-因此,復制原始字符串並僅在必要時進行更改才有意義。 當需要進行此類更改時,我們還應該以最小的數量更改字符串。
為此,讓我們使用以下信息:對於n
字母的字符串,F的最大值為n(n-1)/2
。 如果不更改原始字符串,只要所需的求反數大於該數量,這意味着我們必須在此時交換一個字母。 Python程式碼:
used = set()
def get_inversions(S1):
inv = 0
for index, ch in enumerate(S1):
character = ord(ch)-ord('a')
cnt = sum(1 for x in range(character) if x not in used)
inv += cnt
used.add(character)
return inv
def f_recursive(n, S1, inv, ign):
if n == 0: return ""
delta = inv - (n-1)*(n-2)/2
if ign:
cnt = 0
ch = 0
else:
ch = ord(S1[len(S1)-n])-ord('a')
cnt = sum(1 for x in range(ch) if x not in used)
for letter in range(ch, len(S1)):
if letter not in used:
if cnt < delta:
cnt += 1
continue
used.add(letter)
if letter != ch: ign = True
return chr(letter+ord('a'))+f_recursive(n-1, S1, inv-cnt, ign)
def F_inv(S1):
used.clear()
inv = get_inversions(S1)
used.clear()
return f_recursive(len(S1), S1, inv+1, False)
print F_inv("acb")
通過用諸如二進制索引樹之類的數據結構替換最內層的循環,也可以使其在O(n log n)
運行。
您是否嘗試過交換字符串中的兩個相鄰字符? 看來可以幫助解決問題。 如果交換S [i]和S [j],其中i <j和S [i] <S [j],則F(S)增加1,因為所有其他索引對均不受此置換影響。
如果我沒記錯的話,F會計算排列的反轉次數。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.