数据结构之常见六大排序算法

前言

排序算法是一种将一组元素按照特定顺序排列的算法。常见的排序算法包括冒泡排序、选择排序、插入排序、快速排序、归并排序等。下面将简要介绍几种常见的排序算法。


一、选择排序

选择排序(Selection Sort)是一种简单直观的排序算法,它的思想是每次从待排序的数据中选择最小(或最大)的元素,放到已排序部分的末尾,直到全部数据排序完成。

下面是选择排序的基本步骤:
1.遍历待排序的数组,将当前位置视为最小值的索引。
2.在未排序部分中,从当前位置开始,遍历数组,找到比当前最小值小的元素,更新最小值的索引。
3.将找到的最小值与当前位置交换位置,即将最小值放到已排序部分的末尾。
4.重复步骤2和步骤3,直到遍历完整个数组。
5.数组排序完成。

选择排序的时间复杂度为O(n^2),其中n是待排序数组的长度。它的空间复杂度为O(1),属于原地排序算法。

从小到大排序实现如下:

int num[9]={163,161,158,165,171,170,163,159,162};
int len=sizeof(num)/sizeof(num[0]);
for(int i=0;i<len-1;i++)//外层比较轮数
{
	int max=0;
	for(j=1;j<len-i;j++)//内层搜索最大值
	{
		if(num[j]>num[max])
			max=j;
	}
	if(max!=len-i-1)
	{
		swap(&num[max],&num[len-i-1]);//swap为自定义函数交换max和len-i-1下标的两个值
	}
}

从大到小排序实现如下:

int num[9] = {163,161,158,165,171,170,163,159,162 };
	int len = sizeof(num) / sizeof(num[0]);
	
	for (int i = 0; i < len-1 ; i++)//外层判断轮数
	{
		int min = i;
		for (int j =i+1; j <len; j++)//内层搜索最小值
		{
			if (num[j] <num[min])
				min = j;
		}
		if (min != i)
		{
			swap(&num[min], &num[i]);//swap为自定义函数交换min和i下标的两个值
		}
	}

二、冒泡排序

冒泡排序(Bubble Sort)是一种简单的排序算法,它重复地遍历要排序的元素列表,比较相邻的两个元素,并且交换顺序不正确的元素。通过多次遍历列表,将最大(或最小)的元素逐渐"浮"到列表的末尾,从而实现排序。

下面是冒泡排序的基本步骤:
1.从列表的第一个元素开始,依次比较相邻的两个元素。
2.如果这两个元素的顺序不正确(例如,前一个元素大于后一个元素),则交换它们的位置。
3.继续向列表的下一个位置移动,并重复步骤1和步骤2,直到达到列表的末尾。
4.重复上述步骤,每次遍历列表时,都会将最大的元素"浮"到当前遍历范围的末尾。
5.重复执行步骤1到步骤4,直到列表中的所有元素都排好序为止。

冒泡排序的时间复杂度为O(n^2),其中n是待排序数组的长度。它的空间复杂度为O(1)。
从小到大排序实现如下:

    int num[9] = { 163,161,158,165,171,170,163,159,162 };
	int len = sizeof(num) / sizeof(num[0]);
	for (int i = 0; i < len - 1; i++)
	{
		bool sorted = true;
		for (int j = 0; j < len - i-1; j++)
		{
			if (num[j] > num[j + 1])
			{
				swap(&num[j], &num[j + 1]);
				sorted = false;
			}
		}
		if (sorted) break;
	}

从大到小排序实现如下:

int num[9] = { 163,161,158,165,171,170,163,159,162 };
	int len = sizeof(num) / sizeof(num[0]);
	for (int i = 0; i < len - 1; i++)
	{
		bool sorted = true;
		for (int j = len-1; j > i; j--)
		{
			if (num[j] >num[j-1])
			{
				swap(&num[j], &num[j - 1]);
				sorted = false;
			}
		}
		if (sorted) break;
	}

三、插入排序

插入排序是一种简单直观的排序算法,其基本思想是将一个待排序的数据序列分为已排序和未排序两部分。初始时,已排序部分只有一个元素(即第一个元素),然后依次将未排序部分的元素插入到已排序部分的合适位置,直到所有元素都被插入完毕。

