常见的排序算法

目录

  1. 选择排序
  2. 堆排序
  3. 冒泡排序
  4. 快速排序
  5. 插入排序
  6. 希尔排序
  7. 归并排序
  8. 基数排序
  9. 测试用例
  10. 复杂度和稳定性

将不同的排序算法封装并继承共同的抽象类 Sorter,使它们可以相互替换:

public abstract class Sorter<T> {

    private Comparator<? super T> comparator;

    protected Sorter() {}
    protected Sorter(Comparator<? super T> comparator) {
        this.comparator = comparator;
    }

    public void setComparator(Comparator<? super T> comparator) {
        this.comparator = comparator;
    }

    public abstract void sort(T[] array);

    public void sort(T[] array, Comparator<T> comparator) {
        this.setComparator(comparator);
        this.sort(array);
        this.setComparator(null);
    }

    @SuppressWarnings("unchecked")
    protected int compare(T t1, T t2) {
        if (comparator != null) {
            return comparator.compare(t1, t2);
        }
        return ((Comparable<? super T>) t1).compareTo(t2);
    }

    protected void swap(T[] array, int i, int j) {
        if (i == j) {
            return;
        }
        T temp = array[i];
        array[i] = array[j];
        array[j] = temp;
    }
}

选择排序

首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,然后,再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。以此类推,直到所有元素均排序完毕。

选择排序

public class SelectionSorter<T> extends Sorter<T> {

    public SelectionSorter() {}
    public SelectionSorter(Comparator<? super T> comparator) {
        super(comparator);
    }

    @Override
    public void sort(T[] array) {
        int length = array.length;
        for (int i = 0; i < length - 1; i++) {
            int max = length - i - 1;
            for (int j = 0; j < length - i - 1; j++) {
                if (compare(array[j], array[max]) > 0) {
                    max = j;
                }
            }
            swap(array, max, length - i - 1);
        }
    }
}

堆排序

堆排序

public class HeapSorter<T> extends Sorter<T> {

    private List<T> heap;

    public HeapSorter() {}
    public HeapSorter(Comparator<? super T> comparator) {
        super(comparator);
    }

    @Override
    public void sort(T[] array) {
        int length = array.length;
        heap = new ArrayList<>();
        for (T t : array) {
            heap.add(t);
        }
        for (int i = getParent(length - 1); i >= 0; i--) {
            heapify(i);
        }
        for (int i = length - 1; i >= 0; i--) {
            swap(0, i);
            array[i] = heap.remove(i);
            heapify(0);
        }
    }

    private int getParent(int i) {
        return (i - 1) >>> 1;
    }
    private int getLeft(int i) {
        return (i << 1) + 1;
    }
    private int getRight(int i) {
        return (i + 1) << 1;
    }

    private void swap(int i, int j) {
        if (i == j) {
            return;
        }
        T temp = heap.get(i);
        heap.set(i, heap.get(j));
        heap.set(j, temp);
    }

    private void heapify(int i) {
        int max = i;
        int left = getLeft(i);
        int right = getRight(i);
        if (left < heap.size() && compare(heap.get(left), heap.get(max)) > 0) {
            max = left;
        }
        if (right < heap.size() && compare(heap.get(right), heap.get(max)) > 0) {
            max = right;
        }
        if (max != i) {
            swap(max, i);
            heapify(max);
        }
    }
}

冒泡排序

  1. 比较相邻的元素。如果第一个比第二个大,就交换它们两个。

  2. 对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。这步做完后,最后的元素会是最大的。

  3. 针对所有的元素重复以上的步骤,除了最后一个。

  4. 持续每次对越来越少的元素重复上面的步骤,直到没有任何一对元素需要比较。

冒泡排序

public class BubbleSorter<T> extends Sorter<T> {

    public BubbleSorter() {}
    public BubbleSorter(Comparator<? super T> comparator) {
        super(comparator);
    }

    @Override
    public void sort(T[] array) {
        int length = array.length;
        boolean finished = false;
        for (int i = 0; i < length - 1 && !finished; i++) {
            finished = true;
            for (int j = 0; j < length - i - 1; j++) {
                if (compare(array[j], array[j + 1]) > 0) {
                    swap(array, j, j + 1);
                    finished = false;
                }
            }
        }
    }
}

快速排序

  1. 在数据集之中,选择一个元素作为枢轴(pivot)元素。

  2. 分区(partition):将所有小于枢轴的元素,都移到枢轴的左边;将所有大于枢轴的元素,都移到枢轴的右边。分区操作后,枢轴元素所处的位置就是最终排序后它所在的位置。

  3. 对枢轴元素左右两侧的两个子集,分别重复第一步和第二步(递归),直到所有子集都只剩下一个元素为止。

