Java-数据结构-排序-(二) (๑¯∀¯๑)

news2024/9/23 5:57:42

文本目录:

❄️一、交换排序:

        ➷ 1、 冒泡排序:

      ▶ 代码:

         ➷ 2、 快速排序:

                  ☞ 基本思想:

                  ☞ 方法一:Hoare法

      ▶ 代码:

                   ☞ 方法二:挖坑法

      ▶ 代码:

                   ☞ 方法三:前后指针法

      ▶ 代码:

                  ☞ 优化:

                  ☑ 方法一:三数取中法

      ▶ 优化后的代码:

                  ☑ 方法二:递归到一定小的区间时,进行插入排序

      ▶ 优化后的代码:

                   ☞ 非递归实现快速排序:

       ▶ 代码:

❄️总结:


❄️一、交换排序:

     对于交换排序的思想就是:所谓交换,就是根据序列中两个记录键值的比较结果来对换这两个记录在序列中的位置。

      交换排序的特点是:将键值较大的记录向序列的尾部移动,键值较小的记录向序列的前部移动


         1、 冒泡排序:

     对于这个排序方法就是我们很熟悉的一种排序了,我们的在 C语言中也使用过这种排序,在这里呢我们呢就简单的看一遍代码就可以了。来看看最优化的代码:

      ▶ 代码:

/**
 * 冒泡排序
 * 时间复杂度:这里需要分情况:
 *           没有优化:O(N^2) 优化后:最好可以达到O(N)
 * 空间复杂度:O(1)
 * 稳定性:稳定
 * @param array
 */
public static void bubbleSort(int[] array) {
        for (int i = 0; i < array.length - 1; i++) {
            boolean flag = true;
            for (int j = 0; j < array.length - i - 1; j++) {
                if (array[j] > array[j + 1]) {
                    swap(array,j,j + 1);
                    flag = false;
                }
            }
            if (flag) {
                break;
            }
        }
}

这个呢就是我们的优化后的冒泡排序了。 


          2、 快速排序:

快速排序是 Hoare 提出的一种二叉树结构的排序方法。

                  ☞ 基本思想:

    任取待排序元素序列中的某个元素作为基准值,按照该排序码将其待排序集合分割成两个序列左子序列中所有元素均小于基准值,右子序列中所有元素均大于基准值,然后所有左右序列重复该过程,直到所有元素都排在相应的位置上为止。

   我们对于快速排序呢有三种做法,我们一个一个来看:


                  ☞ 方法一:Hoare法

     对于这个方法呢,我们就是在最开始的数组中

    0 下标位置设一个left,在最后一个下标设一个right,我们的把 left 这个下标的值 作为基准值

    之后我们从后面开始找比 基准值 小的值停下来,从前面开始找比 基准值 大的值停下来,

    之后交换我们的 left 和 right 的值,之后继续走,直到left和right相遇停止,之后把 left 下标的值和我们的基准值进行交换。

这样呢,我们的 基准值 的左面都比基准值小,基准值 的右面都比基准值大

     之后我们再把基准值左面的和右面的再次执行上述的操作,直至剩下一个数据就有序了

我们来看看这个方法的流程图:

OK,这就是我们的这个方法的流程,这个呢是比较难理解的,要多看几遍如果不是很理解的话。

   但是在上面图中,我们要注意,我们的开始和结束为止呢,不能使用 left 和 right 的,我们的left和right 就是被赋值的形参,我们的实参使用 start 和 end来表示 开始与结束位置。


      ▶ 代码:

我们来看看这个方法一的代码:

/**
 * 快速排序的 Hoare 方法
 *
 * 时间复杂度:
 *      最坏的情况下:当数据为1 2 3 4 5 6 7 8 9 10 ......或者
 *                        .......10 9 8 7 6 5 4 3 2 1  时候呢为 O(N^2)
 *      最好的情况下:O(N*logN)
 *      我们使用快排呢都是 O(N*logN)
 * 空间复杂度:
 *      最坏的情况下:O(N)
 *           都在一面,我们的递归需要开辟空间
 *
 *      最好的情况下:O(logN)
 * 稳定性:
 *      不稳定的
 * @param array
 */
public static void quickSort(int[] array) {
        quick(array,0,array.length - 1);
}
private static void quick(int[] array,int start,int end) {
        if (start >= end) {
            return;
        }

        int pivot = partition(array,start,end);//找到基准值并交换到一定位置,这时候就把待排序的数据分成了左右两份
        quick(array,start,pivot - 1);//排序基准值左面的
        quick(array,pivot + 1,end);//排序基准值右面的
        //左右排完就是有序的了
}
private static int partition(int[] array,int left,int right) {
        int tmp = array[left];
        int tmpleft = left;//把一开始的left位置保存下来,方便我们后面进行交换

        while (left < right) {
            while (left < right && array[right] >= tmp) {
                right--;
            }

            while (left < right && array[left] <= tmp) {
                left++;
            }
            //两个if语句判断结束之后呢,我们的 right 是比 tmp 小的
            //left 是比 tmp 大的
            //进行交换
            swap(array,left,right);
        }
        //循环结束,我们的left和right相遇,把基准值交换
        swap(array,left,tmpleft);
        //这时候我们的 left 就是我们的 基准值 交换后的下标
        return left;
}

这里要注意我们要从后往前开始找,不能从前往后,因为可能会和大的值进行交换

这样就把 7 交换到 前面去了,不可以。 

而且我们的从后往前找和从前往后找的时候一定要有等于号,如果没有就会出现死循环的情况: 这个呢就是我们快速排序的 Hoare 方法了。我们来看看下一种方法:


                   ☞ 方法二:挖坑法

    挖坑法就是我们一开始把我们 left 位置设置的基准值 拿出来放到 pivot 变量中

    我们之后 从后往前走 right 找到比 pivot 小的元素 放到我们的 left 中(因为我们一开始把 left 的基准值拿出来了,所以相当于里面没有元素),放完之后我们的 right 停下,之后再移动我们的 left 从前面找比 pivot 的值大的元素,放到我们的 right 中

    之后循环这个过程,直至我们的 right 和 left 相遇,这时把 pivot 放到 left 中,这样就实现了我们的基准值的左面都比其小,右面都比其大。

     之后再在基准值的左面和右面都执行这个操作,直至剩余一个元素。

    我们呢可以发现啊,对于这个挖坑法呢和上面我们介绍的 Hoare 的方法还是很相似的。我们来看看这个挖坑法的流程图:

 这个呢就是我们挖坑法的流程图了,虽然我没有画的完全,但是呢还是可以理解的,我们呢来看代码如何编写: 


      ▶ 代码:
public static void quickSort(int[] array) {
        quick(array,0,array.length - 1);
    }
    private static void quick(int[] array,int start,int end) {
        if (start >= end) {
            return;
        }

        int pivot = partition(array,start,end);//找到基准值并交换到一定位置,这时候就把待排序的数据分成了左右两份
        quick(array,start,pivot - 1);//排序基准值左面的
        quick(array,pivot + 1,end);//排序基准值右面的
        //左右排完就是有序的了
    }

    private static int partition(int[] array,int left,int right) {
        int tmp = array[left];//这个就是基准值
        while (left < right) {
            while (left < right && array[right] >= tmp) {
                right--;
            }
            //找到比 基准值小的放到 left中
            array[left] = array[right];
            
            while (left < right && array[left] <= tmp) {
                left++;
            }
            //找到比 基准值大的放到 right中
            array[right] = array[left];
        }
        //right和left相遇后,把 tmp 放到left中
        array[left] = tmp;
        return left;
    }

    我们一般以挖坑法为主,挖坑法是三种方法最重要的,如果题中没有要求使用哪种方法,我们默认使用挖坑法。

这个方法就结束了,我们来看看下一种方法:前后指针法。


                   ☞ 方法三:前后指针法

    对于这个方法就是,在 left 的位置设置一个 prev 在 left +1 的位置设置一个 cur

    之后我们判断 cur 的元素是否小于 left 的元素(基准值),并且 prev 的下一个元素是否等于 cur 的元素

    如果 cur 的元素小于left 的元素的值并且 prev 的下一个元素不等于 cur 的元素,就把cur 和prev 的元素进行交换。反之,则 cur++。

     一直循环这个操作,直至 cur 大于了right 这个位置。这个时候,我们把 prev的值和left 的值进行交换。

     就实现了 基准值的左面都是小于基准值的,右面都是大于基准值的。

