我们都知道排序是非常重要的,排序的方法也是有很多,这里面有很多思想,所以应该重点掌握以下。
1.冒泡排序: 依次比较两个相邻的位置的大小,每次将最大的值放到最后一个位置;
时间复杂度: O(n^2)
public static void bubbleSort(int[] arr) {
if(arr == null || arr.length <2) {
return;
}
//首先说冒泡排序会进行n次,每一次都会确定最后一个位置的数
for(int end = arr.length; end>0; end--) {
//每一次遍历都要比较end个数的大小
for(int i=0; i<end-1; 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 void swap2(int[] arr, int i, int j) {
arr[i] = arr[i] ^ arr[j];
arr[j] = arr[i] ^ arr[j];
arr[i] = arr[i] ^ arr[j];
2.选择排序: 每次都将第一个位置的数字与其他位置的数字作比较,将最小值放到第一个位置
时间复杂度: O(n^2)
public static void selectSort(int[] arr) {
if(arr == null || arr.length<2) {
return;
}
//冒泡是确定最后一个位置,所以从最后一个开始,而选择排序则是确定第一个位置,所以从第一个位置开始
for (int i = 0; i<arr.length; i++) {
int minIndex = i;
//找到当前数中最小值的索引
for (int j=i; 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 temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
3.插入排序: 要插入第i个位置,首先前i-1个位置是肯定已经比较好的,比较arr[i-1]和arr[i]的大小
时间复杂度:与数据状况有关:若是已经排好的情况,则为O(n),若是全无序,则是O(n^2)
但是一般时间复杂度都按照最差的结果,所以插入排序的事件复杂度为O(n^2)
public static void insertSort(int[] arr) {
if(arr == null || arr.length <2) {
return;
}
//因为第一个数刚开始不需要比较,所以都从第二个数开始进行比较
for(int i=1; i<arr.length; i++) {
for(int j=i-1; j>=0 && arr[j]>arr[j+1]; j--) {
swap(arr, j, j+1);
}
}
}
//用位运算进行数字的交换
public static void swap(int[] arr, int i, int j) {
arr[i] = arr[i] ^ arr[j];
arr[j] = arr[i] ^ arr[j];
arr[i] = arr[i] ^ arr[j];
}
4.归并排序: 首先将数组分成两组,将左边排好序,将右边排好序,然后中间合并
时间复杂度可以根据master公式进行计算,为: O(n*log(n))
public static void mergeSort(int[] arr) {
if(arr == null || arr.length<2) {
return;
}
mergeProcess(arr, 0, arr.length-1);
}
public static void mergeProcess(int[] arr, int L, int R) {
if(L == R) {
return;
}
int mid = (L+R)/2; //等价: mid = L+((R-L) >> 1)
mergeProcess(arr, L, mid);
mergeProcess(arr, mid+1, R);
merge(arr, L, mid, R);
}
//当arr数组的左右都排好顺序以后,该方法将左右整合成一个完全有序数组
public static void merge(int[] arr, int L, int mid, int R) {
int[] help = new int[R-L+1];
int i = 0;
int p1 = L;
int p2 = mid+1;
while (p1 <= mid && p2 <= R) {
help[i++] = arr[p1] < arr[p2]? arr[p1++]: arr[p2++];
}
//两个必有且只有一个越界
while (p1 <= mid) {
help[i++] = arr[p1++];
}
while (p2 <= R) {
help[i++] = arr[p2++];
}
//此时帮助数组中的数就是排好顺序的数组,将该数组复制到数组arr中就可以了.
for(i=0; i<help.length; i++) {
arr[L+i] = help[i];
}
}
5.快速排序:
其实就是按照荷兰国旗的思想进行排序:给定一个数组,将最后一个数作为参数,将小于该数字的放到左边,,大于该数字的放到右边,等于该数字的放到中间,然后一直递归,就可以得到一个排好序的数组。
快排分为 经典快排 和 随机快排:
经典快排就是一直将数组的最后一个数作为分界点
随机快排是将数组中的任意一个位置的值与最后一个数字进行交换,使其时间复杂度转变为一个随机问题。
随机快排增加的代码:swap(arr, L+(int) (Math.random() * (R-L+1)), R);
时间复杂度:
经典快排: O(n^2)
随机快排: O(n*logn)
//快速排序,其实就是按照荷兰国旗的思想进行排序;
//给定一个数组,将最后一个数作为参数,将小于该数字的放到左边,,大于该数字的放到右边,等于该数字的放到中间,然后一直递归,就可以得到一个排好序的数组
public static void quickSort(int[] arr, int L, int R) {
if(L < R) {
//随机快排,就是将任意一个位置与最后一个位置进行交换,长期期望的时间复杂度位O(nlog(n)),空间复杂度位O(logn)
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 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++);
} else if (arr[L] > arr[R]) {
swap(arr, more--, L);
} else {
L++;
}
}
return new int[]{less+1, more};
}
//交换顺序的方法
public static void swap(int[] arr, int i, int j) {
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
6.堆排序:
步骤:
-
将数组变为一个大根堆 heapInsert()
-
将大根堆的第一个数字与最后一个交换位置,并将堆的大小-1
-
将交换后的堆转变为继续转变为大根堆(heapfy())方法,然后返回2,直到堆的大小为0,该数组的大小就排好位置了
时间复杂度: O(n*logn)
//堆排序:
// 1.将数组变为一个大根堆 heapInsert()
//2.将大根堆的第一个数字与最后一个交换位置,并将堆的大小-1
//3.将交换后的堆转变为继续转变为大根堆(heapfy())方法,然后返回2,知道堆的大小为0,该数组的大小就排好位置了
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) {
heapfy(arr, 0, heapSize);
swap(arr, 0, --heapSize);
}
}
//将一个数组中的前index个数变成一个大顶堆(完全二叉树):任何一颗子树的最大值都是这颗子树的头部
public static void heapInsert(int[] arr, int index) {
while (arr[index] > arr[(index-1)/2]) {
swap(arr, index, (index-1)/2);
index = (index-1)/2;
}
}
//若大顶堆中任意一个位置变小了,则需要调整该大顶椎
//@param: index: 表示当前数组中第index个数的值变小了
//@param heapSize: 表示当前大顶堆的总个数
//其实就是比较当前的值与左右孩子的值的大小,如果比当前左右孩子的值都大,则不需要做调整,否则,就要变化该大顶堆
//左孩子: 2*index+1 右孩子: 2*index+2
public static void heapfy(int[] arr, int index, int heapSize) {
int left = 2*index+1;
//首先必须保证当前的节点有左孩子
while (left< heapSize) {
//计算出左右孩子较大的那个值,并且其右孩子存在
int largest = (left+1 < heapSize && arr[left+1] > arr[left])? left+1: left;
//判断当前左右孩子的最大值是否比该节点的值大,
// 如果大,则将largest转变为当前左右孩子中最大节点的坐标,并将左孩子的值转变为下一级
//如果小,则不需要做转变,直接退出
largest = arr[largest]>arr[index]? largest: index;
if (largest == index) {
break;
}
swap(arr, index, largest);
left = 2*index + 1;
}
}
//实现一个数组中任意两个位置交换
public static void swap(int[] arr, int i, int j) {
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
在以上的排序方法中,
- 具有稳定性的排序方法: 冒泡、插入、归并
- 不稳定的排序方法:选择排序、快速排序、堆排序
补充:
(1)可以用归并排序内部缓存法,使得归并排序的S(n)做到 O(1)
(2)快排一般不稳定,单也可以通过 01 stable sort 文章的思想实现稳定