知识扫盲--算法--排序算法

img

冒泡排序

冒泡排序的核心思想是:从无序队列的头部开始,进行两两比较,根据大小交换位置,直到最后将最大(小)元素放在队列的尾部,从而成为有序队列的一部分

冒泡排序维护两个for循环,第一个for循环控制次数,第二个for循环控制两两比较。,需要注意的是:冒泡排序从哪冒?一般来将都会从右边冒出,也就是说右边(队列的尾部)就是有序的队列

public void bullleSort(int[] nums){
    
    
    for(int i = 0;i < nums.length - 1;i++){
    
    
        for(int j = 1;j < nums.length  - i;j++){
    
    
            if(nums[j - 1] < nums[j]){
    
    
                int temp = nums[j - 1];
                nums[j - 1] = nums[j];
                nums[j] = temp;
            }
        }
    }
}

代码讲解:第一个for循环控制次数,那么很显然 i 的初始值为0,上限(取得到)为num.length-2,思考:可不可以从其他数(1)开始?如果只是单纯的控制次数从几开始都问题不大,但是在后续的for循环中,i还承担一个重要的角色:已经排好序的元素数量。前面讲过是从无序队列的头部开始一直到尾部结束,那如何知道尾部在哪?就是靠这个i变量

第二个for循环,控制两两比较,因为我是习惯于 i 与 i -1进行比较,所以初始值为1,上限(取得到)为nun.length - i - 1

优化

对于序列{2,1,3,4,5,}第一次比较的时候就已经是序列有序,但是还会不停的进行后续的比较,所以可以进行优化

public void bullleSort(int[] nums){
    
    
    boolean flag = true;
    for(int i = 0;i < nums.length - 1 && flag;i++){
    
    
        flag = false;
        for(int j = 1;j < nums.length  - i;j++){
    
    
            if(nums[j - 1] > nums[j]){
    
    
                int temp = nums[j - 1];
                nums[j - 1] = nums[j];
                nums[j] = temp;
                flag = true;
            }
        }
    }
}

性能分析

时间复杂度:最小时间为O(n)跑一遍即可,最坏情况需要比较(n - 1),那么根据求和公式得到O( n(n-1) / 2 ),所以平均复杂度为O(N2)

空间复杂度:就需要一个temp变量O(1)

稳定性:因为使用的>所以是稳定算法

选择排序

基本思想:每一趟从待排序序列中选择一个最大或者最小的元素放在没有排序的尾部

public void selectionSort(int[] nums){
    
    
    for(int i = 0;i < nums.length - 1;i++){
    
    
        int minIndex = i;
        for(int j = i;j < nums.length;j++){
    
    
            if(nums[j] < nums[minIndex]){
    
    
                minIndex = j;
            }
        }
        int temp = nums[minIndex];
        nums[minIndex] = nums[i];
        nums[i] = temp;
    }
}

性能分析

时间复杂度:O(N2)

空间复杂度:O(1)

稳定性:不稳定,例如{_7,7,1,2},因为第一个选择的是1,所以就变为了{1,7_7,2},所以不稳定

插入排序

基本思想:每趟将一个元素,插入到已经排好序的正确位置

public void insertionSort(int[] nums){
    
    
    for(int i = 1;i < nums.length;i++){
    
    
        int temp = nums[i];
        int j;
        for(j = i - 1;j >= 0 && temp < nums[j];j--){
    
    
            nums[j + 1] = nums[j];
        }
        nums[j + 1] = temp;	//注意这里是j+1 因为上面for循环最后还是进行了j--
    }
}

同样是两个for循环,第一个for控制插入的次数,第二个for控制要插入的位置,将一些元素进行后移,将当前元素插入到正确位置

性能分析

时间复杂度:O(N2)

空间复杂度:O(1)

稳定性:稳定

希尔排序

基本思想:在插入排序的基础上进行分组排序,插入排序是将所有元素看做一个组,而希尔排序是没隔一个步长分为一个组,组内进行插入排序,有了插入排序的思想应该很好理解