我们来看流程图:

  这个呢就是我们的呢前后指针的流程图了,我虽然没有在上图中把遍历左面和右面的流程画出来,但是呢和我们图中的执行流程是差不多的,我呢就不在这里画了,我们来直接看代码: 


      ▶ 代码:
public static void quickSort(int[] array) {
        quick(array,0,array.length - 1);
    }
    private static void quick(int[] array,int start,int end) {
        if (start >= end) {
            return;
        }

        int pivot = partition2(array,start,end);//找到基准值并交换到一定位置,这时候就把待排序的数据分成了左右两份
        quick(array,start,pivot - 1);//排序基准值左面的
        quick(array,pivot + 1,end);//排序基准值右面的
        //左右排完就是有序的了
    }

    //前后指针法
    private static int partition2(int[] array,int left,int right) {
        int prev = left;
        int cur = left + 1;
        while (cur <= right) {

            if (array[cur] < array[left] && array[++prev] != array[cur]) {
                swap(array,cur,prev);
            }

            cur++;
        }
        swap(array,prev,left);

        return prev;
    }

这个时候我们返回的是 prev 而非 left,这里需要注意。


                  ☞ 优化:

当我们使用快排的时候可能会出现这种情况:

我们有两种优化方式: 

                  ☑ 方法一:三数取中法

     我们的这个是什么意思呢?我们上面的数据为例,就是找到 left 和 right 的中位数 mid 这时候呢,我们从 left 和 right 和 mid 中找到一个中位数,和我们的的开始位置进行交换,就是我们的基准值了。我们来看看例子:

这就是我们 三数取中法, 这样是不是就不是单分支的了,就可以变成多分支的了。

那么我们要如何才能找到我们的中间值呢?我们有两种情况:

第一种情况:array[left] < array[right] 的时候。

第二种情况:array[left] > array[right] 的时候。

简单来说就是 找中间值 和 开始的位置进行交换,之后再执行我们的 挖坑法 来进行快速排序。


      ▶ 优化后的代码:
public static void quickSort(int[] array) {
        quick(array,0,array.length - 1);
    }
    private static void quick(int[] array,int start,int end) {
        if (start >= end) {
            return;
        }

        //优化:三数取中法
        int mid = getMiddleNum(array,start,end);
        swap(array,start,mid);

        int pivot = partition(array,start,end);//找到基准值并交换到一定位置,这时候就把待排序的数据分成了左右两份
        quick(array,start,pivot - 1);//排序基准值左面的
        quick(array,pivot + 1,end);//排序基准值右面的
        //左右排完就是有序的了
    }

    //优化:三数取中法
    private static int getMiddleNum(int[] array,int left,int right) {
        int mid = (left + right) / 2;

        if (array[left] < array[right]) {
            if (array[mid] < array[left]) {
                return left;
            } else if (array[mid] > array[right]) {
                return right;
            }else {
                return mid;
            }
        }else {
            if (array[mid] > array[left]) {
                return left;
            } else if (array[mid] < array[right]) {
                return right;
            }else {
                return mid;
            }
        }
    } 

这个就是我们的第一种优化方法。 


                  ☑ 方法二:递归到一定小的区间时,进行插入排序

      我们来想,当我们对一组数据进行排序的时候呢,是不是 越排序数据越趋于有序的,在我们上个博客中介绍 直接插入排序的时候呢,也介绍了当数据越有序效率越高所以我们可以在我们递归到一定小的区间时候呢,我们使用直接插入排序,来减少递归的次数,减少内存的开销

      我们的思路就是这样的,我们来看看代码是如何编写的,不能直接使用 直接插入排序,因为我们有区间,不是对整个数据进行 直接插入排序。

上一篇博客的传送门:

Java-数据结构-排序-(一) (。・ω・。)


      ▶ 优化后的代码:
