数据结构:排序

Posted 山舟

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了数据结构:排序相关的知识,希望对你有一定的参考价值。


本文的排序算法均以升序为例


一、排序的相关概念

排序:所谓排序,就是使一串记录,按照其中的某个或某些关键字的大小,递增或递减的排列起来的操作。

稳定性:假定在待排序的记录序列中,存在多个具有相同的关键字的记录,若经过排序,这些记录的相对次序保持不变,即在原序列中,r[i]=r[j],且r[i]在r[j]之前,而在排序后的序列中,r[i]仍在r[j]之前,则称这种排序算法是稳定的;否则称为不稳定的。


二、直接插入排序

直接插入排序是一种简单的插入排序法,其基本思想是:把待排序的记录按其关键码值的大小逐个插入到一个已经排好序的有序序列中,直到所有的记录插入完为止,得到一个新的有序序列 。

在这里插入图片描述


直接插入排序在查找第i个元素的位置时,它前面的元素已经有序,所以只要在前面的有序序列找到第i个元素的位置,那么这i个元素就是有序的。

以上面的动图为例讲解插入排序的过程:

1.第一趟排序:从第二个元素7开始,向它前面的有序序列(9)中插入,7比9小且9之前没有其它元素,所以放在9的前面。
第一趟排序结束后,序列为7 9 8 2 5 1 3 6 4

2.第二趟排序:从第三个元素8开始,向它前面的有序序列(7 9)中插入,8比9小且比9前面的7大,所以放在9的前面、7的后面。
第一趟排序结束后,序列为7 8 9 2 5 1 3 6 4

3.第三趟排序:从第4个元素2开始,向它前面的有序序列(7 8 9)中插入,向前寻找比它小的元素,直到找到数组开始也没有找到,则把2设置为数组的第一个元素。
第一趟排序结束后,序列为2 7 8 9 5 1 3 6 4

4.第四趟排序:从第5个元素5开始,向它前面的有序序列(2 7 8 9)中插入,向前寻找,5比7小且比2大,将其插入到2和7中间。
第一趟排序结束后,序列为2 5 7 8 9 1 3 6 4

5.第五趟排序:从第6个元素1开始,向它前面的有序序列(2 5 7 8 9)中插入,向前寻找,直到找到数组开始也没有找到,则把1设置为数组的第一个元素。
第一趟排序结束后,序列为1 2 5 7 8 9 3 6 4

6.第六趟排序:从第7个元素3开始,向它前面的有序序列(1 2 5 7 8 9)中插入,向前寻找,3比5小且比2大,将其插入到2和5中间。
第一趟排序结束后,序列为1 2 3 5 7 8 9 6 4

7.第七趟排序:从第8个元素6开始,向它前面的有序序列(1 2 3 5 7 8 9)中插入,向前寻找,6比7小且比5大,将其插入到5和7中间。
第一趟排序结束后,序列为1 2 3 5 6 7 8 9 4

8.第八趟排序:从第9个元素4开始,向它前面的有序序列(1 2 3 5 6 7 8 9)中插入,向前寻找,4比5小且比3大,将其插入到3和5中间。
第一趟排序结束后,序列为1 2 3 4 5 6 7 8 9

遍历到数组结尾,排序完成


代码如下(示例):

void InsertSort(int* a, int n)
{
	int i = 0;
	//注意循环的结束条件使i<n-1
	for (i = 0; i < n - 1; i++)
	{
		int end = i;//当前位置
		int temp = a[end + 1];//拿到下一个位置的数
		while (end >= 0)
		{
			//当temp比end位置的数小时
			if (temp < a[end])
			{
				a[end + 1] = a[end];//用end位置的数覆盖end+1位置的数
				end--;//end向前移动
			}
			//temp比end位置的数大,跳出循环
			else
				break;
		}
		a[end + 1] = temp;//把temp放到end+1的位置
	}
}

直接插入排序:

  1. 元素集合越接近有序,直接插入排序算法的时间效率越高。当元素集合基本有序时,直接插入排序算法的时间复杂度可逼近O(N)
  2. 时间复杂度:O(N^2)
  3. 空间复杂度:O(1)
  4. 稳定性:稳定

二、希尔排序

希尔排序法又称缩小增量法。希尔排序法的基本思想是:先选定一个整数gap,把待排序数组中所有数分成gap组,所有距离为gap的数分在同一组内,并对每一组内的数进行直接插入排序。然后,重复上述分组和排序。当gap=1时,此时算法就是一个直接插入排序,但是数组中的数已经基本有序。


