十大排序算法
常见的递推式与复杂度
Sort抽象类
定义排序的抽象父类,具体的排序算法延迟到sort()方法中实现
/**
* @Description:定义排序的一些统计属性(如比较次数、交换次数、时间)
* @author: cyb
* @date: 2021-03-15 14:41
* @version V1.0
*/
public abstract class Sort<T extends Comparable<T>> implements Comparable<Sort<T>> {
protected T[] array;
private int cmpCount;
private int swapCount;
private long time;
private DecimalFormat fmt = new DecimalFormat("#.00");
public void sort(T[] array) {
if (array == null || array.length < 2) {
return;
}
this.array = array;
long begin = System.currentTimeMillis();
sort();
time = System.currentTimeMillis() - begin;
}
@Override
public int compareTo(Sort<T> o) {
int result = (int)(time - o.time);
if (result != 0) {
return result;
}
result = cmpCount - o.cmpCount;
if (result != 0) {
return result;
}
return swapCount - o.swapCount;
}
/**
* 排序算法具体实现
*/
protected abstract void sort();
/**
* 返回值等于0,代表 array[i1] == array[i2]
* 返回值小于0,代表 array[i1] < array[i2]
* 返回值大于0,代表 array[i1] > array[i2]
*/
protected int cmp(int i1, int i2) {
cmpCount++;
return array[i1].compareTo(array[i2]);
}
protected int cmp(T v1, T v2) {
cmpCount++;
return v1.compareTo(v2);
}
protected void swap(int i1, int i2) {
swapCount++;
T tmp = array[i1];
array[i1] = array[i2];
array[i2] = tmp;
}
@Override
public String toString() {
String timeStr = "耗时:" + (time / 1000.0) + "s(" + time + "ms)";
String compareCountStr = "比较:" + numberString(cmpCount);
String swapCountStr = "交换:" + numberString(swapCount);
String stableStr = "稳定性:" + isStable();
return "【" + getClass().getSimpleName() + "】\n"
+ stableStr + " \t"
+ timeStr + " \t"
+ compareCountStr + "\t "
+ swapCountStr + "\n"
+ "------------------------------------------------------------------";
}
private String numberString(int number) {
if (number < 10000) {
return "" + number;
}
if (number < 100000000) {
return fmt.format(number / 10000.0) + "万";
}
return fmt.format(number / 100000000.0) + "亿";
}
private boolean isStable() {
if (this instanceof RadixSort) {
return true;
}
if (this instanceof CountingSort) {
return true;
}
if (this instanceof ShellSort) {
return false;
}
if (this instanceof SelectionSort) {
return false;
}
Student[] students = new Student[20];
for (int i = 0; i < students.length; i++) {
students[i] = new Student(i * 10, 10);
}
sort((T[]) students);
for (int i = 1; i < students.length; i++) {
int score = students[i].score;
int prevScore = students[i - 1].score;
if (score != prevScore + 10) {
return false;
}
}
return true;
}
}
基于比较的排序
冒泡排序(Bubble Sort)
从头开始比较每一对相邻元素,如果第1个比第2个大,就交换它们的位置 。执行完一轮后,最末尾那个元素就是最大的元素
public class BubbleSort1 extends Sort{
@Override
protected void sort() {
for (int end = array.length - 1; end > 0; end--) {
//每一轮比较的范围是[0,end)
for (int begin = 0; begin < end; begin++) {
if (cmp(begin+1,begin)<0) {
swap(begin+1,begin);
}
}
}
}
}
优化1
如果序列已经完全有序,可以提前终止冒泡排序
public class BubbleSort2 extends Sort{
@Override
protected void sort() {
for (int end = array.length - 1; end > 0; end--) {
boolean sorted = true;
for (int begin = 0; begin < end; begin++) {
if (cmp(begin+1,begin)<0) {
swap(begin+1,begin);
sorted = false;
}
}
//如果经过一轮排序,sort依然为true,说明没有发生一次交换,序列已经有序了
if (sorted) {
break;
}
}
}
}
优化2
如果序列尾部已经局部有序,可以记录最后1次交换的位置,减少比较次数
public class BubbleSort3 extends Sort{
@Override
protected void sort() {
for (int end = array.length - 1; end > 0; end--) {
int sortedIndex = 0;
for (int begin = 0; begin < end; begin++) {
if (cmp(begin+1,begin)<0) {
swap(begin+1,begin);
sortedIndex = begin + 1;
}
}
end = sortedIndex;
}
}
}
- 最坏、平均时间复杂度:O(n 2 )
- 最好时间复杂度:O(n)
- 空间复杂度:O(1)
选择排序(Selection Sort)
从序列中找出最大的那个元素,然后与最末尾的元素交换位置 。执行完一轮后,最末尾的那个元素就是最大的元素
public class SelectionSort<T extends Comparable<T>> extends Sort<T> {
@Override
protected void sort() {
for (int end = array.length - 1; end > 0; end--) {
int maxIndex = 0;
for (int begin = 1; begin <= end; begin++) {
if (cmp(begin, maxIndex) > 0) {
maxIndex = begin;
}
}
swap(maxIndex, end);
}
}
}
堆排序(Heap Sort)
可以认为是对选择排序的优化
执行流程
- 对序列进行原地建堆(heapify)–大顶堆
- 重复执行以下操作,直到堆的元素数量为 1
- 交换堆顶元素与尾元素
- 堆的元素数量减 1
- 对 0 位置进行 1 次 siftDown() ,即下滤操作
public class HeapSort<E extends Comparable<E>> extends Sort<E>{
private int heapSize;
@Override
protected void sort() {
//原地建堆,有两种形式,自上而下的上滤,或者自下而上的下滤
//这里是自下而上的下滤
heapSize=array.length;
//(heapSize >> 1) - 1只有非叶子节点
for (int i = (heapSize >> 1) - 1; i >= 0; i--) {
siftDown(i);
}
while (heapSize>1){
//交换堆顶元素和尾部
swap(0,--heapSize);
//对0位置下滤
siftDown(0);
}
}
/**
* 下滤
* @param index
*/
private void siftDown(int index) {
E element = array[index];
//第一个叶子节点的索引=非叶子节点的数量
int half = heapSize >> 1;
while (index < half) { //必须保证index非叶子节点
//index的子节点有两种情况
//1、只有左节点
//2、同时有左右节点
//默认为左节点
//找出左右节点中大的那个
int childIndex = (index << 1) + 1;
E child = array[childIndex];
//右子节点
int rightIndex = childIndex + 1;
if (rightIndex < heapSize && cmp(array[rightIndex], child) > 0) {
childIndex = rightIndex;
child = array[rightIndex];
}
if (cmp(element, child) >= 0) {
break;
}
//将子节点存放到index
array[index] = child;
index = childIndex;
}
array[index] = element;
}
}
插入排序(Insertion Sort)
在执行过程中,插入排序会将序列分为2部分,头部是已经排好序的,尾部是待排序的 。从头开始扫描每一个元素 ,每当扫描到一个元素,就将它插入到头部合适的位置,使得头部数据依然保持有序
public class InsertionSort1<T extends Comparable<T>> extends Sort<T> {
/**
* 逆序对越多,时间复杂度越高
*/
@Override
protected void sort() {
for (int begin = 1; begin < array.length; begin++) {
int cur = begin;
while (cur > 0 && cmp(cur, cur - 1) < 0) {
swap(cur, cur - 1);
cur--;
}
}
}
}
优化
思路是将【交换】转为【挪动】
public class InsertionSort2<T extends Comparable<T>> extends Sort<T> {
@Override
protected void sort() {
for (int begin = 1; begin < array.length; begin++) {
int cur = begin;
T v=array[cur];
while (cur > 0 && cmp(v, array[cur - 1]) < 0) {
// swap(cur, cur - 1);
array[cur]=array[cur-1];
cur--;
}
array[cur]=v;
}
}
}
二分搜索优化
在元素 v 的插入过程中,可以先二分搜索出合适的插入位置,然后再将元素 v 插入
public class InsertionSort3<T extends Comparable<T>> extends Sort<T> {
@Override
protected void sort() {
for (int begin = 1; begin < array.length; begin++) {
insert(begin,search(begin));
}
}
/**
* 将source位置的元素插入到dest位置
* @param source
* @param dest
*/
private void insert(int source, int dest) {
T v=array[source];
for (int i = source; i >dest ; i--) {
array[i]=array[i-1];
}
array[dest]=v;
}
/**
* @param index
* @Description: 利用二分搜索找到index位置元素的待插入位置
* @Return int
* @Exception
* @author: cyb
* @date: 2021-03-17 14:24
*/
private int search(int index){
T v = array[index];
int begin=0;
int end=index;
while (begin<end){
int mid=(begin+end)>>1;
if (cmp(v,array[mid])<0){
end=mid;
}else {
begin=mid+1;
}
}
return begin;
}
}
归并排序(Merge Sort)
- 不断地将当前序列平均分割成2个子序列 , 直到不能再分割(序列中只剩1个元素)
- 不断地将2个子序列合并成一个有序序列 ,直到最终只剩下1个有序序列
public class MergeSort<T extends Comparable<T>> extends Sort<T> {
private T[] leftArray;
// T(n) = T(n/2) + T(n/2) + O(n)
@Override
protected void sort() {
leftArray = (T[]) new Comparable[array.length >> 1];
sort(0, array.length);
}
/**
* @param begin
* @param end
* @Description: 对[begin, end)范围内的数据归并排序
*/
private void sort(int begin, int end) {
if (end - begin < 2) {
return;
}
int mid = (begin + end) >> 1;
sort(begin, mid);
sort(mid, end);
merge(begin, mid, end);
}
/**
* @param begin
* @param mid
* @param end
* @Description: 将[begin, mid),[mid,end)合并
*/
private void merge(int begin, int mid, int end) {
//左边数组的起始
int li = 0;
int le = mid - begin;
//右边数组的起始
int ri = mid;
int re = end;
//备份的左边数组的index
int ai = begin;
//备份左边数组
for (int i = li; i < le; i++) {
leftArray[i] = array[begin + i];
}
//如果左边还没结束
while (li < le) {
if (ri < re && cmp(array[ri], leftArray[li]) < 0) {
array[ai++] = array[ri++];
} else {
array[ai++] = leftArray[li++];
}
}
}
}
快速排序(Quick Sort)
- 从序列中选择一个轴点元素(pivot)
- 假设每次选择 0 位置的元素为轴点元素
- 利用 pivot 将序列分割成 2 个子序列
- 将小于 pivot 的元素放在pivot前面(左侧)
- 将大于 pivot 的元素放在pivot后面(右侧)
- 等于pivot的元素放哪边都可以
- ③ 对子序列进行 ① ② 操作
- 直到不能再分割(子序列中只剩下1个元素)
public class QuickSort<T extends Comparable<T>> extends Sort<T> {
@Override
protected void sort() {
sort(0, array.length);
}
/**
* @param begin
* @param end
* @Description: 对[begin, end)范围内的元素快速排序
*/
private void sort(int begin, int end) {
if (end - begin < 2) {
return;
}
//确定轴点位置
int mid = pivotIndex(begin, end);
//对子序列快速排序
sort(begin, mid);
sort(mid + 1, end);
}
/**
* 构造轴点,以第一个元素作为轴点
* @param begin
* @param end
* @return
*/
private int pivotIndex(int begin, int end) {
//随机选择一个元素跟begin交换
swap(begin, begin+(int) (Math.random()*(end-begin)));
//备份begin位置的元素
T pivot = array[begin];
end--;
// boolean turn=true;
//类似左右切换的可以用三个while循环加break解决
while (begin < end) {
while (begin < end) {
if (cmp(pivot, array[end]) < 0) { // 右边元素 > 轴点元素
end--;
} else { // 右边元素 <= 轴点元素
array[begin++] = array[end];
break;
}
}
while (begin < end) {
if (cmp(pivot, array[begin]) > 0) { // 左边元素 < 轴点元素
begin++;
} else { // 左边元素 >= 轴点元素
array[end--] = array[begin];
break;
}
}
}
//将轴点元素放到最终的位置
array[begin] = pivot;
return begin;
}
}
希尔排序(Shell Sort)
希尔排序把序列看作是一个矩阵,分成 𝑚 列,逐列进行排序 ,m从某个整数逐渐减为1,当 𝑚 为1时,整个序列将完全有序
因此,希尔排序也被称为递减增量排序(Diminishing Increment Sort)
矩阵的列数取决于步长序列(step sequence),比如,如果步长序列为{1,5,19,41,109,…},就代表依次分成109列、41列、19列、5列、1列进行排序。不同的步长序列,执行效率也不同
希尔本人给出的步长序列是 𝑛/2 𝑘,比如 𝑛 为16时,步长序列是{1, 2, 4, 8}
目前已知的最好的步长序列,最坏情况时间复杂度是 O(n 4/3 ) ,1986年由Robert Sedgewick提出
public class ShellSort<T extends Comparable<T>> extends Sort<T> {
@Override
protected void sort() {
//步长序列
List<Integer> stepSequence = sedgewickStepSequence();
for (Integer step : stepSequence) {
sort(step);
}
}
/**
* 分成step列进行排序
*
* @param step
*/
private void sort(int step) {
for (int col = 0; col < step; col++) {
//第col列排序
//需要排序的元素 col、col+step、col+2step
for (int begin = col + step; begin < array.length; begin += step) {
int cur = begin;
while (cur > col && cmp(cur, cur - step) < 0) {
swap(cur, cur - step);
cur -= step;
}
}
}
}
private List<Integer> shellStepSequence() {
ArrayList<Integer> stepSequence = new ArrayList<>();
int step = array.length;
while ((step >>= 1) > 0) {
stepSequence.add(step);
}
return stepSequence;
}
/**
* 目前已知的最好的步长序列,最坏情况时间复杂度是 O(n的4/3次方),1986年由Robert Sedgewick提出
* @return
*/
private List<Integer> sedgewickStepSequence() {
List<Integer> stepSequence = new LinkedList<>();
int k = 0, step = 0;
while (true) {
if (k % 2 == 0) {
int pow = (int) Math.pow(2, k >> 1);
step = 1 + 9 * (pow * pow - pow);
} else {
int pow1 = (int) Math.pow(2, (k - 1) >> 1);
int pow2 = (int) Math.pow(2, (k + 1) >> 1);
step = 1 + 8 * pow1 * pow2 - 6 * pow2;
}
if (step >= array.length) break;
stepSequence.add(0, step);
k++;
}
return stepSequence;
}
}
计数排序(Counting Sort)
统计每个整数在序列中出现的次数,进而推导出每个整数在有序序列中的索引
public class CountingSort extends Sort<Integer> {
/**
* 改进最简单的实现
*/
@Override
protected void sort() {
int max = array[0];
int min = array[0];
for (int i = 1; i < array.length; i++) {
if (array[i] > max) {
max = array[i];
}
if (array[i] < min) {
min = array[i];
}
}
//开辟内存空间,存储每个整数出现的次数
int[] counts = new int[max - min + 1];
for (int i = 0; i < array.length; i++) {
counts[array[i] - min]++;
}
//累加次数
for (int i = 1; i < counts.length; i++) {
counts[i] += counts[i - 1];
}
//从后往前(保证稳定性)遍历元素,将它放到有序数组的合适位置
int[] newArray = new int[array.length];
for (int i = array.length - 1; i >= 0; i--) {
newArray[--counts[array[i] - min]] = array[i];
}
for (int i = 0; i < newArray.length; i++) {
array[i] = newArray[i];
}
}
/**
* 最简单的实现
* 存在以下问题:
* 1、无法对负整数进行排序
* 2、极其浪费内存空间
* 3、是个不稳定的排序
*/
protected void sort0() {
//找出最大值
int max = array[0];
for (int i = 1; i < array.length; i++) {
if (array[i] > max) {
max = array[i];
}
}
//开辟内存空间,存储每个整数出现的次数
int[] counts = new int[1 + max];
for (int i = 0; i < array.length; i++) {
counts[array[i]]++;
}
//根据整数出现次数进行排序
int arrayIndex = 0;
for (int i = 0; i < counts.length; i++) {
while (counts[i] > 0) {
array[arrayIndex++] = i;
counts[i]--;
}
}
}
}
基数排序(Radix Sort)
依次对个位数、十位数、百位数、千位数、万位数…进行排序(从低位到高位)
个位数、十位数、百位数的取值范围都是固定的0~9,可以使用计数排序对它们进行排序||或者用二维数组实现
public class RadixSort extends Sort<Integer>{
@Override
protected void sort() {
int max = array[0];
for (int i = 1; i < array.length; i++) {
if (array[i] > max) {
max = array[i];
}
}
// 个位数: array[i] / 1 % 10 = 3
// 十位数:array[i] / 10 % 10 = 9
// 百位数:array[i] / 100 % 10 = 5
// 千位数:array[i] / 1000 % 10 = ...
for (int divider = 1; divider <= max; divider *= 10) {
countingSort(divider);
}
}
protected void countingSort(int divider) {
//开辟内存空间,存储每个整数的基数出现的次数
int[] counts = new int[10];
for (int i = 0; i < array.length; i++) {
counts[array[i]/divider%10]++;
}
//累加次数
for (int i = 1; i < counts.length; i++) {
counts[i] += counts[i - 1];
}
//从后往前(保证稳定性)遍历元素,将它放到有序数组的合适位置
int[] newArray = new int[array.length];
for (int i = array.length - 1; i >= 0; i--) {
newArray[--counts[array[i]/divider%10]] = array[i];
}
for (int i = 0; i < newArray.length; i++) {
array[i] = newArray[i];
}
}
}