public static void quickSort(int[] array) {
        quick(array,0,array.length - 1);
    }
    private static void quick(int[] array,int start,int end) {
        if (start >= end) {
            return;
        }
        //优化:直接插入排序
        //当快排到一定的区间的时候我们的使用直接插入排序,来减少内存的开销
        //这里的区间是自定义的
        if (end - start +1 <= 8) {
            insetSortRange(array,start,end);
            return;
        }

        //优化:三数取中法
        int mid = getMiddleNum(array,start,end);
        swap(array,start,mid);

        int pivot = partition(array,start,end);//找到基准值并交换到一定位置,这时候就把待排序的数据分成了左右两份
        quick(array,start,pivot - 1);//排序基准值左面的
        quick(array,pivot + 1,end);//排序基准值右面的
        //左右排完就是有序的了
    }
    //优化:直接插入排序
    private static void insetSortRange(int[] array,int start,int end) {
        for (int i = start + 1; i <= end; i++) {
            int tmp = array[i];
            int j = i - 1;
            for (; j >= 0; j--) {
                if (array[j] > tmp) {
                    array[j + 1] = array[j];
                }else {
                    array[j + 1] = tmp;
                    break;
                }
            }
            //这是当我们的 j < 0的时候呢,
            //我们退出循环之后相当于 j+1 为0下标
            array[j + 1] = tmp;
        }
    }

这时候的整体的代码就是我们的最优状态了。 


                   ☞ 非递归实现快速排序:

      对于 快速排序 的非递归方法呢,我们需要借用 栈 来完成,我们的步骤就是:

1、先把数据进行一次 挖坑法 排序,这时候呢我们就有了 pivot 这个位置。

2、判断 pivot 的左右是否是存在 2个或 2个以上的元素,判断方法:

     如果 :pivot > start + 1 左面就有 2个或 2个以上的元素

     如果: pivot < end - 1  右面就有 2个或 2个以上的元素

3、我们把 start 这个小标 、pivot - 1 这个下标、pivot + 1 这个下标 和 end 这个下标按顺序进行        入栈操作。

4、我们出栈顶数据先给 end 再给 start ,这时候使用出栈的 start 和 end 值进行 挖坑法排序

5、排完序后,再执行 操作2 和 操作3,之后是操作4,直至栈为空。就排好序了。

 

我们来看看代码如何编写的,理解之后呢就非常的简单了: 


       ▶ 代码:
public static void quickSort(int[] array) {
        quickNor(array,0,array.length - 1);
    }
 //快速排序的非递归实现
    private static void quickNor(int[] array,int start,int end) {
        Stack<Integer> stack = new Stack<>();
        //求第一次挖坑法之后的 基准值位置
        int pivot = partition(array,start,end);
        //我们的把start、pivot - 1 、pivot + 1 、end 都入栈
        //我们还需要判断 基准值的左面和右面是否是大于等于2个的元素
        if (pivot > start + 1) {
            stack.push(start);
            stack.push(pivot - 1);
        }
        if (pivot < end - 1) {
            stack.push(pivot + 1);
            stack.push(end);
        }

        while (!stack.isEmpty()) {
            //出两个栈顶,先给end 后给start
            end = stack.pop();
            start = stack.pop();
            pivot = partition(array,start,end);
            //再次求完pivot 之后呢,在执行入栈操作
            if (pivot > start + 1) {
                stack.push(start);
                stack.push(pivot - 1);
            }
            if (pivot < end - 1) {
                stack.push(pivot + 1);
                stack.push(end);
            }
        }
    }
//挖坑法
    private static int partition(int[] array,int left,int right) {
        int tmp = array[left];//这个就是基准值
        while (left < right) {
            while (left < right && array[right] >= tmp) {
                right--;
            }
            //找到比 基准值小的放到 left中
            array[left] = array[right];

            while (left < right && array[left] <= tmp) {
                left++;
            }
            //找到比 基准值大的放到 right中
            array[right] = array[left];
        }
        //right和left相遇后,把 tmp 放到left中
        array[left] = tmp;
        return left;
    }

到这里我们的交换排序就结束了。


❄️总结:

     OK,我们这次的博客就到这里就结束了,我们这次博客虽然我们只介绍了 一类排序——交换排序,但是呢我们这篇博客是非常重要的,因为我们的快速排序是经常使用的排序方法,所以要多加理解这个排序。

   我们在下一篇博客就会把排序收尾,让我们尽情期待吧!!!拜拜~~~

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

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

