常见排序算法
排序
时间复杂
类别 | 平均复杂度 | 最好 | 最坏 | 空间复杂度 |
---|---|---|---|---|
插入排序 | n^2 | n | n^2 | 1 |
希尔排序 | n^1.3 | n | n^2 | 1 |
堆排序 | nlogn | nlogn | nlogn | 1 |
归并排序 | nlogn | nlogn | nlogn | n |
插入排序 | nlogn | nlogn | n^2 | n or logn |
基数排序 | nd | nd | nd | r + n |
插入排序
运行时间N^2
将每个位置元素插入到合适的位置,其他位置后移
/**
* 将数组指定范围内进行插入排序
*/
public static <E extends Comparable<? super E>> void insertionSort(E[] a, int left, int right) {
int j;
// 插排执行的交换次数是逆序的个数(具有性质i<j但a[i]>a[j]的序偶(a[i],a[j]))
// 相当于把每个值向前遍历插入到正确的位置
for (int i = left + 1; i <= right; i++) {
E temp = a[i];
for (j = i; j > left && temp.compareTo(a[j - 1]) < 0; j--) {
// 将大于i的值整体右移
a[j] = a[j - 1];
}
// 此时的j为temp的正确位置
a[j] = temp;
}
}
希尔排序
最坏运行时间N2,有时能达到N(3/2)
log N次小数组插入排序,每个数组间隔gap,又称增量序列,即缩减增量排序
/**
* 运行时间最差N^2 ,使用1,3,7,...,2^k - 1增量序列运行时间优化至N^(3/2)
* 希尔排序:实际上是n个增量序列分别进行插入排序,且第一个序列h1必须为1
*/
public static <E extends Comparable<? super E>> void shellSort(E []a) {
System.out.println(Math.log(8));
int j;
// 第一个for循环,增量循环 次数为log N
for (int gap = a.length / 2; gap > 0; gap /= 2) {
// 单次插排,元素为间隔gap增量的序列
for (int i = gap; i < a.length; i++) {
E temp = a[i];
for (j = i; j >= gap && temp.compareTo(a[j - gap]) < 0; j -= gap) {
a[j] = a[j - gap];
}
a[j] = temp;
}
}
}
堆排序
运行时间Nlog N
利用二叉堆的堆序性质进行排序
利用一个数组完成排序,根为最大值,每次排序交换根与二叉堆最后一个元素+1位置的元素,然后对根进行下滤
public static final int DESC = 1;
public static final int ASC = 0;
/**
* 堆数组从0开始 故而左子节点为当前节点的索引*2+1
* @param i
* @return
*/
private static int leftChild(int i) {
return 2 * i + 1;
}
/**
* 下滤,构成根为最小值堆序的二叉堆;用于构建降序数组
* @param a 堆的源数组
* @param i 下滤的索引位置
* @param n 二叉堆的长度
*/
private static <E extends Comparable<? super E>> void percUp(E [] a, int i, int n) {
int child;
E temp = a[i];
while (leftChild(i) < n) {
child = leftChild(i);
if (child != n - 1 && a[child].compareTo(a[child + 1]) > 0) {
child++;
}
if (temp.compareTo(a[child]) > 0) {
a[i] = a[child];
} else {
break;
}
i = child;
}
a[i] = temp;
}
/**
* 下滤,构成根为最大值堆序的二叉堆;用于构建升序数组
* @param a 二叉堆性质的数组
* @param i 需要下滤的索引位置
* @param n 二叉堆的长度
*/
private static <E extends Comparable<? super E>> void percDown(E [] a, int i, int n) {
int child;
E temp;
for (temp = a[i]; leftChild(i) < n; i = child) {
child = leftChild(i);
// 左子节点小于右子节点,则空穴hop下滤到右子节点
if (child != n - 1 && a[child].compareTo(a[child + 1]) < 0) {
child++;
}
// 空穴下滤,大的值上移
if (temp.compareTo(a[child]) < 0) {
a[i] = a[child];
} else {
break;
}
}
a[i] = temp;
}
public static <E extends Comparable<? super E>> void heapSort(E [] a, int sort) {
boolean isDesc = DESC == sort;
// 最下层的数据不需要操作
for (int i = a.length / 2 - 1; i >= 0; i--) {
if (isDesc) {
percUp(a, i, a.length);
} else {
percDown(a, i, a.length);
}
}
// deleteMax
for (int i = a.length - 1; i > 0; i--) {
swapReferences(a, 0, i);
if (isDesc) {
percUp(a, 0, i);
} else {
percDown(a, 0, i);
}
}
for (E e : a) {
System.out.println(e);
}
}
private static <E extends Comparable<? super E>> void swapReferences(E [] a, int i, int n) {
E temp = a[n];
a[n] = a[i];
a[i] = temp;
}
归并排序
运行时间Nlog N
分治迭代 ,比较次数少
基本操作是合并两个已排序的表
public static <E extends Comparable<? super E>> void mergeSort(E[] a) {
E [] tempArray = (E[]) new Comparable[a.length];
mergeSort(a, tempArray, 0, a.length - 1);
for (E e : a) {
System.out.println(e);
}
}
private static <E extends Comparable<? super E>> void mergeSort(E[] a, E[] tempArray, int left, int right) {
if (left < right) {
int center = (left + right) / 2;
mergeSort(a, tempArray, left, center);
mergeSort(a, tempArray, center + 1, right);
merge(a, tempArray, left, center + 1, right);
}
}
/**
* 进行合并操作 默认a,tempArray有序数组
* @param a 需要进行排序的数组
* @param tempArray 临时存储空间
* @param leftPos 第一个有序数组的最左索引
* @param rightPos 第二个有序数组的最左索引
* @param rightEnd 第二个有序数组的最右索引
*/
private static <E extends Comparable<? super E>> void merge(E[] a, E[] tempArray, int leftPos, int rightPos, int rightEnd) {
int leftEnd = rightPos - 1;
int tmpPos = leftPos;
int numElements = rightEnd - leftPos + 1;
// Main loop
while (leftPos <= leftEnd && rightPos <= rightEnd) {
if (a[leftPos].compareTo(a[rightPos]) <= 0) {
tempArray[tmpPos++] = a[leftPos++];
} else {
tempArray[tmpPos++] = a[rightPos++];
}
}
while (leftPos <= leftEnd) {
tempArray[tmpPos++] = a[leftPos++];
}
while (rightPos <= rightEnd) {
tempArray[tmpPos++] = a[rightPos++];
}
// 复制tempArray到a
System.arraycopy(tempArray, rightEnd - numElements + 1, a, rightEnd - numElements + 1, numElements);
// 或者以下for循环复制
// for (int i = 0; i < numElements; rightEnd--, i++) {
// a[rightEnd] = tempArray[rightEnd];
// }
}
快速排序
运行时间NlogN
取枢纽元pivot
一种分治的递归算法
枢纽元左边都是小于枢纽元的元素,右边都是大于枢纽元的元素:本质上是把枢纽元放到合适的位置上
取枢纽元:三数中值分割法
private static <E extends Comparable<? super E>> E median3(E[] a, int left, int right) {
int center = (left + right) / 2;
// 保证最左,最右小于中位值
if (a[left].compareTo(a[center]) > 0) {
swapReferences(a, left, center);
}
if (a[right].compareTo(a[left]) < 0) {
swapReferences(a, left, right);
}
if (a[right].compareTo(a[center]) < 0) {
swapReferences(a, center, right);
}
// 中位数与最右边倒数第二个元素互换位置
swapReferences(a, center, right - 1);
return a[right - 1];
}
快速排序:当需要排序的元素小于10时使用插入排序
public static <E extends Comparable<? super E>> void quickSort(E[] a) {
quickSort(a, 0, a.length - 1);
}
private static final int CUTOFF = 4;
private static <E extends Comparable<? super E>> void quickSort(E[] a, int left, int right) {
if (left + CUTOFF <= right) {
E pivot = median3(a, left, right);
// begin partitioning
int i = left, j = right - 1;
while (true) {
while (a[++i].compareTo(pivot) < 0) {}
while (a[--j].compareTo(pivot) > 0) {}
if (i < j) {
swapReferences(a, i, j);
} else {
break;
}
}
// pivot back
swapReferences(a, i, right - 1);
quickSort(a, left, i - 1);
quickSort(a, i + 1, right);
} else {
InsertSorted.insertionSort(a, left, right);
}
}
/**
* 互换位置
*/
private static <E extends Comparable<? super E>> void swapReferences(E[] a, int left, int right) {
E temp = a[left];
a[left] = a[right];
a[right] = temp;
}
快速选择
针对选择问题,即第k个最大元或最小元
运行时间最坏N^2,平均N
第k个最小元:
public static <E extends Comparable<? super E>> void quickSelect(E [] a, int left, int right, int k) {
if (left + CUTOFF <= right) {
// 前三步快速排序
E pivot = median3(a, left, right);
int i = left, j = right - 1;
for (;;) {
while (a[++i].compareTo(pivot) < 0) {}
while (a[--j].compareTo(pivot) > 0) {}
if (i < j) {
swapReferences(a, i, j);
} else {
break;
}
}
swapReferences(a, i, right - 1);
// 选择问题
if (k <= i) {
quickSelect(a, left, i - 1, k);
} else if (k > i + 1) {
quickSelect(a, i + 1, right, k);
}
} else {
InsertSorted.insertionSort(a, left, right);
}
}
线性时间的排序:桶排序和基数排序
使用桶排序需要一些附加的信息,如输入数据必须仅有小于M的正整数组成
即使用一个大小为M的变量名count的数组,初始全为0.此时count有M个单元(桶),初始计数为空。输入N个数据,当读入一个输入a时,count[a] += 1,增1,此时索引顺序就是排序顺序
运行时间O(M+N)
桶排序
public static void bucketSort(int[] intArr) {
// 输入不超过100的排序
int[] bucketArr = new int[100];
for (int i = 0; i < intArr.length; i++) {
bucketArr[intArr[i]] += 1;
}
int flag = 0;
for (int i = 0; i < bucketArr.length; i++) {
if (bucketArr[i] != 0) {
for (int j = 0; j < bucketArr[i]; j++) {
intArr[flag++] = i;
}
}
}
}
基数排序
字符串的基数排序
/**
* 计数基数排序,本质是多躺桶排序
* @param arr
* @param stringLen
*/
public static void radixSortA(String[] arr, int stringLen)
{
final int BUCKETS = 256;
// 泛型数组不建议使用
ArrayList<String> [] buckets = new ArrayList[BUCKETS];
// 初始化桶
for (int i = 0; i < BUCKETS; i++) {
buckets[i] = new ArrayList<>();
}
for (int pos = stringLen - 1; pos >= 0; pos--) {
for (String s : arr) {
buckets[s.charAt(pos)].add(s);
}
int idx = 0;
for (ArrayList<String> theBucket : buckets) {
for (String s : theBucket) {
arr[idx++] = s;
}
theBucket.clear();
}
}
}
定长字符串的计数基数排序
/**
* 定长字符串的计数基数排序
* @param arr
* @param stringLen
*/
public static void countingRadixSort(String[] arr, int stringLen) {
final int BUCKETS = 256;
int n = arr.length;
String[] buffer = new String[n];
String[] in = arr;
String[] out = buffer;
for (int pos = stringLen - 1; pos >= 0; pos--) {
int[] count = new int[BUCKETS + 1];
for (int i = 0; i < n; i++) {
count[in[i].charAt(pos) + 1]++;
}
for (int i = 1; i <= BUCKETS; i++) {
count[i] += count[i - 1];
}
for (int i = 0; i < n; i++) {
out[count[in[i].charAt(pos)]++] = in[i];
}
String[] temp = in;
in = out;
out = temp;
}
if (stringLen % 2 == 1) {
for (int i = 0; i < arr.length; i++) {
out[i] = in[i];
}
}
}