diff --git a/notes/算法.md b/notes/算法.md index e404a3c8..f0e889db 100644 --- a/notes/算法.md +++ b/notes/算法.md @@ -347,25 +347,32 @@ public class Insertion> extends Sort { 希尔排序使用插入排序对间隔 h 的序列进行排序。通过不断减小 h,最后令 h=1,就可以使得整个数组是有序的。 -

+

```java public class Shell> extends Sort { + @Override public void sort(T[] nums) { + int N = nums.length; int h = 1; - while (h < N / 3) - h = 3 * h + 1; // 1, 4, 13, 40, ... + + while (h < N / 3) { + h = 3 * h + 1; // 1, 4, 13, 40, ... + } while (h >= 1) { - for (int i = h; i < N; i++) - for (int j = i; j >= h && less(nums[j], nums[j - h]); j -= h) + for (int i = h; i < N; i++) { + for (int j = i; j >= h && less(nums[j], nums[j - h]); j -= h) { swap(nums, j, j - h); + } + } h = h / 3; } } } + ``` 希尔排序的运行时间达不到平方级别,使用递增序列 1, 4, 13, 40, ... 的希尔排序所需要的比较次数不会超过 N 的若干倍乘于递增序列的长度。后面介绍的高级排序算法只会比希尔排序快两倍左右。 @@ -374,7 +381,7 @@ public class Shell> extends Sort { 归并排序的思想是将数组分成两部分,分别进行排序,然后归并起来。 -

+

### 1. 归并方法 @@ -385,21 +392,28 @@ public abstract class MergeSort> extends Sort { protected T[] aux; + protected void merge(T[] nums, int l, int m, int h) { + int i = l, j = m + 1; - for (int k = l; k <= h; k++) - aux[k] = nums[k]; // 将数据复制到辅助数组 + for (int k = l; k <= h; k++) { + aux[k] = nums[k]; // 将数据复制到辅助数组 + } for (int k = l; k <= h; k++) { - if (i > m) + if (i > m) { nums[k] = aux[j++]; - else if (j > h) + + } else if (j > h) { nums[k] = aux[i++]; - else if (aux[i].compareTo(nums[j]) <= 0) - nums[k] = aux[i++]; // 先进行这一步,保证稳定性 - else + + } else if (aux[i].compareTo(nums[j]) <= 0) { + nums[k] = aux[i++]; // 先进行这一步,保证稳定性 + + } else { nums[k] = aux[j++]; + } } } } @@ -407,10 +421,13 @@ public abstract class MergeSort> extends Sort { ### 2. 自顶向下归并排序 -

+将一个大数组分成两个小数组去求解。 + +因为每次都将问题对半分成两个子问题,而这种对半分的算法复杂度一般为 O(NlogN),因此该归并排序方法的时间复杂度也为 O(NlogN)。 ```java public class Up2DownMergeSort> extends MergeSort { + @Override public void sort(T[] nums) { aux = (T[]) new Comparable[nums.length]; @@ -418,8 +435,9 @@ public class Up2DownMergeSort> extends MergeSort { } private void sort(T[] nums, int l, int h) { - if (h <= l) + if (h <= l) { return; + } int mid = l + (h - l) / 2; sort(nums, l, mid); sort(nums, mid + 1, h); @@ -428,23 +446,27 @@ public class Up2DownMergeSort> extends MergeSort { } ``` -因为每次都将问题对半分成两个子问题,而这种对半分的算法复杂度一般为 O(NlogN),因此该归并排序方法的时间复杂度也为 O(NlogN)。 - ### 3. 自底向上归并排序 先归并那些微型数组,然后成对归并得到的微型数组。 ```java public class Down2UpMergeSort> extends MergeSort { + @Override public void sort(T[] nums) { + int N = nums.length; aux = (T[]) new Comparable[N]; - for (int sz = 1; sz < N; sz += sz) - for (int lo = 0; lo < N - sz; lo += sz + sz) + + for (int sz = 1; sz < N; sz += sz) { + for (int lo = 0; lo < N - sz; lo += sz + sz) { merge(nums, lo, lo + sz - 1, Math.min(lo + sz + sz - 1, N - 1)); + } + } } } + ``` ## 快速排序 @@ -454,10 +476,11 @@ public class Down2UpMergeSort> extends MergeSort { - 归并排序将数组分为两个子数组分别排序,并将有序的子数组归并使得整个数组排序; - 快速排序通过一个切分元素将数组分为两个子数组,左子数组小于等于切分元素,右子数组大于等于切分元素,将这两个子数组排序也就将整个数组排序了。 -

+

```java public class QuickSort> extends Sort { + @Override public void sort(T[] nums) { shuffle(nums); @@ -482,9 +505,9 @@ public class QuickSort> extends Sort { ### 2. 切分 -取 a[lo] 作为切分元素,然后从数组的左端向右扫描直到找到第一个大于等于它的元素,再从数组的右端向左扫描找到第一个小于等于它的元素,交换这两个元素,并不断进行这个过程,就可以保证左指针 i 的左侧元素都不大于切分元素,右指针 j 的右侧元素都不小于切分元素。当两个指针相遇时,将切分元素 a[lo] 和 a[j] 交换位置。 +取 a[l] 作为切分元素,然后从数组的左端向右扫描直到找到第一个大于等于它的元素,再从数组的右端向左扫描找到第一个小于等于它的元素,交换这两个元素。不断进行这个过程,就可以保证左指针 i 的左侧元素都不大于切分元素,右指针 j 的右侧元素都不小于切分元素。当两个指针相遇时,将切分元素 a[l] 和 a[j] 交换位置。 -

+

```java private int partition(T[] nums, int l, int h) { @@ -528,20 +551,23 @@ private int partition(T[] nums, int l, int h) { ```java public class ThreeWayQuickSort> extends QuickSort { + @Override protected void sort(T[] nums, int l, int h) { - if (h <= l) + if (h <= l) { return; + } int lt = l, i = l + 1, gt = h; T v = nums[l]; while (i <= gt) { int cmp = nums[i].compareTo(v); - if (cmp < 0) + if (cmp < 0) { swap(nums, lt++, i++); - else if (cmp > 0) + } else if (cmp > 0) { swap(nums, i, gt--); - else + } else { i++; + } } sort(nums, l, lt - 1); sort(nums, gt + 1, h); @@ -555,24 +581,28 @@ public class ThreeWayQuickSort> extends QuickSort { 可以利用这个特性找出数组的第 k 个元素。 +该算法是线性级别的,因为每次能将数组二分,那么比较的总次数为 (N+N/2+N/4+..),直到找到第 k 个元素,这个和显然小于 2N。 + ```java public T select(T[] nums, int k) { int l = 0, h = nums.length - 1; while (h > l) { int j = partition(nums, l, h); - if (j == k) + + if (j == k) { return nums[k]; - else if (j > k) + + } else if (j > k) { h = j - 1; - else + + } else { l = j + 1; + } } return nums[k]; } ``` -该算法是线性级别的。因为每次能将数组二分,那么比较的总次数为 (N+N/2+N/4+..),直到找到第 k 个元素,这个和显然小于 2N。 - ## 堆排序 ### 1. 堆 diff --git a/pics/0157d362-98dd-4e51-ac26-00ecb76beb3e.png b/pics/0157d362-98dd-4e51-ac26-00ecb76beb3e.png new file mode 100644 index 00000000..fc0999f9 Binary files /dev/null and b/pics/0157d362-98dd-4e51-ac26-00ecb76beb3e.png differ diff --git a/pics/220790c6-4377-4a2e-8686-58398afc8a18.png b/pics/220790c6-4377-4a2e-8686-58398afc8a18.png new file mode 100644 index 00000000..79105257 Binary files /dev/null and b/pics/220790c6-4377-4a2e-8686-58398afc8a18.png differ diff --git a/pics/864bfa7d-1149-420c-a752-f9b3d4e782ec.png b/pics/864bfa7d-1149-420c-a752-f9b3d4e782ec.png new file mode 100644 index 00000000..72cc988f Binary files /dev/null and b/pics/864bfa7d-1149-420c-a752-f9b3d4e782ec.png differ diff --git a/pics/f8047846-efd4-42be-b6b7-27a7c4998b51.png b/pics/f8047846-efd4-42be-b6b7-27a7c4998b51.png new file mode 100644 index 00000000..86e2294f Binary files /dev/null and b/pics/f8047846-efd4-42be-b6b7-27a7c4998b51.png differ