直接插入、冒泡、快排、二路归并、堆排序算法的笔记

前言:

本文是个人阅读以下三篇文章做的笔记。跟着敲了几遍并尝试背诵。

这里仅仅记录个人阅读代码的注解,建议还是去看原文,从排序思想到代码都十分详细,拷过来就能跑,就能直接提交leetcode的那种。

​​​​​​排序(一)——简单排序:插入排序 && 冒泡排序_无限之阿尔法的博客-CSDN博客

排序(二):分而治之——快速排序 && 归并排序_无限之阿尔法的博客-CSDN博客_分而治之的意思是什么

排序(三):谁主沉浮——堆与堆排序_无限之阿尔法的博客-CSDN博客

算法这块以前没有重视过,希望之后能在前辈们的博客中慢慢学习。

原创老哥的个人博客:搬砖日志

大家多多支持呀


一、直接插入排序

        /**
         * 直接插入排序
         */
        int i,j;
        for (i  =1; i < nums.length; i++){
            // 先比较排好序的最后一个元素,如果小于最后一个元素,开始交换
            if (nums[i] < nums[i-1]){
                int temp = nums[i];
                nums[i] = nums[i-1];
                // 一直比较,如果nums[i]小于这些元素,这些元素一直向后移动
                for (j = i-2; j >= 0 && nums[j] > temp; j--){
                    nums[j+1] = nums[j];
                }
                // 直到nums[i]>nums[j], 应该把nums[i]放在nums[j+1]的位置
                nums[j+1] = temp;
            }
        }


二、冒泡排序及其优化

1. 简单的冒泡排序。

        /**
         * 冒泡排序
         *
         * i=0, j从0扫描到length-1,最后第length-1个元素是最大元素
         * i=1, j从0扫描到length-2,最后第length-2,length-1两个位置是有序的。
         * 以此类推
         * 第i躺排序开始前,无序序列是0到length-i-1
         * 结束时,第length-i-1个元素是这趟排序的最大值,
         * 结束时,无序序列是0到length-i-2
         * 然后第i+1趟排序开始
          */
        int i, j;
        for (i = 0; i < nums.length; i++){
            for (j = 0; j < nums.length-i-1; j++){
                if (nums[j] > nums[j+1]){
                    int temp = nums[j];
                    nums[j] = nums[j+1];
                    nums[j+1] = temp;
                }
            }
        }

2. 优化的冒泡排序。

如果一趟排序发现没有发生交换,则说明已经有序,直接退出循环。

        /**
         * 冒泡排序优化1
         * 记下最后一次交换的位置,后边没有交换,必然是有序的,
         * 然后下一次排序从第一个比较到上次记录的位置结束即可。
         * 同时引入布尔变量 sorted 作为标记,如果在一趟排序中没有交换元素,
         * 说明这组数据已经有序,不用再继续下去。
         */
        int temp = 0;
        /**
         * 最后一次交换的位置j,就是无序、有序的边界
         * 0到j是无序的,j+1到length-1是有序的
         */
        int lastExchangeIndex = 0; // 上次(最后一次)交换位置
        int border = nums.length-1; // 无序列表边界
        for (int i = 0; i < nums.length; i++){
            /**
             * 每一躺开始前,把sorted置为true,如果发生交换,就置为false,说明这一趟经历的序列是无序的
             * 一趟结束后,sorted如果还是true,说明这一趟没有交换,序列是有序的,直接跳出排序
             */
            boolean sorted = true;
            for (int j = 0;j<border;j++){
                if (nums[j]>nums[j+1]){
                    temp = nums[j+1];
                    nums[j+1]= nums[j];
                    nums[j]=temp;
                    /**
                     * a[j] a[j+1],交换后j+1到length-1是有序的,0到j是无序的
                     */
                    lastExchangeIndex = j;
                    sorted = false;
                }
            }
            if (sorted) {
                break;
            }
        /**
         * 注意区分,边界border 最后交换位置lastExchangeIndex
         */
            border = lastExchangeIndex;
        }

3. 鸡尾酒排序

