算法4-第二章-排序
排序
2.1 初级排序
2.1.2 选择排序
基本思路:
遍历数组依次找到最小的,放入队列的前面即可。
代码示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
public class Selection {
public static void sort(Comparable[] a) {
int N = a.length;
for (int i = 0; i < N; i++) {
int min = i;
for (int j = i + 1; j < N; j++) {
if (less(a[j], a[min])) min = j;
exch(a, i, min);
}
}
}
}
2.1.3 插入排序
基本思路:
根据索引遍历数组,发现右边的比左边的小,则直接调换位置。发现右边>=左边的后,则停止对比(因为左边的已经是从小到大排列好的了)
代码示例:
1
2
3
4
5
6
7
8
9
10
11
public class Insertion {
public static void sort(Comparable[] a) {
int N = a.length;
for (int i = 1; i < N; i++) {
for (int j = i; j > 0 && less(a[j], a[j - 1]); j--) {
exch(a, j, j - 1);
}
}
}
}
2.1.6 希尔排序
基本思想:
主要是在插入排序的基础上,减少对比替换的次数。
例如:插入排序如果最小数放在最末尾,则排序替换的时候需要执行 N 次,才能替换成功,希尔排序是将原数组拆分成多个数组,然后分别进行插入排序,并确保小数在前,大数在后,以此来减少对比替换次数。
例如:有数组[3,4,1,6,8,7,5,29,10],可以将其拆分为 4 列:
1
2
3 4 1 8
7 5 29 10
然后每列插入排序就,再次缩小列数
1
2
3
4
3 4
1 8
7 5
29 10
1
2
3
4
1 4
3 5
7 8
29 10
可以按此进行多次拆分,直到最后只剩下一列,然后进行正常的插入排序,即可完成整个排序
1
2
3
4
5
6
7
8
1
4
3
5
7
8
29
10
代码示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Shell {
public static void sort(Comparable[] a) {
int N = a.length;
int h = 1;
while (h < N / 3) h = 3 * h + 1;
while (h >= 1) {
for (int i = h; i < N; i++) {
for (int j = i; j >= h && less(a[j], a[j - h]); j -= h)
exch(a, j, j - h);
}
h = h / 3;
}
}
}
公用的基础的比较替换方法
1
2
3
4
5
6
7
8
9
private static boolean less(Comparable v, Comparable w) {
return v.compareTo(w) < 0;
}
private static void exch(Comparable[] a, int i, int j) {
Comparable t = a[i];
a[i] = a[j];
a[j] = t;
}
2.2 归并排序
2.2.1 原地归并的抽象方法
主要思想:将一个数组拆分为多个数组,之后在进行合并排序 示例代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public static void merge(Comparable[] a, int lo, int mid, int hi) {
int i = lo, j = mid + 1;
Comparable[] aux = new Comparable[hi - 1]; //首先将数组统一复制到aux中
for (int k = lo; k < hi; k++) {
aux[k] = a[k];
}
for (int k = lo; k < hi; k++) { //遍历所有数据
if (i > mid) a[k] = aux[j++]; //如果左边的数组用完了,则直接将右边的数组填入
else if (j > hi) a[k] = aux[i++]; //如果右边的数组用完了,则直接将左边的数组填入
else if (less(aux[j], aux[i])) a[k] = aux[j++]; //如果两边数组都还没用完,则对比当前左右两边第一个数据的大小,填入小的
else a[k] = aux[i++];
}
}
2.2.2 自顶向下的归并排序
递归将大数组拆分为小数组,并将小数组逐步递归合并排序为大数组
示例代码:
1
2
3
4
5
6
7
8
9
10
11
12
public static void sort(Comparable[] a) {
aux = new Comparable[a.length];
sort(a, 0, a.length - 1);
}
public static void sort(Comparable[] a, int lo, int hi) {
if (hi <= lo) return;
int mid = lo + (hi - lo) / 2;
sort(a, lo, mid);
sort(a, mid + 1, hi);
merge(a, lo, mid, hi);
}
2.2.3 自低向上的归并排序
两两数组归并排序,再四四数组合并,如此最终合并所有数组 示例:
1
2
3
4
5
6
7
8
9
public static void sort(Comparable[] a) {
int N = a.length;
aux = new Comparable[N];
for (int sz = 1; sz < N; sz = sz + sz) {
for (int lo = 0; lo < N - sz; lo += sz + sz) {
merge(a, lo, lo + sz - 1, Math.min(lo + sz + sz - 1, N - 1));
}
}
}
2.3 快速排序
2.3.1 基本算法
基本思想:找到一个数,对数组进行切分,确保左边的都比它小,右边的都比它大,然后对左右再次如此递归,直到左右 index 相等不再需要排序为止 示例代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
private static void sort(Comparable[] a, int lo, int hi) {
if (hi <= lo) return;
int j = partition(a, lo, hi);
sort(a, lo, j - 1);
sort(a, j + 1, hi);
}
private static int partition(Comparable[] a, int lo, int hi) {
int i = lo, j = hi + 1;
Comparable v = a[lo];
while (true) {
while (less(a[++i], v)) if (i == hi) break;
while (less(v, a[--j])) if (j == lo) break;
if (i >= j) break;
exch(a, i, j);
}
exch(a, lo, j);
return j;
}
2.3.3 算法改进
2.3.3.2 三取样切分
主要思想:从数组中取 3 个数,进行排序,然后将大的放到数组的最末尾,小的放到数组中间,中位数的放到数组的开头。然后用中位数当作切分元素。进行快排的逻辑。(这可以带来约 5%~10% 的性能提升)
主要是因为快排比较依靠切分值,切分值选的好的话,直接就可以将数组切分为平均的两半(在切分不平衡时这个程序可能会极为低效。例如,如果第一次从最小的元素切分,第二次从第二小的元素切分,如此这般,每次调用 只会移除一个元素。这会导致一个大子数组需要切分很多次。)
2.3.3.2 三向切分的快速排序
主要思想:将数组排序,将小的放左边,相等的放中间,大于的放右边。然后再将小/大的这两部份分别递归排序(更快速排序的区分数,快排是找个数,然后来排序,三分取样没有具体针对某个数,而是整体遍历替换,保证大/中/小三部份,然后再递归三分取样)
示例代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
private static void sort(Comparable[] a, int lo, int hi) {
if (hi <= lo) return;
int lt = lo, i = lo + 1, gt = hi;
Comparable v = a[lo];
while (i <= gt) {
int cmp = a[i].compareTo(v);
if (cmp < 0) exch(a, lt++, i++);
else if (cmp > 0) exch(a, i, gt--);
else i++;
}
sort(a, lo, lt - 1);
sort(a, gt + 1, hi);
}
}
2.4 优先队列
2.4.1 优先队列
优先队列是一种抽象数据类型,优先队列最重要的操作就是删除最大元素和插入元素。
二叉堆
二叉堆是一种特殊的堆,二叉堆是完全二叉树或者是近似完全二叉树。二叉堆满足堆特性:父节点的键值总是保持固定的序关系于任何一个子节点的键值,且每个节点的左子树和右子树都是一个二叉堆。 当父节点的键值总是大于或等于任何一个子节点的键值时为“最大堆”。当父节点的键值总是小于或等于任何一个子节点的键值时为“最小堆”。 例如下图:
1
2
3
4
5
6
7
8
1 11
/ \ / \
2 3 9 10
/ \ / \ / \ / \
4 5 6 7 5 6 7 8
/ \ / \ / \ / \
8 9 10 11 1 2 3 4
2.4.2 初级实现
数组实现二叉堆,因为二叉堆是有序的,所以在插入的时候直接在尾部直接插入,然后用当前的 index/2 得到它的上面一级,作对比替换就可以了
2.4.5 堆排序
主要思想:先将数组进行排序,构造成最大二叉堆,然后,再将堆底部的数顺序跟最顶部的数进行交换(确保所有二叉堆右侧的也都经历了排序)并重新修复堆结构(确保二叉堆左侧的经历了排序),如此递归到堆的堆顶部,即可完成排序。 堆排序类似于选择排序,但是不需要额外的空间
代码示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public static void sort(Comparable[] pq) {
int n = pq.length;
for (int k = n / 2; k >= 1; k--)
sink(pq, k, n);
while (n > 1) {
exch(pq, 1, n--);
sink(pq, 1, n);
}
}
private static void sink(Comparable[] pq, int k, int n) {
while (2 * k <= n) {
int j = 2 * k;
if (j < n && less(pq, j, j + 1)) j++;
if (!less(pq, k, j)) break;
exch(pq, k, j);
k = j;
}
}
private static boolean less(Comparable[] pq, int i, int j) {
return pq[i - 1].compareTo(pq[j - 1]) < 0;
}
private static void exch(Object[] pq, int i, int j) {
Object swap = pq[i - 1];
pq[i - 1] = pq[j - 1];
}
2.5 应用
--EOF--
若无特别说明,本站文章均为原创,转载请保留链接,谢谢