【排序】插入排序、希尔排序和堆排序

插入排序

最简单的排序算法之一,由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
图1 希尔排序每趟之后的情况
两种增量序列
  • 希尔增量序列: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);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

zhugenmi

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值