认识O(NlogN)的排序

归并排序

归并排序(任何一个递归)如果不懂可以画一个树状结构去帮助自己去理解。

核心排序方法为Merger

public class 归并排序 {
    public static void main(String[] args) {
        int[] arr1 = {3, 1, 2, 2, 5, 6};
        int[] arr2 = Arrays.copyOf(arr1, arr1.length);
        process(arr1, 0, arr1.length - 1);
        sort(arr2);
    }

    /**
     * 归并排序
     * 1.将左边排好序,再将右边排好序
     * 2.对比左右两个区间再进行排序
     * 递归过程,当 L==R 时停止,也就是当数组被二分为只有一个数时停止,
     * 这时他的上层只有两个数,调用merger进行排序
     */
    public static void process(int[] arr, int L, int R) {
        if (L == R) {
            return;
        }
        //获取中点位置,为避免整数溢出现象而不写为(R + L) / 2,
        //原因是如果R与L都是int边缘,相加将越界
        int mid = L + ((R - L) >> 1);
        //左区域排序
        process(arr, L, mid);
        //右区域排序
        process(arr, mid + 1, R);
        //对比左右两个区间再进行排序
        merger(arr, L, mid, R);
        //打印
        print(arr, "arr1");
    }

    /**
     * 两个指针,一个指向L(最左索引),一个指向R(最右索引)
     * 如果L,R都不越界,对比L,R大小将排好序的数据放入新数组
     * 如果其中一个越界,将未越界数组剩余数据放入新数组的后面
     */
    private static void merger(int[] arr, int L, int mid, int R) {
        int[] help = new int[R - L + 1];
        //记录新数组排序到的位置
        int i = 0;
        int p1 = L;
        int p2 = mid + 1;
        while (p1 <= mid && p2 <= R) {
            help[i++] = arr[p1] <= arr[p2] ? arr[p1++] : arr[p2++];
        }
        //如果右边越界,左边剩余数据会全部填充到新数组后边
        while (p1 <= mid) {
            help[i++] = arr[p1++];
        }
        while (p2 <= R) {
            help[i++] = arr[p2++];
        }
        for (i = 0; i < help.length; i++) {
            arr[L + i] = help[i];
        }
    }

    public static void sort(int[] arr) {
        Arrays.sort(arr);
        print(arr, "arr2");
    }

    public static void print(int[] arr, String a) {
        StringBuilder sb = new StringBuilder();
        sb.append(a + "= {");
        for (int i = 0; i < arr.length; i++) {
            if (i > 0) {
                sb.append(", ");
            }
            sb.append(arr[i]);
        }
        sb.append("}");
        System.out.println(sb.toString());
    }
}

小和问题/逆序对问题(归并排序变种)

        小和问题:在一个数组中,每一个数左边比当前数小的数累加起来,叫做数据的小和。

例:数组{1,3,4,2,5} 1的小和为0 、3的小和为 1、4的小和为1+3 = 4、2的小和为 1、5的小和为1+3+4+2 = 10。

        逆序对问题:在一个数组中,左边的数如果比右边的数大,则两个数构成一个逆序对,请计算出逆序对的数量。

思考逻辑        

 正常思维(暴力解法):小和问题,需要向左寻找比自己小的数,例如:3向前寻找1,4向前寻找1,3。如果是这种情况下数组左边的数每次都需要被遍历一次,没有重复利用其之前搜寻到的数据。

逆向思考:小和问题,正常向左寻找比自己小的数,边为向右寻找比自己大的数。也就是1右边有4个数比自己大,就是4个1、3右边有2个数比自己大,就是2个3、4右边有一个数比自己大,就是一个4、2右边有一个数比自己大也就是一个2.共计16.

在上述过程中我们发现1考虑过之后后面数就无需考虑了,也就是利用到之前的信息了。

小和问题/逆序对问题的关键就是找到逆向思路,根据其能够重复利用信息的特点就可以考虑归并排序。

public class 小和问题/逆序对问题 {
    public static void main(String[] args) {
        int[] arr = {3, 2, 4, 5, 0};
        int[] testArr = Arrays.copyOf(arr, arr.length);
        int nums = process(testArr, 0, testArr.length - 1);
        System.out.println(nums);
    }

    public static int process(int[] arr, int L, int R) {
        if (L == R) {
            return 0;
        }
        int mid = L + ((R - L) >> 1);
        //需要左右两边统计和合并统计
        return process(arr, L, mid)
                + process(arr, mid + 1, R)
                + merger(arr, L, mid, R);
    }

