数据结构与算法:排序算法(1)

news2025/2/24 1:42:24

目录

冒泡排序

思想

代码实现

优化

鸡尾酒排序

优缺点

适用场景

快速排序

介绍

流程

基准元素选择

元素交换

1.双边循环法

使用流程

代码实现

2.单边循环法

使用流程

代码实现

3.非递归实现


排序在生活中无处不在,看似简单,背后却隐藏着多种多样的算法和思想;

根据时间复杂度的不同,主流的排序算法可以分为三大类:

1.时间复杂度为O(n^2)的排序算法

        冒泡排序

        选择排序

        插入排序

        希尔排序

2.时间复杂度为O(nlogn)的排序算法

        快速排序

        归并排序

        堆排序

3.时间复杂度为线性的排序算法

        计数排序

        桶排序

        基数排序

根据稳定性:稳定排序、不稳定排序(如果值相同的元素在排序后仍然保持着排序前的顺序,则这样的排序算法是稳定排序;如果值相同的元素在排序后打乱了排序前的顺序,则这样的排序算法是
不稳定排序)

冒泡排序

        冒泡排序,是一种基础的交换排序;这种排序算法的每一个元素都可以像小气泡一样,根据自身大小,一点一点地向着数组的一侧移动

思想

        把相邻的元素两两比较,当一个元素大于右侧相邻元素时,交换它们的位置;当一个元素小于或等于右侧相邻元素时,位置不变

        元素9作为数列中最大的元素,就像是汽水里的小气泡一样,“漂”到了最右侧;再经过多轮排序,所有元素都是有序的。

        冒泡排序是一种稳定排序,值相等的元素并不会打乱原本的顺序。由于该排序算法的每一轮都要遍历所有元素,总共遍历(元素数量-1)轮,所以平均时间复杂度是O(n^2);

代码实现

public static void main(String[] args){
        int[] array = new int[]{5,8,6,3,9,2,1,7};
        bubbleSort(array);
        System.out.println(Arrays.toString(array));
    }

    public static void bubbleSort(int array[]){
        for(int i = 0; i < array.length - 1; i++){
            for(int j = 0; j < array.length - i - 1; j++){
                int tmp = 0;

                if(array[j] > array[j+1]){
                    tmp = array[j];
                    array[j] = array[j+1];
                    array[j+1] = tmp;
                }
            }
        }
    }

使用双循环进行排序。外部循环控制所有的回合,内部循环实现每一轮的冒泡处理,先进行元素比较,再进行元素交换

优化

利用布尔变量作为标记。如果在本轮排序中,元素有交换,则说明数列无序;如果没有元素交换,则说明数列已然有序,然后直接跳出大循环

public static void bubbleSort(int array[]){
        for(int i = 0; i < array.length - 1; i++){
            boolean isSorted = true;
            
            for(int j = 0; j < array.length - i - 1; j++){
                int tmp = 0;

                if(array[j] > array[j+1]){
                    tmp = array[j];
                    array[j] = array[j+1];
                    array[j+1] = tmp;

                    isSorted = false;
                }
            }
            
            if(isSorted){
                break;
            }
        }
    }

鸡尾酒排序

        鸡尾酒排序的元素比较和交换过程是双向的;排序过程就像钟摆一样,第1轮从左到右,第2轮从右到左,第3轮再从左到右……

public static void main(String[] args){
        int[] array = new int[]{5,8,6,3,9,2,1,7};
        bubbleSort(array);
        System.out.println(Arrays.toString(array));
    }

    public static void bubbleSort(int array[]){
        int tmp = 0;

        for(int i = 0; i < array.length/2; i++){
            //有序标记,每一轮的初始值都是true
            boolean isSort = true;

            //奇数轮,从左向右比较和交换
            for(int j = i; j < array.length - i - 1; j++){
                if(array[j] > array[j+1]){
                    tmp = array[j];
                    array[j] = array[j+1];
                    array[j+1] = tmp;

                    //有元素交换,所以不是有序的,标记变为false
                    isSort = false;
                }
            }

            if(isSort){
                break;
            }

            //在偶数轮之前,将isSorted重新标记为true
            isSort=true;

            //偶数轮,从右向左比较和交换
            for(int j=array.length-i-1; j>i; j--){
                if(array[j] < array[j-1]){
                    tmp = array[j];
                    array[j] = array[j-1];
                    array[j-1] = tmp;

                    //因为有元素进行交换,所以不是有序的,标记变为false
                    isSort = false;
                }
            }

            if(isSort){
                break;
            }
        }
    }

