【从头学数据结构和算法】插入排序及其优化(c++实现)

c++实现的插入排序及其优化



普通插入排序

原理

每次取一个,按次在前面已排好序的区间中 依次比较找到第一个大于当前待排数据的位置,然后移动原有数据,插入新的数据。

基本性质

  • 时间复杂度(主要是看移动次数)
    ——最好情况:O(n)
    ——最坏情况和平均:O(n^2)
  • 空间复杂度
    ——O(1):原地排序
  • 稳定性
    ——可做到稳定
  • 相比冒泡排序的优点
    ——移动只需一次赋值操作

代码

template<typename T> 
void insert_sort0(T *a, int len) {
	// 首先检查数据的合法性(TODO 不完善).
	if (a == NULL || len <= 1) {
		return;
	}
	
	int cur = 0;	// 当前待排序的数据
	int j = 0;		// 内层循环(移动数据)计数变量   
	
	// 对未排序区间进行遍历 
	for (int i = 1; i < len; ++i) {
		// 记录当前数据值
		cur = a[i]; 
		// 在已排序区间中查找插入位置
		for (j = i-1; j >= 0; --j) {
			if (a[j] > cur) {
				// 未找到插入位置,则往后移动已排序数据 
				a[j+1] = a[j]; 
			}
			else {
				// 比当前值小,则找到插入位置,结束循环 
				break;	
			}
		}  
		a[j+1] = cur; 
	} 
} 

优化1:二分查找找插入位置

原理

与普通的相同,只不过在找插入位置时,由于前面的都是已排好序的,所以可以用二分查找来加速。(此处二分查找查找第一个大于待插入数据的位置)

基本性质

  • 时间复杂度(主要是看移动次数)
    ——最好情况:O(n)
    ——最坏情况和平均:O(n^2)
  • 空间复杂度
    ——O(1):原地排序
  • 稳定性
    ——可做到稳定

代码

/** 
 * @brief 辅助插入排序优化1进行 二分查找[从小到大] (临时) 
 * @param a:查找数据; len:查找区间(a[0]-a[len]),tar:待查找数据	
 * @return int:插入位置下标 
 * @note
 *	-辅助插入排序优化1,查找待插入位置 
 *	-不完善,不过无所谓,以后写道查找的时候再优化
 *  -查找的是第一个大于给定值的元素的下标(维持稳定性) 
 *	-待查数据区间已经排好序 
 */
template<typename T> 
int binary_search_simple(T *a, int len, T tar) {
	// 首先检查数据的合法性(TODO 不完善).
	if (a == NULL || len < 1) {
		return -1;
	}
	
	// 特殊条件 
	if (a[0] > tar) {
		return 0;
	}
	else if (a[len-1] <= tar) {
		return len;
	} 
	
	int low = 0;	// 待查最左位置
	int high = len-1;	// 待查最右位置
	int mid = 0;	// 待查中间位置
	
	// 查找第一个大于等于给定值的元素
	while (low <= high) {
		// 待查中间位置【不使用(a+b)/2是防止溢出;位运算比除法效率高;位运算比加法优先级低】
		mid = low+((high-low)>>1);
		if (a[mid] > tar) {
			// 如果mid大于目标,就要看是否是需要的
			if (a[mid-1] <= tar) {
				return mid;
			}
			high = mid - 1;
		}
		else {
			low = mid + 1; 
		}	
	}
	return -1;	
}
/** 
 * @brief 插入排序优化1[从小到大](TODO 不完善) 
 * @param a:待排序数组; len:待排序元素	
 * @return void	原地排序,直接修改
 * @note
 *	-时间复杂度:最好 - O(n);最坏 - O(n^2);平均 - O(n^2) 
 *	-空间复杂度:O(1);是原地排序算法 
 *	-稳定性:稳定 
 *	-使用二分查找找插入位置
 */
template<typename T> 
void insert_sort1(T *a, int len) {
	// 首先检查数据的合法性(TODO 不完善).
	if (a == NULL || len <= 1) {
		return;
	}
	
	T cur = 0;		// 当前待排序的数据 
	int index = -1;	// 二分查找到的插入位置
	int j = 0;		// 内层循环(移动数据)计数变量  
	
	// 对未排序区间进行遍历 
	for (int i = 1; i < len; ++i) {
		// 记录当前数据值
		cur = a[i]; 
		// 在已排序区间中查找插入位置
		index = binary_search_simple(a, i, cur);
		// 移动
		for (j = i; j > index; --j) {
			a[j] = a[j-1];	
		} 
		// 插入数据
		a[j] = cur; 
	}
}

优化2:希尔排序(分治)

原理

  • 待插入的元素如果需要插入到有序区间的首部时,需要移动大量的数据来腾出空位置供新元素插入。为了解决这个问题,希尔排序算法就产生了。

希尔排序是在直接插入排序的基础上增加了分组的思想:先取较大的间隔来分组,对每组进行插入排序,然后缩小间隔重新分组插入排序。 由于前期工作使得数据变得整体有序,所以总的来说,插入排序的效率是提高的。

间隔一般取: 间隔=向下取整【间隔(初始为整个数据长度)/ 3】+1 ;

性质

  • 时间复杂度(主要是看移动次数)
    ——最好情况:O(n)
    ——最坏情况和平均:O(logn)
  • 空间复杂度
    ——O(1):原地排序
  • 稳定性
    ——不稳定!!!

代码

template<typename T> 
void insert_sort2(T *a, int len) {
	// 首先检查数据的合法性(TODO 不完善).
	if (a == NULL || len <= 1) {
		return;
	}
	
	T cur = 0;		// 当前待排序的数据 
	int index = -1;	// 二分查找到的插入位置
	int zu = 0;		// 内层循环(分组)计数变量    
	int i = 0;		// 内层循环(每组未排序空间)计数变量
	int j = 0;		// 内层循环(移动数据)计数变量  
	
	// 对gap进行遍历
	for (int gap = len/3+1; ; gap = gap/3+1) {
		// 分组 
		for (zu = 0; zu < gap; ++zu) 
			// 对每组的未排序区间进行遍历 
			for (i = gap+zu; i < len; i+=gap) {
				// 记录当前数据值
				cur = a[i]; 
				// 在已排序区间中查找插入位置
				for (j = i-gap; j >= 0+zu; j-=gap) {
					if (a[j] > cur) {
						// 未找到插入位置,则往后移动已排序数据 
						a[j+gap] = a[j]; 
					}
					else {
						// 比当前值小,则找到插入位置,结束循环 
						break;	
					}
				}  
				a[j+gap] = cur; 
			}
		
		// gap==1之后就可以结束了
		if (gap == 1) {
			break;
		} 
	} 
}

测试

以20000个整数进行测试,记录时间,同时assert是否与algorithm中sort结果相同。测试代码略,可见完整代码中。
测试结果时间(同时,assert未报错)如下图所示:
在这里插入图片描述
可见,优化效率提升还是非常明显的,而且插入排序本身也比冒泡排序要快(详情见【从头学数据结构和算法】冒泡排序及其优化(c++实现))。
看上希尔排序都快赶上sort函数的效率了,但实际上数据量再增大后还是能够看到有差距的。


完整代码

排序及优化及测试 完整代码在:
https://github.com/zhangdanzhu/basic_data-structure_algorithm/blob/master/sort/cpp/insertion_sort.cpp

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值