(1)、冒泡:O(N^2) 最好:O(N) 空间复杂度:O(1) 稳定
在要排序的一组数中,对当前还未排好序的范围内的全部数,自上而下对相邻的两个数依次进行比较和调整,让较大的数往下沉,较小的往上冒。
最好情况是因为队列有序。
public void bubbleSort(int arr[]) {
boolean didSwap;
for(int i = 0, len = arr.length; i < len - 1; i++) {
didSwap = false;
for(int j = 0; j < len - i - 1; j++) {
if(arr[j + 1] < arr[j]) {
swap(arr, j, j + 1);
didSwap = true;
}
}
if(didSwap == false)
return;
}
}
/*
didSwap的数值为true,说明本次排序并未发生交换,说明数组整体有序,则没必要再进行下面的循环了
*/
(2)、选择:O(N^2) 最好:O(N^2) 空间复杂度:O(1) 不稳定
每趟从待排序的记录序列中选择关键字最小的记录放置到已排序表的最前位置,直到全部排完。
序列5 8 5 2 9, 我们知道第一遍选择第1个元素5会和2交换,那么原序列中2个5的相对前后顺序就被破坏了,所以选择排序不是一个稳定的排序算法。
public static void selectSort(int[] arr) {
for (int i = 0, point = 0; i < arr.length; i++) {
for (int j = i + 1; j < arr.length - 1; j++) {
if (arr[point] > arr[j]) {
point = j;
}
}
swap(arr, i, point);
}
}
(3)、直接插入:O(N^2) 最好:O(N) 空间复杂度:O(1) 稳定
每步将一个待排序的记录,按其顺序码大小插入到前面已经排序的字序列的合适位置,直到全部插入排序完为止。
最好情况是因为队列有序。
/*
*按照从小到大的顺序插入
* */
public static void insertSort(int[] arr) {
int i, j;
for (i = 1; i < arr.length; i++) {
for (j = i - 1; j >= 0; j--) {
//如果待插入的数字大于等于它的前一个数值则说明有序,如果小于前面的数值说明要将这个数字插入到j所指示的下标位置的下一个位置
if (arr[i] >= arr[j]) {
break;
}
}
if (j < i - 1) {//如果j==i-1则说明有序
int temp = arr[i];
for (int m = i; m > j+1; m--) {
arr[m] = arr[m - 1];
}
arr[j+1] = temp;
}
}
}
(4)、归并排序:O(nlogn) 最好:O(nlogn) 空间复杂度:O(N) 稳定
采用分治法(Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。
空间复杂度是因为需要一个辅助数组,可以定义为全局变量。
冒泡排序、选择排序、插入排序为什么慢?因为有很多无效的比较
一次排序只搞定了一个数,浪费了比较的次数
归并,两个有序序列和的过程中不会被浪费,组内比较是不会被浪费的
public static void mergeSort(int[] arr) {
if (arr == null || arr.length < 2) {
return;
}
mergeSort(arr, 0, arr.length - 1);
}
public static void mergeSort(int[] arr, int left, int right) {
if (left == right) {
return;
}
int middle = left + ((right - left) >> 1);
mergeSort(arr, left, middle);
mergeSort(arr, middle + 1, right);
merge(arr, left, middle, right);
}
public static void merge(int[] arr, int left, int middle, int right) {
int[] p = new int[right - left + 1];
int p1 = left;
int p2 = middle + 1;
int i = 0;
while (p1 <= middle && p2 <= right) {
p[i++] = arr[p1] < arr[p2] ? arr[p1++] : arr[p2++];
}
while (p1 <= middle) {
p[i++] = arr[p1++];
}
while (p2 <= right) {
p[i++] = arr[p2++];
}
i = 0;
while (i < p.length) {
arr[left + i] = p[i++];
}
}
(5)、快速排序
快速排序:O(nlogn) 最坏:O(N^2) 空间复杂度:O(logn) 不稳定
先从数列中取出一个数作为基准数,把比基准数大的放到它的右边,小的放到它的左边,再对左右两边重复上述步骤,直到整个序列有序。
经典快排容易出现最坏情况,跟数据状况有关系。
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 left, int right) {
if (left >= right) {
return;
}
int p = partition(arr, left, right);
quickSort(arr, left, p-1);
quickSort(arr, p + 1, right);
}
public static int partition(int[] arr, int left, int right1) {
int pright = right1;//右边界的下标
while (left < pright) {//如果left指向小于pright,说明它会比较,直到两者相等终止比较
if (arr[left] <= arr[right1]) {//左边界的值小于等于基准数字
left++;
} else {
swap(arr, left, --pright);//交换后仍需要再次比较本位置的数字
}
}
swap(arr, right1, pright);
return pright;//此时pright所指向的是左边界的下标
}
public static void swap(int[] arr, int l, int r) {
if (l == r) {
return;
}
arr[l] = arr[l] ^ arr[r];
arr[r] = arr[l] ^ arr[r];
arr[l] = arr[l] ^ arr[r];
}
最坏情况是因为队列有序。
改进后的快速排序(荷兰国旗问题):一次搞定多个数
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 left, int right) {
if (left >= right) {
return;
}
int[] p = partition(arr, left, right);
quickSort(arr, left, p[0] - 1);
quickSort(arr, p[1] + 1, right);
}
public static int[] partition(int[] arr, int left, int right1) {
int pright = right1;
int pleft = left;
while (left < pright) {
if (arr[left] < arr[right1]) {
left++;
pleft++;
} else if (arr[left] > arr[right1]) {
swap(arr, left, --pright);
} else {
left++;
}
}
swap(arr, right1, pright);
return new int[]{pleft, pright};
}
public static void swap(int[] arr, int l, int r) {
if (l == r) {
return;
}
arr[l] = arr[l] ^ arr[r];
arr[r] = arr[l] ^ arr[r];
arr[l] = arr[l] ^ arr[r];
}
随机快排:估计的时候不能说有最差情况,成为了一个概率事件,只能以长期期望的方式算出他的时间复杂度O(nlogn)。
随机快排最常用,是因为常数项少,当指标相同拼常数项,mergeSort输给快排就是因为常数项的问题。
空间浪费在划分点,数据需要被划分多少次,断点就需要多少个。最差情况下断点需要O(N),最好情况下断点需要O(logn)
时间复杂度O(N*logN),额外空间复杂度O(logN)
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 left, int right) {
if (left >= right) {
return;
}
swap(arr, left + (int) (Math.random() * (right - left + 1)), right);
int[] p = partition(arr, left, right);
quickSort(arr, left, p[0] - 1);
quickSort(arr, p[1] + 1, right);
}
public static int[] partition(int[] arr, int left, int right1) {
int pright = right1;
int pleft = left;
while (left < pright) {
if (arr[left] < arr[right1]) {
left++;
pleft++;
} else if (arr[left] > arr[right1]) {
swap(arr, left, --pright);
} else {
left++;
}
}
swap(arr, right1, pright);
return new int[]{pleft, pright};
}
public static void swap(int[] arr, int l, int r) {
if (l == r) {
return;
}
arr[l] = arr[l] ^ arr[r];
arr[r] = arr[l] ^ arr[r];
arr[l] = arr[l] ^ arr[r];
}
(6)、堆排序:O(N*logN) 最坏:O(N*logN) 空间复杂度:O(1)
1)、建堆时间复杂度:建立堆的过程,新加入节点时间复杂度log1+log2+log3+log4+log(n-1)=O(N)
左孩子坐标:2*i+1
右孩子坐标:2*i+2
优先级队列结构,就是堆结构(PriorityQueue)
堆结构的heapInsert与heapify
2)、两种建立堆的方法HeapInsert&Heapify
①第一种方法HeapInsert
它可以假定我们事先不知道有多少个元素,通过不断往堆里面插入元素进行调整来构建堆。
它的大致步骤如下:
首先增加堆的长度,在最末尾的地方加入最新插入的元素。 比较当前元素和它的父结点值,如果比父结点值大,则交换两个元素,否则返回。 重复步骤2.
这种插入建堆的时间复杂度是O(NlogN)
②第二种方法Heapify(重构堆)
从最后一个非叶子节点一直到根结点进行堆化的调整。如果当前节点小于某个自己的孩子节点(大根堆中),那么当前节点和这个孩子交换。Heapify是一种类似下沉的操作,HeapInsert是一种类似上浮的操作。
这种建堆的时间复杂度是O(N)
3)、代码实现
public class Demo8 {
public static void main(String[] args) {
int[] arr = {1, 52, 3, 6, 4, 89, 123, 25, 2, 3};
int[] arr1 = {59, 78, 63, 52, 4, 10, 26, 32, 545, 59, 15};
heapSort(arr1);
}
//堆排序,构建大顶堆
public static void heapSort(int[] arr) {
if (arr == null || arr.length < 2) {
return;
}
for (int index = 1; index < arr.length; index++) {
heapInsert(arr, index);
}
print(arr);
System.out.println();
int endIndex = arr.length - 1;//当前的堆中最后一个元素的下标
swap(arr, 0, endIndex--);//交换后堆的大小减去1
while (endIndex > 0) {//当数组中的元素数目至少为两个时才进行下一步的交换操作,此时才有必要恢复为大根堆
heapIfy(arr, 0, endIndex);//此时传过去的--endIndex指的是交换后的最后一个有效位置的下标,需要重新构建恢复大根堆
swap(arr, 0, endIndex--);//将最后一个位子的数字与首部交换
//当最后交换至swap(arr,0,1)时则没必要再对堆的大小进行调整,调整后endIndex数值为0
}
printInverse(arr);
}
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;
}
}
public static void heapIfy(int[] arr, int index, int endIndex) {
int left = index * 2 + 1;
while (left <= endIndex) {
int maxindex = left + 1 <= endIndex && arr[left + 1] > arr[left] ? left + 1 : left;
print(arr);
System.out.println("交换位置:" + index + "," + endIndex);
swap(arr, index, maxindex);
index = maxindex;
left = index * 2 + 1;
}
}
public static void swap(int[] arr, int l, int r) {
if (l == r) {
return;
}
arr[l] = arr[l] ^ arr[r];
arr[r] = arr[l] ^ arr[r];
arr[l] = arr[l] ^ arr[r];
}
public static void print(int[] arr) {
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i] + "\t");
}
}
public static void printInverse(int[] arr) {
for (int i = arr.length - 1; i >= 0; i--) {
System.out.print(arr[i] + "\t");
}
}
}