    private static int merger(int[] arr, int L, int mid, int R) {
        int[] help = new int[R - L + 1];
        int i = 0;
        int p1 = L;
        int p2 = mid + 1;
        int res = 0;
        while (p1 <= mid && p2 <= R) {
            //逆序对问题可以转变为,找出左边比当前数大的个数
            res += arr[p1] > arr[p2] ? mid - p1+1 : 0;//逆序对问题
            //res += arr[p1] <= arr[p2] ? (R-p2+1)*arr[p1] : 0; 小和问题
            //按照顺序排序是必须的,这样就可以根据第一个位置的大小快速确认后续数据是否需要统计
            //后续数据就可以根据索引进行计算
            help[i++] = arr[p2] >= arr[p1] ? arr[p1++] : arr[p2++];
        }
        while (p1 <= mid) {
            help[i++] = arr[p1++];
        }
        while (p2 <= R) {
            help[i++] = arr[p2++];
        }
        for (i = 0; i < help.length; i++) {
            arr[L+i] = help[i];
        }
        return res;
    }
}

快排

荷兰旗问题

        给定一个数num,要求将数组划分为三部分,一部分是小于num,一部分是等于num,一部分是大于num。

        三指针解法:

        当前数称为current,如果current < num,指针1的前一个位置与arr[i]交换位置,指针1向右移动(为什么要交换?因为当current = num时 指针3要向前移动)

        如果current = num,那么指针3++

        如果current > num,指针2向左移动一个位置,指针2的前一个位置与与i交换位置

        指针1划分小于区域,指针2划分大于区域,指针1、2共同划分出来等于区域。指针3的作用是找出等于num的数并跳过,并且指针1、2的交换对象都是指针2.(先交换再移动。写代码的时候要知道指针该怎么移动,这是写代码的重点)

       

public class 荷兰旗问题 {
    public static void main(String[] args) {
        int[] arr = {3,2,5,4,4,0,1,9};
        int[] copy = Arrays.copyOf(arr, arr.length);
        sort(copy,4);
        for (int i : copy) {
            System.out.print(i+" ");
        }
    }
    public static void sort(int[] arr, int num) {
        int less = -1;
        int greate = arr.length;
        int index = 0;
        while(index < greate){
            if (arr[index] < num) {
                swap(arr, ++less, index++);
            }else if(arr[index] == num){
                index++;
            }else {
                swap(arr, index, --greate);
            }
        }
    }
    // 交换数组中两个元素的位置
    public static void swap(int[] arr, int i, int j) {
        int temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }
}

快排实现

        快排就是在荷兰旗问题上添加排序过程。怎么理解那,荷兰旗问题划分为大于、小于、等于三个区间,数如果处于等于的位置那么这个数在整个有序集合的位置就不会变,利用递归就可以划分出无数个小区间,小区间到只有一个数的时候,那么整个大区间就都有序了

public class 快排 {
    public static void main(String[] args) {
        int[] arr = {3,2,5,4,4,0,1,9};
        int[] copy = Arrays.copyOf(arr, arr.length);
        quickSort(copy,0,copy.length-1);
        for (int i : copy) {
            System.out.print(i+" ");
        }
    }

    public static void quickSort(int[] arr, int L, int R) {
        if (L < R) {
            //swap在这里随机抽取目标数放入R位置
            swap(arr, L + (int) (Math.random()) * (R - L + 1), R);
            //分区,需要返回下一次递归划分的区间
            int[] partition = partition(arr, L, R);
            quickSort(arr,L,partition[0]);
            //需要+1的原因是右区间右交换了一个原先在末尾位置的目标数
            quickSort(arr,partition[1]+1,R);
        }
    }

    /**
     *
     * @param L L代表着遍历的索引位置
     *          就是荷兰旗问题中的index,这里可以直接使用L代替
     * @param R 代表目标数
     *          因为随机抽取的目标数会换到最后一个位置,可以使用R代替
     */
    public static int[] partition(int[] arr, int L, int R) {
        int less = L - 1;
        int more = R;
        while (L < more) {
            if (arr[L] < arr[R]) {
                swap(arr, ++less, L++);
            } else if (arr[L] == arr[R]) {
                L++;
            } else if (arr[L] > arr[R]) {
                swap(arr, L, --more);
            }
        }
        //将目标数重新交换到等于区间内
        swap(arr, more, R);
        //返回的数组表示,大于区间与小于区间的边界,目的是为下一次递归提供划分区间信息
        return new int[]{less, more};
    }

