常用排序算法复习
排序中的对数器
import java.util.Arrays;
public class Main {
public static void main(String[] args) {
int times = 100000;
int maxValue = 1000;
int size = 1000;
for (int i = 0; i < times; i++) {
int[] arr = getRandomArr(size, maxValue);
int[] copyArr = arr.clone(); // 深拷贝一个数组
Arrays.sort(arr);
bubbleSort(copyArr);
if (!isEqualArr(arr, copyArr)) {
System.out.println("fuck!!!");
break;
}
}
}
public static void bubbleSort(int[] arr) {
if (arr == null || arr.length < 2) {
return;
}
int n = arr.length;
for (int j = n - 1; j >= 0; j--) {
for (int i = 0; i < j; i++) {
if (arr[i] > arr[i + 1]) {
swap(arr, i, i + 1);
}
}
}
}
public static void swap(int[] arr, int i, int j) {
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
public static int[] getRandomArr(int size, int maxValue) {
int[] arr = new int[(int)(Math.random() * size)];
for (int i = 0; i < arr.length; i++) {
arr[i] = (int)(Math.random() * maxValue - Math.random() * maxValue);
}
return arr;
}
// 验证两个数组的值是否相同
public static boolean isEqualArr(int[] arr1, int[] arr2) {
if (arr1 == null || arr2 == null) {
return arr1 == null && arr2 == null;
}
if (arr1.length != arr2.length) {
return false;
}
int n = arr1.length;
for (int i = 0; i < n; i++) {
if (arr1[i] != arr2[i]) {
return false;
}
}
return true;
}
}
1、冒泡排序
每次将最大的冒泡到最后一个
public static void bubbleSort(int[] arr) {
if (arr == null || arr.length < 2) {
return;
}
int n = arr.length;
for (int j = n - 1; j >= 0; j--) {
for (int i = 0; i < j; i++) {
if (arr[i] > arr[i + 1]) {
swap(arr, i, i + 1);
}
}
}
}
2、选择排序
每次选择最大的与最后一个交换
public static void selectSort(int[] arr) {
if (arr == null || arr.length < 2) {
return;
}
int n = arr.length;
for (int j = n - 1; j > 0; j--) {
int maxIndex = j;
for (int i = 0; i <= j; i++) {
if (arr[i] > arr[maxIndex]) {
maxIndex = i;
}
}
swap(arr, j, maxIndex);
}
}
public static void swap(int[] arr, int i, int j) {
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
3、插入排序
每到一个结尾就对其及其之前的进行排序
public static void insertSort(int[] arr) {
if (arr == null || arr.length < 2) {
return;
}
int n = arr.length;
for (int j = 1; j < n; j++) {
for (int i = j; i > 0; i--) {
if (arr[i] < arr[i - 1]) {
swap(arr, i - 1, i);
} else {
break;
}
}
}
}
public static void swap(int[] arr, int i, int j) {
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
4、希尔排序
将整个序列通过指定增量gap分成若干段,分别进行插入排序,gap不断缩小,最后为1,排序完成
public static void shellSort(int[] arr) {
if(arr == null || arr.length < 2) {
return;
}
int n = arr.length;
for (int gap = arr.length / 2; gap > 0; gap /= 2) {
// 插入排序
for (int j = gap; j < n; j += gap) {
for (int i = j; i >= gap; i-= gap) {
if (arr[i] < arr[i - gap]) {
swap(arr, i, i - gap);
} else {
break;
}
}
}
}
}
5、归并排序
不断二分,然后合并
public static void mergeSort(int[] arr) {
if (arr == null || arr.length < 2) {
return;
}
merge(arr, 0, arr.length - 1);
}
public static void merge(int[] arr, int l, int r) {
if (l == r) {
return;
}
int mid = l + ((r - l) >> 1);
merge(arr, l, mid);
merge(arr, mid + 1, r);
int[] temp = new int[r - l + 1];
int p1 = l;
int p2 = mid + 1;
int p = 0;
while (p1 <= mid && p2 <= r) {
temp[p++] = arr[p1] < arr[p2] ? arr[p1++] : arr[p2++];
}
while (p1 <= mid) {
temp[p++] = arr[p1++];
}
while (p2 <= r) {
temp[p++] = arr[p2++];
}
for (int i = 0; i < temp.length; i++) {
arr[i + l] = temp[i];
}
}
6、快速排序
不断partition,不断递归即可
public static void fastSort(int[] arr) {
if (arr == null || arr.length < 2) {
return;
}
partition(arr, 0, arr.length - 1);
}
public static void partition(int[] arr, int l, int r) {
if (l >= r) {
return;
}
int randomIndex = l + (int)(Math.random() * (r - l));
swap(arr, r, randomIndex);
int num = arr[r];
int left = l - 1;
int right = r;
int i = l;
while (i < right) {
if (arr[i] < num) {
swap(arr, i++, ++left);
} else if (arr[i] > num) {
swap(arr, i, --right);
} else {
i++;
}
}
swap(arr, r, right);
partition(arr, l, left);
partition(arr, right + 1, r);
}
public static void swap(int[] arr, int i, int j) {
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
7、堆排序
构建大根堆,每次取出堆的根,然后拿堆的最后一个元素替换堆根并调整堆即可
public static void heapSort(int[] arr) {
if (arr == null || arr.length < 2) {
return;
}
int n = arr.length;
// 向上堆化
// for (int i = 0; i < n; i++) {
// heapifyUp(arr, i);
// }
int heapSize = n - 1;
// 向下堆化
for (int i = n - 1; i >= 0; i--) {
heapifyDown(arr, i, heapSize);
}
while (heapSize > 0) {
swap(arr, 0, heapSize--);
heapifyDown(arr, 0, heapSize);
}
}
public static void heapifyUp(int[] arr, int index) {
// 向上堆化,找父节点
while (arr[index] > arr[(index - 1) / 2]) {
swap(arr, index, (index - 1) / 2);
index = (index - 1) / 2;
}
}
public static void heapifyDown(int[] arr, int index, int heapSize) {
// 向下堆化
int leftIndex = 2 * index + 1;
while (leftIndex <= heapSize) {
int largeChildIndex = leftIndex + 1 <= heapSize && arr[leftIndex + 1]
> arr[leftIndex] ? leftIndex + 1 : leftIndex;
int largeIndex = arr[largeChildIndex] > arr[index] ? largeChildIndex : index;
if (largeIndex == index) {
break;
}
swap(arr, index, largeIndex);
index = largeChildIndex;
leftIndex = 2 * index + 1;
}
}
public static void swap(int[] arr, int i, int j) {
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
8、桶排序
8.1 最简单的桶排序
1、将待排序元素划分到不同的桶。先扫描一遍序列求出最大值 maxV 和最小值 minV ,设桶的个数为 k ,则把区间 [minV, maxV] 均匀划分成 k 个区间,每个区间就是一个桶。将序列中的元素分配到各自的桶。
2、对每个桶内的元素进行排序。可以选择任意一种排序算法。
3、将各个桶中的元素合并成一个大的有序序列。
假设数据是均匀分布的,则每个桶的元素平均个数为 n/k 。假设选择用快速排序对每个桶内的元素进行排序,那么每次排序的时间复杂度为 O(n/klog(n/k)) 。总的时间复杂度为 O(n)+O(k)O(n/klog(n/k)) = O(n+nlog(n/k)) = O(n+nlogn-nlogk 。当 k 接近于 n 时,桶排序的时间复杂度就可以金斯认为是 O(n) 的。即桶越多,时间效率就越高,而桶越多,空间就越大。
public static void bucketSort(int[] arr) {
if (arr == null || arr.length < 2) {
return;
}
bucketSortProcess(arr, 10);
}
public static void bucketSortProcess(int[] arr, int k) {
int max = Integer.MIN_VALUE;
int min = Integer.MAX_VALUE;
for (int n : arr) {
max = Math.max(n, max);
min = Math.min(n, min);
}
List<Integer>[] bucketList = new List[k];
for (int i = 0; i < k; i++) {
bucketList[i] = new ArrayList<Integer>();
}
for (int n : arr) {
int index = (k - 1) * (n - min) / (max - min);
bucketList[index].add(n);
}
int index = 0;
for (int i = 0; i < k; i++) {
Collections.sort(bucketList[i]);
for (int num : bucketList[i]) {
arr[index++] = num;
}
}
}
8.2 基数排序
从低到高对每一位进行排序,每一次排序都要保证稳定性
public static int getBit(int num) {
if (num == 0) {
return 1;
}
int ans = 0;
while (num != 0) {
ans++;
num /= 10;
}
return ans;
}
public static int getDigit(int num, int index) {
// 返回一个数字十进制的某一位,从后往前数(从1开始)
index --;
while (index != 0) {
num /= 10;
index--;
}
return num % 10;
}
public static void radixSortInPositive(int[] arr) {
// 对正数进行基数排序
if (arr == null || arr.length < 2) {
return;
}
int mostBit = 1; // 获取数组中的数字最大的有多少位
for (int n : arr) {
mostBit = Math.max(mostBit, getBit(n));
}
int[] temp = new int[arr.length]; // 辅助数组
for (int i = 1; i <= mostBit; i++) {
int[] countArr = new int[10];
for (int n : arr) {
countArr[getDigit(n, i)]++;
}
for (int j = 1; j < 10; j++) {
countArr[j] += countArr[j - 1];
}
// 此时必须要保证排序的稳定性
// 因为百位数的排序过程中要保留十位数的排序结果
for (int j = arr.length - 1; j >= 0; j--) {
temp[--countArr[getDigit(arr[j], i)]] = arr[j];
}
for (int j = 0; j < temp.length; j++) {
arr[j] = temp[j];
}
}
}
public static void radixSortInNegative(int[] arr) {
// 对负数进行基数排序
if (arr == null || arr.length < 2) {
return;
}
int mostBit = 1; // 获取数组中的数字最大的有多少位
for (int n : arr) {
mostBit = Math.max(mostBit, getBit(n));
}
int[] temp = new int[arr.length]; // 辅助数组
for (int i = 1; i <= mostBit; i++) {
int[] countArr = new int[10];
for (int n : arr) {
countArr[-getDigit(n, i)]++;
}
for (int j = 1; j < 10; j++) {
countArr[j] += countArr[j - 1];
}
for (int j = arr.length - 1; j >= 0; j--) {
temp[--countArr[-getDigit(arr[j], i)]] = arr[j];
}
for (int j = 0; j < temp.length; j++) {
arr[j] = temp[j];
}
}
for (int i = 0; i < arr.length; i++) {
temp[i] = arr[arr.length - 1 - i];
}
for (int i = 0; i < arr.length; i++) {
arr[i] = temp[i];
}
}
public static void radixSort(int[] arr) {
if (arr == null || arr.length < 2) {
return;
}
int positiveNum = 0;
int negativeNum = 0;
for (int n : arr) {
if (n >= 0) {
positiveNum++;
} else {
negativeNum++;
}
}
int[] positiveArr = new int[positiveNum];
int[] negativeArr = new int[negativeNum];
positiveNum = 0;
negativeNum = 0;
for (int n : arr) {
if (n >= 0) {
positiveArr[positiveNum++] = n;
} else {
negativeArr[negativeNum++] = n;
}
}
radixSortInPositive(positiveArr);
radixSortInNegative(negativeArr);
for (int i = 0; i < negativeNum; i++) {
arr[i] = negativeArr[i];
}
for (int i = 0; i < positiveNum; i++) {
arr[i + negativeNum] = positiveArr[i];
}
}
8.3 计数排序
使用一个长度为max-min+1的数组,每一个元素都是计数器,然后统计原始数组中各个数字的个数(计数排序)
public static void countSort(int[] arr) {
if (arr == null || arr.length < 2) {
return;
}
int max = Integer.MIN_VALUE;
int min = Integer.MAX_VALUE;
for (int n : arr) {
max = Math.max(max, n);
min = Math.min(min, n);
}
int N = max - min + 1;
int[] countArr = new int[N];
for (int n : arr) {
countArr[n - min]++;
}
int j = 0;
for (int i = 0; i < N; i++) {
while (countArr[i] != 0) {
arr[j++] = i + min;
countArr[i]--;
}
}
}
桶排序总结
- 计数排序就是当桶的个数为max-min+1的桶排序
- 基数排序是对每一位进行桶排序
排序的稳定性
排序前后两个相等的数相对位置不变,则这种排序是稳定的,反之不稳定
各种排序对比
排序名称 | 时间复杂度 | 空间复杂度 | 稳定性 |
---|---|---|---|
冒泡排序 | O(n^2) | O(1) | 稳定 |
选择排序 | O(n^2) | O(1) | 稳定 |
插入排序 | O(n)~O(n^2) | O(1) | 稳定 |
希尔排序 | O(nlog(n)) | O(K) | 稳定 |
归并排序 | O(nlog(n)) | O(n) | 稳定 |
快速排序 | O(nlog(n)) | O(log(n))~O(n) | 不稳定 |
堆排序 | O(nlog(n)) | O(1) | 不稳定 |
桶排序 | O(nlog(n/K)) | O(n) | 稳定 |
基数排序 | O(n) | O(K) | 稳定 |
计数排序 | O(Kn) | O(n+K) | 稳定 |