auto commit

This commit is contained in:
CyC2018 2018-04-02 10:37:02 +08:00
parent 9fd44aa96d
commit a1438441fd
5 changed files with 22 additions and 19 deletions

View File

@ -75,13 +75,13 @@ public class ThreeSum {
}
```
该算法的内循环为 if(a[i]+a[j]+a[k]==0) 语句,总共执行的次数为 N(N-1)(N-2) = N<sup>3</sup>/6-N<sup>2</sup>/2+N/3因此它的近似执行次数为 \~N<sup>3</sup>/6增长数量级为 N<sup>3</sup>
该算法的内循环为 if(a[i]+a[j]+a[k]==0) 语句,总共执行的次数为 N(N-1)(N-2) = N<sup>3</sup>/6-N<sup>2</sup>/2+N/3因此它的近似执行次数为 \~N<sup>3</sup>/6增长数量级为 O(N<sup>3</sup>)
<font size=4> **改进** </font></br>
通过将数组先排序,对两个元素求和,并用二分查找方法查找是否存在该和的相反数,如果存在,就说明存在三元组的和为 0。
该方法可以将 ThreeSum 算法增长数量级降低为 N<sup>2</sup>logN。
该方法可以将 ThreeSum 算法增长数量级降低为 O(N<sup>2</sup>logN)
```java
public class ThreeSumFast {
@ -399,7 +399,7 @@ public void union(int p, int q) {
理论研究证明,加权 quick-union 算法构造的树深度最多不超过 logN。
<div align="center"> <img src="../pics//8229e8e7-a183-4d29-94e6-e8d8537c6ce5.png" width="200"/> </div><br>
<div align="center"> <img src="../pics//a4c17d43-fa5e-4935-b74e-147e7f7e782c.png" width="200"/> </div><br>
```java
public class WeightedQuickUnionUF {
@ -450,13 +450,11 @@ public class WeightedQuickUnionUF {
| :---: | :---: | :---: |
| quick-find | N | 1 |
| quick-union | 树高 | 树高 |
| 加权 quick-union | lgN | lgN |
| 加权 quick-union | logN | logN |
| 路径压缩的加权 quick-union | 非常接近 1 | 非常接近 1 |
# 四、排序
<font size=4> **约定** </font><br>
待排序的元素需要实现 Java 的 Comparable 接口,该接口有 compareTo() 方法,可以用它来判断两个元素的大小关系。
研究排序算法的成本模型时,计算的是比较和交换的次数。
@ -464,11 +462,11 @@ public class WeightedQuickUnionUF {
使用辅助函数 less() 和 exch() 来进行比较和交换的操作,使得代码的可读性和可移植性更好。
```java
private boolean less(Comparable v, Comparable w){
private boolean less(Comparable v, Comparable w) {
return v.compareTo(w) < 0;
}
private void exch(Comparable[] a, int i, int j){
private void exch(Comparable[] a, int i, int j) {
Comparable t = a[i];
a[i] = a[j];
a[j] = t;
@ -479,7 +477,7 @@ private void exch(Comparable[] a, int i, int j){
找到数组中的最小元素,将它与数组的第一个元素交换位置。再从剩下的元素中找到最小的元素,将它与数组的第二个元素交换位置。不断进行这样的操作,直到将整个数组排序。
<div align="center"> <img src="../pics//ae3fc93a-44d5-4beb-b05a-874bd9c0a657.png" width="200"/> </div><br>
<div align="center"> <img src="../pics//ed7b96ac-6428-4bd5-9986-674c54c2a959.png" width="200"/> </div><br>
```java
public class Selection {
@ -517,7 +515,11 @@ public class Insertion {
}
```
插入排序的复杂度取决于数组的初始顺序,如果数组已经部分有序了,那么插入排序会很快。平均情况下插入排序需要 \~N<sup>2</sup>/4 比较以及 \~N<sup>2</sup>/4 次交换,最坏的情况下需要 \~N<sup>2</sup>/2 比较以及 \~N<sup>2</sup>/2 次交换,最坏的情况是数组是逆序的;而最好的情况下需要 N-1 次比较和 0 次交换,最好的情况就是数组已经有序了。
插入排序的复杂度取决于数组的初始顺序,如果数组已经部分有序了,那么插入排序会很快。
- 平均情况下插入排序需要 \~N<sup>2</sup>/4 比较以及 \~N<sup>2</sup>/4 次交换;
- 最坏的情况下需要 \~N<sup>2</sup>/2 比较以及 \~N<sup>2</sup>/2 次交换,最坏的情况是数组是逆序的;
- 最好的情况下需要 N-1 次比较和 0 次交换,最好的情况就是数组已经有序了。
插入排序对于部分有序数组和小规模数组特别高效。
@ -529,7 +531,7 @@ public class Insertion {
希尔排序使用插入排序对间隔 h 的序列进行排序,如果 h 很大,那么元素就能很快的移到很远的地方。通过不断减小 h最后令 h=1就可以使得整个数组是有序的。
<div align="center"> <img src="../pics//4bb7ed45-ec14-4d31-9da4-94024d9d3b05.png" width="500"/> </div><br>
<div align="center"> <img src="../pics//cdbe1d12-5ad9-4acb-a717-bbc822c2acf3.png" width="500"/> </div><br>
```java
public class Shell {
@ -628,7 +630,8 @@ public static void busort(Comparable[] a) {
### 1. 基本算法
归并排序将数组分为两个子数组分别排序,并将有序的子数组归并使得整个数组排序;快速排序通过一个切分元素将数组分为两个子数组,左子数组小于等于切分元素,右子数组大于等于切分元素,将这两个子数组排序也就将整个数组排序了。
- 归并排序将数组分为两个子数组分别排序,并将有序的子数组归并使得整个数组排序;
- 快速排序通过一个切分元素将数组分为两个子数组,左子数组小于等于切分元素,右子数组大于等于切分元素,将这两个子数组排序也就将整个数组排序了。
<div align="center"> <img src="../pics//ab77240d-7338-4547-9183-00215e7220ec.png" width="500"/> </div><br>
@ -652,7 +655,7 @@ public class QuickSort {
取 a[lo] 作为切分元素,然后从数组的左端向右扫描直到找到第一个大于等于它的元素,再从数组的右端向左扫描找到第一个小于等于它的元素,交换这两个元素,并不断进行这个过程,就可以保证左指针 i 的左侧元素都不大于切分元素,右指针 j 的右侧元素都不小于切分元素。当两个指针相遇时,将切分元素 a[lo] 和 a[j] 交换位置。
<div align="center"> <img src="../pics//8af348d0-4d72-4f76-b56c-0a440ed4673d.png" width="400"/> </div><br>
<div align="center"> <img src="../pics//5aac64d3-2c7b-4f32-9e9a-1df2186f588b.png" width="400"/> </div><br>
```java
private static int partition(Comparable[] a, int lo, int hi) {
@ -679,15 +682,15 @@ private static int partition(Comparable[] a, int lo, int hi) {
### 4. 算法改进
**(一)切换到插入排序**
(一)切换到插入排序
因为快速排序在小数组中也会调用自己,对于小数组,插入排序比快速排序的性能更好,因此在小数组中可以切换到插入排序。
**(二)三取样**
(二)三取样
最好的情况下是每次都能取数组的中位数作为切分元素,但是计算中位数的代价很高。人们发现取 3 个元素并将大小居中的元素作为切分元素的效果最好。
**(三)三向切分**
(三)三向切分
对于有大量重复元素的数组,可以将数组切分为三部分,分别对应小于、等于和大于切分元素。
@ -720,7 +723,7 @@ public class Quick3Way {
堆的某个节点的值总是大于等于子节点的值,并且堆是一颗完全二叉树。
堆可以用数组来表示,因为堆是一种完全二叉树,而完全二叉树很容易就存储在数组中。位置 k 的节点的父节点位置为 k/2而它的两个子节点的位置分别为 2k 和 2k+1。这里我们不使用数组索引为 0 的位置,是为了更清晰地理解节点的关系。
堆可以用数组来表示,因为堆是一种完全二叉树,而完全二叉树很容易就存储在数组中。位置 k 的节点的父节点位置为 k/2而它的两个子节点的位置分别为 2k 和 2k+1。这里我们不使用数组索引为 0 的位置,是为了更清晰地描述节点的位置关系。
<div align="center"> <img src="../pics//f3080f83-6239-459b-8e9c-03b6641f7815.png" width="200"/> </div><br>
@ -813,13 +816,13 @@ public Key delMax() {
由于堆可以很容易得到最大的元素并删除它,不断地进行这种操作可以得到一个递减序列。如果把最大元素和当前堆中数组的最后一个元素交换位置,并且不删除它,那么就可以得到一个从尾到头的递减序列,从正向来看就是一个递增序列。因此很容易使用堆来进行排序,并且堆排序是原地排序,不占用额外空间。
**构建堆**
(一)构建堆
无序数组建立堆最直接的方法是从左到右遍历数组,然后进行上浮操作。一个更高效的方法是从右至左进行下沉操作,如果一个节点的两个节点都已经是堆有序,那么进行下沉操作可以使得这个节点为根节点的堆有序。叶子节点不需要进行下沉操作,因此可以忽略叶子节点的元素,因此只需要遍历一半的元素即可。
<div align="center"> <img src="../pics//b84ba6fb-312b-4e69-8c77-fb6eb6fb38d4.png" width="300"/> </div><br>
**交换堆顶元素与最后一个元素**
(二)交换堆顶元素与最后一个元素
交换之后需要进行下沉操作维持堆的有序状态。

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB