对排序知识的回顾+再整理。
排序算法 | 时间复杂度(平均) | 时间复杂度(最好) | 时间复杂度(最坏) | 空间复杂度 | 稳定性 | 排序方式 |
---|---|---|---|---|---|---|
冒泡排序 | O(n2) | O(n) | O(n2) | O(1) | 稳定 | 交换排序 in-place |
选择排序 | O(n2) | O(n2) | O(n2) | O(1) | 不稳定 | 选择排序 in-place |
插入排序 | O(n2) | O(n) | O(n2) | O(1) | 稳定 | 插入排序 in-place |
希尔排序 | O(n1.3) | O(n) | O(n2) | O(1) | 不稳定 | 插入排序 in-place |
归并排序 | O(nlogn) | O(nlogn) | O(nlogn) | O(n) | 稳定 | 归并排序 out-place |
快速排序 | O(nlogn) | O(nlogn) | O(n2) | O(log n) | 不稳定 | 交换排序 in-place |
堆排序 | O(nlogn) | O(nlogn) | O(nlogn) | O(1) | 不稳定 | 选择排序 in-place |
计数排序 | O(n+k) | O(n+k) | O(n+k) | O(k) | 稳定 | 非比较排序 out-place |
桶排序 | O(n+k) | O(n+k) | O(n2) | O(n+k) | 稳定 | 非比较排序 out-place |
基数排序 | O(n*k) | O(n*k) | O(n*k) | O(n+k) | 稳定 | 非比较排序 out-place |
这十个排序算法主要分为两类:比较排序和非比较排序。比较排序之中又分为交换、插入、选择排序和归并排序四种。
先笼统的讲一下:
冒泡、选择、插入三个排序的实现思路和它的名字一样。
希尔排序是改进版的插入排序,将序列分为若干个子序列,但不是子序列内部自己比较。
归并排序和快速排序都是采用了分治法的思想,但是归并排序需要额外空间。
堆排序采用了堆(近似完全二叉树的结构)这个数据结构,并满足堆积的性质:即子结点的键值或索引总是小于它的父节点,最大的是根节点。
计数、桶、基数三个排序都运用了类似于桶的概念,分别是以每个数为桶(计数)、以一定区间为桶(后续的排序还有两种方法)、以0-9为桶(数字某一位)。
冒泡排序
就像鱼儿吐泡泡一样,上面的最大下面的最小。通过相邻元素的依次比较,让最大的元素浮出到序列顶端。
算法描述
- 比较相邻的元素。如果第一个比第二个大,就交换它们两个;
- 对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对,这样在最后的元素应该会是最大的数;
- 针对所有的元素重复以上的步骤,除了最后一个;
- 重复步骤1~3,直到排序完成。
public class BubbleSort implements ISort {
@Override
public int[] sort(int[] arr) {
for (int i = 0; i < arr.length - 1; i++) {// 第一层循环,设置比较次数
for (int j = 0; j < arr.length - 1 - i; j++) {// 第二层循环,相邻元素进行比较
// 如果前一个数比后面一个数大,则交换。最大的会交换到最后
if (arr[j] > arr[j + 1]) {
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
return arr;
}
}
选择排序
首先在序列中找到最小元素,存放到有序区的起始位置,然后再从无序区中继续寻找最小元素,放到有序区的末尾。
算法描述
- 初始状态:无序区为R[1…n],有序区为空;
- 第i趟排序(i=1,2,3…n-1)开始时,当前有序区和无序区分别为R[1…i-1]和R(i…n)。该趟排序从当前无序区中-选出关键字最小的记录R[k],将它与无序区的第1个记录R交换,使R[1…i]和R[i+1…n)分别变为记录个数增加1个的新有序区和记录个数减少1个的新无序区;
- n-1趟结束,数组有序化了。
public class SelectionSort implements ISort {
@Override
public int[] sort(int[] arr) {
for (int i = 0; i < arr.length - 1; i++) {// 第一层循环,寻找最小数的次数
int minIndex = i;
for (int j = i + 1; j < arr.length; j++) {// 第二个循环,逐个比较选出最小数的位置
if (arr[j] < arr[minIndex]) {
minIndex = j;
}
}
// 最小数与当前位置的数交换
int temp = arr[minIndex];
arr[minIndex] = arr[i];
arr[i] = temp;
}
return arr;
}
}
插入排序
选取无序区的第一个元素,在有序区中从后向前扫描,寻找合适位置插入。
算法描述
- 从第一个元素开始,该元素可以认为已经被排序;
- 取出下一个元素,在已经排序的元素序列中从后向前扫描;
- 如果该元素(已排序)大于新元素,将该元素移到下一位置;
- 重复步骤3,直到找到已排序的元素小于或者等于新元素的位置;
- 将新元素插入到该位置后;
- 重复步骤2~5。
public class InsertionSort implements ISort {
@Override
public int[] sort(int[] arr) {
for (int i = 1; i < arr.length; i++) {// 第一层循环,为有序区的后一位,无序区的第一位
int temp = arr[i];// 取出
for (int j = i - 1; j >= 0; j--) {// 第二层循环,和有序区从后向前比较
if (temp > arr[j]) {// temp如果比有序区的当前数大,就插入它的后面
arr[j + 1] = temp;
break;
} else { // temp如果比有序区的当前数小,那么当前数向后移,给temp空位置
arr[j + 1] = arr[j];
if (j == 0) {// 如果已经是最后一个元素了,则插入temp
arr[j] = temp;
}
}
}
}
return arr;
}
}
希尔排序
先将整个待排序的记录序列分割成为若干子序列分别进行直接插入排序,待整个序列中的记录"基本有序"时,再对全体记录进行依次直接插入排序。
算法描述
- 选择一个增量序列t1,t2,…,tk,其中ti>tj,tk=1;
- 按增量序列个数k,对序列进行k 趟排序;
- 每趟排序,根据对应的增量ti,将待排序列分割成若干长度为m 的子序列,分别对各子表进行直接插入排序。仅增量因子为1 时,整个序列作为一个表来处理,表长度即为整个序列的长度。
public class ShellSort implements ISort {
@Override
public int[] sort(int[] arr) {
int gap = 1;// 定义增量,将序列分为arr.length/gap个子序列
while (gap < arr.length) {
gap = gap * 3 + 1;
}
while (gap > 0) {
for (int i = gap; i < arr.length; i++) {
int tempIndex = i - gap;
int temp = arr[i];
while (tempIndex >= 0 && arr[tempIndex] > temp) {// 前一子序列对应位置的数大于当前数
arr[tempIndex + gap] = arr[tempIndex];// 前一子序列对应位置的数赋给后一子序列的对应位置数
tempIndex -= gap;// 再往前移一组
}
arr[tempIndex + gap] = temp;
}
gap = (int) Math.floor(gap / 3);
}
return arr;
}
public int[] sort2(int[] arr) {
int len = arr.length;
for (int gap = len / 2; gap > 0; gap = gap / 2) {
for (int i = gap; i < len; i++) {
int j = i;
int current = arr[i];
while (j - gap >= 0 && current < arr[j - gap]) {
arr[j] = arr[j - gap];
j = j - gap;
}
arr[j] = current;
}
}
return arr;
}
}
归并排序
它是7个比较排序中唯一一个需要额外空间的,它是先使每个子序列有序,再使子序列段间有序的。
算法描述
- 把长度为n的输入序列分成两个长度为n/2的子序列;
- 对这两个子序列分别采用归并排序;
- 将两个排序好的子序列合并成一个最终的排序序列。
public class MergeSort implements ISort {
@Override
public int[] sort(int[] arr) {
int len = arr.length;
if (len < 2) {
return arr;
}
int middle = len / 2;
int[] left = new int[middle];
int[] right = new int[len - middle];
for (int i = 0; i < middle; i++) {
left[i] = arr[i];
}
for (int i = middle; i < len; i++) {
right[i - middle] = arr[i];
}
return merge(sort(left), sort(right));// sort()拆分,merge()合并
}
private int[] merge(int[] left, int[] right) {// 合并
int[] result = new int[left.length + right.length];// 需要开辟额外的空间
int i = 0;
int leftIndex = 0;
int rightIndex = 0;
while (leftIndex < left.length && rightIndex < right.length) {// 谁小放谁,直到有数组放完为止
if (left[leftIndex] < right[rightIndex]) {
result[i] = left[leftIndex];
leftIndex++;
} else {
result[i] = right[rightIndex];
rightIndex++;
}
i++;
}
while (leftIndex < left.length) {// 如果左数组没放完继续放
result[i] = left[leftIndex];
leftIndex++;
i++;
}
while (rightIndex < right.length) {// 如果右数组没放完继续放
result[i] = right[rightIndex];
rightIndex++;
i++;
}
return result;
}
}
快速排序
通过一趟排序将待排记录分隔成独立的两部分,其中一部分记录的关键字均比另一部分的关键字小,则可分别对这两部分记录继续进行排序,以达到整个序列有序。
算法描述
- 从数列中挑出一个元素,称为 “基准”;
- 重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区操作;
- 递归地把小于基准值元素的子序列和大于基准值元素的子序列排序。
public class QuickSort implements ISort {
@Override
public int[] sort(int[] arr) {
return quickSort(arr, 0, arr.length - 1);
}
private int[] quickSort(int[] arr, int left, int right) {
if (left < right) {
int partitionIndex = partition(arr, left, right);// 选出基准值后,分成两个子序列(不包含以前的基准值),然后各玩各的
quickSort(arr, left, partitionIndex - 1);
quickSort(arr, partitionIndex + 1, right);
}
return arr;
}
private int partition(int[] arr, int left, int right) {
// 设定基准值(pivot)
int pivot = left;
int index = pivot + 1;
for (int i = index; i <= right; i++) {
if (arr[i] < arr[pivot]) {
swap(arr, i, index);
index++;
}
}
swap(arr, pivot, index - 1);// 和比基准值小的最后一位交换位置
return index - 1;
}
private void swap(int[] arr, int i, int j) {
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
堆排序
构建堆,保证堆顶元素最大,然后堆顶和最后一个数互换后,距离减一(相当于删除之前的堆顶),再通过交换使堆顶元素最大。
算法描述
- 将初始待排序关键字序列(R1,R2….Rn)构建成大顶堆,此堆为初始的无序区;
- 将堆顶元素R[1]与最后一个元素R[n]交换,此时得到新的无序区(R1,R2,……Rn-1)和新的有序区(Rn),且满足R[1,2…n-1]<=R[n];
- 由于交换后新的堆顶R[1]可能违反堆的性质,因此需要对当前无序区(R1,R2,……Rn-1)调整为新堆,然后再次将R[1]与无序区最后一个元素交换,得到新的无序区(R1,R2….Rn-2) 和新的有序区(Rn-1,Rn)。不断重复此过程直到有序区的元素个数为n-1,则整个排序过程完成。
public class HeapSort implements ISort {
@Override
public int[] sort(int[] arr) {
int len = arr.length;
buildHeap(arr, len);// 构造堆
for (int i = len - 1; i > 0; i--) {
swap(arr, 0, i);// 根节点与最后一个元素,换位置,并重新排序
len--;
heapify(arr, 0, len);// 时刻保持根节点最大,且每个父节点都比子节点大
}
return arr;
}
private void buildHeap(int[] arr, int len) {
for (int i = (int) Math.floor(len / 2); i >= 0; i--) {
heapify(arr, i, len);
}
}
private void heapify(int[] arr, int i, int len) {
int left = i * 2 + 1;// 左子节点索引
int right = i * 2 + 2;// 右子节点索引
int largest = i;// 父节点索引
if (left < len && arr[left] > arr[largest]) {// 防止越界,并让父节点为最大
largest = left;
}
if (right < len && arr[right] > arr[largest]) {
largest = right;
}
if (largest != i) {// 如果被替换,还需要再往下比较
swap(arr, i, largest);
heapify(arr, largest, len);
}
}
private void swap(int[] arr, int i, int j) {
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
计数排序
将输入的数据值转化为键存储在额外开辟的数组空间中,适合于有范围数据较集中的情况,比一般的比较排序都要快。
算法描述
- 找出待排序的数组中最大和最小的元素;
- 统计数组中每个值为i的元素出现的次数,存入数组C的第i项;
- 对所有的计数累加(从C中的第一个元素开始,每一项和前一项相加);
- 反向填充目标数组:将每个元素i放在新数组的第C(i)项,每放一个元素就将C(i)减去1。
public class CountingSort implements ISort {
@Override
public int[] sort(int[] arr) {
return countingSort(arr, getMaxValue(arr));
}
private int[] countingSort(int[] arr, int maxValue) {
int minValue = getMinValue(arr);// 考虑负数,开辟更多空间
if (minValue < 0) {
int[] bucket = new int[maxValue - minValue + 5];
for (int i = 0; i < arr.length; i++) {
bucket[arr[i] - minValue]++;
}
int arrindex = 0;
for (int i = 0; i < bucket.length; i++) {
while (bucket[i] > 0) {
arr[arrindex] = i + minValue;
bucket[i]--;
arrindex++;
}
}
} else {
int[] bucket = new int[maxValue + 5];
for (int i = 0; i < arr.length; i++) {
bucket[arr[i]]++;
}
int arrindex = 0;
for (int i = 0; i < bucket.length; i++) {
while (bucket[i] > 0) {
arr[arrindex] = i;
bucket[i]--;
arrindex++;
}
}
}
return arr;
}
private int getMaxValue(int[] arr) {// 寻找数组中最大的数,用来开辟额外空间
int maxValue = arr[0];
for (int i = 1; i < arr.length; i++) {
if (arr[i] > maxValue) {
maxValue = arr[i];
}
}
return maxValue;
}
private int getMinValue(int[] arr) {// 考虑负数
int minValue = arr[0];
for (int i = 1; i < arr.length; i++) {
if (arr[i] < minValue) {
minValue = arr[i];
}
}
return minValue;
}
}
桶排序
计数排序的升级版,将数据分到有限数量的桶(区间)里,每个桶再分别排序。对于每个桶可以用递归继续使用桶排序,也可以用排序算法排序
算法描述
- 设置一个定量的数组当作空桶;
- 遍历输入数据,并且把数据一个一个放到对应的桶里去;
- 对每个不是空的桶进行排序;
- 从不是空的桶里把排好序的数据拼接起来。
import java.util.Arrays;
public class BucketSort implements ISort {
@Override
public int[] sort(int[] arr) {
int bucketSize = 5;// 设置桶的大小,如0-4;5-9;
return bucketSort(arr, bucketSize);
}
private int[] bucketSort(int[] arr, int bucketSize) {
int[] maximum = maximum(arr);
int maxValue = maximum[0];
int minValue = maximum[1];
int bucketCount = (maxValue - minValue) / bucketSize + 1;
int[][] buckets = new int[bucketCount][0];
for (int i = 0; i < arr.length; i++) {// 将数分据配到各个桶中
int index = (arr[i] - minValue) / bucketSize;
buckets[index] = arrAppend(buckets[index], arr[i]);
}
int arrIndex = 0;
for (int i = 0; i < buckets.length; i++) {
if (buckets[i].length <= 0) {
continue;
}
// 对每个桶进行排序,这里用的插入排序
ISort iSort = new InsertionSort();
buckets[i] = iSort.sort(buckets[i]);
for (int j = 0; j < buckets[i].length; j++) {
arr[arrIndex] = buckets[i][j];
arrIndex++;
}
}
return arr;
}
private int[] arrAppend(int[] arr, int value) {// 扩容加保存
arr = Arrays.copyOf(arr, arr.length + 1);
arr[arr.length - 1] = value;
return arr;
}
private int[] maximum(int[] arr) {// 遍历一遍,寻找最值
int[] maximum = new int[2];
int maxValue = arr[0];
int minValue = arr[0];
for (int i = 0; i < arr.length; i++) {
if (arr[i] > maxValue) {
maxValue = arr[i];
}
if (arr[i] < minValue) {
minValue = arr[i];
}
}
maximum[0] = maxValue;
maximum[1] = minValue;
return maximum;
}
}
基数排序
基数排序是按照低位先排序,然后收集;再按照高位排序,然后再收集;依次类推,直到最高位。
算法描述
- 取得数组中的最大数,并取得位数;
2)arr为原始数组,从最低位开始取每个位组成radix数组; - 对radix进行计数排序(利用计数排序适用于小范围数的特点)。
import java.util.Arrays;
public class RadixSort implements ISort {
@Override
public int[] sort(int[] arr) {
return radixSort(arr);
}
private int[] radixSort(int[] arr) {
int mod = 10;
int dev = 1;
int maxValue = getMaxValue(arr);
int n = 0;// 最大数位数
while (maxValue > 0) {
n++;
maxValue /= 10;
}
int tempmod = mod;
int tempdev = dev;
for (int i = 1; i <= n; i++) {// 桶排序次数
int[][] buckets = new int[mod * 2][0];// 考虑负数,10-19为正,0-9为负
for (int j = 0; j < arr.length; j++) {// 将数放入相应的桶中
int num = arr[j] % tempmod / tempdev + mod;
buckets[num] = arrayAppend(buckets[num], arr[j]);
}
int arrIndex = 0;
for (int x = 0; x < buckets.length; x++) {// 将数据从桶中放入数组
for (int y = 0; y < buckets[x].length; y++) {
arr[arrIndex] = buckets[x][y];
arrIndex++;
}
}
tempmod = tempmod * 10;
tempdev = tempdev * 10;
}
return arr;
}
private int getMaxValue(int[] arr) {
int maxValue = arr[0];
for (int i = 0; i < arr.length; i++) {
if (maxValue < arr[i]) {
maxValue = arr[i];
}
}
return maxValue;
}
private int[] arrayAppend(int[] arr, int value) {
arr = Arrays.copyOf(arr, arr.length + 1);
arr[arr.length - 1] = value;
return arr;
}
}