我想将排序列表合并到一个列表中。这个解决方案怎么样?我相信它会在O(n)时间内运行。任何明显的缺陷,效率低下或风格问题?
我真的不喜欢为“这是第一次迭代”设置标志的习惯用法,并使用它来确保“最低”具有默认值。有更好的方法吗?
public static <T extends Comparable<? super T>> List<T> merge(Set<List<T>> lists) {
List<T> result = new ArrayList<T>();
int totalSize = 0; // every element in the set
for (List<T> l : lists) {
totalSize += l.size();
}
boolean first; //awkward
List<T> lowest = lists.iterator().next(); // the list with the lowest item to add
while (result.size() < totalSize) { // while we still have something to add
first = true;
for (List<T> l : lists) {
if (! l.isEmpty()) {
if (first) {
lowest = l;
first = false;
}
else if (l.get(0).compareTo(lowest.get(0)) <= 0) {
lowest = l;
}
}
}
result.add(lowest.get(0));
lowest.remove(0);
}
return result;
}
注意:这不是作业,但也不适用于生产代码。
答案 0 :(得分:9)
如果lists
包含ArrayList,效率会很低,因为lowest.remove(0)
将占用列表长度的线性时间,使得算法为O(n ^ 2)。
我会这样做:
List<T> result = new ArrayList<T>();
for (List<T> list : lists) {
result.addAll(list);
}
Collections.sort(result);
在O(n log n)中,并且用于测试,调试和维护的代码要少得多。
答案 1 :(得分:5)
扩展Anton的评论:
通过将每个List的最新结果以及它的列表指示符放入堆中,然后不断地从堆中取出顶部,并从属于该项的列表中将新项放入堆中你刚刚起飞。
Java的PriorityQueue可以提供堆实现。
答案 2 :(得分:4)
您的解决方案可能是最快的解决方案。 SortedLists的插入成本为log(n),因此您最终会得到M log(M)(其中M是列表的总大小)。
将它们添加到一个列表并排序,同时更容易阅读,仍然是M log(M)。
你的解决方案只是M。
您可以通过调整结果列表的大小来清理代码,并使用对最低列表的引用而不是布尔值。
public static <T extends Comparable<? super T>> List<T> merge(Set<List<T>> lists) {
int totalSize = 0; // every element in the set
for (List<T> l : lists) {
totalSize += l.size();
}
List<T> result = new ArrayList<T>(totalSize);
List<T> lowest;
while (result.size() < totalSize) { // while we still have something to add
lowest = null;
for (List<T> l : lists) {
if (! l.isEmpty()) {
if (lowest == null) {
lowest = l;
} else if (l.get(0).compareTo(lowest.get(0)) <= 0) {
lowest = l;
}
}
}
result.add(lowest.get(0));
lowest.remove(0);
}
return result;
}
如果你真的特别喜欢,可以使用List对象作为输入,而最低值可以初始化为lists.get(0),你可以跳过空检查。
答案 3 :(得分:4)
这是一个非常古老的问题,但我不喜欢任何提交的答案,所以这就是我最终要做的。
只是将它们全部添加到一个列表中并且由于日志线性复杂度(O(m n log(m n))
)而排序很糟糕的解决方案。如果这对你来说并不重要,那么这绝对是最简单,最直接的答案。你的初始解决方案并不坏,但它有点混乱,@ Dathan指出m列表和n个元素的复杂度为O(m n)
。您可以通过使用堆来减少每个元素的比较次数,从而将其减少到O(n log(m))
。我使用一个帮助程序类,允许我比较迭代。这样我就不会破坏初始列表,无论输入什么类型的列表,它都应该以合理的复杂度运行。我在下面的实现中看到的唯一缺陷是它不支持具有null
元素的列表,但是如果需要,可以使用标记来修复。
public static <E extends Comparable<? super E>> List<E> merge(Collection<? extends List<? extends E>> lists) {
PriorityQueue<CompIterator<E>> queue = new PriorityQueue<CompIterator<E>>();
for (List<? extends E> list : lists)
if (!list.isEmpty())
queue.add(new CompIterator<E>(list.iterator()));
List<E> merged = new ArrayList<E>();
while (!queue.isEmpty()) {
CompIterator<E> next = queue.remove();
merged.add(next.next());
if (next.hasNext())
queue.add(next);
}
return merged;
}
private static class CompIterator<E extends Comparable<? super E>> implements Iterator<E>, Comparable<CompIterator<E>> {
E peekElem;
Iterator<? extends E> it;
public CompIterator(Iterator<? extends E> it) {
this.it = it;
if (it.hasNext()) peekElem = it.next();
else peekElem = null;
}
@Override
public boolean hasNext() {
return peekElem != null;
}
@Override
public E next() {
E ret = peekElem;
if (it.hasNext()) peekElem = it.next();
else peekElem = null;
return ret;
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
@Override
public int compareTo(CompIterator<E> o) {
if (peekElem == null) return 1;
else return peekElem.compareTo(o.peekElem);
}
}
返回列表的每个元素都涉及两个O(log(m))堆操作,还有一个对所有列表的初始迭代。因此,对于n个总元素和m个列表,总体复杂度为O(n log(m)+ m)。使这总是比连接和排序更快。
答案 4 :(得分:0)
由于Balus和meriton对你对这个算法的问题给出了很好的回答,我会对你说“第一”的成语。
肯定有其他方法(比如设置最低到'魔术'值),但我碰巧觉得“第一”(我可能会给出一个更长的名字,但那是迂腐的)是最好的,因为它非常清楚。存在像“first”这样的布尔值是一个明确的信号,表明你的循环将在第一次做一些特殊的事情。它有助于读者。
当然,如果采用Balus / meriton方法,你不需要它,但这种情况就会出现。