【数据结构入门】排序算法之插入排序与选择排序

目录

前言

一、排序的概念及运用

1.排序的概念

2.排序的运用

3.常见排序算法

二、插入排序与选择排序

2.1插入排序

2.1.1直接插入排序

1)基本思想

2)具体步骤

3)算法特性

4)算法实现

2.1.2希尔排序

1) 基本思想 基本思想)

2)具体步骤

3)算法特性

4)算法实现

2.2选择排序

2.2.1直接选择排序

1) 基本思想 基本思想)

2)具体步骤

3)算法特性

4)算法实现

[2.2.2 堆排序](#2.2.2 堆排序)

1) 基本思想 基本思想)

2)具体步骤

3)算法特性

4)算法实现

三、算法复杂度及稳定性分析

总结


前言

在数据结构中,排序是指将一组数据按照特定的规则重新排序的过程。排序可以使数据按照升序或者降序排列,从而方便后续的操作和查找。

一、排序的概念及运用

1.排序的概念

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

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

例如:

内部排序:数据元素全部放在内存中的排序。

外部排序:数据元素太多不能同时放在内存中,根据排序过程的要求不能在内外存之间移动数据的排序。

2.排序的运用

排序在生活中的使用无处不在,成绩排名、商品排名、电影榜单等等数不胜数。

3.常见排序算法

二、插入排序与选择排序

2.1插入排序

2.1.1直接插入排序

1)基本思想

直接插入排序是一种简单的排序算法,它的基本思想是将待排序的数据分成已排序和未排序两部分,每次从未排序部分中取出一个元素,然后将其有序地插入到已排序部分的合适位置。

当插入第i(i>=1)个元素时,前面的array[0],array[1],...,array[i-1]已经排好序,此时用array[i]的排序码与array[i-1],array[i-2],...的排序码顺序进行比较,找到插入位置即将array[i]插入,原来位置上的元素顺序后移

动画演示:

2)具体步骤
  1. 将第一个元素视为已排序部分,将剩余的元素视为未排序部分。

  2. 从未排序部分取出第一个元素,将其插入到已排序部分的合适位置。插入时,从后往前逐个比较已排序部分的元素,将大于待插入元素的元素依次后移,直到找到一个小于或等于待插入元素的位置。

  3. 重复步骤2,直到未排序部分中的所有元素都插入到已排序部分。

3)算法特性
  1. 元素集合越接近有序,直接插入排序算法的时间效率越高

  2. 时间复杂度:直接插入排序的时间复杂度是O(n^2),其中n是待排序数据的个数。当输入数据已经基本有序时,直接插入排序的性能较好,时间复杂度可以降低到O(n)。但当输入数据完全逆序时,直接插入排序的性能较差,时间复杂度会达到最大值O(n^2)。

  3. 空间复杂度:O(1),它是一种稳定的排序算法

  4. 稳定性:稳定

4)算法实现
cpp 复制代码
void InsertSort(int* a, int n)
{
	for (int i = 0; i < n; i++)
	{
		int end = i-1;
		int tmp = a[i];
		//将tmp插入到[0, end]区间中,保持有序
		while (end >= 0)
		{
			if (tmp < a[end])
			{
				a[end + 1] = a[end];
				--end;
			}
			else
			{
				break;
			}
		}
		a[end + 1] = tmp;
	}	
}

2.1.2希尔排序

希尔排序法又称缩小增量法。

1) 基本思想

将待排序的数据按照一定的增量分组,对每组数据进行插入排序,然后逐渐减小增量,重复上述步骤,直至增量为1,完成最后一轮的插入排序。

图示:

2)具体步骤
  1. 选择一个增量序列,常用的增量序列是希尔增量(N/2, N/4, N/8...,直到增量为1)。

  2. 对于每个增量,以增量作为间隔将待排序的数据分成多个组,分别对每个组进行插入排序。

  3. 逐渐减小增量,重复上述步骤,直至增量为1。

3)算法特性
  1. 希尔排序是对直接插入排序的优化。

  2. 当gap > 1时都是预排序,目的是让数组更接近于有序。当gap == 1时,数组已经接近有序的了,这样就会很快。这样整体而言,可以达到优化的效果。我们实现后可以进行性能测试的对比。

  3. 希尔排序的时间复杂度较为复杂,最好情况下为O(n log^2 n),最坏情况下为O(n^2),平均情况下为O(n log n)。希尔排序的性能优于直接插入排序,尤其是对于数据量较大的情况,其性能优势更加明显。这里一般可以认为时间复杂度为O(N^1.3)左右。

  4. 稳定性:不稳定

4)算法实现

多组同时排法:

cpp 复制代码
void ShellSort(int* a, int n)
{
	//预处理
	int gap = n;
	while (gap > 1)
	{
		//这里必须保证gap最后一次是1
		//gap /= 2;
		gap = gap / 3 + 1;

		for (int i = gap; i < n; i++)
		{
			int end = i - gap;
			int tmp = a[end + gap];

			while (end >= 0)
			{
				if (tmp < a[end])
				{
					a[end + gap] = a[end];
					end -= gap;
				}
				else
				{
					break;
				}
			}
			a[end + gap] = tmp;
		}
	}	
}

排完一组再排下一组:

cpp 复制代码
void ShellSort(int* a, int n)
{
	//预处理
	int gap = n;
	while (gap > 1)
	{
		//这里必须保证gap最后一次是1
		//gap /= 2;
		gap = gap / 3 + 1;

		for (int j = 0; j < gap; j++)
		{
			for (int i = gap+j; i < n; i += gap)
			{
				int end = i - gap;
				int tmp = a[end + gap];

				while (end >= 0)
				{
					if (tmp < a[end])
					{
						a[end + gap] = a[end];
						end -= gap;
					}
					else
					{
						break;
					}
				}
				a[end + gap] = tmp;
			}
		}
	
	}	
}

2.2选择排序

2.2.1直接选择排序

1) 基本思想

每一次从待排序的数据中选择最小(或最大)的元素,放到已排序序列的末尾,直到全部待排序的数据元素排完 。

动画演示:

2)具体步骤
  1. 找到待排序序列中最小(或最大)的元素,记为A。

  2. 将A与待排序序列的第一个元素交换位置。

  3. 然后在剩余的待排序序列中找到最小(或最大)的元素,再与待排序序列的第二个元素交换位置。

  4. 重复上述步骤,直到待排序序列变为空。

3)算法特性
  1. 直接选择排序思考非常好理解,但是效率不是很好。实际中很少使用

  2. 时间复杂度:直接选择排序的时间复杂度是O(n^2),无论输入数据的情况如何,都需要进行n-1次的比较和若干次元素交换。虽然直接选择排序的时间复杂度较高,但是它的优点是原地排序,不需要额外的空间。

  3. 空间复杂度:O(1)

  4. 稳定性:不稳定

4)算法实现
cpp 复制代码
void Swap(int* p1, int* p2)
{
	int tmp = *p1;
	*p1 = *p2;
	*p2 = tmp;
}


void SelectSort(int* a, int n)
{
	int left = 0, right = n - 1;

	while (left < right)
	{
		int mini = left, maxi = left;
		for (int i = left + 1; i <= right; i++)
		{
			if (a[i] < a[mini])
			{
				mini = i;
			}
			if (a[i] > a[maxi])
			{
				maxi = i;
			}
		}
		Swap(&a[left], &a[mini]);
		//如果left和maxi重叠,交换后修正一下,否则这里会出问题,换两次换回去了
		if (maxi == left)
		{
			maxi = mini;
		}

		Swap(&a[right], &a[maxi]);

		left++;
		right--;
	}
}

2.2.2 堆排序

堆排序是一种利用堆的数据结构进行排序的算法。堆是一种特殊的二叉树,满足堆性质:任意节点的值都大于等于(或小于等于)其子节点的值。需要注意的是排升序要建大堆,排降序建小堆。

1) 基本思想

将待排序的数据构造成一个堆,然后将堆顶元素与末尾元素交换,再对剩余的元素重新构造堆,以此类推,最终得到有序的序列。

2)具体步骤
  1. 构建最大堆(或最小堆),将待排序的数据转换为堆。

  2. 将堆顶元素(即最大值或最小值)与堆的最后一个叶子节点交换位置。

  3. 重新调整堆,将堆顶元素下沉,使得堆仍然满足堆性质。

  4. 重复上述步骤,直到堆只剩一个元素或为空。