基于冒泡排序的优化的双向冒泡排序,适合用于基本有序的序列。


        /**
         * 冒泡排序优化2
         * 鸡尾酒排序(双向冒泡排序)
         * 奇数轮从左到右,偶数轮从右到左
         *
         * 适用于基本有序的序列
         */
        int borderRight = nums.length - 1;
        int borderLeft = 0;
        int lastExchangeIndexRight = 0;
        int lastExchangeIndexLeft = 0;

//i<len/2. 不取等号
        for (int i = 0; i< nums.length/2; i++){
            //正向
            boolean sorted = true;
// i到borderRight.   不取等号
// 为什么是i? 因为第i趟左边有i个已经排好序的元素
            for (int j = i;j< borderRight; j++){
                if (nums[j]>nums[j+1]){
                    int temp = nums[j];
                    nums[j] = nums[j+1];
                    nums[j+1] = temp;
                    lastExchangeIndexRight = j;
                    sorted = false;
                }
            }
            if (sorted){
                break;
            }
            borderRight = lastExchangeIndexRight;

            //反向
            sorted = true;
// len-i-1.  不取等号
// 为什么是len-1-i? 因为第i趟右边有i个已经排好序的元素
            for (int j = nums.length-1-i; j>borderLeft;j--){
//特别注意这里是j与j-1比较
                if (nums[j] < nums[j-1]){
                    int temp = nums[j];
                    nums[j] = nums[j-1];
                    nums[j-1] = temp;
                    lastExchangeIndexLeft = j;
                    sorted = false;
                }
            }
            if (sorted){
                break;
            }
            borderLeft = lastExchangeIndexLeft;
        }


三、快速排序

/**
     * 每个部分排序
     */
    public int partition(int[] nums, int low, int high){
        // 1. 取出枢纽(第一个元素)
        int base = nums[low];
        // 2. 两个指针 left,right
        int left = low, right = high;
        // 3. 扫描,要时刻注意left<right这个条件!
        while(left<right){
            // 从后开始扫描,找到第一个比base小的元素
            while (left<right && nums[right]>=base){
                right--;
            }
            //left<right这个条件一定要加上
            if(left<right){
                nums[left] = nums[right];
            }
            // 从前开始扫描,找到第一个比base大的元素
            while(left<right && nums[left]<=base){
                left++;
            }
            if(left<right){
                nums[right] = nums[left];
            }
        }
        // 4. left == right,把base放到这个位置
        nums[left] = base;
        // 5. 返回枢纽位置
        return left;
    }
    /**
     * 递归快速
     */
    public void quickSort(int[] nums, int left, int right){
        // 1. 递归结束条件,特别注意!
        if (left>right){
            return;
        }
        // 2. 递归调用
        count++;
            // 先进行一趟排序,再对两部分进行排序
        int pos = partition(nums, left, right);
                //0 - pos-1
        quickSort(nums,left, pos-1);
                //pos+1 - right
        quickSort(nums,pos+1,right);
    }
/**
* 快速排序
*/
quickSort(nums,0,nums.length-1);