代码外层的大循环控制着所有排序回合,大循环内包含2个小循环,第1个小循环从左向右比较并交换元素,第2个小循环从右向左比较并交换元素

优缺点

        优点:能够在特定条件下,减少排序的回合数

        缺点:代码量几乎增加了1倍

适用场景

        大部分元素已经有序的情况

快速排序

介绍

        快速排序也属于交换排序,通过元素之间的比较和交换位置来达到排序的目的。快速排序则在每一轮挑选一个基准元素,并让其他比它大的元素移动到数列一边,比它小的元素移动到数列的另一边,从而把数列拆解成两个部分;

这种思路叫做分治法;

流程

在分治法的思想下,原数列在每一轮都被拆分成两部分,每一部分在下一轮又分别被拆分成两部分,直到不可再分为止

 每一轮的比较和交换,需要把数组全部元素都遍历一遍,时间复杂度是O(n)。

基准元素选择

基准元素:在分治过程中,以基准元素为中心把其他元素移到它的左右两边。

1.直接选择数列的第1个元素

2.随机选择一个元素

可以随机选择一个元素作为基准元素,并且让基准元素和数列首元素交换位置

元素交换

选定了基准元素以后,我们要做的就是把其他元素中小于基准元素的都交换到基准元素一边,大于基准元素的都交换到基准元素另一边。

有两种实现元素交换的方法:双边循环法、单边循环法

1.双边循环法

实现了元素的交换,让数列中的元素依据自身大小,分别交换到基准元素的左右两边。

使用流程

        1.选定基准元素pivot,并且设置两个指针left和right,指向数列的最左和最右两个元素

        2.从right指针开始,让指针所指向的元素和基准元素做比较。如果大于或等于pivot,则指针向左移动;如果小于pivot,则right指针停止移动,切换到left指针

        3.轮到left指针行动,让指针所指向的元素和基准元素做比较。如果小于或等于pivot,则指针向右移动;如果大于pivot,则left指针停止移动

        4.指针大于基准元素时,所指定的元素进行交换,并切换指针

        5.当左、右指针重合时,把重合点的元素和基准元素进行交换

代码实现
public static void main(String[] args){
        int[] array = new int[]{5,8,6,3,9,2,1,7};
        bubbleSort(array, 0, array.length-1);
        System.out.println(Arrays.toString(array));
    }

    public static void bubbleSort(int[] arr, int startIndex,int endInde){
        //递归结束:startIndex >= endInde时
        if(startIndex >= endInde){
            return;
        }

        //得到基准元素
        int pivotIndex = partition(arr, startIndex, endInde);

        //根据基准元素,分成两部分进行递归排序
        bubbleSort(arr,startIndex,pivotIndex-1);
        bubbleSort(arr,pivotIndex+1,endInde);
    }

    /**
     * 双边循环法,返回基准元素位置
     * @param arr           待交换的数组
     * @param startIndex    起始下标
     * @param endIndex      结束下标
     * @return
     */
    private static int partition(int[] arr, int startIndex,int endIndex){

        //取第1个位置(也可以选择随机位置)的元素作为基准元素
        int pivot = arr[startIndex];

        int left = startIndex;
        int right = endIndex;

        while (left != right){
            //控制right 指针比较并左移
            while(left<right && arr[right] > pivot){
                right--;
            }
            //控制left  指针比较并右移
            while(left<right && arr[left] > pivot){
                left++;
            }

            if(left < right){
                int p = arr[left];
                arr[left] = arr[right];
                arr[right] = p;
            }
        }

        //pivot 和指针重合点交换
        arr[startIndex] = arr[left];
        arr[left] = pivot;

        return left;
    }

2.单边循环法

        双边循环法从数组的两边交替遍历元素,虽然更加直观,但是代码实现相对烦琐。而单边循环法则简单得多,只从数组的一边对元素进行遍历和交换。

