排序算法(一)——插入排序【C/C++】

目录

  • 前言
  • 一、几点注意
    • 1.1 排序时长
    • 1.2 获得随机数组
    • 1.3 单调性
  • 二、插入类排序
    • 2.1 插入类排序的分类
    • 2.2 直接插入排序
      • 2.2.1 直接插入排序的思路
      • 2.2.2 具体代码
      • 2.2.3 测试结果
    • 2.3 折半插入排序
      • 2.3.1 折半插入排序的思路
      • 2.3.2 具体代码
      • 2.3.3 测试代码
    • 2.4 希尔排序
      • 2.4.1 折半插入排序的思路
      • 2.4.2 具体代码
      • 2.4.3 测试结果
    • 2.5 三种插入排序的分析
  • 总结


前言

这里保存个人对常见插入排序算法的理解和实现思路过程。
供大家学习和参考。


一、几点注意

1.1 排序时长

这里对于代码的排序时长采用C语言中获取时间的方法。
引入头文件,其中定义了结构体timeb和函数ftime,可以获得秒和毫秒的时间。

struct timeb
  {
    time_t time;		/* Seconds since epoch, as from `time'.  */
    unsigned short int millitm;	/* Additional milliseconds.  */
    short int timezone;		/* Minutes west of GMT.  */
    short int dstflag;		/* Nonzero if Daylight Savings Time used.  */
  };
  /* Fill in TIMEBUF with information about the current time.  */
 
extern int ftime (struct timeb *__timebuf);

可以将结构体timeb作为ftime的传出参数来获取时间。
timeb中time为秒,millitm为毫秒,则time*1000+millitm则可以获得毫秒级的时间。

#include
#include

long getSystemTime() {
	struct timeb tb;
	ftime(&tb);
	return tb.time * 1000 + tb.millitm;
}

若要获取排序时长,则做两个差值即可。

long t_start = getSystemTime();
long t_end = getSystemTime();
printf("排序用时:%ld ms\n", t_end - t_start);

同理也可以使用在需要了解时间复杂度的地方。

1.2 获得随机数组

这里采用srand()来获得随机数组。
srand()为初始化随机数发生器,用于设置rand()产生随机数时的种子。传入的参数seed为unsigned int类型,通常我们会使用time(0)或time(NULL)的返回值作为seed。
想要获得<=99的元素即可rand()%100,其他同理。

1.3 单调性

这里均为递增序列,如果想要递减序列反之即可,自己琢磨琢磨。

二、插入类排序

2.1 插入类排序的分类

常见的插入类排序分为直接插入排序,折半插入排序,希尔排序等等,这里着重实现这三种插入排序。

2.2 直接插入排序

2.2.1 直接插入排序的思路

将第i个记录插入到前面i-1个已经排好序的记录中。
步骤就两步,第一步把比他大的往他后面挪,第二部,把他放进去。
这里最开始排序的序号从1开始,因为0号元素就一个,当只有一个元素时,默认有序。用temp保存1号元素,如果0号元素比他大,0号元素覆盖1号元素,再将temp插入。

2.2.2 具体代码

void InSertSort(int* arr, int n) {
	printf("直接插入排序:\n");
	int i, j;
	for (i = 1; i < n; i++) {
		int temp = arr[i];
		for (j = i - 1; j >= 0 && arr[j] > temp; j--) {
			arr[j + 1] = arr[j];
		}
		arr[j + 1] = temp;
	}
}

着重说一下插入算法的核心

for (j = i - 1; j >= 0 && arr[j] > temp; j--) {
			arr[j + 1] = arr[j];
		}
		arr[j + 1] = temp;

j的初值为i的前一个元素,如果该元素下标大于0并且该元素大于temp(temp保存的是arr[i],temp也是待排序的元素),那么就把这个元素往后放一格,也就是arr[j + 1] = arr[j]。
如果一直大就一直往后挪。如果待排元素为排好数组中最小的,那么j>=0的最后一次循环后,j=-1,则temp要放的位置也就是0号元素即j+1,因此arr[j + 1] = temp。
所以temp最后放置的下标也就是j+1。如果是最大的,那么第二层for循环就进不去,侧面说明直接插入排序是稳定的。
直接插入排序这里是一次放一格,步长为1,相当于将一个n个元素的数组按步长为1分成了n份,理解这里的步长对后面的希尔排序有很大帮助。

2.2.3 测试结果

这里先测试一个20个元素的数组。

排序算法(一)——插入排序【C/C++】_第1张图片
因为元素个数太少,排序时长小于1ms所以显示0ms,再测试一个元素个数为10000的数组,就不打印了。
排序算法(一)——插入排序【C/C++】_第2张图片
时间复杂度和空间复杂度之后分析。