快速排序

分区(partition)是快速排序的主要内容,步骤如下:

  1. 把枢轴元素移到末尾(如果直接选择最后一个元素作为枢轴元素,则不需要移动)。

  2. 从左到右(除了最后的枢轴元素),将小于等于枢轴的元素依次移动到数组的开头,每次移动后将 index 加 1,表示下一个小于等于枢轴的元素将要移动到的位置。

  3. 循环结束后 index 所代表的位置就是最终排序后枢轴元素所在的位置,所以将枢轴元素(末尾)与 index 处的元素交换。

public class QuickSorter<T> extends Sorter<T> {

    public QuickSorter() {}
    public QuickSorter(Comparator<? super T> comparator) {
        super(comparator);
    }

    @Override
    public void sort(T[] array) {
        partition(array, 0, array.length - 1);
    }

    private void partition(T[] array, int head, int tail) {
        if (head >= tail) {
            return;
        }
        T pivot = array[tail];
        int index = head;
        for (int i = head; i < tail; i++) {
            if (compare(array[i], pivot) <= 0) {
                swap(array, i, index++);
            }
        }
        swap(array, index, tail);
        partition(array, head, index - 1);
        partition(array, index + 1, tail);
    }
}

插入排序

  1. 从第一个元素开始,该元素可以认为已经被排序

  2. 取出下一个元素,在已经排序的元素序列中从后向前扫描

  3. 如果该元素(已排序)大于新元素,将该元素移到下一位置

  4. 重复步骤 3,直到找到已排序的元素小于或者等于新元素的位置

  5. 将新元素插入到该位置后

  6. 重复步骤 2 - 5

插入排序

插入排序

public class InsertionSorter<T> extends Sorter<T> {

    public InsertionSorter() {}
    public InsertionSorter(Comparator<? super T> comparator) {
        super(comparator);
    }

    @Override
    public void sort(T[] array) {
        int length = array.length;
        for (int i = 1; i < length; i++) {
            T temp = array[i];
            int j = i - 1;
            for (; j >= 0 && compare(array[j], temp) > 0; j--) {
                array[j + 1] = array[j];
            }
            array[j + 1] = temp;
        }
    }
}

希尔排序

希尔排序是基于插入排序的以下两点性质而提出改进方法的:

  1. 插入排序在对几乎已经排好序的数据操作时,效率高,即可以达到线性排序的效率。

  2. 插入排序一般来说是低效的,因为插入排序每次只能将数据移动一位。

希尔排序通过将全部元素分为几个区域来提升插入排序的性能,这样可以让一个元素可以一次性地朝最终位置前进一大步。然后算法再取越来越小的步长进行排序,算法的最后一步就是普通的插入排序,但是到了这步,需排序的数据几乎是已排好的了(此时插入排序较快)。

希尔排序

public class ShellSorter<T> extends Sorter<T> {

    public ShellSorter() {}
    public ShellSorter(Comparator<? super T> comparator) {
        super(comparator);
    }

    @Override
    public void sort(T[] array) {
        int length = array.length;
        int gap = 1;
        while (gap < length / 3) {
            gap = gap * 3 + 1;
        }
        for (; gap > 0; gap /= 3) {
            for (int i = gap; i < length; i++) {
                T temp = array[i];
                int j = i - gap;
                for (; j >= 0 && compare(array[j], temp) > 0; j -= gap) {
                    array[j + gap] = array[j];
                }
                array[j + gap] = temp;
            }
        }
    }
}

归并排序

  1. 将序列每相邻两个元素进行归并操作,形成 floor(n/2) 个序列,排序后每个序列包含两个元素

  2. 将上述序列再次归并,形成 floor(n/4) 个序列,每个序列包含四个元素

  3. 重复步骤2,直到所有元素排序完毕

归并排序

归并排序

public class MergeSorter<T> extends Sorter<T> {

    public MergeSorter() {}
    public MergeSorter(Comparator<? super T> comparator) {
        super(comparator);
    }

    @Override
    public void sort(T[] array) {
        merge(array, 0, array.length - 1);
    }