使用流程

        1.首先选定基准元素pivot。同时,设置一个mark指针指向数列起始位置,这个mark指针代表小于基准元素的区域边界

        2.从基准元素的下一个位置开始遍历数组;如果遍历到的元素大于基准元素,就继续往后遍历;如果遍历到的元素小于基准元素,则需要做两件事:第一,把mark指针右移1位,因为小于pivot的区域边界增大了1;第二,让最新遍历到的元素和mark指针所在位置的元素交换位置,因为最新遍历的元素归属于小于pivot的区域

代码实现
public static void main(String[] args){
        int[] array = new int[]{5,8,6,3,9,2,1,7};
        bubbleSort(array, 0, array.length-1);
        System.out.println(Arrays.toString(array));
    }

    public static void bubbleSort(int[] arr, int startIndex,int endInde){
        //递归结束:startIndex >= endInde时
        if(startIndex >= endInde){
            return;
        }

        //得到基准元素
        int pivotIndex = partition(arr, startIndex, endInde);

        //根据基准元素,分成两部分进行递归排序
        bubbleSort(arr,startIndex,pivotIndex-1);
        bubbleSort(arr,pivotIndex+1,endInde);
    }

    /**
     * 双边循环法,返回基准元素位置
     * @param arr           待交换的数组
     * @param startIndex    起始下标
     * @param endIndex      结束下标
     * @return
     */
    private static int partition(int[] arr, int startIndex,int endIndex){

        //取第1个位置(也可以选择随机位置)的元素作为基准元素
        int pivot = startIndex;
        int mark = endIndex;

        for(int i=startIndex+1; i<=endIndex; i++){

            if(arr[i]<pivot){
                mark++;
                int p = arr[mark];
                arr[mark] = arr[i];
                arr[i] = p;
            }
        }

        //pivot 和指针重合点交换
        arr[startIndex] = arr[mark];
        arr[mark] = pivot;

        return mark;
    }

3.非递归实现

把原本的递归实现转化成一个栈的实现,在栈中存储每一次方法调用的参数

public static void main(String[] args){
        int[] array = new int[]{5,8,6,3,9,2,1,7};
        bubbleSort(array, 0, array.length-1);
        System.out.println(Arrays.toString(array));
    }

    public static void bubbleSort(int[] arr, int startIndex,int endInde){
        Stack<Map<String, Integer>> quickSortStack = new Stack<Map<String, Integer>>();

        Map rootParam = new HashMap();

        rootParam.put("startIndex", startIndex);
        rootParam.put("endIndex", endInde);
        quickSortStack.push(rootParam);

        while (!quickSortStack.isEmpty()) {
            Map<String, Integer> param = quickSortStack.pop();

            int pivotIndex = partition(arr, param.get("startIndex"),param.get("endIndex"));

            if(param.get("startIndex") < pivotIndex -1){
                Map<String, Integer> leftParam = new HashMap<String,Integer>();

                leftParam.put("startIndex", param.get("startIndex"));
                leftParam.put("endIndex", pivotIndex-1);
                quickSortStack.push(leftParam);
            }

            if(pivotIndex + 1 < param.get("endIndex")){
                Map<String, Integer> rightParam = new HashMap<String,Integer>();
                rightParam.put("startIndex", pivotIndex + 1);
                rightParam.put("endIndex", param.get("endIndex"));

                quickSortStack.push(rightParam);
            }
        }
    }

    /**
     * 双边循环法,返回基准元素位置
     * @param arr           待交换的数组
     * @param startIndex    起始下标
     * @param endIndex      结束下标
     * @return
     */
    private static int partition(int[] arr, int startIndex,int endIndex){

        //取第1个位置(也可以选择随机位置)的元素作为基准元素
        int pivot = startIndex;
        int mark = endIndex;

        for(int i=startIndex+1; i<=endIndex; i++){

            if(arr[i]<pivot){
                mark++;
                int p = arr[mark];
                arr[mark] = arr[i];
                arr[i] = p;
            }
        }

        //pivot 和指针重合点交换
        arr[startIndex] = arr[mark];
        arr[mark] = pivot;

        return mark;
    }

        非递归方式代码的变动只发生在quickSort方法中。该方法引入了一个存储Map类型元素的栈,用于存储每一次交换时的起始下标和结束下标。
        每一次循环,都会让栈顶元素出栈,通过partition方法进行分治,并且按照基准元素的位置分成左右两部分,左右两部分再分别入栈。当栈为空时,说明排序已经完毕,退出循环

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