在这里插入图片描述


在这里插入图片描述


代码如下(示例):

//gap越大,大的和小的数可以更快的向对应的方向挪,但越不接近有序
//gap越小,大的和小的数可以更慢的向对应的方向挪,但越接近有序
void ShellSort(int* a, int n)
{
	int i = 0;
	int gap = n;
	while (gap > 1)
	{
		//如果gap < 3,最后一次排序时gap是0,+1保证最后一次排序时gap一定是1
		gap = gap / 3 + 1;//这里的3也可设置为4,5……
		//排序的逻辑与直接插入排序相同
		for (i = 0; i < n - gap; i++)//注意这里的循环判断条件,i < n - gap是为保证temp访问数组时不越界
		{
			int end = i;
			int temp = a[end + gap];
			while (end >= 0)
			{
				if (temp < a[end])
				{
					a[end + gap] = a[end];
					end -= gap;
				}
				else
					break;
			}
			a[end + gap] = temp;
		}
	}
}

希尔排序:

  1. 希尔排序是对直接插入排序的优化
  2. gap > 1时都是预排序,目的是让数组更接近于有序。当gap == 1时,数组已经接近有序,此时就是一个直接插入排序,但速度很快。这样整体而言,可以达到优化的效果。
  3. 希尔排序的时间复杂度不易计算,需要进行推导,推导出来平均时间复杂度约为O(N^1.3)
  4. 稳定性:不稳定

三、选择排序

基本思想:每一次从待排序的数据元素中选出最小和最大的一个元素,存放在序列的起始、和末尾位置,直到全部待排序的数据元素排完 。

下面的动态图中每次仅选出最小的元素

在这里插入图片描述


代码如下(示例):

//交换两个数
void Swap(int* a, int* b)
{
	int temp = *a;
	*a = *b;
	*b = temp;
}

//代码中每次循环选出最小和最大值
void SelectSort(int* a, int n)
{
	//left,right是每次排序的范围
	int left = 0;
	int right = n - 1;
	int i = 0;
	while (left < right)
	{
		//把最小、最大值的下标初始化为left和right
		int minIndex = left;
		int maxIndex = right;
		//遍历找到[left,right]区间中的最小最大值的下标
		for (i = left; i <= right; i++)
		{
			if (a[i] < a[minIndex])
				minIndex = i;
			if (a[i] > a[maxIndex])
				maxIndex = i;
		}
		Swap(&a[minIndex], &a[left]);//把最小的数放在下标为left的位置
		
		//如果left和maxIndex是同一位置,下标为left的数与下标为minIndex的数交换后
		//maxIndex位置的数成了最小值,所以要修正maxIndex的值
		if (left == maxIndex)
			maxIndex = minIndex;
		Swap(&a[maxIndex], &a[right]); //把最大的数放在下标为right的位置
		
		left++;
		right--;
	}
}

选择排序:

  1. 选择排序思考非常简单,但是效率很低。实际中很少使用
  2. 时间复杂度:O(N^2)
  3. 空间复杂度:O(1)
  4. 稳定性:不稳定

四、堆排序

堆排序在数据结构(五):堆中已经介绍过,此处不再赘述。


五、冒泡排序

在这里插入图片描述


在这里插入图片描述


代码如下(示例):

void BubbleSort(int* a, int n)
{
	int i = 0, j = 0;
	for (i = 0; i < n - 1; i++)
	{
		int flag = 1;//flag是1说明本次冒泡没发生交换
		for (j = 0; j < n - i - 1; j++)
		{
			//相等不换,保证稳定性
			if (a[j] > a[j + 1])//如果j位置的元素比j+1位置的元素大则交换
			{
				Swap(&a[j], &a[j + 1]);
				flag = 0;//本次发生交换,flag置为0
			}
		}
		//flag是1说明本次冒泡没发生交换,则数组已经有序,跳出循环
		if (flag)
			break;
	}
}

冒泡排序:

  1. 冒泡排序是一种非常容易理解的排序
  2. 时间复杂度:O(N^2)
  3. 空间复杂度:O(1)
  4. 稳定性:稳定

感谢阅读,如有错误请批评指正

以上是关于数据结构:排序的主要内容,如果未能解决你的问题,请参考以下文章

以下代码片段的时间复杂度是多少?

ElasticSearch学习问题记录——Invalid shift value in prefixCoded bytes (is encoded value really an INT?)(代码片段

排序02-直接插入排序法

markdown 数组排序片段

Java排序算法 - 堆排序的代码

Realm和RecyclerView项目排序和自动ViewPager片段通信