插入排序算法的以下Java实现出现在Noel Markham的 Java Programming Interviews Exposed 的第28页上:
public static List<Integer> insertSort(final List<Integer> numbers) {
final List<Integer> sortedList = new LinkedList<>();
originalList: for (Integer number : numbers) {
for (int i = 0; i < sortedList.size(); i++) {
if (number < sortedList.get(i)) {
sortedList.add(i, number);
continue originalList;
}
}
sortedList.add(sortedList.size(), number);
}
return sortedList;
}
我的一位同事回顾了这段代码后发现,作为对“请实施插入排序算法”的面试问题的回答,这是不可接受的。他认为数组对于排序列表来说是更合适的数据结构。但是,正如马克姆在同一页上解释的那样:
链接列表在添加元素中非常有效 列表,只需重新排列列表中节点的指针即可。 如果使用了ArrayList,则向中间添加元素 昂贵。 ArrayList由数组支持,因此插入 列表的前面或中间意味着所有后续元素必须是 沿着一个移位到阵列中的新插槽。这可能非常 如果你有一个包含数百万行的列表,那就太贵了,特别是如果 你是在列表的早期插入的。
这是可接受的实施吗?
答案 0 :(得分:1)
考虑以下用于插入排序的伪代码:
for i ← 1 to length(A) - 1
j ← i
while j > 0 and A[j-1] > A[j]
swap A[j] and A[j-1]
j ← j - 1
end while
end for
来源:http://en.wikipedia.org/wiki/Insertion_sort
1)所以,在这个过程中,你持有每个元素,并将它与前一个元素进行比较,如果前一个元素大于当前元素,则交换,并且这个元素会一直发生,直到条件不满足为止。
2)算法的工作原理是逐个元素交换,而不是将元素插入到应该放置的位置。注意: - 每个交换是o(1)。
3)因此,在这种形式中,如果使用列表,则需要执行2个操作,将前任和当前元素连接起来,反之亦然。另一方面,排序数组只需要一步。
因此,在这种方法中,排序数组比列表更有意义。现在,如果插入排序的方法是直接将当前元素插入到它所适合的位置,那么链表就会更好。
注意: - 排序数组或排序链表,整个过程是相同的,它是中间步骤,而不是排序。
答案 1 :(得分:1)
理论上,Markham可能是一个很好的常识:在链表中插入不应该花费太多(分配新节点,一些参考分配),甚至列表末尾的插入也很便宜,因为{ {1}}实际上是一个双链表,并保留对最后一个元素的引用。
插入新节点(用于LinkedList
)和移动部分数组(用于LinkedList
)之间的争论至少要进行测试,因为ArrayList
使用{{ 1}}应该真正优化这种工作。
你可以随时随地看到“谨防微观基准”。好吧,我想说,当你想大致了解发生了什么时,微基准可以给你一些提示......
以下是您想要比较的两种方法的微观工作台,加上ArrayList#add(int i, E)
有一些参考时间。注意插入排序平均为System.arrayCopy()
,与Collections.sort()
的{{1}} tim sort 进行比较。
在插入排序的建议实现中,我只是传递排序列表实现,以便为两个测试使用相同的函数。
O(N^2)
接下来是一个方法,它将测量排序随机整数列表并打印它所花费的时间:
Collections
函数O(Nlog(N))
将循环增加数组大小,并使用3种方法对相同的随机数组进行排序,并比较排序列表。
public static List<Integer> insertSort(final List<Integer> numbers,
final List<Integer> sortedList) {
//final List<Integer> sortedList = new ArrayList<>();
originalList: for (Integer number : numbers) {
for (int i = 0; i < sortedList.size(); i++) {
if (number < sortedList.get(i)) {
sortedList.add(i, number);
continue originalList;
}
}
sortedList.add(sortedList.size(), number);
}
return sortedList;
}
所有这些都来自public static List<Integer> bench(List<Integer> ints, String tag,
Function<List<Integer>, List<Integer>> sortf) {
long start = System.nanoTime();
List<Integer> sortedInts = sortf.apply(ints);
long end = System.nanoTime();
System.out.println(String.format("type: %6s size: %7d time(ms): %5d",
tag, ints.size(), (end-start)/1000000));
return sortedInts;
}
。从500的数组开始,然后增加大小直到 5,000 (确实非常小的尺寸!)。执行环境是MBP 2,5 GHz英特尔酷睿i7。
microBench()
无需绘制图片以了解插入排序 public static void microBench(int start, int end, int step) {
for (int m = start; m <= end; m+=step) {
List<Integer> ints = new Random()
.ints(m, 0, m).boxed()
.collect(Collectors.toList());
List<Integer> l1 = bench(ints, "coll", (List<Integer> l) -> {
List<Integer> list = new ArrayList<>(l);
Collections.sort(list);
return list;
});
List<Integer> l2 = bench(ints, "array", (List<Integer> l) ->
insertSort(l, new ArrayList<Integer>()));
if (!l1.equals(l2)) {
System.out.println("Oooops array");
}
List<Integer> l3 = bench(ints, "linked", (List<Integer> l) ->
insertSort(l, new LinkedList<Integer>()));
if (!l1.equals(l3)) {
System.out.println("Oooops linked");
}
}
}
不是赢家! 14秒排序5000英寸。但是使用 main
插入并不是那么糟糕。
我删除了public static void main(String[] args) {
microBench(1000, 5000, 1000);
}
type: coll size: 1000 time(ms): 1
type: array size: 1000 time(ms): 8
type: linked size: 1000 time(ms): 66
type: coll size: 2000 time(ms): 1
type: array size: 2000 time(ms): 2
type: linked size: 2000 time(ms): 507
type: coll size: 3000 time(ms): 2
type: array size: 3000 time(ms): 4
type: linked size: 3000 time(ms): 2283
type: coll size: 4000 time(ms): 1
type: array size: 4000 time(ms): 9
type: linked size: 4000 time(ms): 6866
type: coll size: 5000 time(ms): 1
type: array size: 5000 time(ms): 13
type: linked size: 5000 time(ms): 14842
上的工作台,并推动了最大数组 100,000 。
LinkedList
4.5秒vs 15毫秒。毫无疑问,与tim排序/合并排序O(NlogN)相比,插入排序仍为O(N ^ 2)......
由于Markham正在编写大约1,000,000个元素数组,所以我只选择了实验中的唯一实现(来自3个测试版),可以正确地执行它,并使用ArrayList
删除插入排序
LinkedList
1,000,000的223毫秒。
结论,要小心人们可以写什么,并在可以做到的时候测试自己! - 顺便说一句,你的同事是对的。 并且,如果你必须排序,插入排序,通常不是你要去的方式。
答案 2 :(得分:0)
由于get(i)
不支持LinkedList
,因此您必须线性迭代内部循环中的链接列表而不是RandomAccess
。
否则,每个元素访问都需要i
个步骤才能找到相应的元素和基准,因为在比较实现例如ArrayList
时,另一个答案中的元素和基准会产生误导性结果。
public static List<Integer> insertSort(final List<Integer> numbers) {
final LinkedList<Integer> sortedList = new LinkedList<>();
originalList: for (Integer number : numbers) {
int i = 0;
for (Integer compare : sortedList) {
if (number < compare) {
sortedList.add(i, number);
continue originalList;
}
++i;
}
sortedList.addLast(number);
}
return sortedList;
}