【数据结构】第九站:选择排序(附代码和注释)


前言

本文开始介绍选择排序
在这里插入图片描述


一、选择排序

1.1选择排序基本思想

每一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的开始或者结尾位置,直到全部待排序的数据元素排完 。

1.1.1如何从待排数据元素中选出最小(或最大)元素?

①:我们可以直接遍历整个数组去找。
②:利用堆的性质,大/小根堆的根节点的值一定大于/小于其余所有的节点值。

利用这两种不同的思路,我们便有了两条路:利用第一个思路便是“直接选择排序”,用第二个便是“堆排序”。

2.1利用思路实现

2.1.1直接选择排序(SelectSort)

利用上述第一个思路,我们先实现对 单个(一个) 最大数的放置操作:先遍历整个数组,找到最大的数,然后将其放到数组的最后面(此为一次“沉底”操作)。

//a是数组名,n是数组大小,
{
	int max=0;                   //这个max用来记录最大值的下标位置,先假设下标0位置的值最大
	for(int i=0;i<n;i++)         //遍历寻找最大值下标
	{
		if(a[i]>a[max]);
		{
			max=i;
		}
	}
	Swap(a+n-1,a+max);         //交换最后一个值和max下标的最大值,使得一个最大数沉底
}

实现完对一次最大数的沉底后,我们只需要控制我们每次查找最大值的数组范围,直到所有的数都被依次沉底即可。

完整SelectSort代码:

void SelectSort(Datatype* a, int n)
{
	int max;
	for(int j=n;j>0;j--)         //此for循环用来控制遍历数组找值的范围
	{
		max=0;                   //这个max用来记录最大值的下标位置,先假设下标0位置的值最大
		for(int i=0;i<j;i++)         //遍历寻找最大值下标
		{
			if(a[i]>a[max])
			{
				max=i;
			}
		}
		Swap(a+j-1,a+max);         //交换最后一个值和max下标的最大值,使得一个最大数沉底
	}
}

我们的选择排序虽然说是最简单的,但同时也是最烂的,它的时间复杂度不受数组顺序的影响,是恒定的O(N^2),具体时间复杂度的分析后面会有一章专门说明(所有排序),所以我们可以对它稍微优化一下:反正我们都要遍历一遍去找最大值的,不如顺便将最小值也找出来。
优化一下代码:

void SelectSort2(Datatype* a, int n)
{
	int left = 0, right = n - 1;
	while (left < right)
	{
		int mini = left, maxi = right;
		for (int i = left; i <= right; i++)
		{
			if (a[i] < a[mini])
			{
				mini = i;
			}
			if (a[i] > a[maxi])
			{
				maxi = i;
			}
		}
		Swap(a + left, a + mini);
		//如果left和right重叠,交换后修正一下
		if (left == maxi)
		{
			maxi = mini;
		}
		Swap(a + right, a + maxi);
		++left;
		--right;
	}
}

这个代码排序过程中需要注意left和right的位置冲突问题,请自创数据后自走逻辑验证可以加深理解。

2.1.2堆排序

想要用到我们堆的特性去实现选择最大或者最小,我们必须先成堆!
至于堆基本概念不懂得请移步文章: 【数据结构】第六站:树的入门和堆的实现(附完整代码和注释)

建堆过程
我们建堆可以用向上调整建堆,也可以用向下调整建堆:
①向上调整建堆

向上调整建堆,其实就是对堆依次插入数组中元素的过程

void AdjustUp(Datatype* a,int child)
{
	int parent = (child - 1) / 2;
	while (parent >= 0)
	{
		if (a[child] > a[parent])
		{
			Swap(&a[child], &a[parent]);
			child = parent;
			parent = (child - 1) / 2;
		}
		else
		{
			break;
		}
	}

}
//这里的a是传入的数组,n是数组的大小
void CreatHeap2(Datatype* a, int n)
{
	for (int i=1;i<n;i++)
	{
		AdjustUp(a, i);
	}
}

②向下调整建堆
我们的向下调整建堆要稍微难理解一点,但是它的时间复杂度也要快一些(向下建堆:O(N),向上建堆:O(N*logN),具体分析另有文章)。

向下建堆需要从物理存储下标最大的非叶子节点开始,也就是最后一个叶子节点(物理存储中下标最大的那个节点)的父节点,最后一个节点的下标为n-1,通过我们父子节点的下标关系可以轻松找到:(n-1-1)/2。


void AdjustDown(Datatype* a,int n,int parent)
{
	int child = parent * 2 + 1;
	while (child<n)
	{
		//如果右孩子存在的话,比较一下哪个大
		if (child + 1 < n && a[child + 1] > a[child])
		{
			++child;
		}
		if (a[child] > a[parent])
		{
			Swap(&a[child], &a[parent]);
			parent = child;
			child= parent * 2 + 1;
		}
		else
		{
			break;
		}
		
	}
}

//建堆的函数
void CreatHeap1(Datatype* a, int n)
{
	for (int i = (n - 1 - 1) / 2; i >= 0; i--)
	{
		AdjustDown(a, n, i);
	}
}

完整代码:

建完堆后,我们只需要正常利用堆的特性,将最大的根节点和尾节点(堆中n-1位置的节点)交换位置,然后改变堆(数组)的范围,就完成了一次最大数的沉底了。完成沉底后别忘了对新换来根节点用向下调整,使前面数组维持为堆。

void HeapSort(Datatype* a, int n)
{
	CreatHeap1(a, n);
	int end = n - 1;
	while (end >= 0)
	{
		Swap(&a[0], &a[end]);
		AdjustDown(a, end, 0);
		end--;
	}
}

总结

本文对选择排序进行详细的分析和代码注释。


本文章为作者的笔记和心得记录,顺便进行知识分享,有任何错误请评论指点:)。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值