一、排序算法复杂度比较
T(N) = a*T(N/b) + O(N^d)
- -> 复杂度为
- -> 复杂度为
- -> 复杂度为
二、冒泡排序
- 时间复杂度 ,额外空间复杂度
- 可实现成稳定的
经过上图一轮后,最大的数就到了最后位置,接下来我们对剩下的1,2,3,6,4进行同样的方式,最终可以将数值从小到大排序
Java代码:
package day01;
/**
* 冒泡排序
* @author Danycym
*
*/
public class Code01_BubbleSort {
public static void bubbleSort(int[] arr) {
if (arr == null || arr.length < 2) {
return;
}
for (int i = arr.length - 1; i > 0; i--) {
for (int j = 0; j < i; j++) {
if (arr[j] > arr[j + 1]) {
swap(arr, j, j + 1);
}
}
}
}
public static void swap(int[] arr, int i, int j) {
int tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
}
public static void printArray(int[] arr) {
if (arr == null) {
return;
}
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i] + " ");
}
System.out.println();
}
public static void main(String[] args) {
int[] arr = {2, 1, 4, 3, 6, 5, 9, 8, 7};
printArray(arr);
bubbleSort(arr);
printArray(arr);
}
}
动图参考来自网络:
三、插入排序
- 时间复杂度 ,额外空间复杂度
- 可实现成稳定的
插入排序和我们平时插扑克牌类似,上图中橙色框内表示手里的牌,橙色框下一个元素表示下一张要抓的牌,每抓到一张新的牌,我们就将其插入到手里对应的位置(这里需要从新牌的上一张到第一张逐一比较,比手里的牌小就交换,直到比手里的牌大,此时插入到小牌后面,大牌前面完成插入),依次类推,直到最后一张牌
Java代码:
package day01;
/**
* 插入排序
* @author Danycym
*
*/
public class Code02_InsertionSort {
public static void insertionSort(int[] arr) {
if (arr == null || arr.length < 2) {
return;
}
for(int i = 1; i < arr.length; i++) {
for(int j = i - 1; j >= 0; j--) {
if(arr[j] > arr[j+1]) {
swap(arr, j, j + 1);
}
}
}
}
public static void swap(int[] arr, int i, int j) {
int tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
}
public static void printArray(int[] arr) {
if (arr == null) {
return;
}
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i] + " ");
}
System.out.println();
}
public static void main(String[] args) {
int[] arr = {2, 1, 4, 3, 6, 5, 9, 8, 7};
printArray(arr);
insertionSort(arr);
printArray(arr);
}
}
动图参考来自网络:
四、选择排序
- 时间复杂度 ,额外空间复杂度
- 不可实现成稳定的
每次设定橙色框位置为最小值,如果该最小值后面的数有比它小的就和它交换
Java代码:
package day01;
/**
* 选择排序
* @author Danycym
*
*/
public class Code03_SelectionSort {
public static void selectionSort(int[] arr) {
if(arr == null || arr.length < 2) {
return;
}
for(int i = 0; i < arr.length - 1; i++) {
int minIndex = i;
for(int j = i + 1; j < arr.length; j++) {
minIndex = arr[j] < arr[minIndex] ? j : minIndex;
}
swap(arr, i, minIndex);
}
}
public static void swap(int[] arr, int i, int j) {
int tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
}
public static void printArray(int[] arr) {
if(arr == null) {
return;
}
for(int i = 0; i < arr.length; i++) {
System.out.print(arr[i] + " ");
}
System.out.println();
}
public static void main(String[] args) {
int[] arr = {2, 1, 4, 3, 6, 5, 9, 8, 7};
printArray(arr);
selectionSort(arr);
printArray(arr);
}
}
动图参考来自网络:
五、归并排序
- 时间复杂度 ,额外空间复杂度
- 可实现成稳定的
将原问题拆分成子问题,再对子问题进行同样的操作,直到不能再分,然后就是排序合并
Java代码:
package day01;
/**
* 归并排序
* @author Danycym
*
*/
public class Code04_MergeSort {
public static void mergeSort(int[] arr) {
if(arr == null || arr.length < 2) {
return;
}
mergeSort(arr, 0, arr.length - 1);
}
private static void mergeSort(int[] arr, int l, int r) {
if(l == r) {
return;
}
int mid = l + ((r - l) >> 1); //中点位置
mergeSort(arr, l, mid); //左分支
mergeSort(arr, mid + 1, r); //右分支
merge(arr, l, mid, r); //合并
}
private static void merge(int[] arr, int l, int m, int r) {
int[] help = new int[r - l + 1]; //存储合并结果
int i = 0;
int p1 = l;
int p2 = m + 1;
//比较大小,依次添加到help中
while(p1 <= m && p2 <= r) {
help[i++] = arr[p1] < arr[p2] ? arr[p1++] : arr[p2++];
}
//p1还没到最后,直接将后面的都添加到help
while(p1 <= m) {
help[i++] = arr[p1++];
}
//p2还没到最后,直接将后面的都添加到help
while(p2 <= r) {
help[i++] = arr[p2++];
}
//最后将help中的元素都拷贝到原数组中
for(i = 0; i < help.length; i++) {
arr[l + i] = help[i];
}
}
public static void printArray(int[] arr) {
if(arr == null) {
return;
}
for(int i = 0; i < arr.length; i++) {
System.out.print(arr[i] + " ");
}
System.out.println();
}
public static void main(String[] args) {
int[] arr = {2, 1, 4, 3, 6, 5, 9, 8, 7};
printArray(arr);
mergeSort(arr);
printArray(arr);
}
}
动图参考来自网络:
六、快速排序
- 时间复杂度 ,额外空间复杂度
- 不可实现成稳定的
上图中,我们选择最后一个元素作为划分的那个值,程序中做了修改,每次随机选择一个数作为划分的数,具体做法如下:
- 先随机选择一个元素,然后将它与最后一个元素交换,最后在进行看划分
swap(arr, l + (int)(Math.random() * (r - l + 1)), r);
Java代码:
package day01;
/**
* 快速排序
* @author Danycym
*
*/
public class Code05_QuickSort {
public static void quickSort(int[] arr) {
if(arr == null || arr.length < 2) {
return;
}
quickSort(arr, 0, arr.length - 1);
}
public static void quickSort(int[] arr, int l, int r) {
if(l < r) {
//随机选择一个元素作为划分的数
swap(arr, l + (int)(Math.random() * (r - l + 1)), r);
int[] p = partition(arr, l, r);
quickSort(arr, l, p[0] - 1);
quickSort(arr, p[1] + 1, r);
}
}
//交换
public static void swap(int[] arr, int i, int j) {
int tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
}
//划分
public static int[] partition(int[] arr, int l, int r) {
int less = l - 1;
int more = r;
while(l < more) {
if(arr[l] < arr[r]) {//遍历到的元素比最后一个元素小
swap(arr, ++less, l++);//左边界往右扩大1,并与当前遍历到的元素交换
}else if(arr[l] > arr[r]) {//遍历到的元素比最后一个元素大
swap(arr, --more, l);//右边界往左扩大1,并与当前遍历到的元素交换
}else {//否则,直接遍历下个元素
l++;
}
}
swap(arr, more, r);//交换arr[more]与arr[r]
return new int[] {less + 1, more};
}
public static void printArray(int[] arr) {
if(arr == null) {
return;
}
for(int i = 0; i < arr.length; i++) {
System.out.print(arr[i] + " ");
}
System.out.println();
}
public static void main(String[] args) {
int[] arr = {2, 1, 4, 3, 6, 5};
printArray(arr);
quickSort(arr);
printArray(arr);
}
}
动图参考来自网络:
黄色代表基数(划分的数)
红色代表当前遍历的
绿色代表小于基数的
紫色代表大于基数的
橙色代表排序好的
- 该图每次都是以第一个元素作为划分基数的
七、堆排序
- 时间复杂度 ,额外空间复杂度
- 不可实现成稳定的
数组理解为完全二叉树:
大根堆:
- 堆就是完全二叉树
- 大根堆:在这棵完全二叉树中,任何一棵子树的最大值都是该子树的头部
将数组脑补的完全二叉树转换为大根堆:
堆排序:
Java代码:
package day01;
/**
* 堆排序
* @author Danycym
*
*/
public class Code06_HeapSort {
public static void heapSort(int[] arr) {
if(arr == null || arr.length < 2) {
return;
}
//构建大根堆
for(int i = 0; i < arr.length; i++) {
heapInsert(arr, i);
}
//大根堆长度
int heapSize = arr.length;
//将大根堆第一个元素与此时剩下的大根堆的最后一个元素交换
swap(arr, 0, --heapSize);
while(heapSize > 0) {
heapify(arr, 0, heapSize); //进行下沉操作
swap(arr, 0, --heapSize);
}
}
public static void heapInsert(int[] arr, int i) {
while(arr[i] > arr[(i -1) / 2]) { //如果i对应的值大于其父节点
swap(arr, i, (i -1) / 2); //则和父节点交换
i = (i -1) / 2; //并将父节点位置更新为当前遍历
}
}
//
public static void heapify(int[] arr, int i, int heapSize) {
int left = i * 2 + 1; //左孩子下标
while(left < heapSize) {
//判断左右孩子中较大的孩子的下标
int largest = left + 1 < heapSize && arr[left + 1] > arr[left] ? left + 1 : left;
//再将较大者和当前遍历比较,记录较大者下标
largest = arr[largest] > arr[i] ? largest : i;
if(largest == i) { //如果相等,较大者是自己,退出
break;
}
//否则交换
swap(arr, largest, i);
i = largest; //更新i为交换后的位置
left = i * 2 + 1;
}
}
//交换
public static void swap(int[] arr, int i, int j) {
int tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
}
//打印
public static void printArray(int[] arr) {
if(arr == null) {
return;
}
for(int i = 0; i < arr.length; i++) {
System.out.print(arr[i] + " ");
}
System.out.println();
}
public static void main(String[] args) {
int[] arr = {2, 1, 4, 3, 6, 5};
printArray(arr);
heapSort(arr);
printArray(arr);
}
}
动图参考来自网络:
八、桶排序
- 时间复杂度 ,额外空间复杂度
- 可以实现成稳定的
桶排序是一种非基于比较的排序,其实现包括计数排序和基数排序两种,其基本思想如下:
- 得到无序数组的取值范围
- 根据取值范围创建对应数量的桶
- 遍历数组,将每个元素放到对应的桶中
- 按照顺序遍历桶中的每个元素,依次放到数组中,即可完成数组的排序
堆排序是否稳定取决于“桶”用的是什么数据结构,如果是队列,那么可以保证相同的元素取出后的相对位置与“放进去”之前是相同的,如果用栈来实现桶,就不是稳定的
“桶”是一种容器,这个容器可以用多种数据结构实现,包括数组、队列或者栈
1、计数排序
- 桶排序的实现之计数排序
Java代码:
package day01;
/**
* 桶排序
* @author Danycym
*
*/
public class Code07_BucketSort {
public static void bucketSort(int[] arr) {
if(arr == null || arr.length < 2) {
return;
}
int max = Integer.MIN_VALUE;
//求数组的最大值
for(int i = 0; i < arr.length; i++) {
max = Math.max(max, arr[i]);
}
int[] bucket = new int[max + 1];
for(int i = 0; i < arr.length; i++) {
bucket[arr[i]]++;
}
int i = 0;
for(int j = 0; j < bucket.length; j++) {
while(bucket[j]-- > 0) {
arr[i++] = j;
}
}
}
public static void printArray(int[] arr) {
if(arr == null) {
return;
}
for(int i = 0; i < arr.length; i++) {
System.out.print(arr[i] + " ");
}
System.out.println();
}
public static void main(String[] args) {
int[] arr = {2, 1, 12, 1, 3, 3, 4};
printArray(arr);
bucketSort(arr);
printArray(arr);
}
}
2、基数排序
待更新。。。