冒泡、选择、直接插入、归并、快速、堆排序的分析、实现与优化

冒泡排序

图解

在这里插入图片描述

代码

import java.util.Arrays;

/**
 * 冒泡排序(也叫下沉排序)
 * 从前往后,两两比较,大的往后走(像石头沉底),小的往前走(像气泡浮出水面)
 * 时间复杂度:O(N^2)  (两层for循环)
 * 空间复杂度:O(1) (tmp)
 * 稳定性:稳定
 * (1  2  1' 3   5  在排序前后1和1'相对位置不发生变化)
 * 优化:
 * 当本身有序:1 2 3 4 5
 * 第一趟,不会进入if1 2 3 4 5
 * 1 2 3 4
 * 1 2 3
 * 1 2
 *
 * 0 3 6 8 10
 */
public class Bubble {
//注意方法的修饰符
    public static void BubbleSort(int[] array){
        if(array == null || array.length == 0){
            return;
        }
        //趟数
        for(int i=0; i<array.length-1; i++){
        //一趟冒泡的过程 ii
        // j是要处理的元素下标
        // array.length-1是为了j+1不越界,
        // i是因为第i趟有(0到i-1共)i个已到最终位置
            for(int j=0; j<array.length-1-i; j++){
                if(array[j] > array[j+1]){
                    int tmp = array[j];
                    array[j] = array[j+1];
                    array[j+1] = tmp;
                }
            }
        }
    }

//    优化(当序列本身有序: 1 2 3 4 5  不用移动)
    public static void optimizedBubbleSort(int[] array){
        if(array == null || array.length == 0){
            return;
        }
        //趟数
        boolean needNextSort = true;//必须初始化,先为真,才能进入到下面的for
        for(int i=0; i<array.length-1 && needNextSort; i++){
            //一趟冒泡的过程
            needNextSort =  false;//先假定不需要下一次排序
            for(int j=0; j<array.length-1-i; j++){
                if(array[j] > array[j+1]){
                    int tmp = array[j];
                    array[j] = array[j+1];
                    array[j+1] = tmp;
                    needNextSort = true;
                }
            }
        }
    }

    public static void main(String[] args) {
        int[] array = {10, 6, 8, 2, 3, 0, 10, 15, 20, 7, 4};
        //BubbleSort(array);
        optimizedBubbleSort(array);
        System.out.println(Arrays.toString(array));
    }
}

选择排序

分析具体的所有情况,进行基本的代码实现;
进一步的总结统一规律,进行逻辑优化

图解

在这里插入图片描述

代码

import java.util.Arrays;

/**
 * 选择排序
 * (默认都是从小到大的顺序)
 * 从当前 待排序列 中选择最 小 值(或最大值)与第 一 个值(或最后一个值进行交换)(等于时不交换)
 * 时间复杂度:O(N^2)
 * 空间复杂度:O(1)
 * 稳定性:不稳定
 *  2 3 2' 0 4    => 0 3 2' 2 4 =>。。。=>0 2' 2 3 4
 */
public class Select {
    public static void selectSort(int[] array){
        if(array == null || array.length == 0){
            return;
        }

        //最后一趟剩一个就不用比了
        for(int i=0; i<array.length-1; i++){
            int minIndex = i;//指向最小的下标
            // 第i趟时,前面的0...i-1都已经就位了
            for(int j=i+1; j<array.length; j++){
                if(array[j] < array[minIndex]){ //等于时不交换
                    minIndex = j;//更新当前最小
                }
            }
            if(minIndex != i){
                int tmp = array[minIndex];
                array[minIndex] = array[i];
                array[i] = tmp;
            }
        }
    }



