排序在算法中是十分重要的问题,本节主要讲解最基本的排序算法,包括选择排序,插入排序,希尔排序,最后会讲解一个排序的实际应用。
一. 选择排序(Selection Sort)
public class Selection
{
public static void sort(Comparable[] a)
{
int N = a.length;
for (int i = 0; i < N; i++)
{
int min = i;
for (int j = i+1; j < N; j++)
if (less(a[j], a[min]))
min = j;
exch(a, i, min);
}
}
private static boolean less(Comparable v, Comparable w)
{ /* as before */ }
private static void exch(Comparable[] a, int i, int j)
{ /* as before */ }
}
选择排序,顾名思义,就是从左到右不断扫描数组,每次选择目标元素,把它提到前面。经过k次选择,前k项就是有序的,十分简单。
选择排序,并不是效率很高的排序。
二. 插入排序(Insertion Sort)
插入排序,同样可以顾名思义,就是把看到的元素提前,放到它应该放到的位置上去。
public class Insertion
{
public static void sort(Comparable[] a)
{
int N = a.length;
for (int i = 0; i < N; i++)
for (int j = i; j > 0; j--)
if (less(a[j], a[j-1]))
exch(a, j, j-1);
else break;
}
private static boolean less(Comparable v, Comparable w)
{ /* as before */ }
private static void exch(Comparable[] a, int i, int j)
{ /* as before */ }
}
插入排序对于基本有序的数组,具有很高的效率。
三. 希尔排序(Shell Sort)
希尔排序是一种十分重要的排序算法。它产生时间很早(1959年),是最早突破上述排序算法的算法时间复杂度(O(n^2))的算法,它一方面十分简单,数行代码就可以实现,一方面这个算法的细节却依然有讨论和争议(分组大小)。这说明我们目前存在的问题,哪怕十分简单,依然有很大的改进空间,依然有很多精巧的算法等待我们发现。
public class Shell
{
public static void sort(Comparable[] a)
{
int N = a.length;
int h = 1;
while (h < N/3) h = 3*h + 1; // 1, 4, 13, 40, 121, 364, ...
while (h >= 1)
{ // h-sort the array.
for (int i = h; i < N; i++)
{
for (int j = i; j >= h && less(a[j], a[j-h]); j -= h)
exch(a, j, j-h);
}
h = h/3;
}
}
private static boolean less(Comparable v, Comparable w)
{ /* as before */ }
private static void exch(Comparable[] a, int i, int j)
{ /* as before */ }
}
算法思路:我们知道,插入排序对于基本有序的数组具有很高的效率,那么,我们能不能让数组从小到大,逐步变得有序呢?希尔排序采用的方法是先对数组进行分组,比如选择分组大小为3,则0,3,6,9,...,3n是一组,在组内使用插入排序,之后不断缩小分组大小,直到分组大小为1,就完成了排序。
希尔排序的算法复杂度比较难分析,总之小于O(n^2)。
四. 凸包寻找问题
关于凸包寻找问题(Convex Hull),在Graham扫描法找凸包(convexHull)中有比较详尽的描述,在此只是简要介绍一下思路方法。
上图显示了我们要处理的问题和使用的基本思路。
问题
在二维平面中给定一组点,我们希望找到最少的点,把所有点包裹进来。
基本思路
首先,我们选定最底下的点1,作为起始点,之后计算所有点以1为极坐标远点的θ值,把θ值从小到大排序,得到一组点的排列{1,2,3,4,5,6,7,8,9,10,11,12}。
可以确定0,1两点一定会被选中,之后把2加入点集,但是当3要加入点集的时候,我们发现似乎2不在点集中,因为从基点开始,凸包上每条相临的线段的旋转方向应该一致。如果发现新加的点使得新线段与上线段的旋转方向发生变化,则可判定上一点必然不在凸包上。实现时可用向量叉积进行判断,设新加入的点为 pn+1pn+1,上一点为 pnpn,再上一点为 pn−1pn−1。顺时针扫描时,如果向量 <pn−1,pn><pn−1,pn> 与 <pn,pn+1><pn,pn+1> 的叉积为正,则将上一点删除。
我们需要用回溯法来解决这个问题。
算法