排序算法学习(1)(直接插入排序,希尔排序,选择排序,堆排序,冒泡排序)

文章目录

  • 直接插入排序
    • 代码实现
    • 复杂度的计算
  • 希尔排序
    • 希尔排序的预排序
    • 代码实现
  • 选择排序
    • 代码实现
  • 堆排序
  • 冒泡排序
    • 代码实现

注:以下排序都是以排升序为例。

直接插入排序

排序算法学习(1)(直接插入排序,希尔排序,选择排序,堆排序,冒泡排序)_第1张图片

直接插入排序的主要思路就是:
1.首先默认第一个元素是有序的。
2.然后将其下一个元素作为待排序的元素,插入到前面有序序列的相应位置。至于插入的过程,如果遇到比待排序大的元素,则这个元素后移,直到遇到比其小的元素,然后将待插入元素放入其前一个位置。

在这里插入图片描述

代码实现

void Insertsort(int *a, int sz)
{
     
	int i = 0;
	for (i = 0; i < sz-1; i++)
	{
         
		//用j来记录i的位置,这样可以省得到时候有些情况i从头或接近从头开始走
		int j = i;
		int temp = a[i + 1];
		while (i >= 0)
		{
     
			if (temp<a[i])
			{
     
				a[i + 1] = a[i];
				i--;
			}
			else
			{
     
				break;
			}
		}
		a[i + 1] = temp;
		i = j;
	}
}

注:绝大部分的直接插入排序算法其实是没有在代码中记录i的位置的,其实记录了i的位置可以提高执行效率,因为这样可以省得到时候有些情况i从头或接近从头开始走。
在这里插入图片描述

复杂度的计算

在这里插入图片描述

在代码中,创建的临时变量个数是常数个,所以空间复杂度为O(1)。


希尔排序

在上面的直接插入排序中我们会发现:
1.普通插入排序的时间复杂度最坏情况下为O(N2),此时待排序列为逆序,或者说接近逆序。
2.普通插入排序的时间复杂度最好情况下为O(N),此时待排序列为升序,或者说接近升序。

于是希尔这个科学家就想:若是能先将待排序列进行一次预排序,使待排序列接近有序(接近我们想要的顺序),然后再对该序列进行一次直接插入排序。因为此时直接插入排序的时间复杂度为O(N),那么只要控制预排序阶段的时间复杂度不超过O(N^2),那么整体的时间复杂度就比直接插入排序的时间复杂度低了。

希尔排序的预排序

希尔排序,又称缩小增量法。那么如何缩小增量了?
1.先选定一个小于N的整数gap作为第一增量,然后将所有距离为gap的元素分在同一组,并对每一组的元素进行直接插入排序。然后再取一个比第一增量小的整数作为第二增量,重复上述操作 (一般情况下我们取得第一增量大小是序列长度的一半)
2.当增量的大小减到1时,其实此时预排序已经完成了,这时已经达到我们一开始要的目的:让序列接近我们要的顺序。
3.最后对整个序列进行直接插入排序即可。
举个例子来看下。

排序算法学习(1)(直接插入排序,希尔排序,选择排序,堆排序,冒泡排序)_第2张图片

在这个序列中,gap=3时其实就是对原序列进行了预排序。使其接近我们要的升序,方便之后的直接排序。

注:希尔排序和直接插入排序是稳定排序:我们可以发现,在希尔排序的过程中,序列中两个9的相对位置没有发生改变,具有这样特性的排序,我们称之为稳定排序。

代码实现

void Shellsort(int *a, int sz)
{
     
	int i = 0;
	int gap = sz / 2;
	while (gap >= 1)
	{
     
		for (i = 0; i < sz-gap; i++)
		{
     
			int j = i;
			int end = i;
			int temp = a[i + gap]; //这儿一定要记录下那个位置的数据,而不是写成temp=i+gap记录下标的形式,因为之后那个记录的下标的位置的数据可能被前面的数据前移占据了。
			while (end >= 0)
			{
     
				if (temp < a[end])
				{
     
					//a[temp] = a[end]; 因为下面要用到a[end+gap]=a[temp],所以这儿不能写成a[temp]
					a[end + gap] = a[end];
					end -= gap;
				}
				else
				{
     
					break;
				}
			}
			a[end + gap] = temp;
			i = j;
		}
		gap = gap / 2;
	}
}

选取这种增量方法的时间复杂度:O(nlogn),具体这个我是这么理解的,首先预排序阶段,你每次都将gap除以2,其实也就是高度次:logn,预处理中的交换次数可以看成常量,然后预处理完,这时已经接近最好情况的直接排序了,时间复杂度为O(n),所以最终的时间复杂度为O(nlogn),具体的证明过程等后序会补充更新。
空间复杂度:这里我们只定义了常数个常量,故空间复杂度为O(1)。


选择排序

在这里插入图片描述

选择排序
1.首先从整个数组中选出最小值,记录下最小值位置的下标与第一个位置交换,这样相当于最小值就到了头上。
2.此时将除最小值之后的元素再来选出次小的数,与第二个位置交换,之后迭代这个过程,最终就可以排成升序。

代码实现

void chosesort(int *a, int sz)
{
     
	int i = 0;
	for (i = 0; i < sz; i++)
	{
     
		int min = i;
		for (int j = i; j < sz; j++)
		{
     
			if (a[j] < a[min])
			{
     
				min = j;
			}
		}
		swap(&a[i], &a[min]);
	}
}

时间复杂度:最坏的情况下是一个等差数列为O(n^2)
空间复杂度:创建了常数个变量,为O(1)

实际上,我们可以一趟选出两个值,一个最大值一个最小值,然后将其放在序列开头和末尾,这样可以使选择排序的效率快一倍。

void chosesort(int* a, int sz)
{
     
	int start = 0;
	int end = sz - 1;
	while (start < end)
	{
     
		int min = start;
		int max = start;
	    for (int j=start; j <= end; j++)
		{
     
			if (a[j] < a[min])
			{
     
			  min = j;
			}
			if (a[j]>a[max])
			{
     
			  max = j;
			}
		}
		swap(&a[start], &a[min]);
		//万一最大的值在最左边,这样max的位置的值就被最小值覆盖了,如果没有这个处理,会导致下面交换时出错。
		if (max == start)
		{
     
			max = min;
		}
		swap(&a[end], &a[max]);
		start++;
		end--;
	}
}

注:这里要小心一种极端情况:万一最大的值在最左边,这样max的位置的值就被最小值覆盖了,如果没有if这个处理,会导致下面交换时出错。


堆排序

博主之前专门写了一篇关于堆排序以及堆的文章,由于是最近不久写的,这里就不再赘述了,堆排序以及堆相关的知识


冒泡排序

在这里插入图片描述

相信冒泡排序是很多小伙伴第一个知道的排序算法。
其实它的思想正和他的取名一样,它就是每趟排序冒出一个最大(最小)值。那么它是如何冒的了?很简单,相邻两个元素比较,前一个比后一个大,则交换。

代码实现

void Bubblesort(int *a, int sz)
{
     
	int i = 0;
	int flag = 0; //记录原序列是否有序
	for (i = 0; i < sz - 1; i++)
	{
     
		int j = 0;
		for (j = 0; j < sz - i - 1; j++)
		{
     
			if (a[j]>a[j + 1])
			{
     
				swap(&a[j], &a[j + 1]);
				flag = 1;   //交换过了,说明原序列无序
			}
		}
		if (flag == 0)  //原序列一次交换都没有发生,说明原序列本来就有序,无需在继续进行
		{
     
			break;
		}
	}
}

注:这里可以自己定义一个标识符flag来记录原序列是否发生过交换,如果没有发生过,则说明原序列本来就有序,无需再进行之后的趟数。

你可能感兴趣的:(数据结构,算法,数据结构,经验分享)