    //    O(log N)  每一趟即选择最小与第一个交换,又选择最大与在最后一个交换
    public static void optimizedselectSort(int[] array){
        if(array == null || array.length == 0){
            return;
        }
        //次数
        for(int i=0; i<array.length/2; i++){
            int minIndex = i;//(因前面0.。。i-1已就位)
            int maxIndex = i;
            //选择排序的过程
            for(int j=i+1; j<=array.length-1-i; j++){
                if(array[j] > array[maxIndex]){
                    maxIndex = j;//维护指针,先不移动
                    continue; //比max大时,肯定比min小不成立,所以进入到下一轮
                }
                if(array[j] < array[minIndex]){
                    minIndex = j;
                }
            }

            //minIndex保存当前待排序列的最小值   maxIndex保存当前待排序列最大值
            //i保存当前最小值要放的位置    array.length-1-i保存当前最大值要放的位置
            int tmp = array[i];
            array[i] =array[minIndex];
            array[minIndex] = tmp;

            //因为max原本和min指向同一个位置i,有可能i位置是当前最大值,此时若把i位置元素和min交换,应该把max指向当前最大值的新位置
            if(maxIndex == i){
                maxIndex = minIndex;
            }
            tmp = array[array.length-1-i];//i==maxIndex, 当前最大
            array[array.length-1-i] = array[maxIndex];
            array[maxIndex] = tmp;
        }
    }

    public static void main(String[] args) {
        int[] array = {10, 5, 3, 1, 0, 7, 9, 8, 2};
//        selectSort(array);
        optimizedselectSort(array);
        System.out.println(Arrays.toString(array));
    }
}

直接插入排序

图解

在这里插入图片描述

代码

import java.util.Arrays;

/**
 * 直接插入排序
 * 模拟打扑克,从 待排序列 中拿数据(接牌)tmp,
 * 从 手中数据(当前已经排好的牌)  中从前往后找 第一个大于tmp的,插到其前面(放牌);如果前面都比他小,就直接追加到最后面
 *
 * 时间复杂度:O(N^2)
 * 空间复杂度:O(1)
 * 稳定性:稳定
 */
public class Insert {
    public static void insertSort(int[] array){
        if(array == null || array.length == 0){
            return;
        }

        //拿(数据)牌
       //第i趟,有0...i-1个已就位
        for(int i=0; i<array.length; i++){
            int tmp = array[i];//要插入的元素
            int j=0;//要插入的位置(写到这里,为了考虑他的作用域!!!)
            for(; j<i; j++){ // 在已经有序的共前i个元素里,找第一个 大于 的位置
                if(array[j] > tmp){
                    //挪数据,插数据 这一条件有可能进入有可能不进入
                    break;
                }
            }
            //挪数据过程(用了同一个数组  而且 从后往前移 )
            for(int k=i-1; k>=j; k--){
                array[k+1] = array[k];
            }
            //放数据
            array[j] = tmp;
        }
    }


    public static void optimizedInsertSort(int[] array){
        if(array == null || array.length == 0){
            return;
        }
        //拿(数据)牌
        for(int i=0; i<array.length; i++){
            int tmp = array[i];//要放的元素
            //从后往前 边找边移(合并上面算法的两个for循环)
            int j;
            for(j=i-1; j>=0; j--){
                if(array[j] > tmp){  //
                    array[j+1] = array[j];//j位置元素后移一个;
                }else{
                    //第一次 发现要插入位置的元素 比 要插入的元素大,放到他后面
                    break;//不执行j--了
                }
            }
            //放数据
            array[j+1] = tmp;
        }
    }
    public static void main(String[] args) {
        int[] array = {10, 2, 9, 0, 5, 3, 8, 7, 12, 6};
        //insertSort(array);
        optimizedInsertSort(array);
        System.out.println(Arrays.toString(array));

    }
}

归并排序

图解

在这里插入图片描述

代码

非递归

import java.util.Arrays;

/**
 * 归并排序
 * 思想:   把两个有序的序列合并为一个有序的序列
 * 如何保证两个序列是有序的?  两个序列中元素个数为1
 * 时间复杂度:O(N*log2N)   (log2 N趟,每趟遍历所有元素)
 * 空间复杂度:O(N)  (tmp)
 * 稳定性:稳定
 */
