插入排序

本文要介绍三种常见的插入排序——直接插入排序、折半插入排序、希尔排序。后两者是对直接插入排序的优化。

1 直接插入排序(Straight Insertion Sort,简称插入排序Insertion Sort)

(1) 基本思想
把n个待排序的元素看成一个有序表和一个无序表。一开始有序表只包含1个元素,无序表包含n-1个元素。排序过程中每次从无序表中取出第一个元素,将它插入到有序表中的适当位置,使之成为新的有序表。每次排序使有序表长度+1,无序表长度-1,重复n-1次即可完成排序过程。
(2) 图文说明

(3) 代码实现

template <typename T>
void InsertSort(T array[], int len)
{
	if(array == nullptr || len < 0)
		return;
	int i, j;
	for(i = 1; i < len; ++i)
	{
		if(array[i] < array[i-1])						// 无序表的第一个元素小于有序表的最后一个元素
		{
			T temp = array[i];
			for(j = i - 1; j>=0 && array[j]>temp; --j)	// 元素后移,注意j>=0不能省略——当遇到一个元素比有序表的每个元素都小的情况,可确保j不会变为-1,导致越界错误
			{
				array[j+1] = array[j];
			}
			array[j+1] = temp;			// 在合适位置上插入新元素
		}
	}
}

(4) 测试

int main()
{
	int array[] = {5,2,9,1,-1};
	int len = sizeof(array)/sizeof(int);
	InsertSort(array, len);
	for(int i = 0; i < len; ++i)
		cout << array[i] << "\t";
	cout << endl;
	return 0;
}

测试结果

-1    1    2    5    9

(5) 复杂度分析
a.该算法的基本操作是array[j]>temp。
b.为什么不是j>=0呢?因为实际的计算机实现中,j>=0运算几乎肯定比array[j]>temp快。而且,该运算其实与本算法没有必然联系,如果采用限位器来实现的话,j>=0的运算完全可以避免。
(限位器:将数组的0号元素空出,每次将要插入的元素复制到0号元素。牺牲一个空间换取时间上的效率)
c.最好的情况是初始序列正序,此时只需要进行n-1次比较,时间复杂度为O(n);
d.最坏的情况是初始序列逆序,此时需要进行1+2+···+(n-1)=n(n-1)/2次比较和元素的移动,以及n-1次元素的赋值(插入),时间复杂度为O(n^2);
e.平均时间复杂度为O(n^2)。
f. 采用就地排序,空间复杂度为O(1)。

(6) 稳定性
直接插入排序是稳定排序,不会改变相同元素的相对顺序。

2 折半插入排序/二分插入排序(Binary Insertion Sort)

(1) 基本思想
折半插入排序是对直接插入排序的优化。在上面的排序中,为了找到元素的合适插入位置,采用从后向前遍历有序表,按顺序查找进行比较。为了减少比较次数,可以换一种查找策略——采用二分查找
(2) 图文分析

(3) 代码实现

// 二分查找函数
// 返回插入的下标
template <typename T>
int BinarySearch(T array[], int start, int end, T k)
{
	while(start <= end)
	{
		int middle = (start + end) / 2;
		T middleData = array[middle];
		if(middleData > k)
			end = middle - 1;
		else
			start = middle + 1;			// 由于下文返回的是start,所以当middleData == k时,start = middle + 1,这样可以保证算法的稳定性
	}
	return start;
}


// 二分查找插入排序
template <typename T>
void BinaryInsertionSort(T array[], int len)
{
	if(array == nullptr || len < 0)
		return;
	int i, j;
	for(i = 1; i < len; ++i)
	{
		if(array[i] < array[i-1])						// 无序表的第一个元素小于有序表的最后一个元素
		{
			T temp = array[i];
			int insertIndex = BinarySearch(array, 0, i, temp);
			
			for(j = i-1; j >= insertIndex; --j)			// 移动元素
			{
				array[j+1] = array[j];
			}
			array[insertIndex] = temp;			// 在合适位置上插入新元素
		}
	}
}

(4) 测试

#include <iostream>
#include "BinaryInsertionSort.h"
using namespace std;

int main()
{
	int array[] = {5,1,8,3,-2};
	int len = sizeof(array)/sizeof(int);
	BinaryInsertionSort(array, len);
	
	for(int i = 0; i<len; ++i)
		cout << array[i] << " ";
	cout<<endl;
	return 0;
}

测试结果

-2 1 3 5 8

(5) 复杂度分析
a. 其主要操作为查找插入下标(比较)+后移赋值
b. 此处采用的二分查找算法,对于一个长度为n的序列,比较次数为log2 n,故查找下标算法的时间复杂度为O(log2 n)
c. 最好的情况是初始序列正序:每个元素所在位置即为它的插入位置,此时需要进行log2 2 + log2 3 +···+log2 n次查找,时间复杂度为O(log2 n)
d. 最坏的情况是初始序列逆序:每次都要在起始位置插入元素,此时需要进行1+2+···+(n-1)=n(n-1)/2次元素的移动,以及n-1次元素的赋值(插入),时间复杂度为O(n^2)
e.平均时间复杂度为O(n^2)。
f. 采用就地排序,空间复杂度为O(1)。
(6) 稳定性
二分插入排序是稳定的,不过在实现的时候需要注意,详见BinarySearch函数

3 希尔排序(Shell Sort)

(1) 基本思想
希尔排序,又称缩小增量排序,是对直接插入排序的改进。改进的思路是将初始序列按步长gap分组,对每组采用直接插入排序方法进行排序,随着步长逐渐缩小,所分成的组包含的元素越来越多 ,当gap=1时,所有元素合成为1组,构成一组有序序列。
(2) 图文分析

(3) 代码实现

// 希尔排序
template <typename T>
void ShellSort(T array[], int len)
{
	if(array == nullptr || len < 0)
		return;
	
	int gap;
	int i, j;
	for(gap = len / 2; gap > 0; gap /= 2)
	{
		for(i = gap; i < len; ++i)				// 将距离为gap的元素划分到一个分组,共有gap个分组
		{
			if(array[i] < array[i-gap])			// 每个元素与自己组内的元素进行直接插入排序
			{
				T temp = array[i];
				for(j = i - gap; j>=0 && array[j] > temp; j -= gap)
				{
					array[j+gap] = array[j];
				}
				array[j+gap] = temp;
			}
		}
	}
}

(4) 测试

int main()
{
	int array[] = {8,1,3,5,6,4,9,7,2,5};
	int len = sizeof(array)/sizeof(int);	ShellSort(array, len);
	
	for(int i = 0; i<len; ++i)
		cout << array[i] << " ";
	cout<<endl;
	return 0;
}

测试结果

1 2 3 4 5 5 6 7 8 9

(5) 复杂度分析
a. 其主要操作为查找插入下标(比较)+后移赋值
b.时间复杂度与步长序列挂钩

增量序列:
希尔增量:n/(2k),最坏的时间复杂度是O(n2),希尔增量问题在于这些增量未必互素,因此较小的增量可能影响很小。
Hibbard增量:2k-1,最坏的时间复杂度为O(n1.5),这些增量与希尔增量几乎一致,但关键的区别是相邻的增量没有公因子。
Sedgewick增量:(1, 5, 19, 41, 109,...),是目前已知的最好增量序列。
c. 采用就地排序,空间复杂度为O(1)。

(6) 稳定性
从上图可知,相等的数据在排序过程中可能会交换位置(两个5),因此该算法是不稳定的。

posted @ 2017-02-10 20:46  west000  阅读(702)  评论(0编辑  收藏  举报