我有很多字符串数组。根据相同的标准,每个数组中的字符串以相同的方式排序。但是,某些数组可能缺少某些字符串,并且可能没有包含完整字符串集的数组。此外,用于比较字符串的标准对我来说是不可用的:在数组的上下文之外,我无法分辨哪个字符串应该在另一个字符串之前。
我需要一种方法来生成一组完整的字符串,正确排序。或者当阵列没有足够的信息供我这样做时失败。
有人熟悉这类问题吗?什么是正确的算法?
示例:
A B D
A C D
无法正确排序,无法确定B和C的顺序
A B D
A B C
A C D
这有足够的信息来正确订购ABCD。
答案 0 :(得分:5)
我可以想到的一种可能的方式,虽然可能不是最有效的方式:
我将用你的例子来解释:
A B D
A B C
A C D
创建一个唯一字符的数组,这样你就可以得到(例如):
A B D C
你也应该有一个枚举来描述可能的关系
enum Type
{
Unknown = 0,
Greater = 1,
Equal = 2,
Less = 3,
}
现在,创建一个方形矩阵,其尺寸与上面的数组相同,默认为Type.Unknown。
0 0 0 0
0 0 0 0
0 0 0 0
0 0 0 0
当您计算排序时,您的数组将作为矩阵的索引。看看我的意思,看看这里:
A B D C
A 0 0 0 0
B 0 0 0 0
D 0 0 0 0
C 0 0 0 0
通过并将对角线设为Type.Equal
2 0 0 0
0 2 0 0
0 0 2 0
0 0 0 2
现在你需要填充矩阵,遍历每个输入数组并获取每个字符和后面的字符。使用这两个字符,在矩阵中找到每个字符的索引(使用您的数组)并更新矩阵。
for(int i=0; i<len(arr)-1; i++)
{
char c1 = arr[i], c2 = arr[i+1];
int i1,i2;
//get indices of characters in array and put in i1, and i2 respectively
matrix[i1][i2] = Type.Less;
matrix[i2][i1] = Type.Greater
}
每次在网格中分配2个位置,因为当一个char小于另一个char时,它还意味着第二个char大于第一个char。
现在你只需将chars一次插入一个数组中(Linked List将是最简单的,你会在一秒钟内看到原因)
每次插入一个char时,都会从返回数组的开始处开始,遍历,查找第一个数组中的索引,并检查矩阵的Type.Greater或Type.Less(取决于你正在比较哪种方式,curr char到数组,或者数组到当前char)并在遇到与你预期的值不同的值时插入它。
如果你在插入过程中遇到了matix中的Type.Unknown,你知道你没有足够的信息来排序这些数组,如果你点击了Type.Equal,你可以忽略插入这个字符(假设你没有'我想在你的结果中重复。)
然后你会输出你的返回数组
答案 1 :(得分:4)
这听起来像topological sorting问题的一个特例。
答案 2 :(得分:2)
1)tsort数据。如果部分排序不一致,则会出现循环并且tsort将失败。否则你有一个序列s(0)... s(n-1)。
2)对于每对s(i),s(i + 1),在部分序列中搜索彼此相邻出现的相同对。如果你在其中一个中找到它,那么继续下一个i。如果你没有找到它,那么失败,因为部分序列没有命令s(i)和s(i + 1)。
为什么不呢?因为如果他们确实订购了它们,但它们并没有彼此相邻,那么它们之间肯定会存在某种东西。但是如果在部分序列中它们之间存在某种东西而不是在你从tsort中得到的完整序列之间存在某种东西,那么这个闯入者必须在整个序列中在s(i)之前或在s(i + 1)之后。这与完整序列与部分序列的一致性相矛盾,tsort已经证明了这一点。
步骤2是O(n * m),其中n是要排序的元素的数量,m是所有部分序列的总长度。你可能会做得更好,但这很简单,因为你可以在第1步获取一个tsort,而第2步是一堆明显的嵌套循环。请注意,如果找到s(i)(或s(i + 1)),则可以停止搜索每个部分序列,因为它肯定不会再次发生。
答案 3 :(得分:0)
你有几套?如果它们不太稀疏可能会起作用的是:
如果将数组转储到b-trees,则元素搜索可能非常快。您也可以通过播放阵列的顺序来进一步加快步骤3。我还在考虑那个。
如果阵列非常稀疏,我唯一能想到的就是使用某种有监督的机器学习来尝试确定正确的排序方法。
答案 4 :(得分:0)
这与经典diff/merge algorithm有何不同?只要两个数组都“排序”,无论它们的排序机制如何。它们相互比较排序,因此您应该能够使用相似性来合并差异。在那之后,它基本上是任意的。
如果你想将[A]与[B,C,D]合并,那么你不知道A会去哪里(前面?结束?在C和D之间?你不知道)。在这些情况下,你只需要提出一个约定(总是在前面,总是在最后,就像那样)。
如果要将[Q,X,Y,A,B]与[Q,X,W,B,D]合并,则需要确定“W”是否位于前面或后面“Y,A”。因此,[Q,X,Y,A,W,B,D]是正确的还是[Q,X,W,Y,A,B,D]是正确的。坦率地说,你只需要打电话和滚动它。根本没有足够的信息。
答案 5 :(得分:0)
主要问题是,给定一些数据,您需要确定订购功能。之后,它只是列表排序。
生成所有数组的第一个元素列表。删除重复项。所以这个:
A B
A C
B C
C C
C D
成为(A,B,C)。然后,我们需要一组对,表示我们知道的每个值都小于其他值。 ((A,B),(A,C),(B,C))对于这个例子。
现在,对于我们可以在数组开头找到的每个值,将所有以该值开头的数组作为一个组。删除他们的第一个元素这让我们:
B
C
,
C
和
C
D
递归每个阵列列表。
一旦我们完成了对所有子集的递归,我们就会将生成的对合并到我们最初提出的内容中。我们应该以((A,B),(A,C),(B,C),(C,D))结束。根据值的属性,您可以将其扩展为((A,B),(A,C),(A,D),(B,C),(B,D),(C,D) )。
现在你的不到函数只是查看它要比较的值是否在此集合中。如果是的话,是的。否则,是假的。
答案 6 :(得分:0)
你的toposort算法的变化会让你建立一个DAG(你暗示确保了无关紧要性),也许你可以随时跟踪零输入边缘的节点(一个简单的优化)。如果完成后,您有多个“根”节点,那么就完成了,因为您知道没有第一个节点。否则,你只有一个;将其从DAG中删除,检查其每个子节点现在是否为“根”节点;并继续前进,直到你获得多个根节点(失败)或节点用完(成功)。
我在上面描绘的算法应该是字符串数量的线性(基本上,通过它们的两次迭代)。它可以说与字符串数的平方成正比,如果每个节点有很多子节点,暗示每个字符串在列表列表中出现很多次,每个字符串都紧跟在它后面。
答案 7 :(得分:0)
我很想说,为了获得足够的信息来完成合并,在最终合并数组中彼此相邻的每对x,y都必须存在于输入数组的某个位置。换句话说,传递性可能根本不会出现。有人可以产生一个反例吗?
如果您愿意,可以在答案中直接进行。
答案 8 :(得分:0)
简单的递归例程怎么样?
使用你的第二个例子:
A B D
A B C
A C D
创建所有唯一排序的哈希表:
table = {A -> B, C
B -> C, D
C -> D}
计算所有唯一值
countUnique = 4
使用堆栈计算所有可能的路径。当堆栈长度与唯一字符串的数量匹配时,您知道有一个解决方案。如果您找到多个解决方案,那么您可以在某处获得循环引用。 请原谅我可疑的伪代码。
main()
{
foreach (s in table.keys)
{
stack.push(s)
searchForPath()
stack.pop()
}
}
searchForPath()
{
if (table.hasKey(stack.peek))
{
// we're not at the end yet
foreach (s in table[stack.peek])
{
// avoid infinite recursion
if (stack.contains(s)
continue
stack.push(s)
searchForPath()
stack.pop()
}
}
else
{
if (stack.size == countUnique)
{
// solution found
captureSolution()
}
}
}
答案 9 :(得分:-1)
部分解决方案:
您需要确定订购。您可以通过要求数据“投票”一个符号在另一个符号之前的次数来做到这一点。您将需要一个大小等于符号数的方阵。将其初始化为全零,然后扫描所有输入字符串,为每个连续对(a,b)添加1到M(a,b)。然后,你需要清理这些数据,这样你就得到了一个可传递的排序(如果A出现在B之前并且出现在C之前,A也必须在C之前出现)。为此,你需要迭代不同符号的所有三元组,并“协调”不等式的相应三元组,因此它们尊重传递性。
如果您的数据不太嘈杂,则只需一次通过即可。如果数据太嘈杂,您可能需要运行多次传递直到收敛,或采用一些贪婪的方法:而不是以随机顺序迭代所有符号,按照减少的票数顺序迭代所有不等式的三元组