vue 多环境文件配置(开发,测试,生产)

1.经常我们在开发时候会有不同环境&#xff0c;要代理的路由等等都会出现不同 配置一下三个文件打包的时候&#xff0c;执行三个不同的指令就会打包不同的环境 npm run build:dev npm run build:test npm run build:prodpackage.json 中配置scripts 指令 以,env.development…

Qt DoubleSlider双滑块支持float变化的控件,以及单滑块float控件

Qt DoubleSlider 双滑块支持float变化的控件&#xff0c;以及单滑块float控件 - 一杯清酒邀明月 - 博客园 (cnblogs.com)https://www.cnblogs.com/ybqjymy/p/13813001.html参考别人的分享&#xff0c;双滑块 看一下效果&#xff1a;

【力扣每日一题】2023.9.21 收集树中金币

目录 题目&#xff1a; 示例&#xff1a; 分析&#xff1a; 代码&#xff1a; 题目&#xff1a; 示例&#xff1a; 分析&#xff1a; 题目给我们一棵树&#xff0c;不过这棵树不是普通的树&#xff0c;而是无向无根树。给我们一个二维数组表示节点之间的连接关系&#xff…

Canal 实现MySQL与Elasticsearch7数据同步

1 工作原理 canal 模拟 MySQL slave 的交互协议&#xff0c;伪装自己为 MySQL slave &#xff0c;向 MySQL master 发送 dump协议 MySQL master 收到 dump 请求&#xff0c;开始推送 binary log 给 slave (即 canal ) canal 解析 binary log 对象(原始为 byte 流) 优点&…

OmniPlan Pro 4.6(Mac项目流程管理工具)

OmniPlan Pro 是 The Omni Group 为 macOS 和 iOS 操作系统开发的项目管理软件。它允许用户创建和管理复杂的项目、定义任务、分配资源、跟踪进度和生成报告。OmniPlan Pro 包括甘特图、网络图、关键路径分析、资源均衡和成本跟踪等功能。 借助 OmniPlan Pro&#xff0c;用户可…

在月球上看地球和太阳是怎么转的?

文章目录 参数初始化运动模型绝对坐标系以太阳和地球为中心以月球为坐标原点 参数初始化 众所周知&#xff0c;地球围绕太阳转&#xff0c;月球围绕地球转。但在地球上看&#xff0c;月亮和太阳都绕着地球转&#xff0c;那么如果我们是土生土长的月球人&#xff0c;我们看到的…

记-数据库事务隔离级别

记-数据库事务隔离级别 一、MySQL数据库默认隔离级别二、JDBC连接的事务隔离级别1. 查看JDBC连接的事务隔离级别2. JDBC连接的事务隔离级别设置过程 三、修改JDBC连接的事务隔离级别1. 全局修改2. 局部修改 一、MySQL数据库默认隔离级别 MySQL数据库默认事务隔离级别为REPEATAB…

微服务生态系统:使用Spring Cloud构建分布式系统

文章目录 什么是微服务&#xff1f;为什么选择Spring Cloud&#xff1f;Spring Cloud的关键组件示例&#xff1a;构建一个简单的微服务步骤1&#xff1a;创建Spring Boot项目步骤2&#xff1a;配置Eureka服务发现步骤3&#xff1a;创建REST控制器步骤4&#xff1a;运行项目步骤…

【R语言】完美解决devtools安装GitHub包失败的问题(以gwasglue为例)

Rstudio&#xff0c;R4.3.1&#xff0c;命令在Rstudio的命令行即console中运行。 文章目录 一、问题复述二、分析三、解决四、安装示例&#xff1a;gwasglue 一、问题复述 使用devtools安装一个github的包。 devtools&#xff1a; devtools 是 R 语言中一个非常有用的包&…

netty之数据读写源码阅读

数据读写 write 从client端的写开始看 client与服务端建立完connect后可以从future里拿到连接的channel对象。这里的channel是io.netty.channel.Channel对象。 调用其channel.writeAndFlush(msg);方法可以进行数据发送。 writeAndFlush会调用pipeline的writeAndFlush方法 …