public class Merge {
    public static void merge(int[] array, int gap){
       //左归并段首末
        int left1 = 0;
        int left2 = left1 + gap -1;//比如长为2,就是0..1
        //右归并段首末
        int right1 = left2+1;
        int right2 = (right1 + gap -1 < array.length-1) ? right1 + gap -1 : array.length-1;
        //  存放每一轮的结果
        int[] tmp = new int[array.length];
        int index = 0;

        //不断两两归并,把所有元素都过一遍
        //情况1:只要右边归并段 存在 就可以
        while(right1 < array.length){
            //合并两个有序的归并段(当两个归并段 中的任一段都没合并完时)
            while(left1 <= left2 && right1 <= right2){
                //左段的最小 和 右段的最小比
                if(array[left1] <= array[right1]){
                    tmp[index++] = array[left1++];
                }else{
                    tmp[index++] = array[right1++];
                }
            }
            //退出上面的while条件说明已经有一个归并段已经提前一步归并完成
            while(left1 <= left2){
                tmp[index++] = array[left1++];
            }

            while(right1 <= right2){
                tmp[index++] = array[right1++];
            }
            //index 在每一轮merge里都是一直++,往右走

            //更新 归并段的起始和结束位置,准备下一轮while
            left1 = right2+1;
            left2 = left1 + gap - 1;
            right1 = left2+1;
            right2 = (right1 + gap -1 < array.length-1) ? right1 + gap -1 : array.length-1;
        }


        //情况2:只有一个归并段(可能它还不够一个段的长度 ;  相当于最右边的一个段  落单了,不能配对),特殊处理
        while(left1 < array.length){
            tmp[index++] = array[left1++];
        }
        //把临时数组结果  转移给array
        for(int i=0; i<array.length; i++){
            array[i] = tmp[i];
        }
//        array=tmp;// 发现array没得到tmp的值,只可应用,不可修改
    }

    public static void mergeSort(int[] array){
        if(array == null || array.length == 0){
            return;
        }

       //log2N趟,可保证  有序序列长度 > 元素总个数
        for(int i=1; i<array.length; i*=2){
            merge(array, i);//i即 有序段长度
        }
    }
    public static void main(String[] args) {
        int[] array = {10, 15, 2, 10, 18, 3, 5, 7, 20, 19, 35, 23, 1};
        mergeSort(array);
        System.out.println(Arrays.toString(array));
    }
}

递归

在这里插入图片描述

import java.util.Arrays;

public class Merge {
    //==================mergesort=================
    private static void mergeSort(int[] arr){
        int[] temp = new int[arr.length]; //存放临时结果

        mergeSort(arr, temp, 0, arr.length - 1);
        System.out.println(Arrays.toString(arr));
    }
    private static void mergeSort(int[] arr, int[] temp, int l, int r){
        if (l == r) return;

        int mid = l + (r-l)/2;//如  0 +(3-0)/2=1
        mergeSort(arr, temp, l, mid); //递归调自己,层层拆分    左闭 右闭
        mergeSort(arr, temp, mid + 1, r);

        merge(arr, temp, l ,mid, r);// 合并
    }
    private static void merge(int[] arr, int[] temp, int l, int mid, int r){
        for (int i = 0 ; i < arr.length; i ++){
            temp[i] = arr[i];
        }
        int p1 = l;
        int p2 = mid + 1;
        int k = l;// 用于 遍历 这两个小段的每个元素  以进行 有序合并

        // 直到p1 或 p2  到边界了
        while(p1 <= mid && p2 <= r){ //将两个段 中  当前比较位置的  较小元素  先放到array
            if (temp[p1] < temp[p2]){
                arr[k] = temp[p1];
                p1++;
            }else{
                arr[k] = temp[p2];
                p2++;
            }
            k++;
        }

        while(p1 <= mid){
            arr[k] = temp[p1];
            p1++;
            k++;
        }
        while(p2 <= r){
            arr[k] = temp[p2];
            p2++;
            k++;
        }
    }

