初级排序算法
1.1 选择排序
思想
找到数组中最小的元素,然后和数组中第一个元素进行交换。然后找到剩下数中最小的元素,和数组的第二个元素进行交换。如此往复,直到将整个数组排序。
特点
- 运行时间和输入无关
- 数据移动是最少的
1.2 插入排序
思想
从左向右,将每一个元素移动到左边正确的位置。左边的数组始终是有序的。
特点
- 运行时间取决于输入元素的初始顺序
- 对于部分有序的数组比较高效
1.3 希尔排序
思想
由于插入排序一次只能移动1位,希尔排序每次移动h位。数组中任意间隔为h的元素都是有序的。
特点
- 运行时间不能定量
package edu.princeton.cs.algs4.chapter2;
/**
* 排序方法
* Created by TXH-pc on 2017/3/4.
*/
public class Sort {
/**
* 选择排序
*/
public static void selectionSort(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);
}
}
/**
* 插入排序
*/
public static void insertionSort(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 - 1, j);
}
}
}
/**
* 希尔排序
* @param a
*/
public static void shell(Comparable[] a) {
int N = a.length;
int h = 1;
while (h < N) {
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 - h, j);
}
}
h = h / 3;
}
}
private static boolean less (Comparable v, Comparable w) {
return v.compareTo(w) < 0;
}
private static void exch(Comparable[] a, int i, int j) {
Comparable temp = a[i];
a[i] = a[j];
a[j] = temp;
}
private static void show(Comparable[] a) {
for (int i = 0; i < a.length; i++) {
System.out.print(a[i] + " ");
}
System.out.println();
}
public static void main(String[] args) {
String[] a = {"t", "h", "i", "s", "i", "s", "t", "e", "s", "t"};
Sort.selectionSort(a);
Sort.insertionSort(a);
Sort.shell(a);
Sort.show(a);
}
}
归并排序
基本思想
将两个有序的数组合并成一个更大的有序数组,有两种方法来实现归并排序:自顶而下的归并排序(分治法)和自底向上的归并排序。
2.1 分治法
- 新建一个临时数组(成员变量),merge的时候用来存放要排序的元素。
- 使用递归,将原来的数组分成两半,进行排序,并将排好序的数组复制到临时数组当中,最后将排好序的数组归并成一个数组。
- 在归并的过程中,分别用两个指针指向两个已经排好序的数组的初始位置,通过比较,将较小的元素放到合并数组。
Note:
- sort方法的作用在于安排多次merge方法调用的正确顺序
- 对于小规模的数组,可能插入排序比归并排序更快
-
对于长度为N的任意数组,自顶向下的归并排序至少需要1/2NlgN~NlgN次比较
归并排序时间与NlgN成正比的证明
代码实现
public class MergeSort {
private static Comparable[] aux; // 用于辅助的排序数组
public static void sort(Comparable[] a) {
aux = new Comparable[a.length]; // 初始化辅助数组
// 使用递归,进行排序
sort(a, 0, a.length - 1);
}
private static void sort(Comparable[] a, int lo, int hi) {
// 递归终止条件
if (hi <= lo)
return;
int mid = (lo + hi) / 2;
sort(a, lo, mid); //排序左半边
sort(a, mid + 1, hi); //排序右半边
merge(a, lo, mid, hi); //合并
}
private static void merge(Comparable[] a, int lo, int mid, int hi) {
int i = lo, j = mid + 1;
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 (aux[i].compareTo(aux[j]) < 0)
a[k] = aux[i++];
else
a[k] = aux[j++];
}
}
private static void show(Comparable[] a) {
for (int i = 0; i < a.length; i++) {
System.out.print(a[i] + " ");
}
System.out.println();
}
public static void main(String[] args) {
Integer[] a = {1, 2, 5, 6, 3, 8, 6, 9};
MergeSort.sort(a);
MergeSort.show(a);
}
}
2.2 自底向上
- 按顺序对数组中每两个元素进行排序
- 按顺序,每次对2的n次方个元素进行排序,直到所有的元素排序完成
代码实现
public class MergeSort {
private static Comparable[] aux; // 用于辅助的排序数组
/**
* 自底向上的实现
* @param a
*/
public static void sortBU(Comparable[] a) {
aux = new Comparable[a.length];
int N = a.length;
for (int sz = 1; sz < N; sz = sz + sz) { // sz为每次排序元素(子数组)的数量
for (int lo = 0; lo < N - sz; lo += sz) {
// 每次merge两个子数组,最后一个子数组的长度可能小于sz
merge(a, lo, lo + sz - 1, Math.min(lo + sz + sz - 1, N - 1));
}
}
}
private static void merge(Comparable[] a, int lo, int mid, int hi) {
int i = lo, j = mid + 1;
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 (aux[i].compareTo(aux[j]) < 0)
a[k] = aux[i++];
else
a[k] = aux[j++];
}
}
private static void show(Comparable[] a) {
for (int i = 0; i < a.length; i++) {
System.out.print(a[i] + " ");
}
System.out.println();
}
public static void main(String[] args) {
Integer[] a = {1, 2, 5, 6, 3, 8, 6, 9};
MergeSort.sortBU(a);
MergeSort.show(a);
}
}
排序算法的复杂度
没有任何基于比较的算法能保证少于lg(N!) ~ NlgN次比较将长度为N的数组排序
参考链接
http://rason.me/2016/06/29/MergeSort/
http://blog.acbingo.cn/2015/11/26/%E7%AE%97%E6%B3%95%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0-8-%E5%90%88%E5%B9%B6%E6%8E%92%E5%BA%8F/
http://www.cnblogs.com/yangecnu/p/Introduce-Merge-Sort.html
快速排序
基本思想
快速排序是先将切分元素放到合适的位置,切分元素左边的值总是小于切分元素,切分元素右边的值总是大于切分元素。通过递归,将左右两边继续切分,直到排序完成。
切分目的
- 对于某个j,a[j]已经确定
- a[lo]到a[j - 1]中的所有元素都不大于a[j]
- a[j + 1]到a[hi]中的所有元素都不小于a[j]
切分实现步骤
- 用两个指针(lo和hi),分别指向数组的头和尾
- 将指针指向的元素与切分元素(一般是数组的第一个元素)进行比较,如果a[lo]小于待切分的元素,则lo后移;如果a[hi]大于待切分的元素,则hi前移。
- 当lo指向一个大于切分元素的值时,停止移动,如果hi指向一个小于切分元素的值时,停止移动。此时,交换lo和hi所指向的值。
- 如此往复,直到hi<=lo,此时,交换切分元素和hi指向的值,同时返回hi
性能分析
- 将长度为N的无重复数组排序,快速排序平均需要~2NlnN次比较
- 快速排序最多需要N*N/2次比较,但是随机打乱数组能预防这种情况
- 当重复元素很多时,使用三项切分快速排序能将算法的时间复杂度降到N
代码实现
package edu.princeton.cs.algs4.chapter2;
/**
* 快速排序
* Created by TXH-pc on 2017/3/5.
*/
public class QuickSort {
public static void sort(Comparable[] a) {
// 打乱数组 略过
sort(a, 0, a.length - 1);
//quick3WaySort(a, 0, a.length - 1);
}
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 (j <= i)
break;
exch(a, i, j);
}
exch(a, lo, j); // 将v放入正确的位置
return j;
}
/**
* 三项切分快速排序
* @param a
* @param lo
* @param hi
*/
private static void quick3WaySort(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++;
}
quick3WaySort(a, lo, lt - 1);
quick3WaySort(a, gt + 1, hi);
}
private static void exch(Comparable[] a, int i, int j) {
Comparable temp = a[i];
a[i] = a[j];
a[j] = temp;
}
private static boolean less (Comparable v, Comparable w) {
return v.compareTo(w) < 0;
}
private static void show(Comparable[] a) {
for (int i = 0; i < a.length; i++) {
System.out.print(a[i] + " ");
}
System.out.println();
}
public static void main(String[] args) {
String[] a = {"R", "B", "W", "W", "R", "W", "B", "R", "R", "W", "B", "R"};
QuickSort.sort(a);
show(a);
}
}
优先队列
简介及比较
每次出来的都是最小值/最大值,可以用来查找海量数据中最大/最小的N个数
栈 :先进后出
队列 :先进先出
随机队列 : 随机出
优先队列:每次出来的是最大值或最小值
API介绍
public class MaxPQ<Key extends Comparable<Key>>
{
MaxPQ() // 创建一个优先队列
MaxPQ(int max) // 创建一个初始容量为max的优先队列
MaxPQ(Key[] a) // 用a[]中的元素创建一个优先队列
void insert(Key v) // 向优先队列中插入一个元素
Key max() // 返回最大是元素
Key delMax() // 删除并返回一个最大元素
boolean isEmpty() // 优先队列是否为空
int size() // 优先队列中的元素数量
}
优先队列实现的几种思路
- 数组实现(无序)
用一个数组存储数据,insert()时依次向数组中添加数据,delMax()时找出最大的元素,和边界元素进行交换,然后删除它。同时可以加入调整数组的大小的代码。
package edu.princeton.cs.algs4.chapter2;
/**
* 无序数组实现的优先队列
* Created by TXH-pc on 2017/3/5.
*/
public class UnorderedMaxPQ<Key extends Comparable<Key>> {
private Key[] pq;
private int N;
public UnorderedMaxPQ() {} // 创建一个优先队列
// 创建一个初始容量为max的优先队列
public UnorderedMaxPQ(int max) {
pq = (Key[]) new Comparable[max];
}
// 向优先队列中插入一个元素
public void insert(Key v) {
pq[N++] = v;
}
// 删除并返回一个最大元素
public Key delMax() {
int max = 0;
for (int i = 0; i< N; i++) {
if (less(max, i))
max = i;
}
exch(max, N - 1);
return pq[--N];
}
private void exch(int max, int i) {
Key temp = pq[max];
pq[max] = pq[i];
pq[i] = temp;
}
private boolean less(int max, int i) {
if(pq[max].compareTo(pq[i]) < 0)
return true;
else
return false;
}
// 优先队列是否为空
public boolean isEmpty() {
return N == 0;
}
// 优先队列中的元素数量
public int size() {
return N;
}
public static void main(String[] args) {
UnorderedMaxPQ<String> pq = new UnorderedMaxPQ<String>(3);
pq.insert("f");
pq.insert("z");
pq.insert("j");
System.out.println(pq.delMax());
}
}
- 数组实现(有序)
在插入数组的时候,将较大的元素向右移动一格以保证数组有序,这样最大的元素总在数组的一边。 - 链表表示法(有序&无序)
- 堆实现
二叉堆实现优先队列
- 二叉堆的每个节点都大于等于它的两个子节点
- 使用数组表示二叉堆的时候,索引为0的位置不放置元素。那么,位置k的节点的父节点位置为k/2,两个子节点的位置为2k和2k+1
- 由下至上的堆有序化(上浮):当某种原因导致一个节点比它的父节点更大的时候,需要交换它和它父节点的位置,循环往复直到交换后的父节点比当前节点大
- 由上至下的堆有序化(下沉):当某种原因导致一个节点比它的两个子节点更小的时候,需要将它和它的两个子节点当中较小的进行交换来恢复堆
-
插入元素操作:将要插入的元素放置在数组的末尾,增加堆的大小然后通过上浮操作使堆恢复到有序的状态
下沉过程 - 删除最大元素:从数组顶端去掉最大元素,然后将数组的最后一个元素放置到顶端,减小堆的大小然后通过下沉操作恢复堆的有序状态
基于二叉堆的优先队列的实现
package edu.princeton.cs.algs4.chapter2;
/**
* 基于堆的优先队列
* Created by TXH-pc on 2017/3/5.
*/
public class MaxPQ<Key extends Comparable<Key>> {
private Key[] pq;
private int N = 0; // 元素存储在[1...N]当中,pq[0]不使用
public MaxPQ(int maxN) {
pq = (Key[]) new Comparable[maxN + 1];
}
public boolean isEmpty() {
return N == 0;
}
public int size() {
return N;
}
public void insert(Key v) {
pq[++N] = v;
swim(N);
}
public Key delMax() {
Key max = pq[1];
exch(1, N--);
pq[N+1] = null;
sink(1);
return max;
}
private void swim(int k) {
while (k > 1 && less(k/2, k)) {
exch(k / 2, k);
k = k / 2;
}
}
private void sink(int k) {
// 停止条件应该是到达底端或者子节点都比他小
while (2*k <= N) {
int j = 2*k;
if (j < N && less(j, j+1))
j++;
if (less(k, j))
break;
exch(k, j);
k = j;
}
}
private boolean less(int i, int j) {
return pq[i].compareTo(pq[j]) < 0;
}
private void exch(int i, int j) {
Key temp = pq[i];
pq[i] = pq[j];
pq[j] = temp;
}
public static void main(String[] args) {
MaxPQ<String> pq = new MaxPQ<String>(3);
pq.insert("f");
pq.insert("a");
pq.insert("j");
System.out.println(pq.delMax());
}
}
堆排序
堆排序分两个阶段:
- 构造堆结构
通过从右至左用sink()构造子堆,只需要扫描数组中的一半的元素
- 下沉排序
将堆中最大的元素和最后一个元素交换,然后放入堆缩小后空出的位置。然后使用sink()方法使堆有序
package edu.princeton.cs.algs4.chapter2;
/**
* 堆排序
* Created by TXH-pc on 2017/3/5.
*/
public class HeapSort {
public static void sort(Comparable[] a) {
int N = a.length;
// 堆的构造
for (int k = N / 2; k >= 1; k--) {
sink(a, k, N);
}
// 下沉排序
while (N > 1) {
exch(a, 1, N--);
sink(a, 1, N);
}
}
private static void sink(Comparable[] a, int k, int N) {
while (2*k <= N) {
int j = 2*k;
if (j < N && less(a, j, j+1))
j++;
if (less(a, j, k))
break;
exch(a, j, k);
k = j;
}
}
// following two functions should convert from 1-base to 0 base
private static void exch(Comparable[] a, int j, int k) {
Comparable temp = a[j - 1];
a[j - 1] = a[k - 1];
a[k - 1] = temp;
}
private static boolean less(Comparable[] a, int i, int j) {
return a[i - 1].compareTo(a[j - 1]) < 0;
}
private static void show(Comparable[] a) {
for (int i = 0; i < a.length; i++) {
System.out.print(a[i] + " ");
}
System.out.println();
}
public static void main(String[] args) {
String[] a = {"t", "h", "i", "s", "i", "s", "t", "e", "s", "t"};
HeapSort.sort(a);
HeapSort.show(a);
}
}
算法
- Stable means if the two elements have the same key, they remain in the same order or positions. But that is not the case for Heap sort.
网友评论