前言
本文开始介绍选择排序
一、选择排序
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--;
}
}
总结
本文对选择排序进行详细的分析和代码注释。
本文章为作者的笔记和心得记录,顺便进行知识分享,有任何错误请评论指点:)。