    public static void swap(int[] arr, int i, int j) {
        int temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }
}

        核心问题,区间如何划分,也就是如何选出一个目标数。单纯用数组最后一个数作为目标数也可以,但是这样如果遇到{1,2,3,4,5,6,7,8,9}这种顺序的集合那么他的时间复杂度就为O(n^2)。所以为了避免这种情况的发生,目标数在区间内随机抽取,如果是随机抽取的情况下根据数学期望那么时间复杂度就为O(NLogN)

        

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/966417.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Qt中的绘图设备:QPixmap、QImage 和 QPicture(详细图文教程_附代码)

&#x1f4aa; 图像算法工程师&#xff0c;专业从事且热爱图像处理&#xff0c;图像处理专栏更新如下&#x1f447;&#xff1a; &#x1f4dd;《图像去噪》 &#x1f4dd;《超分辨率重建》 &#x1f4dd;《语义分割》 &#x1f4dd;《风格迁移》 &#x1f4dd;《目标检测》 &a…

w199疫情打卡健康评测系统设计与实现

&#x1f64a;作者简介&#xff1a;多年一线开发工作经验&#xff0c;原创团队&#xff0c;分享技术代码帮助学生学习&#xff0c;独立完成自己的网站项目。 代码可以查看文章末尾⬇️联系方式获取&#xff0c;记得注明来意哦~&#x1f339;赠送计算机毕业设计600个选题excel文…

JAVA:Spring Boot 集成 Disruptor 的技术指南

1、简述 在高并发应用中&#xff0c;传统的队列机制如 BlockingQueue 在面对大量请求时容易成为系统瓶颈。而 LMAX Disruptor 是一个高效的无锁队列&#xff0c;适合用来构建高吞吐、低延迟的事件处理系统。本文将介绍如何在 Spring Boot 中集成 Disruptor&#xff0c;并列出详…

使用AI工具(Deepseek or 豆包etc)话业务流程图

①打开AI工具&#xff0c;这里以Deepseek为例子&#xff1a; Deepseek官网 ②输入所要画图的业务流程的文字。 &#xff08;这里以一个用户登录的流程的文字作为例子&#xff09; mermaid在线画图网页&#xff08;根据AI工具对应生成的画图代码&#xff09; ③把AI工具生成的…

自动化测试工具:selenium

&#x1f345; 点击文末小卡片 &#xff0c;免费获取软件测试全套资料&#xff0c;资料在手&#xff0c;涨薪更快 Selenium是一个用于Web应用程序测试的工具。是一个开源的Web的自动化测试工具&#xff0c;最初是为网站自动化测试而开发的&#xff0c;类型像我们玩游戏用的按键…

UIAbility 生命周期方法

生命周期流程图 UIAbility的生命周期官方文档地址https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V13/uiability-lifecycle-V13 1. onCreate(want: Want, launchParam: LaunchParam) 触发时机&#xff1a;Ability首次创建时 作用&#xff1a;初始化核心资源…

C语言:函数栈帧的创建和销毁

目录 1.什么是函数栈帧2.理解函数栈帧能解决什么问题3.函数栈帧的创建和销毁的过程解析3.1 什么是栈3.2 认识相关寄存器和汇编指令3.3 解析函数栈帧的创建和销毁过程3.3.1 准备环境3.3.2 函数的调用堆栈3.3.3 转到反汇编3.3.4 函数栈帧的创建和销毁 1.什么是函数栈帧 在写C语言…

开箱即用的.NET MAUI组件库 V-Control 发布了!

之前写过挺多的MAUI Sample&#xff0c;其中有很多代码可以打包成组件&#xff0c;当组件完善到一定程度&#xff0c;我会把控件封装起来放到控件库中。 今天&#xff0c;在这个仓库建立一年零八个月后&#xff0c;我觉得可以考虑将其作为开源库发布。 有很多网友在观望.NET …

Qt:项目文件解析

目录 QWidget基础项目文件解析 .pro文件解析 widget.h文件解析 widget.cpp文件解析 widget.ui文件解析 main.cpp文件解析 认识对象模型 窗口坐标系 QWidget基础项目文件解析 .pro文件解析 工程新建好之后&#xff0c;在工程目录列表中有⼀个后缀为 ".pro" …

装备库室管控系统|支持国产化、自主研发