相关文章

GNU编译器(GCC):编译的4个过程及.elf、.list、.map文件功能说明

0 参考资料 GNU-LD-v2.30-中文手册.pdf GNU linker.pdf1 前言 一个完整的编译工具链应该包含以下4个部分&#xff1a; &#xff08;1&#xff09;编译器 &#xff08;2&#xff09;汇编器 &#xff08;3&#xff09;链接器 &#xff08;4&#xff09;lib库 在GNU工具链中&…

Linux-文件的压缩、解压

Linux系统常见有两种压缩格式&#xff0c;后缀分别是&#xff1a; .tar 称之为tarball&#xff0c;简单的将文件组装到一个.tar的文件内&#xff0c;并没有太多的文件体积减少&#xff0c;仅仅是简单的封装.gz gzip格式压缩文件&#xff0c;可以极大的减少压缩后的体积 针对这…

Lua中..和...的使用区别

一. .. 的用法 二. ... 的用法 在 Lua 中&#xff0c;... 是一个特殊符号&#xff0c;它用于表示不定数量的参数。当你在函数定义或调用中使用 ... 时&#xff0c;它可以匹配任意数量的参数&#xff0c;并将它们作为列表传递。在您的代码示例中&am…

基于SSD的RAG技术方案,推动LLM规模扩展

随着大型语言模型&#xff08;LLM&#xff09;的不断发展&#xff0c;它们在虚拟助手、聊天机器人和对话系统等应用中发挥着重要作用。然而&#xff0c;LLM面临的挑战之一是它们可能会生成虚假或误导性的信息&#xff0c;即所谓的“幻觉”。为了解决这一问题&#xff0c;检索增…

Java数据库连接——JDBC

目录 1、JDBC简介 2、JDBC应用 2.1 建立数据库连接 2.1.1 DriverManager静态方法获取连接 2.1.2 DataSource对象获取 2.2 获取SQL执行对象 2.2.1 SQL注入 2.2.2 Statement(执行静态SQL) 2.2.3 PreparedStatement(预处理的SQL执行对象) 2.3 执行SQL并返回结果 2.4 关…

Error when custom data is added to Azure OpenAI Service Deployment

题意&#xff1a;在向 Azure OpenAI 服务部署添加自定义数据时出现错误。 问题背景&#xff1a; I receive the following error when adding my custom data which is a .txt file (it doesnt matter whether I add it via Azure Cognitive Search, Azure Blob Storage, or F…

证书学习(五)Java实现RSA、SM2证书颁发

目录 一、知识回顾1.1 X.509 证书1.2 X509Certificate 类二、代码实现2.1 Maven 依赖2.2 RSA 证书颁发1)PfxGenerateUtil 证书文件生成工具类2)CertDTO 证书中间类3)RSACertGenerateTest RSA证书生成测试类4)执行结果2.3 SM2 证书颁发1)SM2Utils 国密SM2算法工具类2)SM2C…

查询一条 SQL 语句的流程

查询一条sql语句的流程 连接器:建立连接&#xff0c;管理连接、校验用户身份查询缓存:查询语句如果命中查询缓存则直接返回&#xff0c;否则继续往下执行&#xff08;MSQL8.0 已删除&#xff09;解析 SQL&#xff1a;通过解析器对 SQL 查询语句进行词法分析、语法分析&#xf…

【RH124】解释Linux文件系统权限

RH124教材中控制对文件的访问一章中有一道解释Linux文件系统权限的测验题&#xff0c;可以一起来看看&#xff1a; 一、权限解释 这是通过 ls -l 命令查看的结果。它显示了文件或目录的权限、拥有者、所属组等信息。 1、长列表的第一个字符表示文件类型&#xff1a; -是常…

(done) 声音信号处理基础知识(6) (How to Extract Audio Features)

参考&#xff1a;https://www.youtube.com/watch?v8A-W1xk7qs8&t2s 先复习之前分类的声学特征 时域特征流水线 如下是 441Khz 下一个采样点播放的时间。这比人类耳朵分辨率(10ms)还低。 所以&#xff0c;把多个采样点组合成一个 frame 的原因有&#xff0c;这是一个人…

