比较排序
七大排序算法
❤️稳定性:假定在待排序的记录序列中,存在多个具有相同的关键字的记录,若经过排序,这些记录的相对次序保持不变,即在原序列中,r[i]=r[j],且r[i]在r[j]之前,而在排序后的序列中,r[i]仍在r[j]之前,则称这种排序算法是稳定的;否则称为不稳定的
插入排序
⭕️插入排序的思想:从第二个元素开始往后遍历,遍历到每个元素都把该元素插入该元素之前的序列中,并保持序列有序性。
比如将第二个元素插入第二个元素之前的序列并保持有序性,然后把第三个元素插入第三个元素之前的序列,并保持有序性,到此前三个元素已经有序,然后把第四个插入前三个元素,并保持有序性,依次类推,直到所有元素都有序。
//插入排序(由小到大)
//时间复杂度:O(N*N) 空间复杂度:O(1)
public static void insertSort(int[] arr){
for (int i = 1; i < arr.length; i++) {
int j = 0;
int cur = arr[i];
for (j = i - 1; j >=0; j--) {
if (cur < arr[j]){
arr[j+1] = arr[j];
}else {
arr[j+1] = cur;
break;
}
}
if(j == -1){
arr[0] = cur;
}
}
}
希尔排序
⭕️希尔排序的思想:希尔排序是插入插入排序的优化算法。希尔排序是先取一个整数gap,意思是将整个序列分为gap组(下面的图会演示具体是怎么分组的),然后针对每个组进行插入排序,这一趟下来,序列就会稍微有序点,然后gap/=2,然后再针对每个组进行插入排序,然后序列就会更有序点。然后gap/=2,再进行下一轮。直到gap==1,然后再对序列整体来个插入排序(这次就不分组了)
⭕️当gap>1时都是预排序,预排序的目的是让序列变得更有序,只有最后一次gap==1时这次的插入排序才能让序列真正的有序。如果不分组排序,直接一上来就插入排序,当数据量比较大时,比较次数就会很多。分组就能减少比较次数。
⭕️希尔排序又称为缩小增量排序,gap就是那个增量。希尔排序的时间复杂度并不好计算,因为增量的取值不确定。资料上显示的时间复杂度是:O(N1.25)到O(1.6*N1.25)
private void shell(int[] arr, int gap){
for (int i = gap; i < arr.length; i++) {
int cur = arr[i];
int j = 0;
for (j = i - gap; j >= 0 ; j -= gap) {
if (cur < arr[j]){
arr[j+gap] = arr[j];
}else {
break;
}
}
arr[j+gap] = cur;
}
}
//希尔排序
public void shellSort(int[] arr){
int gap = arr.length;
while (gap > 1){
shell(arr, gap);
gap/=2;
}
shell(arr,1);
}
选择排序
⭕️选择排序的思想:每次选取未排序序列中最小值放到最前面,第一次选最小值放到0下标处,第二次选剩余序列中最小值放到1下标处,第三次选剩余序列中最小值放到2下标处。依次往后遍历。
public static void swap(int[] arr,int index1,int index2){
int tmp = arr[index1];
arr[index1] = arr[index2];
arr[index2] = tmp;
}
//选择排序,选择排序不管数据是有序还是无序,都要比较这么多次。所以该算法比较慢
//时间复杂度:O(N*N) 空间复杂度:O(1)
public static void selectSort(int[] arr){
for (int i = 0; i < arr.length; i++) {
int minIndex = i;
for (int j = i + 1; j < arr.length; j++) {
if(arr[j] < arr[minIndex]){
minIndex = j;
}
}
swap(arr,minIndex,i);
}
}
时间复杂度:O(N * N),空间复杂度: O(1)
堆排序
⭕️堆排序思想:比如要排成递增序列,要先将无序序列建成一个大根堆,然后第一次取堆顶元素和最后一个元素交换,然后再调整为大根堆(调整的范围不包括已经交换下来的元素),第二次取堆顶元素和倒数第二个元素交换,然后再调整为大根堆(调整的范围不包括已经交换下来的元素)
⭕️第一次交换放到最后位置是最大的元素,第二次交换放到倒数第二位置的是第二大元素,第三次交换放到倒数第三位置的是第三大元素,依次类推,就可以排序
🔑总共两个大步骤:建堆,交换和向下调整
/**
*
* @param arr:向下调整的堆
* @param root 向下调整的子树的根节点
* @param len 调整的界限(调整的数据下标<len)
*/
public static void shiftDown(int[] arr,int root,int len){
int parent = root;
int child = root*2+1; //左孩子
while(child < len){
//让child指向最大的孩子节点
if(child + 1 < len && arr[child+1] > arr[child]){
child++;
}
//孩子比父亲大就交换
if(arr[child] > arr[parent]){
swap(arr,child,parent);
parent = child;
child = parent * 2 + 1;
}else{
break;
}
}
}
//堆排序
public static void heapSort(int[] arr){
//建大根堆
for (int i = (arr.length-2)/2; i >=0 ; i--) {
shiftDown(arr,i,arr.length);
}
//排序
int end = arr.length - 1;
while (end >= 0){
swap(arr,end,0);
shiftDown(arr,0,end);
end--;
}
}
堆排序时间复杂度:O(N*logN) 空间复杂度:O(1)
🔑==堆排序时间复杂度分析:==堆排序有两大步骤,建堆和操作堆。建堆之前在堆那一节已经分析过了,时间复杂度是:O(N)。下面主要来分析操作堆:
以满二叉树来分析:最下一层元素交换到堆顶之后的向下调整次数:h-1(最坏情况下是h-1,h是树的高度),倒数第二层是:h-2,倒数第三层是h-3,第一层是:0
S=(2 ^ 0 * 0) + (2 ^ 1 * 1) + (2 ^ 2 * 2) + (2 ^ 3 * 3) +…+ (2 ^ h-2 * (h-2)) + (2 ^ h-1 * (h-1))
2S=(2 ^ 1 * 0) + (2 ^ 2 * 1) + (2 ^ 3 * 2) + (2 ^ 4 * 3) +…+ (2 ^ h-1 * (h-2)) + (2 ^ h * (h-1))
相减得:- (2 ^ 1+ 2 ^ 2 + 2 ^ 3 + 2 ^ h-1)+ (2 ^ h * (h-1)-0 ,化简后可得时间复杂度是O(n*logn-2n),上面建堆是O(N),相加得O(N * logN-N),再化简得O(N * logN)
冒泡排序
⭕️==冒泡排序思想:==每次遍历将当前序列中最大值交换至最右侧,第一次遍历将前n个元素中最大值交换至最右侧,第二次将前n-1个元素中最大值交换至右侧第二个位置,第三次将前n-2个元素中最大值交换至右侧第三个位置。循环遍历n-1次即可将序列排序。
public static void swap(int[] arr,int index1,int index2){
int tmp = arr[index1];
arr[index1] = arr[index2];
arr[index2] = tmp;
}
public static void bubbleSort(int[] arr){
for (int i = 0; i < arr.length - 1; i++) {
boolean flg = false;
for (int j = 0; j < arr.length - 1 - i; j++) {
if (arr[j] > arr[j+1]){
flg = true;
swap(arr,j,j+1);
}
}
if (!flg){
return;
}
}
}
🔑时间复杂度:O(N * N),空间复杂度:O(1)
快速排序
⭕️快速排序是采用分治的思想,使用递归的方式来排序,快排的思想:先从当前序列中选取一个基准,然后把比基准小的数字放到基准左面,比基准大的数字放到基准右面。然后再分别对分出的左右序列采取相同的思想。
根据基准划分左右序列的算法以下两种都可以:
private static void insert1Sort(int[] arr,int left,int right){
for (int i = left + 1; i <= right; i++) {
int cur = arr[i];
int j = i -