装备库室管控系统&#xff08;DW-S306&#xff09;利用现有内部网络&#xff0c;部署综合管理系统&#xff0c;形成一套上下统一、功能完善的管理体系&#xff0c;建设一个功能完善、规范有序为目标&#xff0c;实现可视化监管、数字化军械管理、安全监管于一体的物联网信息化管…

软件测试就业

文章目录 2.6 初识一、软件测试理论二、软件的生产过程三、软件测试概述四、软件测试目的五、软件开发与软件测试的区别&#xff1f;六、学习内容 2.7 理解一、软件测试的定义二、软件测试的生命周期三、软件测试的原则四、软件测试分类五、软件的开发与测试模型1.软件开发模型…

【Java基础】序列化、反序列化和不可变类

Hi~&#xff01;这里是奋斗的明志&#xff0c;很荣幸您能阅读我的文章&#xff0c;诚请评论指点&#xff0c;欢迎欢迎 ~~ &#x1f331;&#x1f331;个人主页&#xff1a;奋斗的明志 &#x1f331;&#x1f331;所属专栏&#xff1a;Java基础面经 &#x1f4da;本系列文章为个…

upx压缩工具使用说明

UPX&#xff08;Ultimate Packer for Executables&#xff09;是一款开源的可执行文件打包工具&#xff0c;能够将可执行文件&#xff08;如Windows的.exe文件或Linux的ELF文件&#xff09;进行压缩&#xff0c;以减少文件大小&#xff0c;并增加反逆向工程的难度。 下载相关安…

DeepSeek-R1 32B Windows+docker本地部署

最近国产大模型DeepSeek兴起&#xff0c;本地部署了一套deepseek同时集成Open WebUI界面,给大家出一期教程。 软件&#xff1a;Ollama、docker、Open WebUI 一、用Ollama下载模型 首先我们需要安装Ollama&#xff0c;它可以在本地运行和管理大模型。 到Ollama官网 https://ol…

活动预告 |【Part 2】Microsoft 安全在线技术公开课:通过扩展检测和响应抵御威胁

课程介绍 通过 Microsoft Learn 免费参加 Microsoft 安全在线技术公开课&#xff0c;掌握创造新机遇所需的技能&#xff0c;加快对 Microsoft Cloud 技术的了解。参加我们举办的“通过扩展检测和响应抵御威胁”技术公开课活动&#xff0c;了解如何更好地在 Microsoft 365 Defen…

(2024|CVPR,MLLM 幻觉)OPERA:通过过度信任惩罚和回顾分配缓解多模态大型语言模型中的幻觉

OPERA: Alleviating Hallucination in Multi-Modal Large Language Models via Over-Trust Penalty and Retrospection-Allocation 目录 1. 引言 2. 相关研究 2.1 多模态大语言模型 2.2 LLM 的幻觉与解决方案 2.3. 语言模型中的解码策略 3. 方法 3.1 MLLM 生成过程 3.2…

激活函数篇 03 —— ReLU、LeakyReLU、ELU

本篇文章收录于专栏【机器学习】 以下是激活函数系列的相关的所有内容: 一文搞懂激活函数在神经网络中的关键作用 逻辑回归&#xff1a;Sigmoid函数在分类问题中的应用 整流线性单位函数&#xff08;Rectified Linear Unit, ReLU&#xff09;&#xff0c;又称修正线性单元&a…

C++20新特性

作者&#xff1a;billy 版权声明&#xff1a;著作权归作者所有&#xff0c;商业转载请联系作者获得授权&#xff0c;非商业转载请注明出处 前言 C20 是 C 标准中的一个重要版本&#xff0c;引入了许多新特性和改进&#xff0c;包括模块&#xff08;Modules&#xff09;、协程…

新增md、html压缩文档上传,开放接口访问密钥改为多个,zyplayer-doc 2.4.7 发布啦!

zyplayer-doc是一款适合企业和个人使用的WIKI知识库管理工具&#xff0c;支持在线编辑富文本、Markdown、表格、Office文档、API接口、思维导图、Drawio以及任意的文本文件&#xff0c;专为私有化部署而设计&#xff0c;最大程度上保证企业或个人的数据安全&#xff0c;支持以内…

ES管理器焕新升级:紫色银狼主题来袭!

ES管理器&#xff08;安卓版&#xff09;迎来了一次令人眼前一亮的改头换面&#xff01;此次更新最直观的变化集中在UI界面设计上。开发团队大胆突破&#xff0c;摒弃了以往稍显平庸的风格&#xff0c;引入了极具个性的全新主题——以热门游戏《崩坏&#xff1a;星穹铁道》中的…