计算机的错误计算(一百零一)

摘要 展示 在0附近数的函数值的计算精度问题。 计算机的错误计算&#xff08;一百&#xff09;探讨了 在一般情形下的计算精度问题。本节讨论其在0附近的数的函数值的计算精度问题。 例1. 已知 计算 不妨在Python 3.12.5下计算&#xff0c;则有 若在线运行R代码&#x…

阿⾥编码规范⾥⾯Manager分层介绍-专⽤名词和POJO实体类约定

开发⼈员&#xff1a;张三、李四、王五 ⼀定要避免单点故障 ⼀个微服务起码两个⼈熟悉&#xff1a;⼀个是主程⼀个是技术leader 推荐是团队⾥⾯两个开发⼈员 N⽅库说明 ⼀⽅库: 本⼯程内部⼦项⽬模块依赖的库(jar 包)⼆⽅库: 公司内部发布到中央仓库&#xff0c;可供公司…

车辆重识别(CVPR2016图像识别的深度残差学习ResNet)论文阅读2024/9/21

[2] Deep Residual Learning for Image Recognition ( CVPR 2016) 作者&#xff1a;Kaiming He Xiangyu Zhang Shaoqing Ren Jian Sun 单位&#xff1a;微软研究院 摘要&#xff1a; 更深层的神经网络更难训练。我们提出了一个残差学习框架&#xff0c;以减轻对比先前使用的深…

鸿蒙OpenHarmony【轻量系统内核扩展组件(动态加载)】子系统开发

基本概念 在硬件资源有限的小设备中&#xff0c;需要通过算法的动态部署能力来解决无法同时部署多种算法的问题。以开发者易用为主要考虑因素&#xff0c;同时考虑到多平台的通用性&#xff0c;LiteOS-M选择业界标准的ELF加载方案&#xff0c;方便拓展算法生态。LiteOS-M提供类…

【Linux 21】线程安全

文章目录 &#x1f308; 一、线程互斥⭐ 1. 线程间互斥的相关概念&#x1f319; 1.1 临界资源和临界区&#x1f319; 1.2 互斥和原子性 ⭐ 2. 互斥量 mutex⭐ 3. 互斥量接口&#x1f319; 3.1 初始化互斥量&#x1f319; 3.2 销毁互斥量&#x1f319; 3.3 互斥量上锁&#x1f3…

原子结构与电荷

1.原子结构与电荷 1.1 物质到底是由什么构成的 阴极射线 电磁波 电磁波 我们生活中的物质。究竟是由什么构成的呢&#xff1f;这个问题其实困扰了人们很久。 1.提出“原子”的概念 早在2400年前&#xff0c;古希腊哲学家德莫克里特就提出了原子的概念。当时他就认为&…

H264-NAL

目录 错误日志NAL简介参考资料 错误日志 拉流时存在如下日志,会因为拉流失败导致之后的任务也停止 missing picture in access unit with size 16384 Invalid NAL unit size Error splitting the input into NAL units. 之后只要设置抓取异常后&#xff0c;重新拉流&#xff…

Python--TCP/UDP通信

文章目录 前言一、pandas是什么&#xff1f;二、使用步骤 1.引入库2.读入数据总结 一.客户端与服务端通信原理 1. 服务器端 服务器端的主要任务是监听来自客户端的连接请求&#xff0c;并与之建立连接&#xff0c;然后接收和发送数据。 创建套接字&#xff1a;首先&#xff0…

【Fastapi】参数获取,json和query

【Fastapi】参数获取&#xff0c;json和query 前言giteegithub query形式json传递同步方法使用json 前言 花了半个月的时间看了一本小说&#xff0c;懈怠了…今天更新下fastapi框架的参数获取 gitee https://gitee.com/zz1521145346/fastapi_frame.git github https://git…

一些迷你型信息系统 - 2

1 Linux内核数据结构信息查询 Linux内核的数据结构众多&#xff0c;成千上万&#xff0c;做一个程序来存储查询信息&#xff1b;自己录入数据&#xff1b; 代码字段最长可录入65535个字符&#xff1b;每个字段录入时长度超限会有红色告警&#xff1b; 其他的以后想到再做&#…