puboic void shellSort(int[] nums){
    
    
    //控制分组
    for(int gap = nums.length/2; gap > 0;gap /= 2){
    
    
        //和插入排序一样只是上步长为1改为gap
        for(int i = gap;i < nums.length;i++){
    
    
            int temp = nums[i];
            int j;
            for(j = i - gap;i >= 0;temp < nums[j];j -= gap){
    
    
                nums[j + gap] = nums[j];
            }
            nums[j + gap] = nums[j];
        }
    }
}

性能分析

时间复杂度:希尔排序的使劲复杂度取决于gap的选取,但是肯定小于O(N2)

空间复杂度:O(1)

稳定性:稳定

归并排序

归并排序是一种典型的分治思想,先将数组分为每组只有一个元素,因为一个元素默认就是有序的,然后往上合,直接看代码

public int[] sortArray(int[] nums) {
    
    
    int[] temp = new int[nums.length];
    mergeSort(nums, 0, nums.length - 1, temp);
    return nums;
}
public void mergeSort(int[] nums, int left, int right, int[] temp){
    
    
    if(left >= right)	return;
    int mid = (left + right) >> 1;
    mergeSort(nums, left, mid, temp);
    mergeSort(nums, mid + 1, right, temp);
    int k = left, p = mid + 1, t = 0;
    while(k <= mid || p <= right){
    
    
        if(p > right || (k <= mid && nums[k] < nums[p])){
    
    
            temp[t++] = nums[k++];
        }else{
    
    
            temp[t++] = nums[p++];
        }
    }
    for(int q = 0; q < t;q++){
    
    
        nums[left + q] = temp[q];
    }
}

性能分析

时间复杂度: O(nlogn)

空间复杂度:O(n)

稳定性:稳定

堆排序

基本思想:建立大顶堆或者小顶堆,然后利用下标为0的元素与大顶堆(小顶堆)最后一个元素进行交换,关键点:

  • 完全二叉树的最后一个非叶子节点下标:nums.length / 2 - 1
  • 根节点下标为k则,该节点的左子树节点:2k + 1,右子树:2k + 2
public int[] sortArray(int[] nums) {
    
    
    heapSort(nums);
    return nums;
}
public void heapSort(int[] nums){
    
    
    for(int i = nums.length / 2 - 1;i >= 0;i--){
    
    
        adjustHeap(nums, i, nums.length);
    }
    for(int j = nums.length - 1;j > 0;j--){
    
    
        int temp = nums[j];
        nums[j] = nums[0];
        nums[0] = temp;
        adjustHeap(nums, 0, j);
    }
}
public void adjustHeap(int[] nums, int i, int length){
    
    
    int temp = nums[i];
    for(int k = 2 * i + 1; k < length;k = k * 2 + 1){
    
    
        if(k + 1 < length && nums[k] < nums[k + 1]){
    
    
            k++;
        }
        if(nums[k] > temp){
    
    
            nums[i] = nums[k];
            i = k;
        }else break;
    }
    nums[i] = temp;
}

快速排序

快速排序的基本思想是利用分治法找到每一个基准数在数组中的正确位置即可,使得该基准数左边的数比该数小,右边的数比该数大,然后利用分治对该数左边和右边进行递归快速排序即可

public void quickSort(int[] nums, int left, int right){
    
    
    if(left >= right)	return;
    //基准数
    int temp = nums[left];
    int i = left;
    int j = right;
    while(i != j){
    
    
        while(i < j && nums[j] >= temp)	j--;
        while(i < j && nums[i] <= temp)	i++;
        if(i < j){
    
    
            int t = nums[i];
            nums[i] = nums[j];
            nums[j] = t;
        }
    }
    nums[left] = nums[i];
    nums[i] = temp;
    quickSort(nums, left, i - 1);
    quickSort(nums, i + 1, right);
}


猜你喜欢

转载自blog.csdn.net/weixin_44706647/article/details/115105501