目录
如何判断一个排序的稳定性?
在排序前和前序后,两个相同的数字如果没有发生前后的交换,则认为是稳定的排序。
测试排序花费时间的方法:
import java.util.Random;//生成随机数
//生成随机数,存入array数组
Random random = new Random();
for (int i = 0; i < array.length; i++) {
array[i] = random.nextInt(100);
}
long start = System.currentTimeMillis(); //记录排序开始时间
xxxSort(array); //调用排序方法
long end = System.currentTimeMillis(); //记录排序结束时间
System.out.println(end - start); //打印开始结束的相差时间(排序花费的时间)
排序方法的分类:
插入排序:直接插入排序、希尔排序
选择排序:选择排序、堆排序
交换排序:冒泡排序、快速排序
归并排序:归并排序
--------------------------------------------
1. 直接插入排序
特点:越有序越快
时间复杂度:
- 最好情况:O(N)
- 最坏情况(逆序):O(N^2)
- 平均情况:O(N^2)
空间复杂度:O(1)
稳定性:稳定的排序
实现思路:两层循环,一个 i 从1开始往后走,定义 tmp 存储 array [ i ]的值用于比较
定义循环 j 为i-1,往前走,一个一个进行对比
import java.util.Arrays;
public class TestSort {
//直接插入排序
public static void insertSort(int[] array) {
if (array == null || array.length <= 1) return;
for (int i = 1; i < array.length; i++) {
int tmp = array[i];
int j = i - 1;
for (; j >= 0 ; j--) {
if (array[j] > tmp) {
array[j+1] = array[j];
} else {
break;
}
}
array[j+1] = tmp;
}
}
public static void main(String[] args) {
int[] array = {6,2,1,7,5};
System.out.println(Arrays.toString(array));
insertSort(array);
System.out.println(Arrays.toString(array));
}
}
2. 希尔排序
希尔排序是直接插入排序的一个优化
特点:分组(缩小增量法)进行直接插入排序
时间复杂度:O(N^1.3-1.5)
空间复杂度:O(1)
稳定性:不稳定
import java.util.Arrays;
public class TestSort {
//希尔排序
public static void shell(int[] array, int gap) {
if (array == null || array.length <= 1) return;
for (int i = gap; i < array.length; i++) {
int tmp = array[i];
int j = i - gap;
for (; j >= 0; j -= gap) {
if (array[j] > tmp) {
array[j + gap] = array[j];
} else {
break;
}
}
array[j+gap] = tmp;
}
}
//定义每次排序的gap
public static void shellSort(int[] array) {
int gap = array.length-1;
while (gap > 1) {
gap = gap/2;
//gap = (gap / 3) + 1;
shell(array,gap);
}
shell(array,1);
}
public static void main(String[] args) {
int[] array = {6,2,1,7,5};
System.out.println(Arrays.toString(array));
shellSort(array);
System.out.println(Arrays.toString(array));
}
}
3. 直接选择排序
i 从零下标走到 小于 array.length-1的位置,用 j 依次比较大小进行交换。
特点:可以优化,记录下当前i下标之后,最小值的下标才交换
时间复杂度:O(N^2)
空间复杂度:O(1)
稳定性:不稳定的排序
public class test {
//直接选择排序
public static void selectSort(int[] array) {
if (array == null) return;
for (int i = 0; i < array.length; i++) {
for (int j = i+1; j < array.length ; j++) {
if (array[j] < array[i]) {
int tmp = array[j];
array[j] =array[i];
array[i] = tmp;
}
}
}
}
//小优化版
public static void selectSort2(int[] array) {
if (array == null) return;
for (int i = 0; i < array.length; i++) {
int min = i;
for (int j = i+1; j < array.length ; j++) {
if (array[min] > array[j]) {
min = j;
}
}
if (min != i) {
int tmp = array[i];
array[i] = array[min];
array[min] = tmp;
}
}
}
public static void main(String[] args) {
int[] array = {1,6,8,9,4,5};
System.out.println(Arrays.toString(array));
selectSort(array);
System.out.println(Arrays.toString(array));
}
}
4. ⭐堆排序
特点:1.先建大根堆2.堆中排序(向下调整)
时间复杂度:O(N*logn)
空间复杂度:O(1)
稳定性:不稳定
public class test {
//堆排序
//1.创建大根堆,时间复杂度O(N)
public static void createBigHeap(int[] array) {
for (int i = (array.length-1-1)/2; i >= 0 ; i--) {
shiftDown(array, i, array.length);
}
}
//2.调用向下调整,时间复杂度O(logn)
public static void shiftDown (int[] array, int parent, int len) {
int child = 2*parent+1;
while (child < len) {
if (child+1 < len && array[child] < array[child+1]) {
child++;
}
//到这儿说明child下标就是左右孩子最大值的下标
if (array[child] > array[parent]) {
int tmp = array[child];
array[child] = array[parent];
array[parent] = tmp;
} else {
break;
}
}
}
//3.在堆中排序
public static void heapSort(int[] array) {
createBigHeap(array); //先把数组变成大根堆,时间复杂度O(N)
int end = array.length-1;
while (end > 0) { //时间复杂度O(N)
int tmp = array[0];
array[0] =array[end];
array[end] = tmp;
shiftDown(array,0,end); // *时间复杂度O(logn)
end--;
}
}
public static void main(String[] args) {
int[] array = {1,6,8,9,4,5};
System.out.println(Arrays.toString(array));
heapSort(array);
System.out.println(Arrays.toString(array));
}
}
5. 冒泡排序
特点:可以使用 flg 标志进行优化
时间复杂度:O(N^2) 当数据本身有序且优化情况下为:O(N)
空间复杂度:O(1)
稳定性:稳定的排序
public class test {
//冒泡排序
public static void bubbleSort(int[] array) {
//趟数
for (int i = 0; i < array.length-1; i++) {
//设置标志,判断是否发生了交换
boolean flg = false;
for (int j = 0; j < array.length-i-1; j++) {
if (array[j] > array[j+1]) {
int tmp = array[j];
array[j] = array[j+1];
array[j+1] = tmp;
//如果这一趟发生了交换则标志为true
flg = true;
}
}
//如果发现没交换,说明数组有序
if (flg == false) {
break;
}
}
}
public static void main(String[] args) {
int[] array = {1,6,8,9,4,5};
System.out.println(Arrays.toString(array));
bubbleSort(array);
System.out.println(Arrays.toString(array));
}
}
6. ⭐⭐快速排序
特点:缺点:在数组有序的情况下时间复杂度最大为O(N^2),空间复杂度O(N)
时间复杂度:O(N*logn) //logn层的二叉树递归
空间复杂度:O(logn) //和二叉树的高度有关
稳定性:不稳定的排序
import java.util.Arrays;
public class test {
//快速排序
//返回标定结点
public static int partition(int[] array, int start, int end) {
int tmp = array[start];
while (start < end) {
//先判断后面
while (start < end && array[end] >= tmp) {
end--;
}
//后面的给start
array[start] = array[end];
//再判断前面
while (start < end && array[start] <= tmp) {
start++;
}
//把大的给end
array[end] = array[start];
}
array[start] = tmp;
return start;
}
//快速排序主函数
public static void quickSort(int[] array) {
quick(array,0,array.length-1);
}
//递归调用左右
public static void quick(int[] array, int left, int right) {
//left >= right 表明当前结束
if (left >= right) {
return;
}
int pivot = partition(array,left,right);
quick(array, left,pivot-1);
quick(array,pivot+1,right);
}
public static void main(String[] args) {
int[] array = {1,6,8,9,4,5};
System.out.println(Arrays.toString(array));
quickSort(array);
System.out.println(Arrays.toString(array));
}
}
快速排序的优化
随机选择法、三数取中法优化、和直接插入排序结合
//递归调用左右
public static void quick(int[] array, int left, int right) {
//left >= right 表明当前结束
if (left >= right) {
return;
}
//【优化】1.随机选择基准,先将left下标的值交换
//rand 交换array[left] array[rand]
//Random random = new Random();
//int rand = random.nextInt(right-left)+left+1;
//swap(array,left,rand)
//【优化】2.三数中取中法
medianOfThree(array,left,right);
//【优化】3.递归执行到一个区间之后,使用直接插入排序
//if ((right - left +1) <= 100) {
// insertSort2(array,left,right);
// return;
//}
int pivot = partition(array,left,right);
quick(array, left,pivot-1);
quick(array,pivot+1,right);
}
//三数取中(快排的优化)
public static void medianOfThree(int[] array,int left, int right) {
int mid = (left+right)/2;
if (array[mid] > array[left]) {
int tmp = array[mid];
array[mid] = array[left];
array[left] = tmp;
}//array[mid] <= array[left]
if (array[left] > array[right]) {
int tmp = array[left];
array[left] = array[right];
array[right] = tmp;
}//array[left] <= array[right]
if (array[mid] > array[right]) {
int tmp = array[right];
array[right] = array[mid];
array[mid] = tmp;
}
}
♥使用栈优化
import java.util.*;
public class test {
//使用栈优化快排
public static void quickSort(int[] array) {
Stack<Integer> stack = new Stack<>();
int start = 0;
int end = array.length-1;
int pivot = partition(array,start,end);
if (pivot > start+1) {
stack.push(start);
stack.push(pivot-1);
}
if (pivot < end-1) {
stack.push(pivot+1);
stack.push(end);
}
while (!stack.empty()) {
end = stack.pop();
start = stack.pop();
pivot = partition(array,start,end);
if (pivot > start+1) {
stack.push(start);
stack.push(pivot-1);
}
if (pivot < end-1) {
stack.push(pivot+1);
stack.push(end);
}
}
}
public static int partition(int[] array, int start, int end) {
int tmp = array[start];
while (start < end) {
//先判断后面
while (start < end && array[end] >= tmp) {
end--;
}
//后面的给start
array[start] = array[end];
//再判断前面
while (start < end && array[start] <= tmp) {
start++;
}
//把大的给end
array[end] = array[start];
}
array[start] = tmp;
return start;
}
public static void main(String[] args) {
int[] array = {1,6,8,9,4,5};
System.out.println(Arrays.toString(array));
quickSort(array);
System.out.println(Arrays.toString(array));
}
}
7. ⭐归并排序
特点:先分解 -> 再合并
时间复杂度:O(N*logn)
空间复杂度:O(N)
稳定性:稳定的排序
import java.util.*;
public class test {
//归并排序
public static void merge(int[] array, int left, int right, int mid) {
int[] tmp = new int[right-left+1];
int k = 0;
int s1 = left;
int e1 = mid;
int s2 = mid+1;
int e2 = right;
while (s1 <= e1 && s2 <= e2) {
if (array[s1] <= array[s2]) {
tmp[k++] = array[s1++];
} else {
tmp[k++] = array[s2++];
}
}
while (s1 <= e1) {
tmp[k++] = array[s1++];
}
while (s2 <= e2) {
tmp[k++] = array[s2++];
}
//排好序的数组放到原数组的顺序
for (int i = 0; i < k; i++) {
array[i+left] = tmp[i];
}
}
public static void mergeSortInternal(int[] array, int left, int right) {
if (left >= right) return;
int mid = (left+right)/2;
mergeSortInternal(array,left,mid);
mergeSortInternal(array,mid+1,right);
merge(array,left,right,mid);
}
public static void mergeSort(int[] array) {
mergeSortInternal(array,0, array.length-1);
}
public static void main(String[] args) {
int[] array = {6,2,1,6,5,0,-1};
System.out.println(Arrays.toString(array));
mergeSort(array);
System.out.println(Arrays.toString(array));
}
}
非递归方式实现归并排序
public class test {
//非递归方式实现归并排序
public static void mergeSort(int[] array) {
for (int gap = 1; gap < array.length; gap *= 2) {
merge(array,gap);
}
}
public static void merge(int[] array,int gap) {
int[] tmp = new int[array.length];
int k = 0;
int s1 = 0;
int e1 = s1+gap-1;
int s2 = e1+1;
int e2 = s2+gap-1 >= array.length ? array.length-1 : s2+gap-1;
//一定要有两个段
while (s2 < array.length) {
while (s1 <= e1 && s2 <= e2) {
if (array[s1] <= array[s2]) {
tmp[k++] = array[s1++];
}else {
tmp[k++] = array[s2++];
}
}
while (s1 <= e1) {
tmp[k++] = array[s1++];
}
while (s2 <= e2) {
tmp[k++] = array[s2++];
}
s1 = e2 + 1;
e1 = s1+gap-1;
s2 = e1+1;
e2 = s2+gap-1 >= array.length ? array.length-1 : s2+gap-1;
}
while (s1 <= array.length-1) {
tmp[k++] = array[s1++];
}
for (int i = 0; i < k; i++) {
array[i] = tmp[i];
}
}
public static void main(String[] args) {
int[] array = {6,2,1,6,5,0,-1};
System.out.println(Arrays.toString(array));
mergeSort(array);
System.out.println(Arrays.toString(array));
}
}
♥合并两个有序数组(补充)
import java.util.*;
public class test {
//非递归方式实现归并排序
public static void mergeSort(int[] array) {
for (int gap = 1; gap < array.length; gap *= 2) {
merge(array,gap);
}
}
public static void merge(int[] array,int gap) {
int[] tmp = new int[array.length];
int k = 0;
int s1 = 0;
int e1 = s1+gap-1;
int s2 = e1+1;
int e2 = s2+gap-1 >= array.length ? array.length-1 : s2+gap-1;
//一定要有两个段,拿s2判断,用s2至少有一个数据
while (s2 < array.length) {
while (s1 <= e1 && s2 <= e2) {
if (array[s1] <= array[s2]) {
tmp[k++] = array[s1++];
}else {
tmp[k++] = array[s2++];
}
}
while (s1 <= e1) {
tmp[k++] = array[s1++];
}
while (s2 <= e2) {
tmp[k++] = array[s2++];
}
s1 = e2 + 1;
e1 = s1+gap-1;
s2 = e1+1;
e2 = s2+gap-1 >= array.length ? array.length-1 : s2+gap-1;
}
//只剩下第一个段了
while (s1 <= array.length-1) {
tmp[k++] = array[s1++];
}
for (int i = 0; i < k; i++) {
array[i] = tmp[i];
}
}
public static void main(String[] args) {
int[] array = {6,2,1,6,5,0,-1};
System.out.println(Arrays.toString(array));
mergeSort(array);
System.out.println(Arrays.toString(array));
}
}