2.3 折半插入排序

2.3.1 折半插入排序的思路

同直接插入排序一样,也是将第i个记录插入到前面i-1个已经排好序的记录中,也是从1号元素开始排序。(前提是没有哨兵)。
有所不同的是,这两个算法对于元素位置的确定的方法不同。对于大数组来说由明显区别。
直接插入排序是对有序数组一个一个从后往前进行比较,而折半插入排序是将排好元素一分为二来进行查找插入的位置。
同折半查找一样,折半查找就是在有序数组中找到值为key的元素,借助low,mid,high来实现,这里依旧采用。

2.3.2 具体代码

void BinarySort(int* arr, int n) {
	printf("折半插入排序:\n");
	int i, j;
	int low, mid, high;
	for (i = 1; i < n; i++) {
		int temp = arr[i];
		low = 0;
		high = i-1;
		while (low <= high) {
			mid = (low + high) / 2;
			if (arr[mid] > temp) {
				high = mid - 1;
			}
			else {
				low = mid + 1;
			}
		}
		for (j = i - 1; j >= low; j--) {
			arr[j + 1] = arr[j];
		}
		arr[j + 1] = temp;
	}
}

low初值为0,high初值为i-1,也就是排好数组的最后一个元素的下标,因为相当于是在0到i-1中找i的位置。
只要low<=high,mid就他俩一半,如果arr[mid]>temp,说明中值偏大,上限减少,即high=mid-1,反之中值偏小,下限增减,即low=mid+1,mid始终是中值的下标。
如果temp为排好元素中最大的,一直low=mid+1,最终low>high,出循环,j等于排好数组中最后一个元素的下标,low>high也就low>j,所以第二个for循环进不去,arr[j+1]=temp,也就是arr[i]=temp,自己等于自己了属于是,所以侧面说明折半插入排序也是稳定的。

2.3.3 测试代码

还是显示一个20个元素的数组
排序算法(一)——插入排序【C/C++】_第3张图片
再来一个元素个数为10000的,也不打印了。
在这里插入图片描述
时间复杂度和空间复杂度之后分析。

2.4 希尔排序

2.4.1 折半插入排序的思路

将待排数组分成若干个稀疏的子序列,分别进行直接插入排序,使得稀疏的子序列较为有序,然后再全部进行次直接插入排序,即可完成。
这里采用划分子序列的方法采用业界统一的gap = gap / 3 + 1。
步骤也就两步:第一步,分组,第二步,分组排序。
首先分组,每回分gap = gap / 3 + 1。
按照分好的小组每组的第2号元素开始直接插入排序,每回增加的跨度为gap,也就是步长。
直到gap=1,进行一次直接插入排序。

2.4.2 具体代码

void ShellSort(int* arr, int n) {
	printf("希尔排序:\n");
	int gap = n;
	int i, j, k;
	do {
		gap = gap / 3 + 1;
		for (i = 0; i < gap; i++) {//分的小组
			for (j = i + gap; j < n; j = j + gap) {//每个小组的第二号元素(默认一号元素有序)开始直接插入排序,步长为gap
				int temp = arr[j];
				for (k = j - gap; k >= 0 && arr[k] > temp; k = k - gap) {
					arr[k + gap] = arr[k];
				}
				arr[k + gap] = temp;
			}
		}
	} while (gap > 1);
}

do{}while;保证gap=1进行完可以退出。
但是希尔排序虽然组内是直接插入排序,是稳定的,但组外不同小组之间的元素则是不稳定的,所以希尔排序是不稳定的。

2.4.3 测试结果

先进行元素个数为20的数组。
排序算法(一)——插入排序【C/C++】_第4张图片
再进行元素个数为10000的数组

排序算法(一)——插入排序【C/C++】_第5张图片
快的夸张啊。

2.5 三种插入排序的分析

直接插入排序

时间复杂度:最坏情况下为O(N2),此时待排序列为逆序,或者说接近逆序。
      最好情况下为O(N),此时待排序列为升序,或者说接近升序。
      平均为O(N2)
空间复杂度:因为每回只移动一个所以空间复杂度为O(1)。
稳定性:稳定。

折半插入排序

时间复杂度:最坏情况下为O(N2)
      最好情况下为O(Nlog2N)参考折半查找的时间复杂度。
      平均O(N2)
空间复杂度:因为每回只移动一个所以空间复杂度为O(1)。
稳定性:稳定。

希尔排序

时间复杂度平均:业界统一认为为O(N1.3)
空间复杂度:因为每回只移动一个所以空间复杂度为O(1)
稳定性:不稳定。


总结

有什么问题欢迎大家指正!

你可能感兴趣的:(#,排序算法,数据结构与算法-耿国华,排序算法,c语言,c++)