给出3个列表,这些列表由相同但未知的排序顺序任意排序。是否有一个算法将这些列表合并为一个然后仍按相同顺序排序的算法?
示例:
的List1: 一个 b C F ħ
列表2: b C Ë ħ
项目list3: C d Ë ˚F
假设这些列表已排序,但使用的排序顺序未知。我想将这些列表组合到一个不包含重复但仍保持排序顺序的结果: 一个 b C d Ë F ħ
如上所述:众所周知,给定的列表是排序的,但不知道哪个顺序,但要求是合并列表仍按相同(未知)顺序排序。
在上面的例子中,我知道元素“f”位于“e”和“h”之间,因为从List1我知道
“c”< “f”< “H”,
来自List2的我知道
“c”< “e”< “H”
从List3我知道
“e”< “f”和“c”< “e” 的
结合起来:
“c”< “e”< “f”< “H”
如果任何给定列表无法确定排序顺序,则允许将元素附加到结果列表的末尾。此外,如果无法确定元素序列的排序顺序,只要它们位于正确的位置,就可以将它们以任何顺序插入到列表中(例如,如果我知道“b”和“c”必须是在“a”和“d”之间插入,但我不知道它是否应该是abcd或acbd,那么两者都是允许的。)
当然这只是一个例子。实际列表较长(但包含少于100个元素),不包含单个但多个字符元素,并且排序顺序不是字母。另外,我最多有5个列表。
我需要在Delphi中实现这个算法(并且没有:这不是作业,而是现实生活中的问题),但是如果它不包含太多的编译器魔法或复杂的库函数,我会在一种语言中使用算法。
性能不是很大的问题,因为这样做了一次。
答案 0 :(得分:8)
您的输入列表定义了商品的部分订单。根据{{3}},您想要的是拓扑排序。 an answer at Math.SE
答案 1 :(得分:4)
好问题。尽管topological sort可能是最推荐的方法,但您必须首先解析输入以构建依赖项列表。我想到了一种更直接的方法,基于查找多个列表中出现的项目来设置订单定义。
我无法预测任何时间复杂度,但由于你不关心性能,特别是考虑到最多500个项目的总数,我认为这个算法应该很好用。
type
TSorterStringList = class(TStringList)
protected
Id: Integer;
KeyId: Integer;
function Current: String;
public
constructor Create;
end;
TSorterStringLists = class(TObjectList)
private
function GetItem(Index: Integer): TSorterStringList;
public
property Items[Index: Integer]: TSorterStringList read GetItem; default;
end;
TSorter = class(TObject)
private
FInput: TSorterStringLists;
FKeys: TStringList;
procedure GenerateKeys;
function IsKey(const S: String): Boolean;
public
constructor Create;
destructor Destroy; override;
procedure Sort(Output: TStrings);
property Input: TSorterStringLists read FInput;
end;
{ TSorterStringList }
constructor TSorterStringList.Create;
begin
inherited Create;
KeyId := -1;
end;
function TSorterStringList.Current: String;
begin
Result := Strings[Id];
end;
{ TSorterStringLists }
function TSorterStringLists.GetItem(Index: Integer): TSorterStringList;
begin
if Index >= Count then
Count := Index + 1;
if inherited Items[Index] = nil then
inherited Items[Index] := TSorterStringList.Create;
Result := TSorterStringList(inherited Items[Index]);
end;
{ TSorter }
constructor TSorter.Create;
begin
inherited Create;
FInput := TSorterStringLists.Create(True);
FKeys := TStringList.Create;
end;
destructor TSorter.Destroy;
begin
FKeys.Free;
FInput.Free;
inherited Destroy;
end;
threadvar
CurrentSorter: TSorter;
function CompareKeys(List: TStringList; Index1, Index2: Integer): Integer;
var
Input: TSorterStringLists;
I: Integer;
J: Integer;
K: Integer;
begin
Result := 0;
Input := CurrentSorter.Input;
for I := 0 to Input.Count - 1 do
begin
J := Input[I].IndexOf(List[Index1]);
K := Input[I].IndexOf(List[Index2]);
if (J > - 1) and (K > -1) then
begin
Result := J - K;
Break;
end;
end;
end;
procedure TSorter.GenerateKeys;
var
All: TStringList;
I: Integer;
begin
All := TStringList.Create;
try
All.Sorted := True;
All.Duplicates := dupAccept;
for I := 0 to FInput.Count - 1 do
All.AddStrings(FInput[I]);
for I := 0 to All.Count - 2 do
if (All[I] = All[I + 1]) then
if (FKeys.Count = 0) or (FKeys[FKeys.Count - 1] <> All[I]) then
FKeys.Add(All[I]);
finally
All.Free;
end;
CurrentSorter := Self;
FKeys.CustomSort(CompareKeys);
end;
function TSorter.IsKey(const S: String): Boolean;
begin
Result := FKeys.IndexOf(S) > -1;
end;
procedure TSorter.Sort(Output: TStrings);
var
KeyId: Integer;
I: Integer;
List: TSorterStringList;
begin
if FInput.Count = 0 then
Exit;
Output.BeginUpdate;
try
GenerateKeys;
for KeyId := -1 to FKeys.Count - 1 do
begin
for I := 0 to FInput.Count - 1 do
begin
List := FInput[I];
if List.KeyId <= KeyId then
while (List.Id < List.Count) and not IsKey(List.Current) do
begin
Output.Add(List.Current);
Inc(List.Id);
end;
while (List.Id < List.Count) and IsKey(List.Current) do
begin
List.KeyId := FKeys.IndexOf(List.Current);
Inc(List.Id);
end;
end;
if KeyId + 1 < FKeys.Count then
Output.Add(FKeys[KeyId + 1]);
end;
finally
Output.EndUpdate;
end;
end;
procedure TForm1.Button1Click(Sender: TObject);
var
Sorter: TSorter;
begin
Sorter := TSorter.Create;
try
Sorter.Input[0].CommaText := '1, 2, 4, 9, 10, 11, 22, 46, 48, 51, 70, 72';
Sorter.Input[1].CommaText := '3, 9, 23, 43, 44, 45, 47, 48, 51, 71, 90, 91';
Sorter.Input[2].CommaText := '0, 3, 4, 7, 8, 11, 23, 50, 51, 52, 55, 70';
Sorter.Input[3].CommaText := '2, 6, 14, 15, 36, 37, 38, 39, 51, 65, 66, 77';
Sorter.Input[4].CommaText := '5, 27, 120, 130';
ListBox1.Items.Assign(Sorter.Input[0]);
ListBox2.Items.Assign(Sorter.Input[1]);
ListBox3.Items.Assign(Sorter.Input[2]);
ListBox4.Items.Assign(Sorter.Input[3]);
ListBox5.Items.Assign(Sorter.Input[4]);
Sorter.Sort(ListBox6.Items);
// Results in:
// 1, 0, 5, 27, 120, 130, 3, 2, 6, 14, 15, 36, 37, 38, 39, 4, 7, 8, 9, 10,
// 11, 22, 46, 23, 43, 44, 45, 47, 50, 48, 51, 71, 90, 91, 52, 55, 65, 66,
// 77, 70, 72
finally
Sorter.Free;
end;
end;
答案 2 :(得分:2)
所以你有
List1: a b c f h
List2: b c e h
List3: c d e f
逐个列表并输入图表。所以在第一个列表之后你有:
A -> B -> C -> F -> H
然后你从列表2开始.B已经在那里。然后你看到B连接到你已经知道的C.然后你知道C连接到E,它还没有,所以你现在有了:
A -> B -> C -> F -> H
|
E
然后你知道E连接到H所以:
A -> B -> C -> F -> H
| ^
E --------|
然后你去列表3.你知道C在那里,它指向D:
D
^
|
A -> B -> C -> F -> H
| ^
E --------|
然后你知道D指向E.因为C-> E具有与C - >相同的祖先。 D - &gt; E,你可以打破C->的链接; E,因此你现在有:
D -> E ---|
^ |
| |
A -> B -> C -> F -> H
最后你知道E来自F之前。因为你知道E之前直接导致H,现在还有另一条从E(E-> F-> H)到H的路径,你知道F必须介于E和H你可以从E - &gt;中删除链接H.因此你现在有:
D -> E
^ |
| |
A -> B -> C -> F -> H
您知道的可以缩短为
A -> B -> C -> D -> E -> F -> H
现在让我们假设您最终得到了类似的内容:
E -> T
| |
A -> Z
| ^
R -> W
你没有足够的信息来判断E / T是否在R / W之前,但是你知道它们都在Z之前和A之后。因此,你只需随机选择其中一条路径,然后接下来,等等,所以你最终可能得到AETRWZ或ARWETZ。你甚至可以从每条路径随机取一个,这样可以保证那些腿仍然可以排序,也许你会很幸运你的合并也被排序了。所以你可以让A-R-E-W-T-Z仍然有E / T相对排序和R / W仍然相对排序,或者如果你从E腿开始你会很幸运并有A-E-R-T-W-Z
答案 3 :(得分:1)
图论似乎是一个很好的第一直觉。
您可以构建一个有向图,其中列表的元素是顶点,并且您将每个列表元素的有向边插入其后继。然后节点A 小于另一个节点B,当且仅当可以通过遍历图形从A到达B.
图中的一个循环(A小于B且B小于A)表示输入数据损坏或存在两个具有不同名称的等效元素。
在没有循环的情况下,在给定的小于关系下合并应该很简单:重复删除图中任何其他节点无法访问的节点,并将它们添加到输出列表中
答案 4 :(得分:0)
你能使用哈希表吗?这是一个合并两个列表的算法。
T = new HashMap
for(i = 1 to length B)
T.put(B[i],i)
N = array[length A]
for(i = 1 to length A){
if(T containsKey A[i])
N[i] = T.get(A[i])
else
N[i] = -1
}
R = array[length A + length B]
j = 1
k = 1
for(i = 1 to length A){
if(N[i] = -1)
R[j++] = N[i]
else{
while(k <= N[i])
R[j++] = B[k++]
}
}
while(k <= length B)
R[j++] = B[k++]
return R[1 ... j-1]
元素A [i],其中N [i]> 0匹配B的元素,其他元素将被置于有效的顺序中。可能存在一个错误,但这是一般的想法。
要合并三个数组,可以合并前两个,然后将第三个合并到合并数组中。
在编辑时,最后一句话是假的,正如@RobKennedy所指出的那样。您可以更改算法以处理三个列表,但它并不那么简单,因此您可能希望使用拓扑排序。