目标检测算法改进系列之Neck添加渐近特征金字塔网络(AFPN模块)

渐近特征金字塔网络&#xff08;AFPN模块&#xff09; 在目标检测任务中&#xff0c;多尺度特征对具有尺度差异的目标进行编码具有重要意义。多尺度特征提取的常用策略是采用经典的自顶向下和自底向上的特征金字塔网络。 然而&#xff0c;这些方法存在特征信息丢失或退化的问…

【音视频】ffplay源码解析-FrameQueue队列

帧队列架构位置 结构体源码 FrameQueue结构体 /* 这是一个循环队列&#xff0c;windex是指其中的首元素&#xff0c;rindex是指其中的尾部元素. */ typedef struct FrameQueue {Frame queue[FRAME_QUEUE_SIZE]; // FRAME_QUEUE_SIZE 最大size, 数字太大时会占用大量的…

第二证券:什么是a股b股?

在我国的股市中&#xff0c;我们经常会听到“A股”和“B股”这两个名词。那么&#xff0c;终究什么是A股和B股呢&#xff1f; 首先&#xff0c;A股全称为“A股票”&#xff0c;是指在我国境内上市的以人民币计价的股票。A股首要面向国内出资者&#xff0c;只要具有必定条件的内…

如何快速检测代理IP质量?方法与工具全干货

一直以来&#xff0c;IP代理都是出海跨境业务的刚需。质量好的IP代理&#xff0c;除了在跨境业务产生巨大作用&#xff0c;在SEO监控、爬虫抓取、市场研究等领域也发挥着很大的作用。但是&#xff0c;对于IP代理的质量检测是我们选择高标准IP代理的一句&#xff0c;我们一般都会…

美国零售电商平台Target,值得入驻吗?如何入驻?

Target 是美国最大的零售商之一&#xff0c;在品牌出海为大势所趋的背景下&#xff0c;它在北美电商中的地位节节攀升。Target 商店在众多垂直领域提供各种价格实惠的自有品牌&#xff0c;吸引越来越多的跨境商家入驻&#xff0c;如美妆、家居、鞋服、日用百货等&#xff0c;随…

Mybatis学习笔记12 分页插件

Mybatis学习笔记11 缓存相关_biubiubiu0706的博客-CSDN博客 (5) select distinct top(<取数说明>) <选择 列表> (1) from <表1> <连接类型> join <表2> ON <连接条件> (2) where <筛选条件> (3) group by <分组条件> (4) havi…

linux下链接

linux下链接用法 ln链接格式与介绍 linux下链接用法一、链接的使用格式二、链接的介绍 一、链接的使用格式 链接&#xff1a; 格式&#xff1a; ln 源文件 链接文件 硬链接 ln -s 源文件 链接文件 软连接 硬链接文件占磁盘空间 但是删除源文件不会影响硬链接文件 软链接文件不…

秒杀场景设计

1.活动页面静态化处理 没有到活动时间页面静态化处理避免访问服务端 2.使用cdn让用户可以获取就近的所需静态页面内容 3.限制用户同一时间点击次数 4.把商品库存提前放入redis&#xff0c;秒杀请求直接操作redis防止操作直接落库打崩数据库 5.使用lua脚本操作redis保证操作…

transformer系列2---transformer架构详细解析

transformer详细解析 Encoder1 输入1.1 Embedding 词嵌入1.1.1 Embedding 定义1.1.2 几种编码方式对比1.1.3 实现代码 1.2 位置编码1.2.1 使用位置编码原因1.2.2 位置编码方式1.2.3 位置编码代码 2 注意力 Attention2.1 自注意力self-attention2.1.1 QKV含义2.1.2 自注意力公式…

微信图文如何替换成自己的二维码?

二维码样式中的二维码目前都是小蚂蚁的二维码&#xff0c;如何替换成自己的二维码&#xff0c;其实也很简单&#xff0c;就像替换样式中的图片一样&#xff0c;首先点击二维码&#xff0c;选择工具条中的“换图”&#xff0c;然后在弹出来的框中填入二维码图片的链接地址或者直…