    public static void main(String[] args) {
        int[] nums={0,3,2,6,5,8,9,4,7};
        mergeSort(nums);
    }
}

快速排序(重要,不稳定)

图示

一次划分:
在这里插入图片描述

在这里插入图片描述

代码

import java.util.Arrays;
import java.util.Stack;

/**
 * 快速排序
 * 思想:
     * 在待排序列中确定一个 基准mar,一般来说array[0], 然后确定指针low,high分别指向待排序列的第一个元素和最后一个元素,

     * 1.从后往前比较array[high]和mar,
         * 如果high位置元素大于基准,high继续往前(左)走;
         * 如果high位置元素小于基准,high表示的元素 移动 至前面位置;
     * 2.从前往后比较array[low]和mar,
         * 如果low位置元素小于基准,low继续往后(右)走;
         * 如果low位置元素大于基准,low表示的元素 移动 至后面位置,
     * 重复1.2操作,直到low  high相等,然后就把基准放在这里
     * 以上为一次划分,最终的结果为:基准的左边都是比它小的元素,基准的右边都是比它大的元素;

     * 针对基准左右两边的序列 继续 进行一次更细的划分,最终即可得到一个完全有序的序列
     *
 * 时间复杂度:O(N * log2N)  (n次(指数量级)quick调自己,二分(log2N)子序列然后处理partition)
 * 空间复杂度:O(log2N)  (将当前序列一分为二,然后递归调自己(quick)   主要是递归造成的栈空间的使用,平均情况,递归树的深度为log2N)
 * 稳定性:不稳定
 *    2 1  1’=》1' 1 2
 *
 *  先写代码框架,然后实现细节
 */
public class Quick {
//    上面的函数被下面的函数调用
//    划分
    public static int partition(int[] array, int low, int high){
        int mar = array[low];
        while(low < high){
            //从后往前找比基准小的
            //如果比基准大 high--
            while(low < high && array[high] >= mar){
                high--;
            }
            if(low == high){
                break;
            }
            if(array[high] < mar){
                array[low] = array[high];
            }

            //从前往后找比基准大的
            //如果比基准小 low++
            while(low < high && array[low] <= mar){
                low++;
            }
            if(low == high){
                break;
            }
            if(array[low] > mar){
                array[high] = array[low];
            }
        }
        //此时low 和 high 相等
        array[low] = mar;// 此时 基准值位置  左边的全都比他小,右边的都比他大
        return low;//虽然没有传出array,但却真实的改变了数组元素位置
    }
    public static void quick(int[] array, int low, int high){
        if(low >= high){
            return;
        }
        int mar = partition(array, low, high);

        //重复逻辑
        //mar是位置  基准左边序列个数>=2  才用继续划分
        if(mar-1 > low){
            quick(array, low, mar-1);//递归调自己
        }
        //基准右边序列个数>=2 继续划分
        if(mar+1 < high){
            quick(array, mar+1, high);
        }
    }

//实现1(马甲函数)
    public static void quickSort(int[] array){
        //合法性检查
        if(array == null || array.length == 0){
            return;
        }
        //真正主体
        quick(array, 0, array.length-1);
    }
//实现2(合并了quick和quicksort,没有了quick递归调用自己)
    public static void quickByWhile(int[] array){
        if(array == null || array.length == 0){
            return;
        }
        Stack<Integer> stack = new Stack<>();

        stack.push(0);//压入low的下标
        stack.push(array.length-1);//high

        //左右子序列不push时,栈就为空了
        while(!stack.isEmpty()){
            //初始
            int high = stack.pop();
            int low = stack.pop();
            int mar = partition(array, low, high);

            //判断左边序列是否需要进行划分 进行划分序列的起始结束位置继续入栈
            if(mar-1 > low){
                //重新指定界限
                stack.push(low);
                stack.push(mar-1);
            }
            //判断右边序列是否需要进行划分 进行划分序列的起始结束位置继续入栈
            if(mar+1 < high){
                stack.push(mar+1);
                stack.push(high);
            }
        }
    }

