八大排序算法解析及Java实现
1、冒泡排序
时间复杂度:
平均时间 | 最好情况 | 最坏情况 |
---|---|---|
O(n^2) | O(n) | O(n^2) |
空间复杂度:
O(1)
两层循环:
第一层循环从第一个元素到倒数第二个元素,因为每次比较是前一个元素和后一个元素进行比较,
所以在第一层循环,最后一个元素会和前一个元素进行比较,所以就没必要循环到最后一个元素。
第二层循环从0到 length-i-1,也就是还没有比较,此时是乱序的元素。
改良算法:
该冒泡排序为改良的冒泡排序,添加了监视哨 flag,定义初始值为 true,在一层循环的比较中,如果发生了交换,则改为 false,如果没有发生交换,flag 为 true,说明序列有序,就不需要进行循环了,则直接退出,节省了时间。
Java 实现
public class BubbleSort {
public static void bubbleSort(int arrays[]) {
for (int i = 0; i < arrays.length - 1; i++) {
boolean flag = true;
for (int j = 0; j < arrays.length - 1 - i; j++) {
if (arrays[j] > arrays[j + 1]) {
arrays[j] = arrays[j] + arrays[j + 1];
arrays[j + 1] = arrays[j] - arrays[j + 1];
arrays[j] = arrays[j] - arrays[j + 1];
flag = false;
}
}
if (flag) {
break;
}
}
}
public static void main(String[] args) {
int[] arrays = new int[]{5, 4, 3, 2, 1};
bubbleSort(arrays);
for (int array : arrays) {
System.out.print(array + " ");
}
}
}
2、插入排序
时间复杂度:
平均时间 | 最好情况 | 最坏情况 |
---|---|---|
O(n^2) | O(n) | O(n^2) |
空间复杂度:
O(1)
具体思路:
第一层循环从 1 开始,到数组的长度,因为默认第一个元素(下标0)有序,
定义 j 为 i-1,即为待插入的位置,默认有序数组的最后一个位置,
key 为需要新插入的元素,为外层循环 i 下标的元素,
第二层循环,当下标在大于或等于0的范围内则继续,该例子为升序排列,
如果当前的数大于待插入的数 key,循环就继续,元素就到往后移动一位,
最后一步,把待插入的数 key插入到待插入的位置。为什么是 j+1 的位置,因为
在 while 里面,当把 j 位置上的数移到后边时,j 又减了1,如果这个时候循环条件
不满足退出了,理应该是插入 j 的位置,所以需要加回 1。
Java 实现
public class InsertSort {
public static void insertSort(int arrays[]) {
for (int i = 1; i < arrays.length; i++) {
int key = arrays[i];
int j = i - 1;
while (j >= 0 && arrays[j] > key) {
arrays[j + 1] = arrays[j];
j--;
}
arrays[j + 1] = key;
}
}
public static void main(String[] args) {
int[] arrays = new int[]{4, 2, 1, 3, 5};
insertSort(arrays);
for (int array : arrays) {
System.out.print(array + " ");
}
}
}
3、希尔排序
时间复杂度:
平均时间 | 最好情况 | 最坏情况 |
---|---|---|
O(nlogn) | O(nlog^2n) | O(nlog^2n) |
空间复杂度:
O(1)
具体思路:
希尔排序是插入排序的改进,运用了分治的思想,定义了一个增量数组 d,
根据增量的个数,进行 k 趟排序,增量值为从大到小,优先比较较远距离
的数,减少交换的次数。
Java 实现
public class ShellSort {
public static void shellSort(int[] arrays, int d[]) {
for (int k = 0; k < d.length; k++) {
int dk = d[k];
for (int i = dk; i < arrays.length; i++) {
int key = arrays[i];
int j = i - dk;
while (j >= 0 && arrays[j] > key) {
arrays[j + dk] = arrays[j];
j -= dk;
}
arrays[j + dk] = key;
}
}
}
public static void main(String[] args) {
int[] arrays = new int[]{5, 4, 3, 2, 1};
shellSort(arrays, new int[]{3, 2, 1});
for (int array : arrays) {
System.out.print(array + " ");
}
}
}
4、快速排序
时间复杂度:
平均时间 | 最好情况 | 最坏情况 |
---|---|---|
O(nlogn) | O(nlogn) | O(n^2) |
空间复杂度:
O(1)
具体思路:
1、定义两个哨兵,一个准基数,一个哨兵从右边找比准基数小的,
另一个哨兵从左边找比准基数大的,如果找到了就交换位置。
2、当两个哨兵相遇后,就和准基数交换位置,此时,准基数左边的
数都比准基数小,右边的数都比准基数大,再递归对左边的数和右边
的数进行相同的排序方式。
推荐一篇博文:https://blog.csdn.net/shujuelin/article/details/82423852
Java 实现
public class QuickSort {
public static void quickSort(int[] arrays, int low, int high) {
if (low > high)
return;
//i,j 是哨兵,t 是准基数
int i = low, j = high, t = arrays[low];
while (i < j) {
//从右边找一个比基准数小的数
while (arrays[j] >= t && i < j) {
j--;
}
while (arrays[i] <= t && i < j) {
i++;
}
//如果找到了就交换
if (i < j) {
arrays[i] = arrays[i] + arrays[j];
arrays[j] = arrays[i] - arrays[j];
arrays[i] = arrays[i] - arrays[j];
}
//两个哨兵相遇后,与基准数交换
arrays[low] = arrays[i];
arrays[i] = t;
//继续对前一部分和后一部分排序
quickSort(arrays, low, i - 1);
quickSort(arrays, i + 1, high);
}
}
public static void main(String[] args) {
int[] arr = {10, 4, 7, 62, 3, 2, 1, 8, 9, 19};
quickSort(arr, 0, arr.length - 1);
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i] + " ");
}
}
}
5、堆排序
时间复杂度:
平均时间 | 最好情况 | 最坏情况 |
---|---|---|
O(nlogn) | O(nlogn) | O(nlogn) |
空间复杂度:
O(1)
具体思路:
1、首先是建堆,从数组中间的下标开始,建立一个大顶堆,
在建立的过程中,i 依次递减,在起始下标为 1 的前提下,
2*i 为左节点,2*i+1为右节点,然后比较当前节点(根节点),
左节点,右节点的大小,选出最大的,和根节点进行交换,
当进行交换后,可能下层的子树不满足最大堆的性质,则需要
递归调整大小。
2、每次调整为最大堆后,把根节点和最后一个节点交换位置,
然后排除根节点,然后调整剩下的节点,以满足最大堆性质,
重复这个过程,直到排序完成。
Java 实现
public class HeapSort {
//构建一个最大堆
public static void build_max_heap(int array[], int length) {
for (int i = array.length / 2; i > 0; i--) {
max_heapify(array, i, length);
}
}
//调整最大堆
public static void max_heapify(int arrays[], int i, int length) {
//
int l = i * 2;//左节点,数组初始下标为 1,为 0 时为2*i+1
int r = i * 2 + 1;
int largest = -1;
if (l < length && arrays[l] > arrays[i]) {
largest = l;
} else {
largest = i;
}
if (r < length && arrays[r] > arrays[largest]) {
largest = r;
}
if (largest != i) {
//交换当前节点a[i]和最大的节点a[largest]
arrays[i] = arrays[i] + arrays[largest];
arrays[largest] = arrays[i] - arrays[largest];
arrays[i] = arrays[i] - arrays[largest];
max_heapify(arrays, largest, length);
}
}
//堆排序
//最大堆,每次调整,最大元素总在a[1]中,与a[n]互换,然后排除a[n]再调整
//在剩余节点中,由于交换并排序了原来为最大节点的根,可能会违背最大堆的性质
//为了维护最大堆,则调用max_heapify(array,1,i)在a[1..i]上构造一个最大堆
//每一次取出最大元素,绕后调整最大堆
public static void sort_heap(int array[]) {
build_max_heap(array, array.length);
int i = array.length - 1;
while (i > 1) {
array[1] = array[1] + array[i];
array[i] = array[1] - array[i];
array[1] = array[1] - array[i];
i--;
max_heapify(array, 1, i);
}
}
public static void main(String[] args) {
int nums[] = {0, 5, 3, 17, 10, 84, 19, 6, 22, 9, 35};
sort_heap(nums);
for (int i : nums)
System.out.print(i + " ");
}
}
6、归并排序
时间复杂度:
平均时间 | 最好情况 | 最坏情况 |
---|---|---|
O(nlogn) | O((nlogn) | O((nlogn) |
空间复杂度:
O(n)
基本思路:
1、首先实现二路归并的代码,定义i,j两个指针,一个指针从左边的第一个下标开始,
另一个指针从右边的第一个下标开始移动,再定义一个临时数组,用来复制有序的序列,
2、使用一层循环来复制数组,依次比较 i 和 j 下标所对应的数,把较小的数先复制到临时
数组里面。最后在使用两个循环判断左右两组数还有没有没有复制完成的数,如果还有没复制
的数,再全部复制到临时数组里面。
3、最后把临时数组复制到原数组里面。
4、对数组进行递归排序,依次对左边的数和右边的数进行递归,当序列只有一个元素时,
表示递归结束,进行回调。
6、最后把各个有序的子序列合并成整个有序的序列。
Java 实现
public class MergeSort {
public static void merge(int[] arrays, int p, int q, int r) {
int[] temp = new int[arrays.length];
int i = p, j = q + 1, k = p;
while (i <= q && j <= r) {
if (arrays[i] <= arrays[j]) {
temp[k++] = arrays[i++];
} else {
temp[k++] = arrays[j++];
}
}
while (i <= q)
temp[k++] = arrays[i++];
while (j <= r)
temp[k++] = arrays[j++];
//将temp从索引p位置开始,赋值到arrays索引p的位置,赋值r-p+1个数
System.arraycopy(temp,p,arrays,p,r-p+1);
}
public static void mergeSort(int[] arrays, int p, int r) {
if (p < r) {//当子序列只有一个元素是递归结束
int q = (p + r) / 2;
mergeSort(arrays, p, q);
mergeSort(arrays, q + 1, r);
merge(arrays, p, q, r);
}
}
public static void main(String[] args) {
int[] arrays = new int[]{5, 2, 3, 4, 1};
mergeSort(arrays, 0, arrays.length - 1);
for (int array : arrays) {
System.out.print(array + " ");
}
}
}
7、选择排序
时间复杂度:
平均时间 | 最好情况 | 最坏情况 |
---|---|---|
O(n^2) | O(n^2) | O(n^2) |
空间复杂度:
O(1)
基本思路:
1、第一层循环从0到倒数第二个数,为什么是第二个数,因为是每次
选择一个最小的数放到前面,所以最后一个数不用选择。
2、定义一个下标min保存最小的数的下标,每次循环默认下标为i的为最小
的数,依次和 j 进行比较,如果下标为 j 的比下标为 i 的小,就把 j 下标赋值
给 min。
3、每一次第一层循环结束,就把 下标为 i 的数和下标为 min的数进行交换。
Java 实现
public class SelectionSort {
public static void selectSort(int[] arrays) {
int min;
for (int i = 0; i < arrays.length - 1; i++) {
min = i;
for (int j = i + 1; j < arrays.length; j++) {
if (arrays[j] < arrays[min]) {
min = j;
}
}
int t = arrays[i];
arrays[i] = arrays[min];
arrays[min] = t;
}
}
public static void main(String[] args) {
int[] arrays = new int[]{5, 2, 3, 4, 1};
selectSort(arrays);
for (int array : arrays) {
System.out.print(array + " ");
}
}
}
8、计数排序
时间复杂度:|
平均时间 | 最好情况 | 最坏情况 |
---|---|---|
O(n+k) | O(n+k) | O(n+k) |
空间复杂度:|
O(k)|
基本思路:
1、假设 n个输入元素中的每一个都是在 0 到 k区间的一个整数。
2、初始化一个数组,默认值都设置为0。
3、遍历需要排序的数组,如果一个数为a[j],那c[a[j]]就加1。
4、然后循环数组c,计算计算有几个数是小于c[i]的。
5、定义一个数组b,从a的最后一个元素开始,然后把数依次放到数组b里面,
因为c[arrays[j]]存放的是小于arrays[j]的数,所以可以由后往前根据下标和值赋值给
数组b,赋值完成后,c[arrays[j]]需要减一,如果下一个数和arrays[j]相同,就可以
放到前一个位置上。
Java 实现
public class CountingSort {
public static void countSort(int[] arrays, int b[]) {
int c[] = new int[arrays.length];
//初始化操作,数组c的值全部设置为0
for (int i = 0; i < arrays.length; i++) {
c[i] = 0;
}
//遍历每一个输入的值,如果一个元素输入值为i,则从c[i]加1
//c[i]保存的就是等于i的元素的个数
for (int j = 1; j < arrays.length; j++) {
c[arrays[j]]++;
}
//计算有多少个元素是小于或等于i的
for (int i = 1; i < arrays.length; i++) {
c[i] = c[i] + c[i - 1];
}
for (int j = arrays.length - 1; j >= 1; j--) {
//把每个元素a[j]放到输出数组b的正确位置上
b[c[arrays[j]]] = arrays[j];
//如果有多个相同的数,则需要减一,那么下一个相同的数将会放到该数的前一个位置上
c[arrays[j]] -= 1;
}
}
public static void main(String[] args) {
int arrays[] = new int[]{0, 5, 4, 1, 2, 3};
int b[] = new int[arrays.length];
countSort(arrays, b);
for (int i : b) {
System.out.print(i + " ");
}
}
}
9、小结
ABOUT
公众号:【星尘Pro】
github:https://github.com/huangliangyun