排序算法小记 | 青训营笔记

92 阅读2分钟

今天才把go中的排序算法这节课看完,起始课上三种排序算法都讲的比较一笔带过,好久没有实践都有点忘记饿了,这里重新把三种算法实现一下,加深一下记忆,用到的例子是在力扣上找的:912. 排序数组 - 力扣(LeetCode)

1、插入排序

思路:

​ 假设前n-1个元素已经有序,现将第n个元素插入到前面n-1个有序序列中,使得前n个元素有序。按照这样的思路对所有元素进行插入直到整体有序。

​ 因为不知道从哪里开始有序,但是第一个元素只有一个元素肯定是有序的,所以从第一个开始,依次往后面遍历进行插入,直到整体有序。

步骤:

  • 从一个元素开始,认为是有序的
  • 取下一个元素,从以排序的元素序列中从后往前扫描
  • 该元素小于最后一个元素,则将有序序列元素后移
  • 重复上一步骤,该元素大于最后一个元素则将元素插入到后一个位置
  • 重复步骤2-4,直到整体有序

代码:

class Solution {
    public int[] sortArray(int[] nums) {
        // 插入排序
        for(int i = 1; i < nums.length; i++){
            // 记录当前有序数组的最后一个的下标
            int end = i - 1;
            // 记录当前要插入的元素
            int insert = nums[i];
            // 单趟逐一比较元素
            while(end >= 0){
                // 插入数据比较小则将有序数组元素后移
                if(insert < nums[end]){
                    nums[end + 1] = nums[end];
                    end--;
                }else{
                    // 插入数据大直接跳出
                    break;
                }
            }
            // 插入该元素
            nums[end + 1] = insert;
        }
        return nums;
    }
}

总结:

**时间复杂读:**最好情况下是有序序列,时间复杂度为O(N);最坏情况下是逆序序列,时间复杂度为O(N * N);平均复杂度O(N * N)

**空间复杂度:**O(1)

2、快速排序

思路:

​ 通过一趟排序将数据分割成两个部分,一部分所有数据比另外一部分所有数据都要小,用于分割的数据是处于正确的位置,然后左右两边用同样的方法递归进行达到整体有序。

步骤:

1、选出一个key,这里选择最左边作为key

2、定义两个指针,一左一右,左指针右走,右指针左走

3、左指针找到第一个比key大的值,右指针找到第一个比key小的值,以此内推,直到两个指针相遇

4、将key与相遇的节点交换 ,到达key左都比key小,key右都比key大

5、左右两边分别递归

代码:

    public void quickSort(int[] nums, int start, int end){
        // 只有一个元素或者区间不存在
        if(start >= end){
            return;
        }
        int left = start;
        int right = end;
        // 选择最左边的数字为key
        int key = start;
        // 使所有小于key的值在左边,大于key的值在右边
        while(left < right){
            // 右边好到小于key的值 ,先移动右指针,这样才能道道交换过去值比key小
            while(nums[right] >= nums[key] && left < right){
                right--;
            }
            // 左边找到大于key的值 再判断一次防止越界
            while(nums[left] <= nums[key] && left < right){
                left++;
            }
            // 交换两个位置的值
            swap(nums[right],nums[left]);
        }
        // left和right最终会指向同一位置,与key值交换
        swap(nums[key],nums[left]);
        key = left;
        // key的左边和右边分别递归
        quickSort(nums,start,key - 1);
        quickSort(nums,key + 1,end);
    }

总结:

**时间复杂度:**最好情况O(N * logN);最差情况O(N * N);平均O(N * logN)

3、堆排序

思路:

利用大顶堆的性质,每次找出最大值,然后放在最后,然后继续在剩余的数继续找

步骤:

  • 构造大顶堆
  • 根节点是最大值,将根节点与末尾值进行交换
  • 将剩余n-1个值重新构造成堆,重复上操作

代码:

class Solution {
    public int[] sortArray(int[] nums) {
        // 堆排序
        int len = nums.length;

        // 将数组先整理成大顶堆
        heapify(nums);

        for(int i = len - 1; i > 0;){
            // 将根元素(最大值)和最后一个元素互换,达到一个排序的效果
            swap(nums, 0, i);
            i--;
            // 将根元素下沉,去掉已经排序好的元素
            siftDown(nums,0,i);
        }
        return nums;
    }

    // 将数组整理成堆
    public void heapify(int[] nums){
        int len = nums.length;
        for(int i = (len - 1)/2; i >= 0; i--){
            // 元素逐一下沉
            siftDown(nums, i, len - 1);
        }
    }

    // 元素下沉 k是下沉元素, end为范围
    public void siftDown(int[] nums, int k, int end){
        while(2 * k + 1 <= end){
            int j = 2 * k + 1;
            // 下沉元素
            if(j + 1 <= end && nums[j + 1] > nums[j]){
                j++;
            }
            if(nums[j] > nums[k]){
                swap(nums,j,k);
            }else{
                break;
            }
            k = j;
        }
    }

    // 交换数组元素
    public void swap(int[] nums, int index1, int index2){
        int temp = nums[index1];
        nums[index1] = nums[index2];
        nums[index2] = temp;
    }
}

总结:

**时间复杂度:**O(N * logN)