标签:isa who 数据类型 amp nlogn str between 复杂 distance
只要实现了Comparable接口的数据类型就可以被排序。
但要使算法能够灵活地用不同字段进行排序,则是后续需要考虑的问题。
在Java中,指针操作是隐式的,排序算法操作的总是数据引用,而不是数据本身。
如果在排序后,用例还可以改变键值,那么数组很可能就不是有序的了。类似,优先队列也会乱套。
Java中,可以用不可变数据类型作为键来避免这个问题,如String,Integer,Double和File都是不可变的。
使用引用的另一个好处是算法不必移动整个元素,对于元素大键值小的数组来说将会省下很多操作成本。
因为比较只需要访问元素的一小部分,在整个排序过程中,大部分不会被访问到。
因此对于任意大小的元素,使用引用,使得一般情况下交换和比较的成本几乎相同。
如果键值很长,那么交换的成本甚至会低于比较的成本。
实际应用中,用户希望根据情况对一组对象按照不同的方式进行排序。
Java的Comparator接口允许在一个类中实现多种排序方法。通过将Comparator接口对象传递给sort方法,再传递给less方法即可。
Comparator接口实现举例如下:
public class WhoOrder implements Comparator<Transaction> { @Override public int compare(Transaction v, Transaction w) { //add code return 0; } }
sort举例如下:
public static void sort(Object[] a, Comparator c) { int N = a.length; for(int i = 1; i < N; i++) for(int j = i; j > 0 && less(c, a[j], a[j-1]); j--) exch(a, j, j-1); } private static boolean less(Comparator c, Object v, Object w) { return c.compare(v, w) < 0; } private static void exch(Object[] a, int i, int j) { Object t = a[i]; a[i] = a[j]; a[j] = t; }
Comparator接口允许为任意数据定义任意多种排序方法。用Comparator替代Comparable接口可以更好地将数据类型的定义和两个该数据类型对象应该如何比较的定义区分开来。
比如,相对字符串数组a,进行忽略大小写排序,可以使用String类中的CASE_INSENSITIVE_ORDER比较器Comparator,并传递给sort函数:
Insertion.sort(a, String.CASE_INSENSITIVE_ORDER)
在实际应用中,一个元素的多个属性都可能被用作排序的键。
要实现这种灵活性,Comparator接口正合适,我们可以在数据类型中定义多种比较器(Comparator)。
例如:
public class Transaction implements Comparable<Transaction> { private final String who; // customer private final Date when; // date private final double amount; // amount /** * Compares two transactions by customer name. */ public static class WhoOrder implements Comparator<Transaction> { @Override public int compare(Transaction v, Transaction w) { return v.who.compareTo(w.who); } } /** * Compares two transactions by date. */ public static class WhenOrder implements Comparator<Transaction> { @Override public int compare(Transaction v, Transaction w) { return v.when.compareTo(w.when); } } /** * Compares two transactions by amount. */ public static class HowMuchOrder implements Comparator<Transaction> { @Override public int compare(Transaction v, Transaction w) { return Double.compare(v.amount, w.amount); } } }
这样定义之后,想让Transaction按照时间排序,可以调用
Insertion.sort(a, new Transaction.WhenOrder())
如果一个排序算法能够保留数组中重复元素的相对位置的话,则这个排序算法使稳定的。
插入排序和归并排序是稳定的,选择排序、希尔排序、快速排序和堆排序则不是稳定的。
稳定、原地排序
时间复杂度:最坏~N2/2、最好~N、平均~N2/4
空间复杂度:1
交换次数:最坏~N2/2、最好0、平均~N2/4
注:取决于输入元素的排列情况
http://www.cnblogs.com/songdechiu/p/6610515.html
不稳定、原地排序
时间复杂度:N2(~N2/2)
空间复杂度:1
交换次数:N
http://www.cnblogs.com/songdechiu/p/6609896.html
不稳定、原地排序
时间复杂度:达不到平方级别、最坏和N3/2成正比(未知)
空间复杂度:1
http://www.cnblogs.com/songdechiu/p/6611340.html
不稳定、原地排序
时间复杂度:最好~NlogN、最坏~N2、平均1.39NlogN
空间复杂度:最好logN,最坏N,平均logN
运行效率由概率提供保证
http://www.cnblogs.com/songdechiu/p/6629539.html
不稳定、原地排序
时间复杂度:最好N、最坏~N2、平均~NlogN
空间复杂度:最好1、最坏N、平均logN
运行效率由概率提供保证
http://www.cnblogs.com/songdechiu/p/6629539.html
稳定、非原地排序
时间复杂度:1/2NlgN至NlgN
空间复杂度:N
数组访问次数:6NlgN
http://www.cnblogs.com/songdechiu/p/6607341.html
http://www.cnblogs.com/songdechiu/p/6607720.html
不稳定、原地排序
时间复杂度:2NlogN + 2N(最坏)
空间复杂度:1
数组元素交换次数:NlogN + N(最坏)
http://www.cnblogs.com/songdechiu/p/6736502.html
结论:快速排序是最快的通用排序算法,因为其内循环指令少,而且还能利用缓存(顺序访问数据),其增长数量级为~cNlogN。特别是使用三切分之后,快排对某些特殊输入其复杂度变为线性级别。
实际应用中,大多数情况下,快速排序是最好的选择。
但是如果稳定性很重要而且空间又不是问题,那么归并排序是最好的。
首先将数组排序,然后遍历有序的数组,记录连续出现的重复元素即可。
一个排列就是一组N个整数的数组,其中0到N-1的每个数都只出现一次。
两个排列之间的Kendall tau距离就是两组排列中的逆序数对。如0 3 1 6 2 5 4和1 0 3 6 4 2 5的Kendall tau距离是4,因为0-1,3-1,2-4,5-4这四对数字的相对顺序不同。
Kendall tau距离就是逆序数对的数量。
实现:http://algs4.cs.princeton.edu/25applications/KendallTau.java.html
public class KendallTau { // return Kendall tau distance between two permutations public static long distance(int[] a, int[] b) { if (a.length != b.length) { throw new IllegalArgumentException("Array dimensions disagree"); } int n = a.length; int[] ainv = new int[n]; for (int i = 0; i < n; i++) ainv[a[i]] = i; Integer[] bnew = new Integer[n]; for (int i = 0; i < n; i++) bnew[i] = ainv[b[i]]; return Inversions.count(bnew); }
}
求两个排列a和b的逆序数对跟以前归并排序中求一个未排好序的排列相对排好序的排列的逆序数对有点不同。
归并排序中是默认跟标准序列(排好序)即0 1 2 3 4 5 6进行比较,这里是a跟b进行比较,我们以b为标准序列。
所以我们要转换成一个序列跟标准序列之间的逆序数对,再将该序列传给归并排序中的求解方法。
以方便对上述代码进行解释,做出以下假设
a:0 3 1 6 2 5 4
b:1 0 3 6 4 2 5
其中将a[i]称为key,i称为index;b[j]也为key,j为index。
上述代码的思路主要思路是转化为index序列之间的比较,以b为标准序列,其index序列为0 1 2 3 4 5 6,再求出b[i](0<= i <= 6)在数组a中相应的index序列。
ainv是a的逆数组,即存储着数组a的key所在的index,即ainv[k] = i代表着a[i] = k。
当key为b[0] = 1时,其索引为ainv[b[0]]即ainv[1] = 2,以此类推最后求出a相对于b的索引序列为2 0 1 3 6 4 5。
最后将这个相对索引序列2 0 1 3 6 4 5传给归并排序中的求解方法即可,求出为4。
以下为归并排序中邱逆序数对的方法:
http://algs4.cs.princeton.edu/22mergesort/Inversions.java.html
public class Inversions { // do not instantiate private Inversions() { } // merge and count private static long merge(int[] a, int[] aux, int lo, int mid, int hi) { long inversions = 0; // copy to aux[] for (int k = lo; k <= hi; k++) { aux[k] = a[k]; } // merge back to a[] int i = lo, j = mid+1; 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[j] < aux[i]) { a[k] = aux[j++]; inversions += (mid - i + 1); } else a[k] = aux[i++]; } return inversions; } // return the number of inversions in the subarray b[lo..hi] // side effect b[lo..hi] is rearranged in ascending order private static long count(int[] a, int[] b, int[] aux, int lo, int hi) { long inversions = 0; if (hi <= lo) return 0; int mid = lo + (hi - lo) / 2; inversions += count(a, b, aux, lo, mid); inversions += count(a, b, aux, mid+1, hi); inversions += merge(b, aux, lo, mid, hi); assert inversions == brute(a, lo, hi); return inversions; } /** * Returns the number of inversions in the integer array. * The argument array is not modified. * @param a the array * @return the number of inversions in the array. An inversion is a pair of * indicies {@code i} and {@code j} such that {@code i < j} * and {@code a[i]} > {@code a[j]}. */ public static long count(int[] a) { int[] b = new int[a.length]; int[] aux = new int[a.length]; for (int i = 0; i < a.length; i++) b[i] = a[i]; long inversions = count(a, b, aux, 0, a.length - 1); return inversions; } }
两个规约为优先队列操作问题得例子:
TopM,在输入流中找到前M个最大的元素。
MultiWay,将M个有序输入流归并为一个有序输入流。
找到一组数中的第k小的元素(中位数是特殊情况)。
可以规约为快排中的切分方法。
public static <Key extends Comparable<Key>> Key select(Key[] a, int k) { if (k < 0 || k >= a.length) { throw new IndexOutOfBoundsException("Selected element out of bounds"); } StdRandom.shuffle(a); int lo = 0, hi = a.length - 1; while (hi > lo) { int i = partition(a, lo, hi); if (i > k) hi = i - 1; else if (i < k) lo = i + 1; else return a[i]; } return a[lo]; }
平均上,基于切分的选择算法的运行时间是线性级别的。
标签:isa who 数据类型 amp nlogn str between 复杂 distance
原文地址:http://www.cnblogs.com/songdechiu/p/6741533.html