先取一个随机数,然后和数组的最后一个数交换
进行partition
过程,也就是比数组最后一个数小的放在数组左边,大的放在右边,相等的在数组中间,最后把数组的最后一个数也要放到中间位置,然后返回相等的那一批数的最左索引和最右索引。
递归前两个过程
O (N * logN)
public class QuickSort {
private static void quickSort(int[] arr) {
if (arr == null || arr.length < 2) {
return;
}
process(arr, 0, arr.length - 1);
}
private static void process(int[] arr, int left, int right) {
if (left >= right) {
return;
}
swap(arr, left + (int)(Math.random() * (right - left + 1)), right);
int[] partition = partition(arr, left, right);
process(arr, left, partition[0] - 1);
process(arr, partition[1] + 1, right);
}
private static int[] partition(int[] arr, int left, int right) {
int index = left;
int small = left - 1;
int big = right;
while (index < big) {
if (arr[index] == arr[right]) {
index++;
} else if (arr[index] < arr[right]) {
swap(arr, index++, ++small);
}else {
swap(arr, index, --big);
}
}
swap(arr, right, big);
return new int[] {++small, big};
}
private static void swap(int[] arr, int i, int j) {
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
public static void main(String[] args) {
int[] arr = {1,2,8,9,5};
quickSort(arr);
for (int i : arr) {
System.out.print(i + " ");
}
}
}
heapify
过程,也就是把每一个值都要和子节点比较大小,把这个节点为顶的树变成堆结构heapify
操作,把剩下元素的要恢复堆结构O (N * logN)
public class HeapSort {
public static void heapSort(int[] arr) {
if (arr == null || arr.length < 2) {
return;
}
int heapSize = arr.length;
for (int i = arr.length - 1; i >= 0; i--) {
heapify(arr, i, heapSize);
}
swap(arr, 0, --heapSize);
while (heapSize > 0) {
heapify(arr, 0, heapSize);
swap(arr, 0, --heapSize);
}
}
private static void heapify(int[] arr, int index, int heapSize) {
int left = index * 2 + 1;
while (left < heapSize) {
int largest = left + 1 < heapSize ? (arr[left] < arr[left + 1] ? left + 1 : left) : left;
largest = arr[index] < arr[largest] ? largest : index;
if (largest == index) {
return;
}
swap(arr, largest, index);
index = largest;
left = index * 2 + 1;
}
}
private static void swap(int[] arr, int i, int j) {
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
public static void main(String[] args) {
int[] arr = {1,9,7,3,6,4,8,2,5};
heapSort(arr);
for (int i : arr) {
System.out.print(i + " ");
}
}
}
不基于比较的排序算法,,分为两类,但是都有约束条件
给的数越多,代价越大。一旦要升级的话,要付出的代价更加显而易见。
要排序的最大的数越大,计数排序需要的空间就越多,要排序比如{9,1,96656412},那么此时就需要辅助数组的长度就要是96656413了,很明显性价比不高,就需要用基数排序,基数排序只用两个辅助数组,而且一个计数器数组长度长度固定为10,一个辅助数组长度和要排序的数组长度相同,浪费的空间小。但是如果要排序的数中最大数越小,那么此时就可以用计数排序。
O (N)
public class CountSort {
public static void countSort(int[] arr) {
if (arr == null || arr.length < 2) {
return;
}
int max = arr[0];
for (int i = 1; i < arr.length; i++) {
max = Math.max(max, arr[i]);
}
int[] bucket = new int[max + 1];
for (int a : arr) {
bucket[a]++;
}
int index = 0;
for (int i = 0; i < bucket.length; i++) {
while (bucket[i]-- > 0) {
arr[index++] = i;
}
}
}
// 写比较器验证
public static void comparator(int[] arr) {
if (arr == null || arr.length < 2) {
return;
}
Arrays.sort(arr);
}
public static int[] generateRandomArr(int arrLen) {
int[] ans = new int[arrLen];
for (int i = 0; i < arrLen; i++) {
int num = (int) (Math.random() * 1000 + 1);
ans[i] = num;
}
return ans;
}
public static void main(String[] args) {
int arrLength = 1000;
int testTimes = 1000000;
for (int i = 0; i < testTimes; i++) {
int arrLen = (int) (Math.random() * arrLength + 1);
int[] arr1 = generateRandomArr(arrLen);
int[] arr2 = new int[arr1.length];
for (int j = 0; j < arr1.length; j++) {
arr2[j] = arr1[j];
}
countSort(arr1);
comparator(arr2);
for (int j = 0; j < arr1.length; j++) {
if (arr1[j] != arr2[j]) {
System.out.println("Oops!");
}
}
}
System.out.println("Finish!");
}
}
算法步骤:
给定数组的最大数有几位就遍历几遍(最外层的遍历),先从个位数字开始遍历
目的是从个位数字开始,先把数组中的每个数的个位数字排序,之后再排十位,以此类推。
第一次遍历
遍历每个数组,遍历的过程中,只看现在遍历的数的个位数字,也就是不管数组有多大,每次看的数字只有[0,9]
定义一个计数器数组,每个数字出现一个,计数器数组的与这个数字相等的索引上的值加一
第二次遍历
第三次遍历
第四次遍历
O(N)
public class RadixSort {
public static void radixSort(int[] arr) {
if (arr == null || arr.length < 2) {
return;
}
radixSort(arr, 0, arr.length - 1, maxBits(arr));
}
// 求数组中最大的数有几位:1000 ---> 4位
private static int maxBits(int[] arr) {
int max = arr[0];
for (int i = 1; i < arr.length; i++) {
max = Math.max(max, arr[i]);
}
int ans = 0;
while (max != 0) {
max /= 10;
ans++;
}
return ans;
}
// 基数排序的实现
public static void radixSort(int[] arr, int left, int right, int digit) {
final int radix = 10;
int[] help = new int[right - left + 1];
for (int d = 1; d <= digit; d++) {
int[] count = new int[radix];
int i = 0;
int j = 0;
for (i = left; i <= right; i++) {
j= getDigit(arr[i], d);
count[j]++;
}
for (i = 1; i < radix; i++) {
count[i] += count[i - 1];
}
for (i = right; i >= left; i--) {
j = getDigit(arr[i], d);
help[count[j] - 1] = arr[i];
count[j]--;
}
for (i = left, j = 0; i <= right; i++, j++) {
arr[i] = help[j];
}
}
}
// num这个数的digit位的数字:(123,1) ---> 3
private static int getDigit(int num, int digit) {
return ((num / (int) Math.pow(10, digit - 1)) % 10);
}
// 对数器测试
public static void comparator(int[] arr) {
if (arr == null || arr.length < 2) {
return;
}
Arrays.sort(arr);
}
public static int generateRandomNum(int num) {
return (int) (Math.random() * num + 1);
}
public static int[] generateRandomArr(int arrLen, int num) {
int length = (int) (Math.random() * arrLen + 1);
int[] ans = new int[length];
for (int i = 0; i < length; i++) {
ans[i] = generateRandomNum(num);
}
return ans;
}
public static void main(String[] args) {
int num = 1000;
int arrLen = 100;
int testTimes = 1000000;
for (int i = 0; i < testTimes; i++) {
int[] arr1 = generateRandomArr(arrLen, num);
int[] arr2 = new int[arr1.length];
for (int j = 0; j < arr1.length; j++) {
arr2[j] = arr1[j];
}
radixSort(arr1);
comparator(arr2);
for (int j = 0; j < arr1.length; j++) {
if (arr2[j] != arr1[j]) {
System.out.println("Oops!");
}
}
}
System.out.println("Finish!");
}
}
O(N*logN)
O(N*logN)
,额外空间复杂度低于O(N)
且稳定的基于比较的排序是不存在的O(1)
,“归并排序内部缓存法”,但是将变得不稳定。(还不如用堆排)O(N*2)
。(不如用插入排序)O(N)
,额外空间复杂度O(1)
。O(N*logN)
和O(N^2)
排序各自的优势:小样本量直接插入排序