目录
时间复杂度 O(n^2)
冒泡排序
选择排序
插入排序
时间复杂度 O(n log n)
希尔排序
归并排序
快速排序
堆排序
四种常用排序时间复杂度的比较
非比较排序
计数排序
基数排序
时间复杂度 O(n^2)
冒泡排序
/**
* 冒泡排序
*/
public class BubbleSort {
public static void main(String[] args) {
int[] arr = { 5, 4, 3, 7, 6, 9, 8, 1, 2, 0 };
for (int i : bubbleSort(arr)) {
System.out.print(i);
System.out.print(" ");
}
}
/**
* 冒泡排序,将大的数慢慢冒出,每次冒出的数字下标此处称作游标
*
* @param intArray 目标数组
* @return 排序后的数组
*/
public static int[] bubbleSort(int[] intArray) {
for (int i = 0; i < intArray.length; i++) { // 冒泡的次数
for (int j = 1; j < intArray.length - i; j++) {// 游标的位置,游标的最大位置即冒泡的终点
// 每次比较要进行的操作
if (intArray[j - 1] > intArray[j])
swap(intArray, j, j - 1);
}
}
return intArray;
}
/**
* 交换i,j俩个位置的数
*/
public static void swap(int[] intArray, int i, int j) {
int temp = intArray[i];
intArray[i] = intArray[j];
intArray[j] = temp;
}
}
选择排序
/**
* 选择排序
*/
public class SelectSort {
public static void main(String[] args) {
int[] arr = { 5, 4, 3, 7, 6, 9, 8, 1, 2, 0 };
for (int i : selectSort(arr)) {
System.out.print(i);
System.out.print(" ");
}
}
/**
* 选择排序,在未拍序列中选择最大的一个数字放入未排序列的末尾
*
* @param intArray 目标数组
* @return 排序后的数组
*/
public static int[] selectSort(int[] intArray) {
for (int i = intArray.length - 1; i > 0; i--) {// 需要选择放入的位置,第intArray[0]位置不用放
swap(intArray, i, maxSubScript(intArray, i));
}
return intArray;
}
/**
* 交换i,j俩个位置的数
*/
public static void swap(int[] intArray, int i, int j) {
int temp = intArray[i];
intArray[i] = intArray[j];
intArray[j] = temp;
}
/**
* 返回最大值下标
*
* @param intArray 目标数组
* @param len 寻找的长度arr[0]-arr[len]
* @return 该范围内最大值下标
*/
public static int maxSubScript(int[] intArray, int len) {
if (intArray.length == 0)
return -1;
int maxSub = 0;
for (int i = 1; i <= len; i++)
maxSub = intArray[maxSub] > intArray[i] ? maxSub : i;
return maxSub;
}
}
插入排序
/**
* 插入排序
*/
public class InsertSort {
public static void main(String[] args) {
int[] arr = { 5, 4, 3, 7, 6, 9, 8, 1, 2, 0 };
for (int i : insertSort(arr)) {
System.out.print(i);
System.out.print(" ");
}
}
/**
* 插入排序,从头依次检查每个位置的数,插入到其前面最适当的位置
*
* @param intArray 目标数组
* @return 结果数组
*/
public static int[] insertSort(int[] intArray) {
for (int i = 1; i < intArray.length; i++) {
int temp = intArray[i];// 要插入的值
int m = i;// 拷贝需多次赋值变量
while (m != 0 && intArray[m - 1] > temp) {
intArray[m] = intArray[m - 1];
m--;
}
intArray[m] = temp;
}
return intArray;
}
}
时间复杂度 O(n log n)
希尔排序
(如果将希尔排序的初始增量设置为1,则就是简单的插入排序)
/**
* 希尔排序
*/
public class HillSort {
public static void main(String[] args) {
int[] arr = { 5, 4, 3, 7, 6, 9, 8, 1, 2, 0 };
for (int i : hillSort(arr)) {
System.out.print(i);
System.out.print(" ");
}
}
/**
* 希尔排序
*
* @param intArray 目标数组
* @return 结果数组
*/
public static int[] hillSort(int[] intArray) {
int increment = intArray.length / 2; // 初始增量
while (increment > 0) {
intArray = hillSortSubFunction(intArray, increment);
increment /= 2;
}
return intArray;
}
/**
* 根据增量进行插入排序
*
* @param intArray 目标数组
* @param increment 增量
* @return 结果数组
*/
public static int[] hillSortSubFunction(int[] intArray, int increment) {
for (int i = 0; i < increment; i++) {// 根据增量分的组数
for (int j = i + increment; j < intArray.length; j += increment) {
int temp = intArray[j];
int m = j;
while ((m - increment) > -1 && intArray[m - increment] > temp) {
intArray[m] = intArray[m - increment];
m -= increment;
}
intArray[m] = temp;
}
}
return intArray;
}
}
归并排序
归并排序(Merge sort)是建立在归并操作上的一种有效的排序算法。该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。
作为一种典型的分而治之思想的算法应用,归并排序的实现由两种方法:
- 自上而下的递归(所有递归的方法都可以用迭代重写,所以就有了第 2 种方法);
- 自下而上的迭代;
/**
* 归并排序
*/
public class MergeSort {
public static void main(String[] args) {
int[] arr = { 5, 4, 3, 7, 6, 9, 8, 1, 2, 0 };
int[] temp = new int[arr.length];
mergeSort(arr, 0, arr.length-1, temp);
for (int i : arr) {
System.out.print(i);
System.out.print(" ");
}
}
public static void mergeSort(int[] intArray, int start, int end, int[] temp) {
if (start < end) {
int middle = (start + end) / 2;
mergeSort(intArray, start, middle, temp);
mergeSort(intArray, middle + 1, end, temp);
merge(intArray, start, end, temp);
}
}
/**
* 将数组内两部分有序数列合并,合并后的数组放在 intArray[leftStart] ~ intArray[leftStart]中
*
* @param intArray 目标数组
* @param leftStart 左数组的第一个数下标
* @param rightEnd 右数组最后一个元素的下标
* @param temp 大小和intArray一样的临时数组
*/
public static void merge(int[] intArray, int leftStart, int rightEnd, int[] temp) {
int leftEnd = (leftStart + rightEnd) / 2;
// 两标记的开始位置
int i = leftStart, j = leftEnd + 1;
// 临时数组的填充起始位置
int m = leftStart;
// 如果两边还有没排的数,循环
while (i <= leftEnd || j <= rightEnd) {
// 只剩右数组有数
if (i > leftEnd) {
temp[m] = intArray[j];
j++;
}
// 只剩左数组有数
else if (j > rightEnd) {
temp[m] = intArray[i];
i++;
}
// 两数组都有数
else {
if (intArray[i] < intArray[j]) {
temp[m] = intArray[i];
i++;
} else {
temp[m] = intArray[j];
j++;
}
}
m++;
}
// 将合并后的数组赋值到元素组对应的位置
for (int x = leftStart; x <= rightEnd; x++)
intArray[x] = temp[x];
}
}
快速排序
快速排序是由东尼·霍尔所发展的一种排序算法。在平均状况下,排序 n 个项目要 Ο(nlogn) 次比较。在最坏状况下则需要 Ο(n2) 次比较,但这种状况并不常见。事实上,快速排序通常明显比其他 Ο(nlogn) 算法更快,因为它的内部循环(inner loop)可以在大部分的架构上很有效率地被实现出来。快速排序就是快,而且效率高!它是处理大数据最快的排序算法之一了。虽然 Worst Case 的时间复杂度达到了 O(n²),但是人家就是优秀,在大多数情况下都比平均时间复杂度为 O(n logn) 的排序算法表现要更好
(将数组的第一个元素作为基准,放到一个合适的位置,使它左边的所有数小于它,右边所有的数大于它)分别在左右两边的的数组进行括号里的操作,递归下去)
/**
* 快速排序
*/
public class FastSort {
public static void main(String[] args) {
int[] arr = { 5, 4, 3, 7, 6, 9, 8, 1, 2, 0 };
sortByBenchmark(arr, 0, arr.length - 1);
for (int i : arr) {
System.out.print(i);
System.out.print(" ");
}
}
/**
* 将数组指定起始到末尾看作一个数组arr,使arr第一个数到达arr基准位置(左边全小于基准,右边全大于基准)
*
* @param intArray
* @param start 起始地址(包含)
* @param end 终止地址(包含)
* @return 最终基准值的位置(绝对位置 )
*/
public static void sortByBenchmark(int[] intArray, int start, int end) {
if (start < end) {
int benchmark = intArray[start];// 存储基准值
int begin = start;// 排查的起始位置
for (int check = start + 1; check < end + 1; check++) {
if (intArray[check] < benchmark) {
swap(intArray, check, ++begin);
}
}
swap(intArray, start, begin);
sortByBenchmark(intArray, start, begin - 1);
sortByBenchmark(intArray, begin + 1, end);
}
}
/**
* 交换i,j俩个位置的数
*/
public static void swap(int[] intArray, int i, int j) {
int temp = intArray[i];
intArray[i] = intArray[j];
intArray[j] = temp;
}
}
堆排序
(对选择排序的选择方式的一个升级)
堆排序就是将数组按照完全二叉树的进行数组表示,其中分为两类:
- 大顶堆 :堆的父节点总是大于它的子节点
- 小顶堆:堆的父节点总是小于它的子节点
此处还需了解完全二叉树的一个特性,当使用数组表示一个完全二叉树时:
- 左节点对应的数组下标=(其父节点对应的数组下标)* 2 + 1
- 右节点对应的数组下标=(其父节点对应的数组下标)* 2 + 2
此处我们使用大顶堆来举例,其步骤如下:
一.构建初始堆
这部不用使用代码进行构建,因为可以将数组的默认顺序看作初始堆
二.构建大顶堆
将初始堆进行整理,使所有的父节点大于子节点,最终堆顶的元素(数组的一个元素)将是此数组中最大的值
三.将待排数与堆顶元素进行替换
将数组第一个元素与最后一个元素对换位置
四.将被替换的堆顶元素下沉,使其未排序堆重新成为大顶堆
五.重复三,四,两步,直至未排序列长度为1,
/**
* 堆排序
*/
public class HeapSort {
public static void main(String[] args) {
int[] arr = { 5, 4, 3, 7, 6, 9, 8, 1, 2, 0 };
heapSort(arr);
for (int i : arr) {
System.out.print(i);
System.out.print(" ");
}
}
/**
* 堆排序
*
* @param intArray
*/
public static void heapSort(int[] intArray) {
int count = intArray.length;
// 初始化数组(大顶堆)
buildMaxHeap(intArray);
while (count > 1) {
// 将待排数与堆顶元素进行替换
swap(intArray, 0, count - 1);
// 将堆顶元素下沉
sink(intArray, 0, count - 1);
count--;
}
}
/**
* 构建大顶堆
*
* @param intArray 初始堆
*/
public static void buildMaxHeap(int[] intArray) {
// 获取从下往上的第一个父节点
int firstFatherNode = (intArray.length - 2) / 2;
// 从堆底的第一个父节点开始,逐次往上下沉每一个父节点
for (int i = firstFatherNode; i > -1; i--)
sink(intArray, i, intArray.length);
}
/**
* 对特定元素在数组的特定范围内下沉
*
* @param intArray 初始堆
* @param i 目标元素的下标
* @param len 下沉长度范围
*/
public static void sink(int[] intArray, int i, int len) {
// 左右子节点下标
int leftSub = 2 * i + 1;
int rightSub = 2 * i + 2;
// 最大值下标
int maximumSub = i;
// 如果在范围内有左子,且大于目标节点
if (leftSub < len && intArray[leftSub] > intArray[maximumSub]) {
maximumSub = leftSub;
}
if (rightSub < len && intArray[rightSub] > intArray[maximumSub]) {
maximumSub = rightSub;
}
if (maximumSub != i) {
swap(intArray, i, maximumSub);
sink(intArray, maximumSub, len);
}
// 如不满足上述条件,停止下沉
}
/**
* 交换i,j俩个位置的数
*/
public static void swap(int[] intArray, int i, int j) {
int temp = intArray[i];
intArray[i] = intArray[j];
intArray[j] = temp;
}
}
四种常用排序时间复杂度的比较
小编分别对四种数量级大小的数组进行了上述的排序方式,排序时间如下图
可见快速排序还是很牛批的,不过因为归并排序是稳定排序,他们的实力也算是不相上下了
非比较排序
计数排序
由于计数排序不是比较排序,所以它的速度比任何比较排序都快,由于要以数组中最大元素作为大小申请数组内存空间,所以对于数据范围大的数组,此方法会很占用内存
代码示例:
/**
* 非比较排序:计数排序
*/
public class CountSort {
public static void main(String[] args) {
int[] arr = { 5, 4, 3, 7, 6, 9, 8, 1, 2, 0 };
countSort(arr);
for (int i : arr) {
System.out.print(i);
System.out.print(" ");
}
}
/**
* 计数排序
*
* @param arr
*/
public static void countSort(int[] arr) {
int maxValue = findTheMax(arr);
int bucketLen = maxValue + 1;
int[] bucket = new int[bucketLen];//新建一个比最大数多一的一个数组(也成为桶)
for (int value : arr) {
bucket[value]++;//将目标数组的值作为计数数组的下标,使此元素加一
}
int sortedIndex = 0;
for (int j = 0; j < bucketLen; j++) {
while (bucket[j] > 0) {
arr[sortedIndex++] = j;
bucket[j]--;
}
}
}
/**
*返回数组最大值
*/
public static int findTheMax(int[] arr) {
int maxValue = arr[0];
for (int value : arr) {
if (maxValue < value) {
maxValue = value;
}
}
return maxValue;
}
}
基数排序
import java.util.Arrays;
/**
* 非比较排序:基数排序
*/
public class RadixSort {
public static void main(String[] args) {
int[] arr = { 5, 4, 3, 7, 6, 9, 8, 1, 2, 0 };
radixSort(arr);
for (int i : arr) {
System.out.print(i);
System.out.print(" ");
}
}
/**
* 基数排序
*
* @param arr
* @return
*/
public static void radixSort(int[] arr) {
int maxDigit = getMaxDigit(arr);
int mod = 10;
int dev = 1;
// 根据最大数的位数进行基数排序
for (int i = 0; i < maxDigit; i++, dev *= 10, mod *= 10) {
// 考虑负数的情况,这里扩展一倍队列数,其中 [0-9]对应负数,[10-19]对应正数 (bucket + 10)
int[][] counter = new int[mod * 2][0];
for (int j = 0; j < arr.length; j++) {
int bucket = ((arr[j] % mod) / dev) + mod;
counter[bucket] = arrayAppend(counter[bucket], arr[j]);
}
int pos = 0;
for (int[] bucket : counter) {
for (int value : bucket) {
arr[pos++] = value;
}
}
}
}
/**
* 获取数组中最大元素的位数
*/
public static int getMaxDigit(int[] arr) {
// 获取最大元素的值
int maxValue = arr[0];
for (int value : arr) {
if (maxValue < value) {
maxValue = value;
}
}
if (maxValue == 0) {
return 1;
}
// 判断该元素是几位数
int lenght = 0;
for (long temp = maxValue; temp != 0; temp /= 10) {
lenght++;
}
return lenght;
}
/**
* 重新开辟一个比原数组大一的数组,将值添加到末尾
*
* @param arr
* @param value
*/
public static int[] arrayAppend(int[] arr, int value) {
arr = Arrays.copyOf(arr, arr.length + 1);
arr[arr.length - 1] = value;
return arr;
}
}