Chap26 Sorting
1 O(N2) Sorts
1.1 Bubble Sort
- 遍历数组,将每个元素与其相邻的元素进行比较。如果两个元素的顺序不对,冒泡排序将交换它们。遍历数组使每个元素更接近其正确的位置,但是遍历数组一次不足以保证数组是完全排序的。因此,冒泡排序会重复这个过程,直到在数组中不交换任何元素为止
- 最大的元素迅速“冒出来”——第一次遍历数组将最大的元素移动到最后一个位置,然后接下来的每一次遍历确保至少下一个最大的元素被移动到正确的位置
- 小的元素向左挪会经过很多次,因为每次只能移动一个位置
- shaker sort 仍然是O(N2) --> 左到右(最大),右到左(最小),左到右(次最大)……
- 冒泡排序在链表上实现相对容易(交换的是节点中的数据,而不是节点本身)。它在链表上的效率并不比在数组上低——sort按顺序遍历数组的元素,并将一个元素与紧接着的后续元素进行比较。
- shaker sort只对双链表有用,以便反向传递可以跟随前一个指针。
1.2 Insertion Sort
- 将数组分成两部分——一部分是排序的,另一部分是未排序的。然后将未排序部分的第一个元素插入到已排序部分中相对于其他已排序元素的正确位置。该插入操作将数组中已排序的部分增加一个元素,同时收缩未排序的部分。当数组中未排序的部分完全缩小时,整个数组将被排序。
- 排序从排序部分和未排序部分之间的划分开始,这样排序部分只有一个元素,而未排序部分有N-1个元素
- 找到一个位置,将插入点右侧元素们向右移动以腾出空间,并将元素插入一个可能远离起始位置的位置。当算法正确地复制元素以准备插入时,将短暂地存在被移动项的副本。在算法的下一步中(将下一个元素右移或插入当前元素),复制的副本将被覆盖
- 更适合链表 --> 在算法的每一步中,删除原始list的头,并在新list中执行排序插入。可以重用原始节点,因此不需要任何额外的分配或复制
1.3 Selection Sort
- 找到未排序区域的最小元素,将该元素与未排序区域的第一个元素交换(除非它已经在该位置)。此时,已排序的区域会增长(而未排序的区域会收缩),这个过程会重复,直到整个数组都已排序
- 选择排序在链表上的效果并不比在数组上差,因为它按顺序遍历数据结构
2 O(Nlg(N)) Sorts
2.1 Heap Sort
- 重新使用选择排序,但用更有效的方法找到最小元素,O(lg(N))而不是O(N)
- 将数组中的所有项放入堆中,然后反复从堆中删除最大的元素,并将其放入数组中正确的位置。将从右到左输入最大元素,所以我们使用max堆表示升序,使用min堆表示降序。
- 堆实际上是作为数组实现的。因此,可以将数组分成无序数组和堆有序数组两部分。然后,可以采用类似的方法,通过将下一个元素插入到堆中来移动区域之间的边界,O(lg(N) --> heapify --> 总共O(Nlg(N))
- 堆放在左边,数组放在右边,heap中最大元素index为0
- 需要把heap中最大元素放在整个数组的右端——堆和排序数组之间的边界处。堆移除操作恰好这么做。因此,交换数组的第0个元素(即堆的顶部)和堆中的最后一个元素。这个交换操作将排序区域扩展了一个元素,并以正确的方式从堆中开始删除操作——只需要将节点向下bubble到其正确的位置(O(lg(N))。在我们完成将元素向下冒泡之后,重复以上过程,直到堆消失且整个数组都已排序
2.2 Merge Sort
- 归并排序是一种divide and conquer分治算法,通过递归处理问题的较小部分(divide),然后得到递归的结果(conquer)。对数组的左半部分进行拆分和递归排序,然后对数组的右半部分进行递归排序,然后执行合并操作,将两个已排序数组的结果合并在一起
- merge操作遍历数组的两个排序区域,取下一个元素最小的子数组中的元素。将选定的元素写入临时数组。重复进行,直到一个子数组为空。此时,剩下的元素从另一半复制到临时空间,然后整个临时空间(现在已经排序)被写回数组
- 不适合小数组
- 这里使用 n<=2作为base case
再写回
2.3 Quick Sort
- typically O(Nlg(N)) 大约分为一半一半, worst case O(N2) very imbalance
- 首先将数组划分为smaller and larger elements,然后递归排序这些部分。分区是针对一个叫做主元pivot的元素进行的
- 选择pivot, 数组所有小于pivot的元素左边, 大于pivot在右边,swap the pivot into its proper position,然后递归地快速排序左右两个分区
- 分区算法并没有尝试对每一半中的元素彼此进行正确的排序,它只将比pivot小的放左边,比pivot大的放右边
- 选择pivot(中间值最好)
- 对random data选择最后一个元素
- 选择一个ramdom array index并使用该元素作为主元素,通过随机选择三个index,并选择这三个要素的中位数,来提高良好枢轴的几率
- pivot被选择并交换到最后,分区步骤首先从左到右扫描,寻找数组中比主元大的元素(即属于右边)。然后从右到左扫描,寻找小于主元(即属于左侧)的元素。一旦找到这两个元素,互相交换,将它们放在数组的正确一侧。然后,该过程在另一次左扫描/右扫描中重复,并在两个扫描相互交叉时结束(这在从右向左扫描停止后进行检查)。当扫描结束时,从左到右的扫描索引位于要放置主元素的正确位置,因此它所指向的元素与主元素交换,递归排序左右两边
3 Sorting Tradeoffs
- introspection sort内省排序 --> 混合快速排序和堆排序。从快速排序开始,但如果快速排序的递归深度超过了正比于lg(N)的数(例如2lg(N))的限制,则切换到堆排序。快速排序需要一个额外的参数,该参数指定在切换到堆排序之前可以进行多少递归调用深度。当该参数达到0时,修改后的快速排序调用堆排序并返回。当快速排序递归时,它会使当前的limit - 1
4 Sorting Libraries
- C library --> qsort 快排
void qsort(void * base, size_t nmemb, size_t size, int (*compar)(const void *, const void *));
- C++ ,STL中有std::sort, 对任何支持iterator(如vector)的container的元素进行排序,默认ascending order,也可以自定义排序
http://www.cplusplus.com/reference/algorithm/sort/ - 对于equality of items,std::stable_sort
http://www.cplusplus.com/reference/algorithm/stable_sort/