初级排序算法
包含两种初级的排序算法和其中一种的一个变体,他们相对简单,通过学习这几种算法可以帮助我们熟悉一些技巧和术语,为探索那些更复杂但也更高效的排序算法打下基础。
我们事先约定常用的辅助方法,这些方法将在算法中多次使用:
less() 方法对元素进行比较。
exchange() 方法将元素交换位置。代码如下所示:
void exchange(int arr[],int a, int b) {
int temp;
temp = arr[a];
arr[a] = arr[b];
arr[b] = temp;
}
int compareTo(int a, int b) {
if (a < b)return -1;
if (a > b)return 1;
else return 0;
}
int less(int a, int b) {
return compareTo(a, b) < 0;
}
还有比如 show() 方法打印数组中的内容,这里不再过多解释。
选择排序
这种排序的思想是这样的:首先,找到数组中最小的元素,将它和数组中第一个元素交换位置(如果最小的元素就是第一个元素那么就自己和自己交换)。然后,在剩下的元素中找到一个最小的元素,并将其与数组的第二个元素交换,如此往复,直到将整个数组排序。这种方法叫做选择排序,因为其总是在不断地选择剩余元素中的最小者。
void Selection(int arr[], int n) {//选择排序
for (int i = 0;i < n;i++) {
int min = i;
for (int j = i + 1;j < n;j++)
if (less(arr[j],arr[min]))min = j;
exchange(arr, i, min);
}
}
内层的for循环遍历数组,寻找最小的元素的位置,找到之后与数组中的第i位交换,外层的for循环保证了本次产生的最小元素将来不会再被访问。
选择排序有两个鲜明的特点:
运行时间和输入无关。为了找到最小元素而扫描整个数组并不能为下一遍扫描提供什么信息。这种性质在某些情况下是缺点,例如,一个完全有序的数组或是元素全部相同的数组和一个元素随机排列的数组所用的排序时间是一样长的。
数据移动是最少的。每次只交换两个元素,我们研究的其他算法不具备该性质。
插入排序
在插入排序的实现中,每个元素都要插入到已经有序的序列中的适当位置,所以,为了给要插入的元素腾出空间,每次插入我们可能会将一些元素都向右移动一位。这就是插入排序。
与选择排相同,当前索引的左边的所有元素都是有序的,但这些元素的位置并没有确定,因为仍然会有新插入进来的元素改变它们的位置。当索引到达数组的右端时,排序就完成了。
void Insertion(int arr[], int n) {//插入排序
for (int i = 1;i < n;i++) {
for (int j = i;j > 0 && less(arr[j], arr[j - 1]);j--)
exchange(arr, j, j - 1);
}
}
对于1到N-1之间的每一个i,将arr[i] 与 arr[0] 到 arr[i-1] 中比它小的所有元素依次有序的交换。在索引i由左向右变化的过程中,它左侧的元素总是有序的,所以当i到达数组右端排序就完成了。
插入排序的排序速度取决于输入数组的元素排列情况。插入排序对于部分有序的数组,也就是“倒置”的元素很少时的数组十分高效,也很适合小规模数组。
下面是插入排序和选择排序的算法可视化图,可以直观地看出两种排序算法之间的差别。
希尔排序
希尔排序是基于插入排序的一种算法,它的思想是使数组中任意间隔为h的元素都是有序的。这样的数组被称为h有序数组。一个h有序数组就是h个相互独立的有序数组编织在一起组成的一个数组。在进行排序时,如果h很大,我们可以将元素移动到很远的地方去,为实现更小的h有序创建方便。用这种方式,对于任意以1结尾的h序列,我们都可以将数组排序。
希尔排序的高效之处在于它权衡了子数组的规模和有序性。在一开始,h较大,每个子数组较短;当h变小数组中元素增加时,子数组都是部分有序,这两种情况都很适合插入排序。我们的工作就是选择一个h序列,并将它与插入排序相结合就可以完成我们的需求。
void ShellSort(int arr[], int n) {//希尔排序
for (int h = n / 3;h >= 1;h = h / 3) {
for (int i = 1 + h;i < n;i++) {
for (int j = i;j > 0 && less(arr[j], arr[j - h]);j--)
exchange(arr, j, j - h);
}
}
}
可以和之前插入排序的代码对比,我们额外增加了一层循环来控制h的变化,此外和插入排序几乎如出一辙。
对于希尔排序的性能分析至今没有得到解决。该算法的关键在于递增序列的选择,但是我们无法证明用什么样的递增序列是“最佳的”,更加优秀的递增序列还有待我们去发现。不过,我们可以肯定的是,希尔排序比插入排序和选择排序快很多,所以这种排序算法可以处理更多之前介绍的两种算法所无能无力的问题。下面是希尔排序的一个可视化轨迹: