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);
}
}
归并排序整体分解图
合并过程分解图
对应代码为
public void merge(Integer[] arrays, int start, int mid, int end){...}
快速排序
快速排序的原理是:如果排序数组下标为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;
}
}
分区操作分解图
最后
归并和快排都使用了分治的思想,代码通过递归了实现。但归并排序需要申请临时数组,临时数组不会超过需要排序的总数组大小,空间复杂度是O(n)。而快排则是在原数组上进行排序,属于原地排序算法。所以快速排序的运用比归并排序要广泛一些。
另外,凭空想象排序过程比较抽象,不是太好理解,通过手画图,代码打断点的方式,可以帮助理解。