    public static void main(String[] args) {
        int[] array = {8, 9, 2, 2, 10, 15, 8, 7, 19,0, 2, 13};
        //quickSort(array);
        quickByWhile(array);
        System.out.println(Arrays.toString(array));
    }
}

优化基准

https://blog.csdn.net/qq_41571459/article/details/117711828

堆排序(重要 ,不稳定)

概念

二叉树:
有且只能有左右两个节点
在这里插入图片描述

完全二叉树:
若设二叉树的高度为h,则共有h+1层。除第h层外,
其它各层(0 ~ h-1)的结点数都达到最大个数,第h层从右向左连续缺若干结点
在这里插入图片描述

二叉树:
所有的层都是满(看作是特殊的完全二叉树)
在这里插入图片描述
根节点和子节点的关系
在这里插入图片描述

图解

在这里插入图片描述

代码

import java.util.Arrays;

/**
 * 堆排序
 * <p>
 * 时间复杂度:  建 堆的O(N*log2N)   (从最后一个子树根,--到第一个根,是O(n)    调整子树(每次将待排序序列,一分为二,)(log2(N))
 * 空间复杂度:O(1)
 * 稳定性:不稳定
 *
 *
 * 规律: 一般只要涉及到跳跃性的交换顺序 就是不稳定的
 *    3
 *  27 36
 * 27'
 * 如果堆顶3先输出,则第三层的27'(跑到堆顶,然后堆稳定,继续输出堆顶(27'),...=>输出顺序:3  27'   27  36。
 */
public class Heap {
    public static void adjust(int[] array, int start, int end) {
        //start是作为当前调整子树的根节点
        int tmp = array[start];

        //有可能调整后,她的子树不成立了,需要将原根节点值 层层下沉,放到最终位置,从而腾出了根节点位置,放最大值
        for (int i = 2 * start + 1; i <= end; i = 2 * i + 1) {
            //判断 是否存在 右孩子   并找  左右孩子的最大值
            if (i + 1 <= end && array[i] < array[i + 1]) {
                i++; //即若右孩子较大,就使i为右孩子下标;再和tmp比
            }
            if (array[i] > tmp) {
                array[start] = array[i];//让根为最大值(即最大值往上走)
                start = i;//即 最大值原位置 是 新的待调整子树的根节点
            } else {
                break;//直到下面的子树也满足条件
            }
        }
        array[start] = tmp;//原根节点值  能满足所有条件,最终可以放的位置
    }

    public static void heapSort(int[] array) {
        if (array == null || array.length == 0) {
            return;
        }

        //第一步:(从下往上 从右往左)建立大根堆                   (那建立小根堆,由大到小排序怎么写呢??????)
        // 0...array.length -   =>array.length - 1 - 1)/2是最后一个子树的根节点序号
        for (int i = ((array.length - 1 - 1) / 2); i >= 0; i--) {
            adjust(array, i, array.length - 1);
        }

        //第二步:将根节点与待排序列最后一个节点进行互换,互换之后需要继续调整
        // i趟,就有i个元素已就位
        for (int i = 0; i < array.length; i++) {
            // 交换首尾(即将最大值 放在 最后面)
            int tmp = array[0];
            array[0] = array[array.length - 1 - i];//0...array.length - 1,而后面i个已就位
            array[array.length - 1 - i] = tmp;

            //调整 余下的
            adjust(array, 0, array.length - 1 - i - 1);
        }
    }

    public static void main(String[] args) {
        int[] array = {5, 10, 8, 1, 19, 20, 29, 28, 15, 3, 6, 17, 0};
        heapSort(array);
        System.out.println(Arrays.toString(array));
    }
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值