本文将介绍两类选择排序:简单选择排序、二元选择排序的思路,以及探讨在算法实现中可能出现的问题。
选择排序在运行中使数组分为有序区和无序区两部分
简单选择排序(升序)
左侧有序区
public static void selectMinToFront(int[] a) {
int n = a.length;
for (int i = 0; i < n - 1; i++) {
int min = i;
for (int j = i + 1; j < n; j++) {
if (a[j] < a[min]) min = j;
}
int t = a[i];
a[i] = a[min];
a[min] = t;
}
}外层循环
范围:0 ~ n-1 一共n-1轮
终止:枚举到倒数第二个下标 最后一位不需要再进行处理
内层循环
范围:i+1 ~ n-1 起点是外层循环i往后一位(无序区第二位 暂时保留第一位) 终点是数组最后一位
终止:枚举到数组最后一位
每一次外层循环都代表着开启一轮新的内层循环
内层循环开始前创建临时变量min,用以储存当前内层循环遍历到的最小值下标,并且假定新一轮循环得到的最小值为无序区的第一位。
内层循环遍历其余无序区,寻找最小值并记录下标。
将无序区第一位 i 的值与,刚才找到的最小值进行对调。
简易视图
我们假定有序区为A 无序区为B 数组长度 n
现在对含有五个元素的数组进行排序,有序区和无序区的变化如下图所示:
[ B B B B B ] 原始数组
-- 开始外层循环 --
第1轮 i=0: [ A B B B B ]
第2轮 i=1: [ A A B B B ]
第3轮 i=2: [ A A A B B ]
第4轮 i=3: [ A A A A B ] 结束(总共 n-1 轮)右侧有序区
public static void selectMaxToBack(int[] a) {
int n = a.length;
for (int end = n - 1; end > 0; end--) {
int max = 0;
for (int j = 1; j <= end; j++) {
if (a[j] > a[max]) max = j;
}
int t = a[end]; a[end] = a[max]; a[max] = t;
}
}基本思路同上述左侧有序区一致,仅调转执行逻辑,变更为从右向左查找最大值。
二元选择排序(升序)
public static void selectMinMaxBothEnds(int[] a) {
int left = 0, right = a.length - 1;
while (left < right) {
int min = left, max = left;
for (int j = left; j <= right; j++) {
if (a[j] < a[min]) min = j;
if (a[j] > a[max]) max = j;
}
// 先把最小放到 left
int t = a[left]; a[left] = a[min]; a[min] = t;
// 如果最大值原来在 left,被换走了,要修正 max 的位置
if (max == left) max = min;
// 再把最大放到 right
t = a[right]; a[right] = a[max]; a[max] = t;
left++;
right--;
}
}首先创建left 和 right两个下标变量 用来锚定无序区边界
外层while循环
循环条件:左右边界不重合
每一次循环都会创建最大最小值下标变量,并且默认无序区左边界left位置既是最小候选也是最大候选(min=max=left)
内层for循环
范围:left ~ right
在无序区寻找最大和最小值 把最小值放在left 最大值放在right。
处理特殊情况
因为我们先替换最小值到left,如果最大值在left那么就会导致最小值被交换到left,最大值被换到别处。
因此需要把最大值暂存到数组中找到的最小值的位置。
我们需要更改最大值下标到循环查找到的最小值下标,把这种情况下出现的最大值给到right。
最后向中心收缩无序区 并开启下一轮循环
首图图片来源:菜鸟教程(侵删)
评论