下面是插入排序的基本步骤:
1.从第二个元素开始,将其视为当前要插入的元素。
2.将当前元素与已排序部分的最后一个元素比较。
3.如果当前元素小于已排序部分的最后一个元素,则将最后一个元素后移一位,给当前元素腾出插入的位置。
4.重复步骤 3,直到找到当前元素应该插入的位置或者已到达已排序部分的起始位置。
5.将当前元素插入到正确的位置上。
6.重复步骤 1~5,直到所有元素都被插入完毕。
从小到大排序实现如下:

int num[9] = { 163,161,158,165,171,170,163,159,162 };
	int len = sizeof(num) / sizeof(num[0]);

	int current = 0;
	int preIndex = 0;

	for (int i = 1; i < len ; i++)
	{
		current = num[i];
		preIndex = i - 1;
		while (preIndex >= 0 && num[preIndex] > current)
		{
			num[preIndex + 1] = num[preIndex];
			preIndex--;
		}
		num[preIndex + 1] = current
	}

从大到小排序实现如下:

int num[9] = { 163,161,158,165,171,170,163,159,162 };
	int len = sizeof(num) / sizeof(num[0]);

	int current = 0;
	int preIndex = 0;

	for (int i = 1; i < len ; i++)
	{
		current = num[i];
		preIndex = i - 1;
		while (preIndex >= 0 && num[preIndex]<current)
		{
			num[preIndex + 1] = num[preIndex];
			preIndex--;
		}
		num[preIndex + 1] = current;
	}

插入排序的时间复杂度为O(n^2),其中n是待排序数组的长度。它的空间复杂度为O(1)。

四、希尔排序

希尔排序(Shell Sort)是一种基于插入排序的排序算法,也被称为缩小增量排序。它通过将待排序的元素按照一定间隔分组,对每个分组进行插入排序,逐渐减小间隔,直到间隔为1时完成最后一次排序。希尔排序的核心思想是使数组中任意间隔为h的元素都是有序的。

以下是希尔排序的基本步骤:
1.选择一个增量序列(间隔序列),通常取n/2、n/4、n/8…直到间隔为1。这里的n是数组的长度。
2.根据选定的增量序列,将待排序的元素分成若干个子序列,对每个子序列进行插入排序。
3.继续减小增量,重复上述步骤,直到增量为1时进行最后一次插入排序。

希尔排序的时间复杂度是难以精确计算的,最好的情况下可以达到O(n log^2 n),最坏的情况下为O(n^2)。然而,在大多数实际应用中,希尔排序的平均时间复杂度在O(n log n)左右,比一些简单的排序算法要快。

从小到大排序实现如下:

int num[9] = { 163,161,158,165,171,170,163,159,162 };
	int len = sizeof(num) / sizeof(num[0]);

	int gap = len / 2;
	for (; gap > 0; gap = gap / 2)
	{
		for (int i = gap; i < len; i++)
		{
			int current = num[i];
			int j = 0;
			for (j = i - gap; j >= 0 && num[j] > current; j -= gap)
			{
				num[j + gap] = num[j];
			}
			num[j + gap] = current;
		}  
	}

从大到小排序实现如下:

int num[9] = { 163,161,158,165,171,170,163,159,162 };
	int len = sizeof(num) / sizeof(num[0]);

	int gap = len / 2;
	for (; gap > 0; gap = gap / 2)
	{
		for (int i = gap; i < len; i++)
		{
			int current = num[i];
			int j = 0;
			for (j = i - gap; j >= 0 && num[j] < current; j -= gap)
			{
				num[j + gap] = num[j];
			}
			num[j + gap] = current;
		}  
	}

五、归并排序

归并排序是一种经典的排序算法,它基于分治的思想。该算法将待排序的序列不断地二分,直到每个子序列只有一个元素,然后通过比较合并这些子序列,最终得到一个有序的序列。

下面是归并排序的基本步骤:
1.分割:将待排序的序列递归地划分为两个子序列,直到每个子序列只包含一个元素。
2.合并:将两个已排序的子序列合并成一个新的有序序列。具体做法是比较两个子序列的第一个元素,将较小的元素放入新序列中,并从相应子序列中删除该元素,然后继续比较直到其中一个子序列为空,将另一个子序列的剩余部分直接添加到新序列的末尾。

