排序之选择排序、冒泡排序、堆排序

目录

前言:

一、选择排序

选择排序的思想:

选选择排序的优化:

选择排序代码:

选择排序的评价:

二、冒泡排序

冒泡排序的思想:

冒泡排序的代码:

冒泡排序的评价:

三、堆排序

堆排序的思想:

堆排序的向下调整代码:

堆排序代码:

堆排序的评价:

三种排序手段的时间比较


前言:

        这是我继上一篇排序所更新的新一篇排序,其中讲解了选择排序的两头并排,基本的冒泡排序,还有堆排序,不过对于堆排序来说,我在之前学习的时候就已经特别为其写下了一篇博客,在本片中我就会当作大家懂得堆的前提下讲解,所以有想要特别了解其本质的可以转到我之前的那一篇博客上去。


一、选择排序

选择排序的思想:

       选择排序顾名思义,就是每次从一堆数据里面找到最大或则最小的数据,然后与数组的起始位置或则结束位置的数据进行交换。

        上述操作完成之后,我们就将一个数据放到了它正确的位置上去,此时,我们想要找到次大或次小的数据,就需要缩小查找空间,不然又会再次找到原来哪一个数据。看下图操作。

        整个查找操作通过遍历数组,然后对比得知。

选选择排序的优化:

        通过查看上图我们可以看到,每一次只查找一个数据实在是太没有效率了,所以我们可以在每一次排序都找到当前查找数据的最大和最小值,分别与队尾和队首进行交换,这样就直接提速了一倍。

选择排序代码:

        可以看到我们用两个下标表示当前查找数组的长度,注意一点,这里我们的数组是一个闭区间,别把自己坑了。

        进入循环之后,每次找到最大值和最小值,与队尾与队头交换,然后区间缩小2,直到区间不存在。

        如果每一次只查找一个数据不会出现最大值的下标就是起始位置,然后在最小值与起始值交换之后,最大值的位置就会出现在原来最小值的位置,所以需要判断最小值交换之后是否会影响最大值的选取。

//选择排序
void SelectSort(int* arr, int size)
{
	//当次循环的起始值
	int Begin_Index = 0;

	//当次循环的结束值
	int End_Index = size - 1;

	//每一次循环的起始值和结束值都会在自己方向缩小一位
	for (Begin_Index = 0; Begin_Index <= End_Index; Begin_Index++, End_Index--)
	{
		//最大值和最小值对应的下标
		int Min_Index = Begin_Index;
		int Max_Index = Begin_Index;

		//遍历查找最大值和最小值下标
		for (int j = Begin_Index; j <= End_Index; j++)
		{
			//小于最小值
			if (arr[j] < arr[Min_Index])
			{
				Min_Index = j;
			}
			//大于最大值
			if (arr[j]>arr[Max_Index])
			{
				Max_Index = j;
			}
		}
		//交换初始位置与最小位置之间的值
		Swap(&arr[Begin_Index], &arr[Min_Index]);

		//如果初始位置就是最大值的位置,那么交换之后,最大值对应之前最小值位置
		if (Begin_Index == Max_Index)
		{
			Max_Index = Min_Index;
		}

		//交换最后位置与最大位置之间的值
		Swap(&arr[End_Index], &arr[Max_Index]);
	}
}

选择排序的评价:

        伙伴们,无论是从代码的书写还是从逻辑的角度,都能看出选择排序无论怎么优化,它永远都是稳定的差劲,可以看到,它每一次最多选取两个数字去到正确的位置,当一个数组的长度足够长,而且该数组本身还是有序的,这个排序还是需要一次一次的查找,然后交换,再查找,整个程序的时间复杂度稳定处于O(n^2),所以虽然该方法很简单,对于实际应用来说,能不用就不用吧,掌握了就好。

二、冒泡排序

冒泡排序的思想:

        说到冒泡排序,想必大家一定熟悉得不得了,应该说冒泡排序基本上是我们学习语言最早接触得一类排序手段了。

        它的主要思想其实也是每次比较让一个值去到一个正确的位置,例如这里有十个数据[ 10 9 8 7 6 5 4 3 2 1 ],我们想要得到一个升序的数组,那么此时就需要先将10送回家,也就是10与9比,大于,然后交换,10又与8比较,大于,再交换,如此循环操作,直到10到了最后位置,整个过程10比较了9次。

        在下一次排序时,就需要将9送回家,此时10已经回到了正确位置,那么他就不再进入比较环节,所以一共有9个数据比较,需要比较8次,以此类推。直到只有1个数据。看下图。

冒泡排序的代码:

        可以看到冒泡排序的逻辑十分简单,第一层循环确定比较长度,第二层循环表示需要比较的次数,然后通过升序或则降序规则比较交换比较的两个数据就行。

//冒泡排序
void BubbleSort(int* arr, int size)
{
	//size个数,需要比较size-1次
	for (int i = 0; i < size - 1; i++)
	{
		//每次需要size-1-i次交换才能到正确位置
		for (int j = 0; j < size - 1 - i; j++)
		{
			if (arr[j]>arr[j + 1])
			{
				Swap(&arr[j], &arr[j + 1]);
			}
		}
	}
}

冒泡排序的评价:

        冒泡排序是一个很典型的O(n^2)模型,甚至都没有什么优化空间,比起选择排序来说都要差劲一点,不过对于我第一个接触的排序手段来说,我还是很喜欢用的,因为它写起来很简单,对于一些少量数据的排序,用冒泡排序还是很舒服的,不过数据量较大的时候还是老老实实换成其余几个大哥排序手段吧。

三、堆排序

堆排序的思想:

        对于堆排序来说,我们首先拿到一组数据就需要将该组数据整理成为堆。可能大伙对于堆有一点点疑惑,所以我还是简单讲解一下,堆其实就是一个用数组表示的完全二叉树,堆顶的数据一定会比它之后的数据大或小,以此来判断,它是一个大堆或者小堆。

        如果我们想要将数据变为一个升序序列,那么就需要建立一个大堆,每一次都将堆顶数据与当前数组的最后一个数据进行交换,然后调整堆,重复此操作。

         可以看到,我们将10与1交换之后,10到了正确的位置,所以10就退出了我们的排序序列中,但是1却不是,甚至破坏了堆的结构,所以这个时候需要向下调整1的位置,因为该组数据除了1,其余位置都满足堆的要求,所以直接调整1就行,让它不断向下移动,选出次大位置坐到堆顶位置。

堆排序的向下调整代码:

        因为孩子结点与父节点的对应关系为父亲结点下标*2+1或+2,那么此时,就能完成我们不断向下查找的方式,因为我们的最大值被换下去了,所以需要找到次大值,次大值只能在最大值的下一层也就是堆顶的儿子结点,我们不知道谁大,所以需要对两个数据作比较。然后将大的那个值重新作为堆顶。又因为1这个数被交换下来之后可能也不是正确位置,所以我们当作它还在堆顶位置处理,重复刚才操作,与它的孩子结点比较,然后看是否交换,当1到了正确位置,也就是比它的孩子结点大或者,走到叶子结点时就结束,也就重新成为了新的堆。

//向下调整法
void Adjust_Down(int* arr, int size, int Parent)
{
	//传入父节点的下标,求出孩子结点的下标
	int Child = Parent * 2 + 1;

	//当孩子结点还没有超过总结点个数证明还可以继续比较
	while (Child < size)
	{
		//比较左右孩子谁大谁小,前提是在右孩子还存在的情况下
		if (Child + 1 < size && arr[Child] < arr[Child + 1])
		{
			Child++;
		}
		//当父节点小于孩子结点,交换两个结点的值
		if (arr[Parent] < arr[Child])
		{
			//继续向下传递,为下一次比较做准备
			Swap(&arr[Parent], &arr[Child]);
			Parent = Child;
			Child = Parent * 2 + 1;
		}
		//因为向下调整的基础为原本就是一个堆,所以当父节点大于了子节点,表示不需要再次向下调整,退出
		else
			break;
	}
}

