我有一个已排序的输入列表:
let x = [2; 4; 6; 8; 8; 10; 12]
let y = [-8; -7; 2; 2; 3; 4; 4; 8; 8; 8;]
我想编写一个与SQL INNER JOIN类似的函数。换句话说,我想返回x和y的笛卡尔积,其中只包含两个列表中共享的项:
join(x, y) = [2; 2; 4; 4; 8; 8; 8; 8; 8; 8]
我写了一个如下的天真版本:
let join x y =
[for x' in x do
for y' in y do
yield (x', y')]
|> List.choose (fun (x, y) -> if x = y then Some x else None)
它有效,但这可以在O(x.length * y.length)
中运行。由于我的两个列表都已排序,我认为可以在O(min(x.length, y.length))
中获得我想要的结果。
如何在线性时间内找到两个排序列表中的常用元素?
答案 0 :(得分:8)
我无法帮助您使用F#,但基本思路是使用两个索引,每个列表一个。在该列表的当前索引处选择每个列表中的项目。如果这两个项的值相同,则将该值添加到结果集中并递增两个索引。如果项目具有不同的值,则仅增加包含两个值中较小值的列表的索引。重复比较,直到其中一个列表为空,然后返回结果集。
答案 1 :(得分:8)
O(min(n,m))时间是不可能的:取两个列表[x; x; ...; x; y]和[x; x; ...; x; z]。您必须浏览两个列表直到结束才能比较y和z。
即使O(n + m)也是不可能的。采取 [1,1,...,1] - n次 和 [1,1,...,1] - m次 然后结果列表应该有n * m个元素。你需要至少O(n m)(正确的Omega(n m))时间来创建这样的列表。
没有笛卡尔积(简单合并),这很容易。 Ocaml代码(我不知道F#,应该相当接近;编译但未经过测试):
let rec merge a b = match (a,b) with
([], xs) -> xs
| (xs, []) -> xs
| (x::xs, y::ys) -> if x <= y then x::(merge xs (y::ys))
else y::(merge (x::xs) (y::ys));;
(编辑:我太晚了)
因此,在最坏的情况下,O(n m)中的代码是最好的。但是,IIUIC它执行总是 n * m次操作,这不是最佳的。
我的方法是
1)写一个函数
组:'列表 - &gt; ('a * int)列表
计算相同元素的数量:
组[1,1,1,1,1,2,2,3] == [(1,5);(2,2);(3,1)]
2)使用它来使用与之前类似的代码合并两个列表(可以将这些系数相乘)
3)写一个函数
取消组合:('a * int)列表 - &gt; '列表
并撰写这三个。
这具有复杂度O(n + m + x),其中x是结果列表的长度。这是最好的常数。
编辑:你走了:
let group x =
let rec group2 l m =
match l with
| [] -> []
| a1::a2::r when a1 == a2 -> group2 (a2::r) (m+1)
| x::r -> (x, m+1)::(group2 r 0)
in group2 x 0;;
let rec merge a b = match (a,b) with
([], xs) -> []
| (xs, []) -> []
| ((x, xm)::xs, (y, ym)::ys) -> if x == y then (x, xm*ym)::(merge xs ys)
else if x < y then merge xs ((y, ym)::ys)
else merge ((x, xm)::xs) ys;;
let rec ungroup a =
match a with
[] -> []
| (x, 0)::l -> ungroup l
| (x, m)::l -> x::(ungroup ((x,m-1)::l));;
let crossjoin x y = ungroup (merge (group x) (group y));;
# crossjoin [2; 4; 6; 8; 8; 10; 12] [-7; -8; 2; 2; 3; 4; 4; 8; 8; 8;];;
- : int list = [2; 2; 4; 4; 8; 8; 8; 8; 8; 8]
答案 2 :(得分:2)
以下也是尾递归(据我所知),但输出列表因此反转:
let rec merge xs ys acc =
match (xs, ys) with
| ((x :: xt), (y :: yt)) ->
if x = y then
let rec count_and_remove_leading zs acc =
match zs with
| z :: zt when z = x -> count_and_remove_leading zt (acc + 1)
| _ -> (acc, zs)
let rec replicate_and_prepend zs n =
if n = 0 then
zs
else
replicate_and_prepend (x :: zs) (n - 1)
let xn, xt = count_and_remove_leading xs 0
let yn, yt = count_and_remove_leading ys 0
merge xt yt (replicate_and_prepend acc (xn * yn))
else if x < y then
merge xt ys acc
else
merge xs yt acc
| _ -> acc
let xs = [2; 4; 6; 8; 8; 10; 12]
let ys = [-7; -8; 2; 2; 3; 4; 4; 8; 8; 8;]
printf "%A" (merge xs ys [])
输出:
[8; 8; 8; 8; 8; 8; 4; 4; 2; 2]
请注意,正如sdcvvc在他的回答中所说,在最坏的情况下,这仍然是O(x.length * y.length)
,仅仅因为两个重复相同元素列表的边缘情况需要在{1}中创建x.length * y.length
值。输出列表,它本身就是一个O(m*n)
操作。
答案 3 :(得分:2)
我不知道F#,但我认为它有阵列和数组的二进制搜索实现(也可以实现)
复杂度O(min + max * log min),其中min = sizeof small list和max - sizeof(big list)
答案 4 :(得分:1)
我不知道F#,但我可以根据tvanfosson概述的算法提供功能性的Haskell实现(由Lasse V. Karlsen进一步指定)。
import Data.List
join :: (Ord a) => [a] -> [a] -> [a]
join l r = gjoin (group l) (group r)
where
gjoin [] _ = []
gjoin _ [] = []
gjoin l@(lh@(x:_):xs) r@(rh@(y:_):ys)
| x == y = replicate (length lh * length rh) x ++ gjoin xs ys
| x < y = gjoin xs r
| otherwise = gjoin l ys
main :: IO ()
main = print $ join [2, 4, 6, 8, 8, 10, 12] [-7, -8, 2, 2, 3, 4, 4, 8, 8, 8]
这会打印[2,2,4,4,8,8,8,8,8,8]
。我的情况是你不熟悉Haskell,一些文档的引用:
答案 5 :(得分:1)
我认为可以通过使用哈希表来完成。哈希表存储每个列表中元素的频率。然后将这些用于创建一个列表,其中每个元素e的频率是X中e的频率乘以Y中的e的频率。这具有O(n + m)的复杂度。
(编辑:刚看到这可能是最糟糕的情况O(n ^ 2),在阅读其他帖子的评论之后。非常喜欢这样的事情已经发布。抱歉复制。我保留帖子代码有帮助的情况。)
我不知道F#,所以我附加了Python代码。我希望代码足够可读,以便轻松转换为F#。
def join(x,y):
x_count=dict()
y_count=dict()
for elem in x:
x_count[elem]=x_count.get(elem,0)+1
for elem in y:
y_count[elem]=y_count.get(elem,0)+1
answer=[]
for elem in x_count:
if elem in y_count:
answer.extend( [elem]*(x_count[elem]*y_count[elem] ) )
return answer
A=[2, 4, 6, 8, 8, 10, 12]
B=[-8, -7, 2, 2, 3, 4, 4, 8, 8, 8]
print join(A,B)
答案 6 :(得分:0)
他想要的问题是它显然必须重新遍历列表。
为了让8,8,8显示两次,该函数必须循环到第二个列表。最坏情况(两个相同的列表)仍然会产生O(x * y)
注意,这不是利用自行循环的外部函数。
for (int i = 0; i < shorterList.Length; i++)
{
if (shorterList[i] > longerList[longerList.Length - 1])
break;
for (int j = i; j < longerList.Length && longerList[j] <= shorterList[i]; j++)
{
if (shorterList[i] == longerList[j])
retList.Add(shorterList[i]);
}
}
答案 7 :(得分:0)
我认为这是交叉/连接代码的O(n),尽管完整的东西遍历每个列表两次:
// list unique elements and their multiplicity (also reverses sorting)
// e.g. pack y = [(8, 3); (4, 2); (3, 1); (2, 2); (-8, 1); (-7, 1)]
// we assume xs is ordered
let pack xs = Seq.fold (fun acc x ->
match acc with
| (y,ny) :: tl -> if y=x then (x,ny+1) :: tl else (x,1) :: acc
| [] -> [(x,1)]) [] xs
let unpack px = [ for (x,nx) in px do for i in 1 .. nx do yield x ]
// for lists of (x,nx) and (y,ny), returns list of (x,nx*ny) when x=y
// assumes inputs are sorted descending (from pack function)
// and returns results sorted ascending
let intersect_mult xs ys =
let rec aux rx ry acc =
match (rx,ry) with
| (x,nx)::xtl, (y,ny)::ytl ->
if x = y then aux xtl ytl ((x,nx*ny) :: acc)
elif x < y then aux rx ytl acc
else aux xtl ry acc
| _,_ -> acc
aux xs ys []
let inner_join x y = intersect_mult (pack x) (pack y) |> unpack
现在我们在您的样本数据
上测试它let x = [2; 4; 6; 8; 8; 10; 12]
let y = [-7; -8; 2; 2; 3; 4; 4; 8; 8; 8;]
> inner_join x y;;
val it : int list = [2; 2; 4; 4; 8; 8; 8; 8; 8; 8]
编辑:我刚刚意识到这与sdcvvc的早期答案(编辑后)相同。
答案 8 :(得分:0)
你不能得到O(min(x.length,y.length)),因为输出可能大于那个。例如,假设x和y的所有元素都相等。然后输出大小是x和y大小的乘积,它给出了算法效率的下限。
这是F#中的算法。它不是尾递归的,可以很容易地修复。诀窍在于相互递归。另请注意,我可能会反转给prod
的列表顺序,以避免不必要的工作。
let rec prod xs ys =
match xs with
| [] -> []
| z :: zs -> reps xs ys ys
and reps xs ys zs =
match zs with
| [] -> []
| w :: ws -> if xs.Head = w then w :: reps xs ys ws
else if xs.Head > w then reps xs ys ws
else match ys with
| [] -> []
| y :: yss -> if y < xs.Head then prod ys xs.Tail else prod xs.Tail ys
Scala中的原始算法:
def prod(x: List[Int], y: List[Int]): List[Int] = x match {
case Nil => Nil
case z :: zs => reps(x, y, y)
}
def reps(x: List[Int], y: List[Int], z: List[Int]): List[Int] = z match {
case w :: ws if x.head == w => w :: reps(x, y, ws)
case w :: ws if x.head > w => reps(x, y, ws)
case _ => y match {
case Nil => Nil
case y1 :: ys if y1 < x.head => prod(y, x.tail)
case _ => prod(x.tail, y)
}
}