排序算法(二)-- 归并、快排

711 阅读3分钟

Sort接口参考上一篇文章排序算法(一)-- 冒泡、插入、选择

测试用例

@Slf4j
class SortTest {
    Integer[] integers;

    Sort<Integer> sort;

    @BeforeEach
    void setUp() {
        integers = new Integer[]{3, 7, 2, 1, 4, 8, 9, 8, 5, 6, 0};
        log.info("排序前: {}", Arrays.toString(integers));
    }

    @AfterEach
    void tearDown() {
        assertSort(sort.sort());
    }

    @Test
    void mergeSortTest() {
        sort = new MergeSort(integers);
    }

    @Test
    void quickSortTest() {
        sort = new QuickSort(integers);
    }

    private void assertSort(Integer[] sort) {
        log.info("排序后: {}", Arrays.toString(sort));
        assertAll(
                () -> assertEquals(integers.length, sort.length),
                () -> assertEquals(0, sort[0]),
                () -> assertEquals(9, sort[sort.length - 1]),
                () -> assertEquals(1, sort[1]),
                () -> assertEquals(2, sort[2]),
                () -> assertEquals(3, sort[3]),
                () -> assertEquals(4, sort[4]),
                () -> assertEquals(5, sort[5]),
                () -> assertEquals(6, sort[6]),
                () -> assertEquals(7, sort[7]),
                () -> assertEquals(8, sort[8]),
                () -> assertEquals(8, sort[9])
        );
    }
}

归并排序

归并排序的思想是将数组从中间拆分为两段,最终将一个数组拆分为单个元素,然后在合并的过程中进行排序。

代码实现可以发现,在排序的过程中,需要申请一个临时数组,将元素排序后放到临时数组,然后再将临时数组copy到原数组中的对应位置,所以它不是原地排序算法。

public class MergeSort implements Sort<Integer> {
    private Integer[] elements;

    public MergeSort(Integer[] elements) {
        this.elements = elements;
    }

    @Override
    public Integer[] sort() {
        int start = 0;
        int end = elements.length - 1;
        mergeSort(elements, start, end);
        return elements;
    }

    public void mergeSort(Integer[] arrays, int start, int end) {
        // 终止条件
        if (start >= end) {
            return;
        }
        int mid = (start + end) / 2;
        mergeSort(arrays, start, mid);
        mergeSort(arrays, mid + 1, end);
        merge(arrays, start, mid, end);
    }

    public void merge(Integer[] arrays, int start, int mid, int end) {
        // 1. 申请临时数组
        Integer[] temp = new Integer[end - start + 1];
        int i = 0;
        int p = start;
        int q = mid + 1;
        // 2. 比较后排序放入临时数组
        while (p <= mid && q <= end) {
            if (arrays[p] < arrays[q]) {
                temp[i++] = arrays[p++];
            } else {
                temp[i++] = arrays[q++];
            }
        }

        while (p <= mid) {
            temp[i++] = arrays[p++];
        }

        while (q <= end) {
            temp[i++] = arrays[q++];
        }
        
        // 3. copy临时数组到原数组对应位置
        for (int k = 0; k < i; k++) {
            arrays[start + k] = temp[k];
        }
//        System.arraycopy(temp, 0, arrays, start, temp.length);
    }

}

归并排序整体分解图 image.png

合并过程分解图

对应代码为public void merge(Integer[] arrays, int start, int mid, int end){...}

image.png

快速排序

快速排序的原理是:如果排序数组下标为q到r,那么从其中任选一个元素作为区分点,小于区分点的放左边,大于区分点的放右边。分区完成后,左右两个区间重复操作,直到不能再分区为止。

public class QuickSort implements Sort<Integer> {

    private Integer[] elements;

    public QuickSort(Integer[] elements) {
        this.elements = elements;
    }

    @Override
    public Integer[] sort() {
        quickSort(elements, 0, elements.length - 1);
        return elements;
    }

    private void quickSort(Integer[] array, int p, int r) {
        if (p >= r) {
            return;
        }
        int mid = partition(array, p, r);
        quickSort(array, p, mid - 1);
        quickSort(array, mid + 1, r);
    }

    private int partition(Integer[] array, int p, int r) {
        int pivot = array[r];
        // i 为小于分区点元素的左侧数组index
        // j 为本次待遍历的元素数组index
        int i = p;
        for (int j = p; j < r; j++) {
            if (array[j] < pivot) {
                if (i != j) {
                    swapArray(array, i, j);
                }
                i++;
            }
        }
        swapArray(array, i, r);
        return i;
    }

    private void swapArray(Integer[] srcArray, int srcPos, int destPos) {
        int temp = srcArray[destPos];
        srcArray[destPos] = srcArray[srcPos];
        srcArray[srcPos] = temp;
    }
}

分区操作分解图

image.png

最后

归并和快排都使用了分治的思想,代码通过递归了实现。但归并排序需要申请临时数组,临时数组不会超过需要排序的总数组大小,空间复杂度是O(n)。而快排则是在原数组上进行排序,属于原地排序算法。所以快速排序的运用比归并排序要广泛一些。

另外,凭空想象排序过程比较抽象,不是太好理解,通过手画图,代码打断点的方式,可以帮助理解。