Algorithm - How to delete duplicate elements in a list efficiently?
有一个列表L,它包含任意类型的元素。如何有效删除该列表中的所有重复元素?必须保留订单
只需要一个算法,所以不允许导入任何外部库。
相关问题在python中,从列表中删除重复项的最快算法是什么,以便所有元素在保持顺序的同时都是唯一的?
如何在保留顺序的同时从python的列表中删除重复项?
从python列表中删除重复项
如何从python的列表中删除重复项?
假设订单重要:
- 创建一个空集合s和一个空列表m。
- 一次扫描一个元素列表。
- 如果元素在集合S中,则跳过它。
- 否则,将其添加到m和s。
- 对L中的所有元素重复此操作。
- 返回M
在Python中:
1 2 3 4 5 6 7 8 9 10 11 | >>> L = [2, 1, 4, 3, 5, 1, 2, 1, 1, 6, 5] >>> S = set() >>> M = [] >>> for e in L: ... if e in S: ... continue ... S.add(e) ... M.append(e) ... >>> M [2, 1, 4, 3, 5, 6] |
如果订单无关紧要:
1 | M = list(set(L)) |
特殊情况:散列和相等
首先,我们需要确定一些关于假设的东西,即一个等号的存在和有函数关系。我这是什么意思?我的意思是,对于一组源对象s,给定任意两个对象x1和x2(s的元素),存在一个(哈希)函数f,这样:
1 | if (x1.equals(x2)) then F(x1) == F(x2) |
Java有这样的关系。这允许您以接近O(1)的操作检查重复项,从而将算法简化为一个简单的O(n)问题。如果订单不重要,它是一个简单的一行程序:
如果订单很重要:
1 2 3 4 5 6 7 8 |
你会注意到我说的"接近O(1)"。这是因为这样的数据结构(如Java hash映射或hash集)依赖于一种方法,其中使用哈希代码的一部分来查找后备存储器中的元素(通常称为桶)。桶数是2的幂。这样,列表中的索引就很容易计算了。hashcode()返回一个int。如果你有16个bucket,你可以通过将hashcode与15相加,得到一个0到15之间的数字来找到要使用的bucket。
当你试图把东西放进那个桶里的时候,它可能已经被占了。如果是这样,那么将对该存储桶中的所有条目进行线性比较。如果碰撞率太高,或者试图在结构中放置太多元素,则会增加一倍(通常是2的幂次),并且所有项目都将放置在新的存储桶中(基于新的掩码)。因此,调整这种结构的尺寸相对昂贵。
查找可能也很昂贵。考虑这个类:
1 2 3 4 5 6 7 8 9 10 11 12 13 | public class A { private final int a; A(int a) { this.a == a; } public boolean equals(Object ob) { if (ob.getClass() != getClass()) return false; A other = (A)ob; return other.a == a; } public int hashCode() { return 7; } } |
此代码是完全合法的,它满足equals hashcode合同。
假设您的集合只包含一个实例,那么您的插入/搜索现在变成一个O(N)操作,将整个插入变成O(N2)。
显然,这是一个极端的例子,但是指出这样的机制也依赖于相对良好的散列分布在映射或集合使用的值空间中。
最后,必须说这是一个特殊的情况。如果你使用的语言没有这种"散列快捷方式",那么情况就不同了。
一般情况:无订货如果列表中不存在排序函数,那么就需要对每个对象与其他所有对象进行O(n2)强力比较。所以在Java中:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
一般情况:订购
如果一个排序函数存在(比如说,一个整数或字符串的列表),那么您可以对列表(即O(n log n))进行排序,然后将列表中的每个元素与下一个(o(n))进行比较,这样总的算法就是O(n log n)。在Java中:
1 2 3 4 5 6 7 8 9 | Collections.sort(inputList); List result = new ArrayList(); Object prev = null; for (Object item : inputList) { if (!item.equals(prev)) { result.add(item); } prev = item; } |
注意:上面的例子假设列表中没有空值。
在haskell中,这将由
1 2 3 4 5 6 7 | nub :: Eq a => [a] -> [a] nub [] = [] nub (x:xs) = x : nub (filter (/= x) xs) nubBy :: (a -> a -> Bool) -> [a] -> [a] nubBy f [] = [] nubBy f (x:xs) = x : nub (filter (not.f x) xs) |
这些函数处理一致的任意类型列表(例如,haskell中不允许使用
为了提高效率,可以使用data.map(或实现平衡树)将数据收集到一个集合中(键是元素,值是索引到原始列表中,以便能够返回原始顺序),然后将结果收集到一个列表中并按索引排序。稍后我将尝试实现此功能。
1 2 3 4 5 6 7 8 |
这是@foglebird解决方案的直接翻译。不幸的是,没有导入就不能工作。
替换数据的一个非常基本的尝试。映射导入将实现一个树,类似于
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | data Tree a = Empty | Node a (Tree a) (Tree a) deriving (Eq, Show, Read) insert x Empty = Node x Empty Empty insert x (Node a left right) | x < a = Node a (insert x left) right | otherwise = Node a left (insert x right) lookup x Empty = Nothing --returning maybe type to maintain compatibility with Data.Map lookup x (Node a left right) | x == a = Just x | x < a = lookup x left | otherwise = lookup x right |
一个改进是通过保持深度属性(防止树降级为链接列表)使其在插入时自动平衡。对于哈希表来说,这一点很好,因为它只要求类型位于typeclass ORD中,这对于大多数类型都是容易派生的。
我似乎接受了请求。作为对@jonno_ftws查询的回应,这里有一个解决方案,可以完全从结果中删除重复项。它并没有完全不同于原版,只是增加了一个额外的案例。但是,运行时性能会慢得多,因为您要检查每个子列表两次,一次用于ELEM,第二次用于收回。还要注意,现在它将不适用于无限列表。
1 2 3 | nub [] = [] nub (x:xs) | elem x xs = nub (filter (/=x) xs) | otherwise = x : nub xs |
有趣的是,您不需要对第二个递归情况进行过滤,因为Elem已经检测到没有重复的情况。
如果顺序无关紧要,您可能需要尝试用python编写的此算法:
1 2 3 4 | >>> array = [1, 2, 2, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6] >>> unique = set(array) >>> list(unique) [1, 2, 3, 4, 5, 6] |
在Python中
1 2 3 4 5 6 7 8 9 | >>> L = [2, 1, 4, 3, 5, 1, 2, 1, 1, 6, 5] >>> a=[] >>> for i in L: ... if not i in a: ... a.append(i) ... >>> print a [2, 1, 4, 3, 5, 6] >>> |
在Java中,它是一个线性。
1 |
将为您提供删除重复项的集合。
在python中删除列表中的重复项案例:列表中的项目不可哈希或比较
也就是说,我们不能使用
1 2 3 4 5 6 7 8 9 10 11 | from itertools import islice def del_dups2(lst): """O(n**2) algorithm, O(1) in memory""" pos = 0 for item in lst: if all(item != e for e in islice(lst, pos)): # we haven't seen `item` yet lst[pos] = item pos += 1 del lst[pos:] |
案例:项目可哈希
解决方案取自:
1 2 3 4 5 6 7 8 9 10 | def del_dups(seq): """O(n) algorithm, O(log(n)) in memory (in theory).""" seen = {} pos = 0 for item in seq: if item not in seen: seen[item] = True seq[pos] = item pos += 1 del seq[pos:] |
案例:项目是可比较的,但不可哈希
也就是说,我们可以使用
1 2 3 4 5 6 7 8 9 10 11 12 | def del_dups3(lst): """O(n*log(n)) algorithm, O(1) memory""" lst.sort() it = iter(lst) for prev in it: # get the first element break pos = 1 # start from the second element for item in it: if item != prev: # we haven't seen `item` yet lst[pos] = prev = item pos += 1 del lst[pos:] |
对于Java可以与此:
1 2 3 4 5 6 7 8 | private static <T> void removeDuplicates(final List<T> list) { final LinkedHashSet<T> set; set = new LinkedHashSet<T>(list); list.clear(); list.addAll(set); } |
我已经为字符串编写了一个算法。实际上,你有什么类型的并不重要。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | static string removeDuplicates(string str) { if (String.IsNullOrEmpty(str) || str.Length < 2) { return str; } char[] arr = str.ToCharArray(); int len = arr.Length; int pos = 1; for (int i = 1; i < len; ++i) { int j; for (j = 0; j < pos; ++j) { if (arr[i] == arr[j]) { break; } } if (j == pos) { arr[pos] = arr[i]; ++pos; } } string finalStr = String.Empty; foreach (char c in arr.Take(pos)) { finalStr += c.ToString(); } return finalStr; } |
这取决于你所说的"有效"。幼稚的算法是O(n^2),我假设你的实际意思是你想要的是比这低一级的东西。
正如Maxim100所说,您可以通过将列表与一系列数字配对来保留顺序,使用您喜欢的任何算法,然后将剩余部分还原为原始顺序。在哈斯克尔,情况如下:
1 2 3 4 5 6 | superNub :: (Ord a) => [a] -> [a] superNub xs = map snd . sortBy (comparing fst) . map head . groupBy ((==) `on` snd) . sortBy (comparing snd) . zip [1..] $ xs |
当然,您需要导入data.list(sort)、data.function(on)和data.ord(comparising)。我可以背诵这些函数的定义,但要点是什么?
- 浏览列表并为每个项分配顺序索引
- 根据元素的某些比较函数对列表进行排序
- 删除重复项
- 根据指定的索引对列表进行排序
为了简单起见,项目的索引可以存储在类似std::map的内容中。
如果我没有漏掉任何东西,看起来像O(n*logn)
算法删除重复项(a[1….n])
//从给定数组中删除重复项
//输入参数:a[1:n],n个元素数组
{
1 2 3 4 5 | temp[i]=a[i];for i=1 to n temp[i].value=a[i] temp[i].key=i |
*//基于"value"对数组temp进行排序。*
//基于"value",从temp中删除重复的元素。
//基于"key"对数组temp排序//使用temp构造数组p。
1 2 3 | p[i]=temp[i].value return p |
在其他元素中,使用"key"在输出数组中维护。考虑到键的长度为o(n),对键和值执行排序所用的时间为o(nlogn)。因此,从数组中删除所有重复项所用的时间是o(nlogn)。
我的Java代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | ArrayList<Integer> list = new ArrayList<Integer>(); list.addAll({1,2,1,3,4,5,2,3,4,3}); for (int i=0; i<list.size(); i++) { for (int j=i+1; j<list.size(); j++) { if (list.get(i) == list.get(j)) { list.remove(i); j--; } } } |
或者简单地这样做:
1 2 3 | SetList<Integer> unique = new SetList<Integer>(); unique.addAll(list); |
两种方式都有时间=nk~o(n^2)
其中n是输入列表的大小,
k是输入列表中唯一成员的数目
也许您应该考虑使用关联数组(在python中也称为dict),以避免在一开始就有重复的元素。
python中的一行解决方案。使用列表压缩:
1 2 3 4 | >>> L = [2, 1, 4, 3, 5, 1, 2, 1, 1, 6, 5] >>> M = [] >>> zip(*[(e,M.append(e)) for e in L if not e in M])[0] (2, 1, 4, 3, 5, 6) |