    private void merge(T[] array, int head, int tail) {
        if (head >= tail) {
            return;
        }
        int middle = (head + tail) >>> 1;
        merge(array, head, middle);
        merge(array, middle + 1, tail);
        Object[] temp = new Object[tail - head + 1];
        int i = head;
        int j = middle + 1;
        int index = 0;
        while (i <= middle && j <= tail) {
            temp[index++] = (compare(array[i], array[j]) < 0)
                ? array[i++]
                : array[j++];
        }
        if (i <= middle) {
            System.arraycopy(array, i, temp, index, middle - i + 1);
        }
        if (j <= tail) {
            System.arraycopy(array, j, temp, index, tail - j + 1);
        }
        System.arraycopy(temp, 0, array, head, tail - head + 1);
    }
}

基数排序

基数排序是一种非比较型整数排序算法,其原理是将整数按位切割,然后按每个位数分别比较。

public class RadixSorter<T> extends Sorter<T> {

    public RadixSorter() {}

    @Override
    public void sort(T[] arr) {
        if (!(arr instanceof Integer[])) {
            throw new IllegalArgumentException("只能对非负整数排序");
        }
        Integer[] array = (Integer[]) arr;
        int length = array.length;
        int digit = getDigit(array);
        for (int count = 0; count < digit; count++) {
            Integer[][] bucket = new Integer[10][length];
            for (int i = 0; i < length; i++) {
                int j = array[i] / pow(count) % 10;
                int k = 0;
                while (bucket[j][k] != null) {
                    k++;
                }
                bucket[j][k] = array[i];
            }
            for (int i = 0, j = 0; j < 10; j++) {
                int k = 0;
                while (bucket[j][k] != null) {
                    array[i++] = bucket[j][k++];
                }
            }
        }
    }

    @Override
    public void setComparator(Comparator<? super T> comparator) {
        throw new IllegalArgumentException("只能以自然顺序排序");
    }

    @Override
    public void sort(T[] array, Comparator<T> comparator) {
        throw new IllegalArgumentException("只能以自然顺序排序");
    }

    private int getDigit(Integer[] array) {
        int maxIndex = 0;
        for (int i = 0; i < array.length; i++) {
            if (array[i] < 0) {
                throw new IllegalArgumentException("只能对非负整数排序");
            }
            if (array[i] > array[maxIndex]) {
                maxIndex = i;
            }
        }
        int digit = 1;
        Integer max = array[maxIndex];
        while ((max /= 10) > 0) {
            digit++;
        }
        return digit;
    }

    private int pow(int count) {
        int result = 1;
        for (int i = 0; i < count; i++) {
            int a = result << 1;
            int b = result << 3;
            result = a + b;
        }
        return result;
    }
}

测试用例

public class SorterTest {

    private final int length = 100;
    private Integer[] array = new Integer[length];
    private Integer[] actuals = null;
    private Sorter<Integer> sorter = null;
    private Comparator<Integer> comparator = null;

    @Rule
    public ExpectedException expectedException = ExpectedException.none();

    @Before
    public void setUp() {
        sorter = new ShellSorter<>();
        Random random = new Random();
        for (int i = 0; i < length; i++) {
            array[i] = random.nextInt(length);
        }
        actuals = Arrays.copyOf(array, length);
        comparator = new Comparator<Integer>() {
            @Override
            public int compare(Integer o1, Integer o2) {
                return o2 - o1;
            }
        };
    }

    @Test
    public void testSort_Comparable() {
        Arrays.sort(actuals);
        sorter.sort(array);
        Assert.assertArrayEquals(array, actuals);
    }

    @Test
    public void testSort_Comparator() {
        if (sorter instanceof RadixSorter) {
            expectedException.expect(IllegalArgumentException.class);
            expectedException.expectMessage("只能以自然顺序排序");
        }
        Arrays.sort(actuals, comparator);
        sorter.sort(array, comparator);
        Assert.assertArrayEquals(array, actuals);
    }
}

复杂度和稳定性

排序算法 时间复杂度 空间复杂度 稳定性 复杂性
平均 最好 最坏
选择排序 O(n2) O(n2) O(n2) O(1) 不稳定 简单
堆排序 O(nlog2n) O(nlog2n) O(nlog2n) O(1) 不稳定 复杂
冒泡排序 O(n2) O(n) O(n2) O(1) 稳定 简单
快速排序 O(nlog2n) O(nlog2n) O(n2) O(log2n) 不稳定 复杂
插入排序 O(n2) O(n) O(n2) O(1) 稳定 简单
希尔排序 O(n1.3) O(n) O(n2) O(1) 不稳定 复杂
归并排序 O(nlog2n) O(nlog2n) O(nlog2n) O(n) 稳定 复杂
基数排序 O(d(n+r)) O(d(n+r)) O(d(n+r)) O(r) 稳定 复杂