3)算法特性
  1. 堆排序使用堆来选数,效率就高了很多。

  2. 时间复杂度:堆排序的时间复杂度是O(nlogn),堆的构建需要O(n)的时间,每次调整堆的时间为O(logn)。堆排序是一种原地排序算法,不需要额外的空间。由于堆排序具有良好的局部性,适合用于大规模数据的排序。

  3. 空间复杂度:O(1)

  4. 稳定性:堆排序是一种不稳定的排序算法,相同元素的相对位置可能会改变。

4)算法实现
cpp 复制代码
void ADjustDown(int* a, int sz, int parent)
{
	int child = parent * 2 + 1;
	while (child < sz)
	{
		//选出左右孩子中大的一个
		//这里child+1的判断在前,不要先访问再判断
		//这里a[child + 1] > a[child] 建大堆用>, 建小堆用<
		if (child + 1 < sz && a[child + 1] > a[child])
		{
			//这地方可能会越界
			++child;
		}
		//这里a[child] > a[parent] 建大堆用>, 建小堆用<
		if (a[child] > a[parent])
		{
			Swap(&a[child], &a[parent]);
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			break;
		}
	}
}

void HeapSort(int* a, int sz)
{
	//1.建堆 -- 向上调整建堆   NlogN
	//左右子树必须是大堆/小堆
	/*for (int i = 1; i < sz; i++)
	{
		ADjustUp(a, i);
	}*/

	//2.向下调整建堆  N
	//左右子树必须是大堆/小堆
	for (int i = (sz - 1 - 1) / 2; i >= 0; i--)
	{
		ADjustDown(a, sz, i);
	}

	int end = sz - 1;
	while (end > 0)
	{
		Swap(&a[end], &a[0]);
		ADjustDown(a, end, 0);
		--end;
	}
}

堆排序在前文中已经详细介绍了,具体见

【数据结构入门】二叉树之堆排序及链式二叉树

三、算法复杂度及稳定性分析

总结

排序算法的选择可以根据数据的特点、数据量以及排序的要求来确定。不同的排序算法具有不同的时间复杂度和空间复杂度,因此在实际应用中需要根据具体情况选择合适的排序算法。

直接插入排序:

  • 直接插入排序是一种简单直观的排序算法,其思想是将待排序的序列分为已排序和未排序两部分,每次从未排序部分选择一个元素插入到已排序部分的合适位置,直到所有元素都插入到已排序部分为止。
  • 直接插入排序的时间复杂度为O(n^2),是稳定的排序算法。

希尔排序:

  • 希尔排序是直接插入排序的一种改进算法,其核心思想是通过多次分组插入排序,每次对间隔较远的元素进行插入排序,逐步缩小间隔直到间隔为1,最后进行一次完整的插入排序。
  • 希尔排序的时间复杂度取决于间隔序列的选择,一般为O(nlogn),是不稳定的排序算法。

直接选择排序:

  • 直接选择排序是一种简单直观的排序算法,其思想是每次从未排序的序列中选择最小(或最大)的元素,将其与未排序部分的第一个元素交换位置,直到所有元素都排序完成。
  • 直接选择排序的时间复杂度为O(n^2),是不稳定的排序算法。

堆排序:

  • 堆排序利用二叉堆这种数据结构进行排序,将待排序的元素依次插入到堆中,然后从堆顶依次取出最大(或最小)的元素,直到所有元素都取出为止。
  • 堆排序的时间复杂度为O(nlogn),是不稳定的排序算法。
相关推荐
唐叔在学习1 小时前
【唐叔学算法】第21天:超越比较-计数排序、桶排序与基数排序的Java实践及性能剖析
数据结构·算法·排序算法
ha20428941947 小时前
认识数据结构之——排序
数据结构·算法·排序算法
前端青山9 小时前
JavaScript 数组操作与排序算法详解
开发语言·javascript·排序算法
汝即来归1 天前
选择排序和冒泡排序;MySQL架构
数据结构·算法·排序算法
光头man1 天前
【八大排序(二)】希尔排序
算法·排序算法
云边有个稻草人1 天前
【优选算法】—移动零(双指针算法)
算法·排序算法·双指针算法
黄亚磊112 天前
选择排序 冒泡排序 MySQL 架构
mysql·排序算法
In 20292 天前
堆【Lecode_HOT100】
java·算法·排序算法
啥也不会的研究僧3 天前
【算法篇】——数据结构中常见八大排序算法的过程原理详解
数据结构·算法·排序算法
小孩玩什么3 天前
堆排:一种高效的比较排序算法
java·c语言·数据结构·经验分享·redis·算法·排序算法