堆排序代码:

        通过向下调整法建堆的方式为,找到最后一个孩子结点的父亲,然后依次向前移动,还记得我们向下调整的基础吗,那就是除了被调整对象,其余的结点满足堆的要求。所以在这样调整完成之后会得到一个完整的堆。具体详解请看我写的关于堆的博客,里面有详细解释。

void HeapSort(int* arr, int size)
{
	//向下建堆法是先找到最后一个孩子的父亲,然后向下调整,找到下一个父亲
	//直到父亲找到根节点并调整完
	int Parent = (size - 1 - 1) / 2;
	while (Parent >= 0)
	{
		Adjust_Down(arr, size, Parent);
		Parent--;
	}

	//因为是升序,所以需要大堆,然后大堆堆顶与最后一个值交换,然后对根向下调整,找到次大值
	int i = size-1;
	while (i > 0)
	{
		Swap(&arr[0], &arr[i]);
		Adjust_Down(arr, i, 0);
		i--;
	}

}

堆排序的评价:

        堆是我接触的第一个大哥排序,也是我感觉学会之后提升巨大的排序手段,它成功的完成了排序的时间复杂度从O(n^2)变为了O(nlogn),这是一个巨大的突破,让我们对于大数据排序也有了应对手段,不再拘泥于原本的O(n^2)了。但是整体来讲堆排序需要的代码能力与逻辑抽象思想有一点高,他需要控制的量比较多,对于初学者有些困难,所以在大哥排序当中,我并不推荐它,但是它对于我们的能力提升有很大帮助,所以还是需要学会。建堆的手段还有向上调整法建堆,但是它的效率没有向下调整法高,不过理解起来更简单,所以如果不太理解向下调整法,可以先尝试理解向上调整法。

三种排序手段的时间比较:

        下面是对冒泡排序,选择排序,还有推排序对于100000个数据的效率比较,可以看到我们的冒泡排序对于十万个数据所消耗的时间甚至到了90秒,选择排序也消耗了5秒左右,但是看到我们的推排序呢,只消耗了36毫秒,这个差距还是很直观的。

         以上就是我对于选择排序,堆排序,冒泡排序的全部讲解,希望对你有帮助。

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
以下是六种排序算法比较: 1. 插入排序 插入排序是一种简单直观的排序算法,它的基本思想是将一个记录插入到已经排好序的有序表中,从而得到一个新的、记录数增加1的有序表。插入排序的时间复杂度为O(n^2),适用于数据量较小的排序。 2. 希尔排序 希尔排序是一种改进的插入排序,它的基本思想是将待排序的数组按照一定的间隔分成若干个子序列,对每个子序列进行插入排序,然后逐步缩小间隔,直到间隔为1,最后对整个数组进行插入排序。希尔排序的时间复杂度为O(nlogn),适用于数据量较大的排序。 3. 选择排序 选择排序是一种简单直观的排序算法,它的基本思想是每次从待排序的数组中选择最小的元素,放到已排序的数组的末尾,直到所有元素都排序完毕。选择排序的时间复杂度为O(n^2),适用于数据量较小的排序。 4. 冒泡排序 冒泡排序是一种简单直观的排序算法,它的基本思想是每次比较相邻的两个元素,如果它们的顺序错误就交换它们的位置,直到所有元素都排序完毕。冒泡排序的时间复杂度为O(n^2),适用于数据量较小的排序。 5. 堆排序 堆排序是一种树形选择排序,它的基本思想是将待排序的数组构建成一个二叉堆,然后依次将堆顶元素与堆底元素交换,再重新调整堆,直到所有元素都排序完毕。堆排序的时间复杂度为O(nlogn),适用于数据量较大的排序。 6. 快速排序 快速排序是一种分治的排序算法,它的基本思想是选择一个基准元素,将数组分成两个子数组,小于基准元素的放在左边,大于基准元素的放在右边,然后递归地对子数组进行排序。快速排序的时间复杂度为O(nlogn),是一种效率比较高的排序算法

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值