diff --git a/.gitignore b/.gitignore index 852dcc2..fd1c528 100644 --- a/.gitignore +++ b/.gitignore @@ -137,4 +137,7 @@ dist .DS_Store # Local history -.history \ No newline at end of file +.history + +# IntelliJ +.idea/ \ No newline at end of file diff --git "a/\345\217\213\351\223\276/\344\272\214\350\257\276\345\256\242\346\210\267\347\253\257/index.md" "b/\345\217\213\351\223\276/\344\272\214\350\257\276\345\256\242\346\210\267\347\253\257/index.md" index 04d150a..92a9ee7 100644 --- "a/\345\217\213\351\223\276/\344\272\214\350\257\276\345\256\242\346\210\267\347\253\257/index.md" +++ "b/\345\217\213\351\223\276/\344\272\214\350\257\276\345\256\242\346\210\267\347\253\257/index.md" @@ -1,6 +1,6 @@ # [SecondClass](https://github.com/thriic/SecondClass) -CUIT 第二课堂安卓客户端(deprecated, 已经弃用) +CUIT 第二课堂安卓客户端 (deprecated, 已经弃用) ***请尽可能在合理时间内进行报名签到*** ***(如只签到进行中的活动)*** diff --git "a/\345\255\246\344\271\240\347\233\270\345\205\263/\345\260\261\344\270\232/\350\256\241\347\256\227\346\234\272\345\270\270\350\200\203\345\205\253\350\202\241\346\226\207\346\200\273\347\273\223/\346\225\260\346\215\256\347\273\223\346\236\204\345\222\214\347\256\227\346\263\225/\346\216\222\345\272\217\347\256\227\346\263\225/1. \345\206\222\346\263\241\346\216\222\345\272\217/index.md" "b/\345\255\246\344\271\240\347\233\270\345\205\263/\345\260\261\344\270\232/\350\256\241\347\256\227\346\234\272\345\270\270\350\200\203\345\205\253\350\202\241\346\226\207\346\200\273\347\273\223/\346\225\260\346\215\256\347\273\223\346\236\204\345\222\214\347\256\227\346\263\225/\346\216\222\345\272\217\347\256\227\346\263\225/1. \345\206\222\346\263\241\346\216\222\345\272\217/index.md" new file mode 100644 index 0000000..22aff89 --- /dev/null +++ "b/\345\255\246\344\271\240\347\233\270\345\205\263/\345\260\261\344\270\232/\350\256\241\347\256\227\346\234\272\345\270\270\350\200\203\345\205\253\350\202\241\346\226\207\346\200\273\347\273\223/\346\225\260\346\215\256\347\273\223\346\236\204\345\222\214\347\256\227\346\263\225/\346\216\222\345\272\217\347\256\227\346\263\225/1. \345\206\222\346\263\241\346\216\222\345\272\217/index.md" @@ -0,0 +1,136 @@ +**本模块是公共类课程,适合绝大部分计算机岗位。由于 Java 受众最广且易读性强,本模块示例代码使用 Java 编写,其他语言也可参考学习。** + +## 冒泡排序原理 + +冒泡排序是一种简单的排序算法,主要思想是从头到尾依次比较相邻的两个元素,如果逆序则交换,直到没有逆序的元素为止。 + +冒泡排序的时间复杂度是 O(n^2),空间复杂度是 O(1)。 + +## 冒泡排序实现 + +如,对数组`arr = [5, 3, 8, 6, 4]`进行升序冒泡排序。 + +设置`end`指向数组末尾,每一轮比较结束后,`end--`,或者说,`end = arr.length - 轮数 - 1`。 + +每一轮让指针`cur`指向数组第 0 个元素,然后依次比较。 + +最后设置一个标志位`flag`,每一轮都初始化为`false`,如果某一轮没有发生交换,则说明数组已经有序,直接跳出循环。 + +排序过程如下: + +1. 第一轮 + + | 5 | 3 | 8 | 6 | 4 | + | --- | ---- | --- | --- | --- | + | cur | next | | | | + + `arr[cur] > arr[next]`,交换位置,`flag = true` + + | 3 | 5 | 8 | 6 | 4 | + | --- | --- | ---- | --- | --- | + | | cur | next | | | + + `arr[cur] < arr[next]`,不交换位置 + + | 3 | 5 | 8 | 6 | 4 | + | --- | --- | --- | ---- | --- | + | | | cur | next | | + + `arr[cur] > arr[next]`,交换位置 + + | 3 | 5 | 6 | 8 | 4 | + | --- | --- | --- | --- | ---- | + | | | | cur | next | + + `arr[cur] > arr[next]`,交换位置 + + | 3 | 5 | 6 | 4 | 8 | + | --- | --- | --- | --- | --- | + | | | | | cur | + + `cur`位于`end`位置,跳出循环,`end--` + +2. 第二轮 + + | 3 | 5 | 6 | 4 | 8 | + | --- | ---- | --- | --- | --- | + | cur | next | | | | + + `arr[cur] < arr[next]`,不交换位置 + + | 3 | 5 | 6 | 4 | 8 | + | --- | --- | ---- | --- | --- | + | | cur | next | | | + + `arr[cur] < arr[next]`,不交换位置 + + | 3 | 5 | 6 | 4 | 8 | + | --- | --- | --- | ---- | --- | + | | | cur | next | | + + `arr[cur] > arr[next]`,交换位置,`flag = true` + + | 3 | 5 | 4 | 6 | 8 | + | --- | --- | --- | --- | --- | + | | | | cur | | + + `cur`位于`end`位置,跳出循环,`end--` + +3. 第三轮 + + | 3 | 5 | 4 | 6 | 8 | + | --- | ---- | --- | --- | --- | + | cur | next | | | | + + `arr[cur] < arr[next]`,不交换位置 + + | 3 | 5 | 4 | 6 | 8 | + | --- | --- | ---- | --- | --- | + | | cur | next | | | + + `arr[cur] > arr[next]`,交换位置,`flag = true` + + | 3 | 4 | 5 | 6 | 8 | + | --- | --- | --- | --- | --- | + | | | cur | | | + + `cur`位于`end`位置,跳出循环,`end--` + +4. 第四轮 + + | 3 | 4 | 5 | 6 | 8 | + | --- | ---- | --- | --- | --- | + | cur | next | | | | + + `arr[cur] < arr[next]`,不交换位置 + + | 3 | 4 | 5 | 6 | 8 | + | --- | --- | --- | --- | --- | + | | cur | | | | + + `cur`位于`end`位置,跳出循环。 + + 这一轮`flag = false`,说明数组已经有序,提前结束排序 + +## 冒泡排序代码实现 + +```java +public void bubbleSort(int[] nums) { + int n = nums.length; + for (int i = 0; i < n - 1; i++) { + boolean flag = false; + for (int j = 0; j < n - i - 1; j++) { + if (nums[j] > nums[j + 1]) { + int temp = nums[j]; + nums[j] = nums[j + 1]; + nums[j + 1] = temp; + // 支持元祖的编程语言可以写成 (nums[j], nums[j + 1]) = (nums[j + 1], nums[j]); + flag = true; + } + } + if (!flag) { + break; + } + } +} +``` \ No newline at end of file diff --git "a/\345\255\246\344\271\240\347\233\270\345\205\263/\345\260\261\344\270\232/\350\256\241\347\256\227\346\234\272\345\270\270\350\200\203\345\205\253\350\202\241\346\226\207\346\200\273\347\273\223/\346\225\260\346\215\256\347\273\223\346\236\204\345\222\214\347\256\227\346\263\225/\346\216\222\345\272\217\347\256\227\346\263\225/2. \351\200\211\346\213\251\346\216\222\345\272\217/index.md" "b/\345\255\246\344\271\240\347\233\270\345\205\263/\345\260\261\344\270\232/\350\256\241\347\256\227\346\234\272\345\270\270\350\200\203\345\205\253\350\202\241\346\226\207\346\200\273\347\273\223/\346\225\260\346\215\256\347\273\223\346\236\204\345\222\214\347\256\227\346\263\225/\346\216\222\345\272\217\347\256\227\346\263\225/2. \351\200\211\346\213\251\346\216\222\345\272\217/index.md" new file mode 100644 index 0000000..792aae9 --- /dev/null +++ "b/\345\255\246\344\271\240\347\233\270\345\205\263/\345\260\261\344\270\232/\350\256\241\347\256\227\346\234\272\345\270\270\350\200\203\345\205\253\350\202\241\346\226\207\346\200\273\347\273\223/\346\225\260\346\215\256\347\273\223\346\236\204\345\222\214\347\256\227\346\263\225/\346\216\222\345\272\217\347\256\227\346\263\225/2. \351\200\211\346\213\251\346\216\222\345\272\217/index.md" @@ -0,0 +1,117 @@ +**本模块是公共类课程,适合绝大部分计算机岗位。由于 Java 受众最广且易读性强,本模块示例代码使用 Java 编写,其他语言也可参考学习。** + +## 选择排序原理 + +选择排序是一种简单的排序算法,主要思想是每次从未排序的元素中选择最小或最大的元素,放到已排序的末尾。 + +选择排序的时间复杂度是 O(n^2),空间复杂度是 O(1)。 + +## 选择排序实现 + +如,对数组`arr = [5, 3, 8, 6, 4]`进行升序选择排序。 + +先让指针`i`指向数组第 0 个元素,表示未排序的第 0 个元素,每经过一轮选择,`i++`。 + +每一轮让指针`minIndex`指向`i`,然后依次比较,找到最小的元素的索引,最后和`i`指向的元素交换位置。 + +排序过程如下: + +1. 第一轮 + + | 5 | 3 | 8 | 6 | 4 | + | --- | --- | --- | --- | --- | + | min | j | | | | + + `arr[min] > arr[j]`,改变指针指向 + + | 5 | 3 | 8 | 6 | 4 | + | --- | --- | --- | --- | --- | + | i | min | j | | | + + `arr[min] < arr[j]`,不改变指针指向 + + | 5 | 3 | 8 | 6 | 4 | + | --- | --- | --- | --- | --- | + | i | min | | j | | + + `arr[min] < arr[j]`,不改变指针指向 + + | 5 | 3 | 8 | 6 | 4 | + | --- | --- | --- | --- | --- | + | i | min | | | j | + + `arr[min] < arr[j]`,不改变指针指向,此时`j`位于数组末尾,开始交换位置 + + | 3 | 5 | 8 | 6 | 4 | + | --- | --- | --- | --- | --- | + +2. 第二轮 + + | 3 | 5 | 8 | 6 | 4 | + | --- | --- | --- | --- | --- | + | | min | j | | | + + `arr[min] < arr[j]`,不改变指针指向 + + | 3 | 5 | 8 | 6 | 4 | + | --- | --- | --- | --- | --- | + | | min | | j | | + + `arr[min] < arr[j]`,不改变指针指向 + + | 3 | 5 | 8 | 6 | 4 | + | --- | --- | --- | --- | --- | + | | min | | | j | + + `arr[min] > arr[j]`,改变指针指向,此时`j`位于数组末尾,开始交换位置 + + | 3 | 4 | 8 | 6 | 5 | + | --- | --- | --- | --- | --- | + +3. 第三轮 + + | 3 | 4 | 8 | 6 | 5 | + | --- | --- | --- | --- | --- | + | | | min | j | | + + `arr[min] > arr[j]`,改变指针指向 + + | 3 | 4 | 8 | 6 | 5 | + | --- | --- | --- | --- | --- | + | | | i | min | j | + + `arr[min] > arr[j]`,改变指针指向,此时`j`位于数组末尾,开始交换位置 + + | 3 | 4 | 5 | 6 | 8 | + | --- | --- | --- | --- | --- | + +4. 第四轮 + + | 3 | 4 | 5 | 6 | 8 | + | --- | --- | --- | --- | --- | + | | | | min | j | + + `arr[min] < arr[j]`,不改变指针指向,此时`j`位于数组末尾,开始交换位置 + + 循环结束后,`i`指向数组末尾,排序完成。 + +## 选择排序代码实现 + +```java +public void selectionSort(int[] nums) { + int n = nums.length; + for (int i = 0; i < n - 1; i++) { + int minIndex = i; + for (int j = i + 1; j < n; j++) { + if (nums[j] < nums[minIndex]) { + minIndex = j; + } + } + if (minIndex != i) { + int temp = nums[i]; + nums[i] = nums[minIndex]; + nums[minIndex] = temp; + } + } +} +``` \ No newline at end of file diff --git "a/\345\255\246\344\271\240\347\233\270\345\205\263/\345\260\261\344\270\232/\350\256\241\347\256\227\346\234\272\345\270\270\350\200\203\345\205\253\350\202\241\346\226\207\346\200\273\347\273\223/\346\225\260\346\215\256\347\273\223\346\236\204\345\222\214\347\256\227\346\263\225/\346\216\222\345\272\217\347\256\227\346\263\225/3. \346\217\222\345\205\245\346\216\222\345\272\217/index.md" "b/\345\255\246\344\271\240\347\233\270\345\205\263/\345\260\261\344\270\232/\350\256\241\347\256\227\346\234\272\345\270\270\350\200\203\345\205\253\350\202\241\346\226\207\346\200\273\347\273\223/\346\225\260\346\215\256\347\273\223\346\236\204\345\222\214\347\256\227\346\263\225/\346\216\222\345\272\217\347\256\227\346\263\225/3. \346\217\222\345\205\245\346\216\222\345\272\217/index.md" new file mode 100644 index 0000000..c57b695 --- /dev/null +++ "b/\345\255\246\344\271\240\347\233\270\345\205\263/\345\260\261\344\270\232/\350\256\241\347\256\227\346\234\272\345\270\270\350\200\203\345\205\253\350\202\241\346\226\207\346\200\273\347\273\223/\346\225\260\346\215\256\347\273\223\346\236\204\345\222\214\347\256\227\346\263\225/\346\216\222\345\272\217\347\256\227\346\263\225/3. \346\217\222\345\205\245\346\216\222\345\272\217/index.md" @@ -0,0 +1,122 @@ +**本模块是公共类课程,适合绝大部分计算机岗位。由于 Java 受众最广且易读性强,本模块示例代码使用 Java 编写,其他语言也可参考学习。** + +## 插入排序原理 + +插入排序是一种简单的排序算法,主要思想是通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。 + +插入排序的时间复杂度是 O(n^2),空间复杂度是 O(1)。 + +## 插入排序实现 + +如,对数组 `arr = [5, 3, 8, 6, 4]` 进行升序插入排序。 + +默认第 0 个元素是有序的, + +先让指针 `i` 指向数组第 1 个元素,表示未排序区域的第 0 个元素,每经过一轮插入,`i++`。 + +用`temp`保存`i`指向的元素。 + +每一轮让指针 `j` 指向 `i - 1`,然后依次向前比较,找到合适的位置插入当前元素。 + +排序过程如下: + +1. 第一轮 + + `temp = arr[1] = 3` + + | 5 | | 8 | 6 | 4 | + | --- | --- | --- | --- | --- | + | j | i | | | | + + `arr[j] > temp`,`arr[j]` 向后移动 + + | | 5 | 8 | 6 | 4 | + | --- | --- | --- | --- | --- | + | | i | | | | + + 此时 `j = -1`,直接在数组头部插入 `temp` + + | 3 | 5 | 8 | 6 | 4 | + | --- | --- | --- | --- | --- | + +2. 第二轮 + + `temp = arr[2] = 8` + + | 3 | 5 | | 6 | 4 | + | --- | --- | --- | --- | --- | + | | j | i | | | + + `arr[j] < temp`,直接插入 `temp` + + | 3 | 5 | 8 | 6 | 4 | + | --- | --- | --- | --- | --- | + +3. 第三轮 + + `temp = arr[3] = 6` + + | 3 | 5 | 8 | | 4 | + | --- | --- | --- | --- | --- | + | | | j | i | | + + `arr[j] > temp`,`arr[j]` 向后移动 + + | 3 | 5 | | 8 | 4 | + | --- | --- | --- | --- | --- | + | | j | | i | | + + `arr[j] < temp`,直接插入 `temp` + + | 3 | 5 | 6 | 8 | 4 | + | --- | --- | --- | --- | --- | + +4. 第四轮 + + `temp = arr[4] = 4` + + | 3 | 5 | 6 | 8 | | + | --- | --- | --- | --- | --- | + | | | | j | i | + + `arr[j] > temp`,`arr[j]` 向后移动 + + | 3 | 5 | 6 | | 8 | + | --- | --- | --- | --- | --- | + | | | j | | i | + + `arr[j] > temp`,`arr[j]` 向后移动 + + | 3 | 5 | | 6 | 8 | + | --- | --- | --- | --- | --- | + | | j | | | i | + + `arr[j] > temp`,`arr[j]` 向后移动 + + | 3 | | 5 | 6 | 8 | + | --- | --- | --- | --- | --- | + | j | | | | i | + + `arr[j] < temp`,直接插入 `temp` + + | 3 | 4 | 5 | 6 | 8 | + | --- | --- | --- | --- | --- | + + `i` 已经到达数组末尾,排序结束。 + +## 插入排序代码实现 + +```java +public void insertionSort(int[] nums) { + int n = nums.length; + for (int i = 1; i < n; i++) { + int key = nums[i]; + int j = i - 1; + while (j >= 0 && nums[j] > key) { + nums[j + 1] = nums[j]; + j--; + } + nums[j + 1] = key; + } +} +``` \ No newline at end of file diff --git "a/\345\255\246\344\271\240\347\233\270\345\205\263/\345\260\261\344\270\232/\350\256\241\347\256\227\346\234\272\345\270\270\350\200\203\345\205\253\350\202\241\346\226\207\346\200\273\347\273\223/\346\225\260\346\215\256\347\273\223\346\236\204\345\222\214\347\256\227\346\263\225/\346\216\222\345\272\217\347\256\227\346\263\225/4. \345\270\214\345\260\224\346\216\222\345\272\217/index.md" "b/\345\255\246\344\271\240\347\233\270\345\205\263/\345\260\261\344\270\232/\350\256\241\347\256\227\346\234\272\345\270\270\350\200\203\345\205\253\350\202\241\346\226\207\346\200\273\347\273\223/\346\225\260\346\215\256\347\273\223\346\236\204\345\222\214\347\256\227\346\263\225/\346\216\222\345\272\217\347\256\227\346\263\225/4. \345\270\214\345\260\224\346\216\222\345\272\217/index.md" new file mode 100644 index 0000000..9e2c7d7 --- /dev/null +++ "b/\345\255\246\344\271\240\347\233\270\345\205\263/\345\260\261\344\270\232/\350\256\241\347\256\227\346\234\272\345\270\270\350\200\203\345\205\253\350\202\241\346\226\207\346\200\273\347\273\223/\346\225\260\346\215\256\347\273\223\346\236\204\345\222\214\347\256\227\346\263\225/\346\216\222\345\272\217\347\256\227\346\263\225/4. \345\270\214\345\260\224\346\216\222\345\272\217/index.md" @@ -0,0 +1,64 @@ +**本模块是公共类课程,适合绝大部分计算机岗位。由于 Java 受众最广且易读性强,本模块示例代码使用 Java 编写,其他语言也可参考学习。** + +## 希尔排序原理 + +希尔排序(Shell Sort)是一种基于插入排序的排序算法,通过将数组分成若干子序列分别进行插入排序,从而加快排序速度。希尔排序的核心思想是先将整个待排序的记录序列分割成若干子序列分别进行直接插入排序,待整个序列中的记录“基本有序”时,再对全体记录进行一次直接插入排序。 + +希尔排序的时间复杂度在最坏情况下为 O(n^2),在平均情况下为 O(n log n),空间复杂度为 O(1)。 + +## 希尔排序实现 + +如,对数组 `arr = [5, 3, 8, 6, 4]` 进行升序希尔排序。 + +希尔排序的步骤如下: + +1. 选择一个增量序列 `gap`,通常初始值为数组长度的一半,然后逐步减小 `gap` 直到为 1。 +2. 对每个 `gap` 进行分组,对每组进行插入排序。 + +排序过程如下: + +1. 初始 `gap = 5 >>> 1 | 0 = 2` + + 得到分组: + + | 5 | 3 | 8 | 6 | 4 | + | --- | --- | --- | --- | --- | + | 组 1 | 组 2 | 组 1 | 组 2 | 组 3 | + + 对每组进行插入排序: + + - 第一组:`[5, 8]` + - 第二组:`[3, 6]` + - 第三组:`[4]` + + 排序后: + + | 5 | 3 | 8 | 6 | 4 | + | --- | --- | --- | --- | --- | + +2. `gap = 2 >>> 1 | 0 = 1` + + 当步长为 1 时,排序退化为插入排序。 + + 流程参考[插入排序](../3.%20插入排序/index.md)。 + + 本轮完成过后,数组排序完成。 + +## 希尔排序代码实现 + +```java +public void shellSort(int[] nums) { + int n = nums.length; + for (int gap = n / 2; gap > 0; gap /= 2) { + for (int i = gap; i < n; i++) { + int temp = nums[i]; + int j = i; + while (j >= gap && nums[j - gap] > temp) { + nums[j] = nums[j - gap]; + j -= gap; + } + nums[j] = temp; + } + } +} +``` \ No newline at end of file diff --git "a/\345\255\246\344\271\240\347\233\270\345\205\263/\345\260\261\344\270\232/\350\256\241\347\256\227\346\234\272\345\270\270\350\200\203\345\205\253\350\202\241\346\226\207\346\200\273\347\273\223/\346\225\260\346\215\256\347\273\223\346\236\204\345\222\214\347\256\227\346\263\225/\346\216\222\345\272\217\347\256\227\346\263\225/5. \345\275\222\345\271\266\346\216\222\345\272\217/index.md" "b/\345\255\246\344\271\240\347\233\270\345\205\263/\345\260\261\344\270\232/\350\256\241\347\256\227\346\234\272\345\270\270\350\200\203\345\205\253\350\202\241\346\226\207\346\200\273\347\273\223/\346\225\260\346\215\256\347\273\223\346\236\204\345\222\214\347\256\227\346\263\225/\346\216\222\345\272\217\347\256\227\346\263\225/5. \345\275\222\345\271\266\346\216\222\345\272\217/index.md" new file mode 100644 index 0000000..824c08e --- /dev/null +++ "b/\345\255\246\344\271\240\347\233\270\345\205\263/\345\260\261\344\270\232/\350\256\241\347\256\227\346\234\272\345\270\270\350\200\203\345\205\253\350\202\241\346\226\207\346\200\273\347\273\223/\346\225\260\346\215\256\347\273\223\346\236\204\345\222\214\347\256\227\346\263\225/\346\216\222\345\272\217\347\256\227\346\263\225/5. \345\275\222\345\271\266\346\216\222\345\272\217/index.md" @@ -0,0 +1,236 @@ +**本模块是公共类课程,适合绝大部分计算机岗位。由于 Java 受众最广且易读性强,本模块示例代码使用 Java 编写,其他语言也可参考学习。** + +## 归并排序原理 + +归并排序(Merge Sort)是一种基于分治法的排序算法。其核心思想是将数组分成两个子数组,分别进行排序,然后将两个有序子数组合并成一个有序数组。 + +归并排序的时间复杂度为 O(n log n),非原地归并排序空间复杂度为 O(n),原地归并排序空间复杂度为 O(1)。 + +***但要注意,原地归并排序的实现较为复杂,且性能可能不如非原地归并排序,因此并不常见,可以不去记忆。*** + +## 归并排序实现 + +如,对数组 `arr = [5, 3, 8, 6, 4]` 进行升序归并排序。 + +归并排序的步骤如下: + +1. 将数组分成两个子数组。 +2. 递归地对每个子数组进行归并排序。 +3. 合并两个有序子数组。 + +排序过程如下: + +1. 初始数组:`[5, 3, 8, 6, 4]` + + 分成两个子数组: + + - 左子数组:`[5, 3]` + - 右子数组:`[8, 6, 4]` + +2. 对左子数组 `[5, 3]` 进行排序: + + - 分成两个子数组:`[5]` 和 `[3]` + - 对每个子数组进行排序(单个元素视为有序) + - 合并两个有序子数组: + + | 主数组 | | | + | -------- | --- | --- | + | 当前指针 | cur | | + | 子数组 1 | 3 | | + | 当前指针 | i | | + | 子数组 2 | 5 | | + | 当前指针 | j | | + + `arr1[i] < arr2[j]`,`arr1[i]` 插入主数组 + + | 主数组 | 3 | | + | -------- | --- | --- | + | 当前指针 | | cur | + | 子数组 1 | 3 | | + | 当前指针 | | i | + | 子数组 2 | 5 | | + | 当前指针 | j | | + + 子数组 1 已经被遍历完成,`arr2` 元素依次插入主数组 + + | 主数组 | 3 | 5 | + | ------ | --- | --- | + +3. 对右子数组 `[8, 6, 4]` 进行排序: + + - 分成两个子数组:`[8]` 和 `[6, 4]` + - 对子数组 `[6, 4]` 进行排序: + - 分成两个子数组:`[6]` 和 `[4]` + - 对每个子数组进行排序(单个元素视为有序) + - 合并两个有序子数组:`[4, 6]`(过程同步骤 2) + - 合并两个有序子数组: + + | 主数组 | | | | + | -------- | --- | --- | --- | + | 当前指针 | cur | | | + | 子数组 1 | 4 | 6 | | + | 当前指针 | i | | | + | 子数组 2 | 8 | | | + | 当前指针 | j | | | + + `arr1[i] < arr2[j]`,`arr1[i]` 插入主数组 + + | 主数组 | 4 | | | + | -------- | --- | --- | --- | + | 当前指针 | | cur | | + | 子数组 1 | 4 | 6 | | + | 当前指针 | | i | | + | 子数组 2 | 8 | | | + | 当前指针 | j | | | + + `arr1[i] < arr2[j]`,`arr1[i]` 插入主数组 + + | 主数组 | 4 | 6 | | + | -------- | --- | --- | --- | + | 当前指针 | | | cur | + | 子数组 1 | 4 | 6 | | + | 当前指针 | | | i | + | 子数组 2 | 8 | | | + | 当前指针 | j | | | + + 子数组 1 已经被遍历完成,`arr2` 元素依次插入主数组 + + | 主数组 | 4 | 6 | 8 | + | ------ | --- | --- | --- | + +4. 合并两个有序子数组 `[3, 5]` 和 `[4, 6, 8]`: + + | 主数组 | | | | | | + | -------- | --- | --- | --- | --- | --- | + | 当前指针 | cur | | | | | + | 子数组 1 | 3 | 5 | | | | + | 当前指针 | i | | | | | + | 子数组 2 | 4 | 6 | 8 | | | + | 当前指针 | j | | | | | + + `arr1[i] < arr2[j]`,`arr1[i]` 插入主数组 + + | 主数组 | 3 | | | | | + | -------- | --- | --- | --- | --- | --- | + | 当前指针 | | cur | | | | + | 子数组 1 | 3 | 5 | | | | + | 当前指针 | | i | | | | + | 子数组 2 | 4 | 6 | 8 | | | + | 当前指针 | j | | | | | + + `arr1[i] > arr2[j]`,`arr2[j]` 插入主数组 + + | 主数组 | 3 | 4 | | | | + | -------- | --- | --- | --- | --- | --- | + | 当前指针 | | | cur | | | + | 子数组 1 | 3 | 5 | | | | + | 当前指针 | | i | | | | + | 子数组 2 | 4 | 6 | 8 | | | + | 当前指针 | | j | | | | + + `arr1[i] < arr2[j]`,`arr1[i]` 插入主数组 + + | 主数组 | 3 | 4 | 5 | | | + | -------- | --- | --- | --- | --- | --- | + | 当前指针 | | | | cur | | + | 子数组 1 | 3 | 5 | | | | + | 当前指针 | | | i | | | + | 子数组 2 | 4 | 6 | 8 | | | + | 当前指针 | | j | | | | + + 子数组 1 已经被遍历完成,`arr2` 元素依次插入主数组 + + | 主数组 | 3 | 4 | 5 | 6 | 8 | + | ------ | --- | --- | --- | --- | --- | + +## 归并排序代码实现 + +非原地归并排序代码实现如下: + +```java +public class MergeSort { + public void mergeSort(int[] nums) { + if (nums == null || nums.length < 2) { + return; + } + int[] temp = new int[nums.length]; + mergeSort(nums, 0, nums.length - 1, temp); + } + + private void mergeSort(int[] nums, int left, int right, int[] temp) { + if (left < right) { + int mid = left + (right - left) / 2; + mergeSort(nums, left, mid, temp); + mergeSort(nums, mid + 1, right, temp); + merge(nums, left, mid, right, temp); + } + } + + private void merge(int[] nums, int left, int mid, int right, int[] temp) { + int i = left, j = mid + 1, k = 0; + while (i <= mid && j <= right) { + if (nums[i] <= nums[j]) { + temp[k++] = nums[i++]; + } else { + temp[k++] = nums[j++]; + } + } + while (i <= mid) { + temp[k++] = nums[i++]; + } + while (j <= right) { + temp[k++] = nums[j++]; + } + for (i = 0; i < k; i++) { + nums[left + i] = temp[i]; + } + } +} +``` + +原地归并排序代码实现如下: + +```java +public class InPlaceMergeSort { + public void mergeSort(int[] nums) { + if (nums == null || nums.length < 2) { + return; + } + mergeSort(nums, 0, nums.length - 1); + } + + private void mergeSort(int[] nums, int left, int right) { + if (left < right) { + int mid = left + (right - left) / 2; + mergeSort(nums, left, mid); + mergeSort(nums, mid + 1, right); + merge(nums, left, mid, right); + } + } + + private void merge(int[] nums, int left, int mid, int right) { + int start2 = mid + 1; + if (nums[mid] <= nums[start2]) { + return; + } + while (left <= mid && start2 <= right) { + if (nums[left] <= nums[start2]) { + left++; + } else { + int value = nums[start2]; + int index = start2; + + while (index != left) { + nums[index] = nums[index - 1]; + index--; + } + nums[left] = value; + + left++; + mid++; + start2++; + } + } + } +} +``` \ No newline at end of file diff --git "a/\345\255\246\344\271\240\347\233\270\345\205\263/\345\260\261\344\270\232/\350\256\241\347\256\227\346\234\272\345\270\270\350\200\203\345\205\253\350\202\241\346\226\207\346\200\273\347\273\223/\346\225\260\346\215\256\347\273\223\346\236\204\345\222\214\347\256\227\346\263\225/\346\216\222\345\272\217\347\256\227\346\263\225/6. \345\277\253\351\200\237\346\216\222\345\272\217/index.md" "b/\345\255\246\344\271\240\347\233\270\345\205\263/\345\260\261\344\270\232/\350\256\241\347\256\227\346\234\272\345\270\270\350\200\203\345\205\253\350\202\241\346\226\207\346\200\273\347\273\223/\346\225\260\346\215\256\347\273\223\346\236\204\345\222\214\347\256\227\346\263\225/\346\216\222\345\272\217\347\256\227\346\263\225/6. \345\277\253\351\200\237\346\216\222\345\272\217/index.md" new file mode 100644 index 0000000..6f5bc56 --- /dev/null +++ "b/\345\255\246\344\271\240\347\233\270\345\205\263/\345\260\261\344\270\232/\350\256\241\347\256\227\346\234\272\345\270\270\350\200\203\345\205\253\350\202\241\346\226\207\346\200\273\347\273\223/\346\225\260\346\215\256\347\273\223\346\236\204\345\222\214\347\256\227\346\263\225/\346\216\222\345\272\217\347\256\227\346\263\225/6. \345\277\253\351\200\237\346\216\222\345\272\217/index.md" @@ -0,0 +1,193 @@ +**本模块是公共类课程,适合绝大部分计算机岗位。由于 Java 受众最广且易读性强,本模块示例代码使用 Java 编写,其他语言也可参考学习。** + +## 快速排序原理 + +快速排序(Quick Sort)是一种基于分治法的排序算法。其核心思想是通过选择一个基准元素(pivot),将数组分成两个子数组,使得左子数组的所有元素都小于基准元素,右子数组的所有元素都大于基准元素,然后递归地对两个子数组进行排序。 + +快速排序的时间复杂度在最优和平均情况下为 O(n log n),在最坏情况下为 O(n^2)。原地快排空间复杂度为 O(log n)(递归调用栈的深度),非原地快排空间复杂度为 O(n log n)(需要额外的空间存储中间结果,每次递归都要创建新的数组)。 + +***但要注意,和[归并排序](../5.%20归并排序/index.md)不同,快排更加推荐使用原地排序,因为原地排序的空间复杂度更低,性能更好。*** + +## 快速排序实现 + +如,对数组 `arr = [5, 3, 8, 6, 4]` 进行升序快速排序。 + +快速排序的步骤如下: + +1. 选择一个基准元素(pivot)。 +2. 将数组分成两个子数组,使得左子数组的所有元素都小于基准元素,右子数组的所有元素都大于基准元素。 +3. 递归地对两个子数组进行快速排序。 + +排序过程如下: + +1. 初始数组:`[5, 3, 8, 6, 4]` + + 选择基准元素:`5` + + 依次同基准元素比对大小,小元素放左,大元素放右,最终分成两个子数组: + + - 左子数组:`[3, 4]` + - 右子数组:`[8, 6]` + + 此时数组变为:`[3, 4, 5, 8, 6]` + +2. 对左子数组 `[3, 4]` 进行排序: + + - 选择基准元素:`3` + - 分成两个子数组:`[]` 和 `[4]` + - 递归地对两个子数组进行排序(单个元素视为有序) + +3. 对右子数组 `[8, 6]` 进行排序: + + - 选择基准元素:`8` + - 分成两个子数组:`[6]` 和 `[]` + - 递归地对两个子数组进行排序(单个元素视为有序) + +4. 合并结果: + + - 左子数组排序结果:`[3, 4]` + - 右子数组排序结果:`[6, 8]` + - 合并结果:`[3, 4, 5, 6, 8]` + +## 快速排序代码实现 + +原地快排代码实现如下: + +```java +public class QuickSort { + public void quickSort(int[] nums) { + if (nums == null || nums.length < 2) { + return; + } + quickSort(nums, 0, nums.length - 1); + } + + private void quickSort(int[] nums, int left, int right) { + if (left < right) { + int pivotIndex = partition(nums, left, right); + quickSort(nums, left, pivotIndex - 1); + quickSort(nums, pivotIndex + 1, right); + } + } + + private int partition(int[] nums, int left, int right) { + int pivot = nums[right]; + int i = left - 1; + for (int j = left; j < right; j++) { + if (nums[j] <= pivot) { + i++; + swap(nums, i, j); + } + } + swap(nums, i + 1, right); + return i + 1; + } + + private void swap(int[] nums, int i, int j) { + int temp = nums[i]; + nums[i] = nums[j]; + nums[j] = temp; + } +} +``` + +快排的核心是 `partition` 方法,该方法将数组分成两个子数组,并返回基准元素的索引。`quickSort` 方法递归地对两个子数组进行排序。 + +非原地快排代码实现如下: + +```java +import java.util.ArrayList; +import java.util.List; + +public class QuickSortNonInPlace { + public static List quickSort(List list) { + if (list.size() <= 1) { + return list; + } + int pivot = list.get(list.size() / 2); + List left = new ArrayList<>(); + List right = new ArrayList<>(); + List equal = new ArrayList<>(); + for (int num : list) { + if (num < pivot) { + left.add(num); + } else if (num > pivot) { + right.add(num); + } else { + equal.add(num); + } + } + List sortedLeft = quickSort(left); + List sortedRight = quickSort(right); + sortedLeft.addAll(equal); + sortedLeft.addAll(sortedRight); + return sortedLeft; + } +} +``` + +## 快速排序优化(非算法岗不常考) + +快速排序的性能取决于基准元素的选择。如果每次选择的基准元素都是最大或最小元素,那么快速排序的时间复杂度将退化为 O(n^2)。 + +为了避免这种情况,可以采用以下优化策略: + +1. 三数取中法:选择基准元素时,使用三数取中法(选择左、中、右三个元素的中间值)来避免最坏情况。 +2. 尾递归优化:在递归调用时,优先对较小的子数组进行递归,以减少递归调用栈的深度。 +3. 小数组使用插入排序:对于小数组(如长度小于 10),可以使用插入排序来提高性能。 + +如,三数取中法的代码实现如下: + +```java +public class OptimizedQuickSort { + public void quickSort(int[] nums) { + if (nums == null || nums.length < 2) { + return; + } + quickSort(nums, 0, nums.length - 1); + } + + private void quickSort(int[] nums, int left, int right) { + if (left < right) { + int pivotIndex = medianOfThree(nums, left, right); + swap(nums, pivotIndex, right); + pivotIndex = partition(nums, left, right); + quickSort(nums, left, pivotIndex - 1); + quickSort(nums, pivotIndex + 1, right); + } + } + + private int medianOfThree(int[] nums, int left, int right) { + int mid = left + (right - left) / 2; + if (nums[left] > nums[mid]) { + swap(nums, left, mid); + } + if (nums[left] > nums[right]) { + swap(nums, left, right); + } + if (nums[mid] > nums[right]) { + swap(nums, mid, right); + } + return mid; + } + + private int partition(int[] nums, int left, int right) { + int pivot = nums[right]; + int i = left - 1; + for (int j = left; j < right; j++) { + if (nums[j] <= pivot) { + i++; + swap(nums, i, j); + } + } + swap(nums, i + 1, right); + return i + 1; + } + + private void swap(int[] nums, int i, int j) { + int temp = nums[i]; + nums[i] = nums[j]; + nums[j] = temp; + } +} +``` \ No newline at end of file