从小到大排序实现如下(假设数组半有序):

    int num[8] = { 1,3,6,7,2,4,5,8 };
	int len = sizeof(num) / sizeof(num[0]);

	int mid = len / 2;//这里默认前半部分有序,后半部分有序,如果数据无序可以递归调用分治法函数,先给整个数据排序,再确定mid的值。
	int temp[64] = { 0 };//临时数组
	int k = 0;//临时数组下标

	int left = 0;
	int right = mid;

	while (left < mid && right < len)
	{
		if (num[left] < num[right])
		{
			temp[k++] = num[left++];
		}
		else
		{
			temp[k++] = num[right++];
		}
	}
	while (left < mid)
	{
		temp[k++] = num[left++];
	}
	while (right < len)
	{
		temp[k++] = num[right++];
	}
	memcpy(num, temp, sizeof(int) * k);

从大到小排序实现如下(假设数组半有序):

    int num[8] = { 7,6,3,1,8,5,4,2};
	int len = sizeof(num) / sizeof(num[0]);

	int mid = len / 2;//这里默认前半部分有序,后半部分有序,如果数据无序可以递归调用分治法函数,先给整个数据排序,再确定mid的值。
	int temp[64] = { 0 };//临时数组
	int k = 0;//临时数组下标

	int left = 0;
	int right = mid;

	while (left < mid && right < len)
	{
		if (num[left] >num[right])
		{
			temp[k++] = num[left++];
		}
		else
		{
			temp[k++] = num[right++];
		}
	}
	while (left < mid)
	{
		temp[k++] = num[left++];
	}
	while (right < len)
	{
		temp[k++] = num[right++];
	}
	memcpy(num, temp, sizeof(int) * k);

利用分治思想递归调使整个无序数组有序实现如下:

void mergeSort(int arr[],int left,int right,int *temp)
{
	int mid=0;
	if(left<right)
	{
		mid=left+(right-left)/2;
		mergeSort(arr,left,mid,temp);    //分治法左边区间排序
		mergeSort(arr,mid+1,right,temp); //分治法右边区间排序
		mergeAdd(arr,left,mid+1,right,temp); //归并排序
	}
}

归并排序的时间复杂度是O(n log n),其中n表示待排序元素的数量。无论最好、最坏还是平均情况,归并排序的时间复杂度都保持稳定。归并排序的空间复杂度是O(n),其中n表示待排序元素的数量。归并排序需要额外的存储空间来存储临时数组和递归过程中产生的中间结果。通常情况下,这个额外的空间是在堆栈上分配的,因此空间复杂度是线性的。

六、快速排序

快速排序(Quicksort)是一种常用的排序算法,它基于分治的思想。它的核心思想是选择一个基准元素(pivot),将待排序序列划分为两个子序列,其中一个子序列中的所有元素都小于等于基准元素,而另一个子序列中的所有元素都大于基准元素,然后对这两个子序列递归地进行排序,最终得到有序序列。

下面是快速排序的一般步骤:
1.选择基准元素:从待排序序列中任选一个元素作为基准元素。
2.划分操作:将比基准元素小的元素放置在基准元素的左边,比基准元素大的元素放置在基准元素的右边,相同大小的元素可以放置在任意一边。划分操作可以使用双指针法或者单指针法实现。
3.递归排序:对基准元素左边和右边的子序列进行递归调用快速排序算法。
4.合并结果:将左边子序列、基准元素和右边子序列合并为最终的排序序列。
基准数放置中间排序实现如下:

  int num[8] = { 7,6,3,1,8,5,4,2};
	int len = sizeof(num) / sizeof(num[0]);
	
	int i = low; //low为自己定义的开始点
	int j = high; //high为自己定义的最后点
	int base = num[i];
	if(low<high){
	while (i < j&&num[j]>=base)
	{
		j--;
	}
	if (i < j)
	{
		num[i++] = num[j];

	}
	while (i < j && num[i] < base)
	{
		i++;
	}
	if (i < j)
	{
		num[j--] = num[i];
	}
	num[i] = base;
	}

递归调用实现快速排序(先将上述实现封装成为函数partition,返回值为i)

void QuickSort(int *num,int low,int high)
{
	if(low<high)
	{
		int index=partition(num,low,high);
		QuickSort(num,low,index-1);
		QuickSort(num,index+1,high);
	}
}

快速排序的时间复杂度通常为 O(nlogn),其中 n 是待排序序列的长度。在最坏的情况下,快速排序的时间复杂度为 O(n^2),但这种情况很少发生,并且可以通过优化方法(如随机选择基准元素或使用三数取中法选择基准元素)来避免。快速排序(Quicksort)的空间复杂度通常为O(log n)。


总结

排序算法比较
在这里插入图片描述

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

芷菁小可爱~

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值