1. 冒泡排序
核心思想
每轮冒泡不断地比较相邻的两个元素,如果逆序,则交换它们的位置。
时间复杂度
最好情况:O(n) --> 数组有序
最坏情况:O(n2) ---> 数组完全逆序
平均:O(n2)
空间复杂度
O(1)
代码实现
public class BubbleSort {
private static void bubble(int[] nums){
int j = nums.length - 1;
do {
int x = 0;
for (int i = 0; i < j; i++) {
if (nums[i] > nums[i + 1]) {
int temp = nums[i];
nums[i] = nums[i + 1];
nums[i + 1] = temp;
x = i;
}
}
j = x;
} while (j != 0);
}
public static void main(String[] args) {
int[] a = new int[]{7,6,5,4,3,2,1};
bubble(a);
Arrays.stream(a).forEach(System.out::print);
}
}
2. 选择排序
核心思想
每一轮选择,找出最大(最小)的元素,并把它交换到合适的位置。
时间复杂度
最好情况:O(n2)
最坏情况:O(n2)
平均:O(n2)
空间复杂度
O(1)
代码实现
public class SelectionSort {
private static void selection(int[] nums){
// 1.选择轮数:nums.length - 1
// 2.交换的索引位置(right),初始为nums.length - 1,每次递减
for (int right = nums.length - 1; right > 0; right--) {
int max = right;
for (int i = 0; i < right; i++) {
if (nums[i] > nums[max]){
max = i;
}
}
if (max != right){
swap(nums, max, right);
}
}
}
private static void swap(int[] a, int i, int j){
int temp = a[i];
a[i] = a[j];
a[j] = temp;
}
public static void main(String[] args) {
int[] nums = {7, 6, 5, 4, 3, 2, 1};
selection(nums);
Arrays.stream(nums).forEach(System.out::print);
}
}
3. 堆排序
核心思想
建立大顶堆
每次将堆顶元素(最大值)交换到末尾,调整堆顶元素,让它重新符合大顶堆特性
时间复杂度
最好情况:O(nlogn)
最坏情况:O(nlogn)
平均:O(nlogn)
空间复杂度
O(1)
代码实现
public class HeapSort {
public static void sort(int[] nums){
beautify(nums,nums.length);
for (int right = nums.length -1; right > 0; right--) {
swap(nums,right,0);
down(nums,0,right);
}
}
//Floyd建堆 O(n)
public static void beautify(int[] a, int size){
for (int i = size / 2 - 1; i >= 0; i--) {
down(a, i, size);
}
}
//下潜
public static void down(int[] a, int parent, int size){
while(true){
int left = parent * 2 + 1;
int right = left + 1;
int max = parent;
if (left < size && a[left] > a[max]){
max = left;
}
if (right < size && a[right] > a[max]){
max = right;
}
if (max == parent){
break;
}
swap(a,max,parent);
parent = max;
}
}
//交换
private static void swap(int[] a, int max, int parent) {
int temp = a[max];
a[max] = a[parent];
a[parent] = temp;
}
public static void main(String[] args) {
int[] a = new int[]{2,3,1,7,6,4,5};
sort(a);
Arrays.stream(a).forEach(System.out::print);
}
}
4. 插入排序
核心思想
将数组a分为两部分[0, low-1], [low, a.length-1],左边是已排序部分,右边是未排序部分
每次从未排序区域取出low位置元素,插入到已排序区域
时间复杂度
最好情况:O(n)
最坏情况:O(n2)
平均:O(n2)
空间复杂度
O(1)
代码实现
public class InsertionSort {
public static void sort(int[] nums){
for (int low = 1; low < nums.length - 1; low++) {
int t = nums[low];
int i = low - 1;
// 自右向左找插入位置,如果比待插入元素大,则不断右移,空出插入位置
while (i >= 0 && nums[i] > t){
nums[i+1] = nums[i];
i--;
}
// 找到插入位置
if (i != low - 1){
nums[i+1] = t;
}
}
}
public static void main(String[] args) {
int[] a = new int[]{2,4,5,6,3,1,7};
sort(a);
Arrays.stream(a).forEach(System.out::print);
}
}
5. 希尔排序
核心思想
分组实现插入,每组元素间隙称为gap
每轮排序后gap逐渐变小,直到gap为1完成排序
时间复杂度
最好情况:O(nlogn)
最坏情况:O(n2)
平均:O(nlogn)
空间复杂度
O(1)
代码实现
public class ShellSort {
public static void sort(int[] nums){
for (int gap = nums.length >> 1; gap >= 1; gap /= 2) {
for (int low = gap; low < nums.length; low++) {
int t = nums[low];
int i = low - gap;
while (i >= 0 && nums[i] > t){
nums[i+gap] = nums[i];
i -= gap;
}
if (i + gap != low){
nums[i+gap] = t;
}
}
}
}
public static void main(String[] args) {
int[] a = new int[]{3,2,1,5,6,7,9};
sort(a);
Arrays.stream(a).forEach(System.out::print);
}
}
6. 归并排序
核心思想
分-每次从中间切一刀,处理的数据少一半
治-当数据仅剩一个时,可以认为有序
合-两个有序的结果,可以进行合并排序
时间复杂度
最好情况:O(nlogn)
最坏情况:O(nlogn)
平均:O(nlogn)
空间复杂度
O(n)
代码实现
public class MergeSort {
public static void sort(int[] a1){
int[] a2 = new int[a1.length];
split(a1,0,a1.length - 1,a2);
}
public static void split(int[] a1, int left, int right, int[] a2){
// 治
if (left == right){
return;
}
// 分
int m = (left + right) >>> 1;
split(a1,left,m,a2);
split(a1,m+1,right,a2);
// 合
merge(a1,left,m,m+1,right,a2);
System.arraycopy(a2, left, a1, left,right - left + 1);
}
// 合并两个有序数组
public static void merge(int[]a1, int i, int iEnd, int j, int jEnd, int []a2){
int k = i;
while(i <= iEnd && j <= jEnd){
if (a1[i] < a1[j]){
a2[k] = a1[i];
i++;
}else {
a2[k] = a1[j];
j++;
}
k++;
}
if (i > iEnd){
System.arraycopy(a1, j, a2, k,jEnd - j + 1);
}
if (j > jEnd){
System.arraycopy(a1, i, a2, k,iEnd - i + 1);
}
}
public static void main(String[] args) {
int[] a1 = new int[]{9,3,7,2,8,5,1,4};
sort(a1);
Arrays.stream(a1).forEach(System.out::print);
}
}
public static void sort(int[] a1){
int n = a1.length;
int[] a2 = new int[n];
// width 代表有序区间的宽度,取值依次为 1 2 4 ...
for (int width = 1; width < n; width *= 2) {
// [left,right] 分别代表待合并区间的左右宽度
for (int left = 0; left < n; left += 2 * width) {
int right = left + 2 * width - 1;
int m = left + width - 1;
merge(a1,left,m,m+1,right,a2);
}
System.arraycopy(a2,0,a1,0,n);
}
}
public static void main(String[] args) {
int[] a1 = new int[]{9,3,7,2,8,5,1,4};
sort(a1);
Arrays.stream(a1).forEach(System.out::print);
}
7. 快速排序
核心思想
每轮找到一个基准点元素,把比它小的放到它左边,比它大的放到它右边,这称为分区。
- 选择最右元素作为基准点元素
- j 指针负责找比基准点小的元素,一旦找到则与 i 交换位置
- i 指针指向大于基准点元素的左边界,也是每次交换的目标索引
- 最后基准点与 i 交换,i 即为分区位置
时间复杂度
最好情况:O(nlogn)
最坏情况:O(nlogn)
平均:O(nlogn)
空间复杂度
O(1)
代码实现
public class QuickSort {
public static void quick(int[] a, int left, int right){
if (left >= right){
return;
}
// p 代表基准点元素索引
int p = partition(a, left, right);
quick(a,left, p - 1);
quick(a,p + 1, right);
}
public static int partition(int[] a, int left, int right){
// pv 代表基准点元素值
int pv = a[right];
int i = left;
int j = left;
while(j < right){
if (a[j] < pv){ // 找到比基准点小的元素
if (i != j){
swap(a,i,j);
}
i++;
}
j++;
}
swap(a,i,right);
return i;
}
public static void swap(int[] a, int i, int j){
int temp = a[i];
a[i] = a[j];
a[j] = temp;
}
public static void main(String[] args) {
int[] a = new int[]{9,3,7,2,8,5,1,4};
quick(a,0,a.length-1);
Arrays.stream(a).forEach(System.out::print);
}
}
public static int partition2(int[] a, int left, int right){
// 随机元素作为基准点
int idx = ThreadLocalRandom.current().nextInt(right - left + 1) + left;
swap(a,left,idx);
// i 从左到右找比基准点元素大的,j 从右到左找比基准点元素小的
int pv = a[left];
int i = left;
int j = right;
while(i < j){
while (i < j && a[j] > pv){
j--;
}
while (i < j && a[i] <= pv){
i++;
}
swap(a, i, j);
}
swap(a, left, i);
return i;
}
public static int partition3(int[] a, int left, int right){
int idx = ThreadLocalRandom.current().nextInt(right - left + 1) + left;
swap(a, left, idx);
int pv = a[left];
int i = left + 1;
int j = right;
while(i <= j){
while(i <= j && a[i] < pv){
i++;
}
while (i <= j && a[j] > pv){
j--;
}
if (i <= j){
swap(a, i, j);
i++;
j--;
}
}
swap(a, j, left);
return j;
}
8. 计数排序
核心思想
- 找到最大值,创建一个大小为 最大值 + 1 的 count 数组
- count 数组的索引对应原始数组的元素,用来统计该元素的出现次数
- 遍历 count 数组,根据 count 数组的索引(即原始数组的元素) 以及出现次数,生成排序后的内容
count 数组的索引是:已排序好的
前提:待排序 >= 0,且最大值不能太大
时间复杂度
最好情况:O(n)
最坏情况:O(n)
平均:O(n)
空间复杂度
O(n)
代码实现
public class CountingSort {
// 前提:待排序 >= 0,且最大值不能太大
public static int[] sort(int[] arr){
int max = arr[0];
for (int i = 1; i < arr.length; i++) {
if (arr[i] > max){
max = arr[i];
}
}
int[] count = new int[max + 1];
for (int num : arr) {
count[num]++; // 原始数组元素 = count数组索引
}
int k = 0;
for (int i = 0; i < count.length; i++) {
while (count[i] > 0){
arr[k++] = i; // i 代表原始数组元素 count[i] 代表元素出现次数
count[i]--;
}
}
return arr;
}
// 优化:处理元素 < 0 的情况
public static int[] sortPositive(int[] arr){
int max = arr[0];
int min = arr[0];
for (int i = 1; i < arr.length; i++) {
if (arr[i] > max){
max = arr[i];
}
if (arr[i] < min){
min = arr[i];
}
}
int[] count = new int[max - min + 1];
for (int num : arr) {
count[num - min]++; // 原始数组元素 - 最小值 = count 索引
}
int k = 0;
for (int i = 0; i < count.length; i++) {
while(count[i] > 0){
arr[k++] = i + min; // i + min 代表原始数组元素 count[i] 代表元素出现次数
count[i]--;
}
}
return arr;
}
public static void main(String[] args) {
int[] arr = new int[]{2,3,1,5,7,-3};
sortPositive(arr);
System.out.println(Arrays.toString(arr));
}
}
9. 基数排序
核心思想
将待排序的数组,按照某个关键字的各位数字进行排序。基数排序可以说是一种桶排序的扩展,它利用了桶排序的思想,将待排序的数组分成多个桶,每个桶存放相同位数的数字。
时间复杂度
最好情况:O(kn)
最坏情况:O(kn)
平均:O(kn)
k是待排序数组中最大的数的位数
空间复杂度
O(n)
代码实现
public class RadixSort {
public static void sort(int[] nums) {
int max = nums[0];
for (int i = 1; i < nums.length; i++) {
max = Math.max(max, nums[i]);
}
int m = 1;
ArrayList<Integer>[] buckets = new ArrayList[10];
for (int i = 0; i < 10; i++) {
buckets[i] = new ArrayList<>();
}
while (m <= max) {
for (int num : nums) {
buckets[num / m % 10].add(num);
}
int k = 0;
for (ArrayList<Integer> bucket : buckets) {
for (Integer i : bucket) {
nums[k++] = i;
}
bucket.clear();
}
m *= 10;
}
}
public static void main(String[] args) {
int[] nums = {1000, 2, 13, 5, 19};
RadixSort.sort(nums);
System.out.println(Arrays.toString(nums));
}
}
10. 桶排序
核心思想
桶排序(Bucket Sort)是一种分布式排序算法,它将待排序的元素分到不同的桶中,对每个桶中的元素进行排序,然后依次将每个桶中的元素取出,即可得到有序的结果。桶排序的核心思想是将待排序的元素映射到不同的桶中,使得每个桶中的元素范围尽量小且有序。
时间复杂度
最好情况:O(n+k)
最坏情况:O(n+k)
平均:O(n+k)
k是桶的数量
空间复杂度
O(n+k)
代码实现
public class BucketSort {
public static void sort(int[] nums,int bucketSize){
if (nums.length == 0){
return;
}
// 找到数组中的最大值和最小值
int max = nums[0];
int min = nums[0];
for (int i = 1; i < nums.length; i++) {
max = Math.max(max,nums[i]);
min = Math.min(min,nums[i]);
}
// 初始化桶
ArrayList<Integer>[] buckets = new ArrayList[(max - min)/bucketSize + 1];
for (int i = 0; i < buckets.length; i++) {
buckets[i] = new ArrayList<>();
}
// 将元素分配到桶中
for (int num : nums) {
buckets[(num-min)/bucketSize].add(num);
}
// 对每个桶中的元素进行排序,并将排序后的元素放回原数组
int k = 0;
for (ArrayList<Integer> bucket : buckets) {
Collections.sort(bucket);
for (Integer v : bucket) {
nums[k++] = v;
}
}
}
public static void main(String[] args) {
int[] arr = {29, 10, 14, 37, 13};
BucketSort.sort(arr, 2);
System.out.println(Arrays.toString(arr));
}
}