经典排序算法分析与实现
算法
一、常见排序算法
1.comparable和comparator
1.Comparable是排序接口;若一个类实现了Comparable接口,就意味着“该类支持排序”。
(作用在实体类上)
2.而Comparator是比较器;我们若需要控制某个类的次序,可以建立一个“该类的比较器”来进行排序。(作用在排序类上)
总结:Comparable相当于“内部比较器”,而Comparator相当于“外部比较器”。
2.约定
待排序的元素需要实现 Java 的 Comparable 接口,该接口有 compareTo() 方法,可以用它来判断两个元素的大小关系。
使用辅助函数 lessThan() 和 swap() 来进行比较和交换的操作,使得代码的可读性和可移植性更好。
排序算法的成本模型是比较和交换的次数。
public abstract class Sort<T extends Comparable<T>> {
/**
* 对目标数组进行排序
* @param array 目标数组
*/
public abstract void sort(T[] array);
/**
* @param v T类型的对象
* @param w T类型的对象
* @return 如果v<w,返回true,否则返回false
*/
protected boolean lessThan(T v, T w){
return v.compareTo(w) < 0;
}
/**
* 比较同一类型的两个示例的大小
* @param v T类型的示例
* @param w T类型的示例
* @return 如果v>w,返回true,否则返回false
*/
protected boolean moreThn(T v, T w){
return v.compareTo(w) > 0;
}
/**
* 用于交换数组中的两个数
* @param array 目标数组
* @param i 数组下标
* @param j 数组下标
*/
protected void swap(T[] array, int i, int j){
T t = array[i];
array[i] = array[j];
array[j] = t;
}
}
3.选择排序算法
原理:
每次从数组中选择最小元素,将它与数组的第i个元素交换位置。
选择排序需要 ~N2/2 次比较和 ~N 次交换,它的运行时间与输入无关,这个特点使得它对一个已经排序的数组也需要这么多的比较和交换操作。
代码实现:
public class Selection<T extends Comparable<T>> extends Sort<T> {
/**
* 使用快速排序算法进行排序
* @param array 目标数组
*/
@Override
public void sort(T[] array) {
int n = array.length;
for (int i = 0; i < n - 1; i++) {
int min = i;
for (int j = i + 1; j < n; j++) {
if (lessThan(array[j], array[i])){
min = j;
}
}
swap(array, i, min);
}
}
}
4.冒泡排序算法
原理:
从左到右不断交换相邻的逆序的元素,在经过一轮的循环之后,可以让最大的元素上浮到最右边。
在一轮循环中,如果没有发生交换,那么说明数组已经是有序的,此时可以直接退出。
代码实现:
public class Bubble<T extends Comparable<T>> extends Sort<T>{
/**
* 用冒泡排序算法进行排序
* @param array 目标数组
*/
@Override
public void sort(T[] array) {
int n = array.length;
boolean isSorted = false;
for (int i = 0; i < n - 1; i++) {
isSorted = false;
for (int j = 0; j < n - i - 1; j++) {
if (lessThan(array[j + 1], array[j])){
swap(array, j + 1, j);
isSorted = true;
}
}
if (!isSorted){
break;
}
}
}
}
5.插入排序算法
原理:
从数组的第i(i>1)个数据开始向前比较,如果比j(j<i)小,交换位置。
代码实现:
public class Insertion<T extends Comparable<T>> extends Sort<T> {
/**
* 使用插入排序算法进行排序
* @param array 目标数组
*/
@Override
public void sort(T[] array) {
for (int i = 1; i < array.length; i++) {
for (int j = i; j > 0; j--) {
if (lessThan(array[j], array[j - 1])){
swap(array, j, j - 1);
}
}
}
}
}
6.希尔排序算法
原理:
希尔排序是把记录按下标的一定增量分组,对每组使用直接插入排序算法排序;随着增量逐渐减少,每组包含的关键词越来越多,当增量减至1时,整个文件恰被分成一组,算法便终止。
代码实现:
public class Shell<T extends Comparable<T>> extends Sort<T> {
/**
* 使用希尔排序算法进行排序
* @param array 目标数组
*/
@Override
public void sort(T[] array) {
int gap = array.length / 2;
while(gap >= 1){
for (int i = gap; i < array.length; i++) {
for (int j = i; j > gap - 1; j -= gap) {
if (lessThan(array[j], array[j - gap])){
swap(array, j, j - gap);
}
}
}
gap /= 2;
}
}
}
7.归并排序算法
原理:
归并排序(MERGE-SORT)是利用归并的思想实现的排序方法,该算法采用经典的分治(divide-and-conquer)策略(分治法将问题分(divide)成一些小的问题然后递归求解,而治(conquer)的阶段则将分的阶段得到的各答案"修补"在一起,即分而治之)。
归并部分代码:
public abstract class Marge<T extends Comparable<T>> extends Sort<T> {
/**
* 归并算法
* @param array 原数组
* @param low 需要进行归并部分的开始下标
* @param mid 需要进行归并部分的中间下标
* @param high 需要进行归并部分的结束下标
* @param aux 辅助数组
*/
protected void marge(T[] array, int low, int mid, int high, T[] aux){
for (int i = low; i <= high; i++) {
aux[i] = array[i];
}
//右指针
int right = mid + 1;
//左指针
int left = low;
for (int i = low; i <= high; i++) {
if (left > mid){
//将右序列剩余元素添加进原数组
array[i] = aux[right++];
} else if (right > high){
//将左序列剩余元素添加进原数组
array[i] = aux[left++];
} else if (aux[left].compareTo(aux[right]) <= 0){
//添加小的元素进入原数组
array[i] = aux[left++];
} else {
//添加小的元素进入原数组
array[i] = aux[right++];
}
}
}
}
7.1 自顶向上归并排序
原理:
自顶向下的排序算法就是把数组元素不断的二分,直到子数组的元素个数为一个,因为这个时候子数组必定是已有序的,然后将两个有序的序列合并成一个新的有序的序列,两个新的有序序列又可以合并成另一个新的有序序列,以此类推,直到合并成一个有序的数组。
代码实现:
public class UpToDownMarge<T extends Comparable<T>> extends Marge<T>{
/**
* 使用自顶向下归并排序算法进行排序
* @param array 目标数组
*/
@Override
public void sort(T[] array) {
//创建辅助数组
T[] auxiliary = (T[]) new Comparable[array.length];
sort(array, 0, array.length