四、归并排序

  • 快速排序,将序列分成两份,是根据一趟排序,枢纽的最终位置划分的
  • 二路归并排序,将序列分成两份,是直接(high+low)/2拿到中间坐标的
  • 两种排序方法都是递归调用,并且都有一个处理函数
    /**
     * 进行归并
     * 归并的具体过程
     *   1. 已经分别得到两个有序序列,
     *   2. 要将他归并成一个有序序列
     */
    public void merge(int[] nums, int left, int mid, int right){
        //1. 临时存储的数组,存储排序好的元素
        int[] temp = new int[right - left +1];
            //两个序列,左序列left -- mid,右序列 mid+1 -- right 分别有序
            //两个指针i, j分别指向左右序列的头
            //k用来遍历临时数组temp
        //注意不是0,是left
        int i = left;
        int j = mid+1;
        int k = 0;
        //2. 开始扫描归并。当两个序列都没扫描完时。注意这个条件。
        while(i <= mid && j <= right){
                //左序列值 < 右序列值, 将左序列值放到temp数组,扫描左序列下一个
            if (nums[i] < nums[j]){
                temp[k++] = nums[i++];
            }else {
                //左序列值 >= 右序列值, 将右序列值放到temp数组,扫描左右序列下一个
                temp[k++] = nums[j++];
            }
        }
        //3. 左右序列有一个已经扫描完(以下两个while只会执行其中一个)
            //情况1:右序列扫描完,左序列没有。
            //把左序列所有的数依次放到temp数组
        while (i <= mid){
            temp[k++] = nums[i++];
        }
            //情况2:左序列扫描完,右序列没有。
            //把右序列所有的数依次放到temp数组
        while (j <= right){
            temp[k++] = nums[j++];
        }

        //4. temp数组就是两个序列归并好的有序的数组,依次放入nums数组中,位置是left--right
        // 不能忘记
        for (k = 0; k<=right-left; k++){
            nums[k+left] = temp[k];
        }
    }
    /**
     * 递归处理归并
     * 递归调用,直接分成两份分别递归,然后归并
     *   ★与快排对比,快排是先partition处理,再两个分别递归quickSort★
     *   ★归并是先递归mergeSort,在merge归并处理★
     *   ★去理解递归★
     */
    public void mergeSort(int[] nums, int left, int right){
        if (left < right){
            //1. 二路归并,将序列拆分成2个序列
            // 注意mid的求法,不是left - right + 1,left - right + 1是长度
            int mid = (left + right)/2;
                //两个序列分别递归处理
            mergeSort(nums, left, mid);
            mergeSort(nums, mid+1, right);

            //2. 进行处理:将上面拆分的2个序列,归并成一个
            merge(nums,left,mid,right);
        }
    }
mergeSort(nums, 0, nums.length-1);


五、堆排序

这里使用筛选法进行调整堆。

/**
         * 堆排序
         * 1. 建大根堆,堆顶是最大元素
         * 2. 将其与末尾元素交换,此时末尾元素为最大值
         * 3. 然后重新调整剩余的元素,得到大根堆,交换倒数第二个元素
         * 4. 循环,最终得到一个有序序列。
         */

        int len = nums.length;
        //1. 从第len/2个元素开始,向上进行建堆
            //因为是 0 -- len-1,所以第len/2个元素,下标是len/2 -1
        for (int i = len/2 -1; i>=0; i--){
            adjustHeap(nums,i,len);
        }

        //2. 堆顶的最大元素 与 未排序好的序列的末尾元素交换,重新调整堆
            //注意这个变量j,堆顶元素与未排序好的末尾元素交换后 nums[0]--nums[j]是无序的序列。需要重新调整堆

            // (1)j=len-1:   (传入adjustHeap方法中时,作为size,意思是对0--j重新调整)
                //① 先把最大的元素放到 len-1 处
                //② 然后对0--len-2这些元素重新构建堆

            // (2)j=len-2
                //① 先把最大的元素放到 len-2 处
                //② 然后对0--len-3这些元素重新构建堆

            //......

            // (len-2)j=2
                //① 先把最大的元素放到 2 处
                //② 然后对0--1这些元素重新构建堆

            // (len-1)j=1
                //① 先把最大的元素放到 1 处
                //② 然后对0这些元素重新构建堆

        for (int j=len-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 size){
        int temp = nums[i];
        /**
         * 原来还可以这样for循环
         */
        //由于是 0 -- size-1, 所以左孩子是2*i+1
        for (int child = 2*i+1; child<size; child = 2*child+1){
            //1. 选择 较大的子节点
                //如果,结点有右孩子,且 右孩子比左孩子大
            if (child+1 < size && nums[child+1]>nums[child]){
                    //那么选择 右孩子
                child++;
            }   //否则就是左孩子

            //2. 父节点 和 较大的子结点 进行比较
                //(1)如果父节点 < 较大的子节点,进行交换
            if (temp < nums[child]){
                    // 这里不是交换,这里是覆盖,因为有temp,可以放到最后正确的位置
                nums[i] = nums[child];
                    // (注意)把这个孩子当作父节点,继续执行循环,向下比较
                i = child;
            }else {
                //(2)如果父节点 >较大的子节点,结束比较,跳出循环
                break;
            }
        }
            //3. 值放到最后的位置
        nums[i] = temp;
    }

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值