插入排序
最简单的排序算法之一,由N-1趟排序组成。对于p=1到N-1趟,插入排序保证从位置0到位置p上的元素为已排序状态。插入排序利用了这样的事实:已知位置0到位置p-1上的元素已经处于排过序的状态。平均时间情形为O(N2)。
实现代码
#include<vector>
#include<functional>
using std::vector;
using std::less;
//简单插入排序
template<typename Comparable>
void insertionSort(vector<Comparable>& a)
{
for (int p = 1; p < a.size(); ++p)
{
Comparable tmp = std::move(a[p]);
int j;
for (j = p; j > 0 && tmp < a[j - 1]; --j)
a[j] = std::move(a[j - 1]);
a[j] = std::move(tmp);
}
}
//插入排序的STL实现,2-参数版排序调用3-参数版排序,用到C++11中的decltype
template<typename Iterator,typename Comparator>
void insertionSort(const Iterator& begin, const Iterator& end, Comparator lessThan)
{
if (begin == end)
return;
Iterator j;
for (Iterator p = begin + 1; p != end; ++p) {
auto tmp = std::move(*p);
for (j = p; j != begin && lessThan(tmp, *(j - 1)); --j)
*j = std::move(*(j - 1));
*j = std::move(tmp);
}
}
template<typename Iterator>
void insertionSort(const Iterator& begin, const Iterator& end)
{
insertionSort(begin, end, less<decltype(*begin)>{});
}
假设不存在重复元素,设输入数据是前N个整数的某个排列并设所有的排列都是等可能的。则有以下定理:
N个互异元素的数组的平均逆序数是N(N-1)/4。
即,通过交换相邻元素进行排序的任何算法平均都需要 Ω(N2) 时间。它不仅对隐含地实施相邻元素交换的插入排序有效,而且对诸如冒泡排序和选择排序等其他一些简单算法也是有效的。
希尔排序
希尔排序也叫缩减增量排序(diminishing increment sort),它使用一个序列h1,h2, … ,ht,叫作增量序列(increment sequence)。只要h1=1,任何增量序列都是可行的。在使用增量 hk 的一趟排序之后,对于每一个 i 我们都有 a[ i ] ≤ a[ i+hk ],所有相隔 hk 的元素都被排序,此时称文件是 hk 排序的。
希尔排序的基本思想是:先将整个待排序的记录序列分割成为若干子序列分别进行直接插入排序,待整个序列中的记录"基本有序"时,再对全体记录进行依次直接插入排序。
希尔排序的一个重要性质:一个 hk 排序的文件(此后将是hk-1的)保持它的 hk 排序性。
一个例子
原始数组 | 81 94 11 96 12 35 17 95 28 58 41 75 15 |
---|---|
在5排序后 | 35 17 11 28 12 41 75 15 96 58 81 94 95 |
在3排序后 | 28 12 11 35 15 41 58 17 94 75 81 96 95 |
在1排序后 | 11 12 15 17 28 35 41 58 75 81 94 95 96 |
两种增量序列
- 希尔增量序列:ht=N/2 和 hk=hk+1/2 ,使用希尔增量时希尔排序的最坏情形运行时间为O(N2)。
- Hibbard增量:1,3,7, … ,2k-1,使用Hibbard增量的希尔排序的最坏情形运行时间为O(N3/2)。
实现代码
//使用希尔(不理想)增量的希尔排序
template<typename Comparable>
void shellSort(vector<Comparable>& a)
{
for(int gap=a.size()/2;gap>0;gap/=2)
for (int i = gap; i < a.size(); ++i) {
Comparable tmp = std::move(a[i]);
int j = i;
for (; j >= gap && tmp < a[j - gap]; j -= gap)
a[j] = std::move(a[j - gap]);
a[j] = std::move(tmp);
}
}
堆排序
优先队列可以用于以 O(NlogN) 时间的排序,基于该想法的算法叫堆排序。
其基本策略是建立N个元素的二叉堆,这个阶段花费O(N)时间。然后执行N次deleteMin操作。按照顺序,最小的元素先离开堆。通过将这些元素记录到第二个数组然后再将数组复制回来,就得到N个元素的排序。由于每次deleteMin花费时间O(logN),因此总的运行时间是 O(NlogN)。
对N个互异项的随机排列进行堆排序所用比较的平均次数为 2N logN-O(N log logN)。
该算法的主要问题在于它使用了一个附加的数组,存储需求增加了一倍。
实现代码
//标准堆排序
template<typename Comparable>
void heapSort(vector<Comparable>& a)
{
for (int i = a.size() / 2 - 1; i >= 0; --i) //buildHeap
percDown(a, i, a.size());
for (int j = a.size() - 1; j > 0; --j) {
std::swap(a[0], a[j]); //deleteMax
percDown(a, 0, j);
}
}
/**
* 堆排序的内部方法
* i是堆中一项的下标
* 返回左儿子的下标
*/
inline int leftChild(int i)
{
return 2 * i + 1;
}
/**
* 在deleteMax和buildHeap中用到的堆排序的内部方法
* i是开始下滤的位置
* n是二叉堆的逻辑大小
*/
template<typename Comparable>
void percDown(vector<Comparable>& a, int i, int n)
{
int child;
Comparable tmp;
for (tmp = std::move(a[i]); leftChild(i) < n; i = child) {
child = leftChild(i);
if (child != n - 1 && a[child] < a[child + 1])
++child;
if (tmp < a[child])
a[i] = std::move(a[child]);
else break;
}
a[i] = std::move(tmp);
}