Java数据结构第十九期:解构排序算法的艺术与科学(一)

专栏:Java数据结构秘籍

个人主页:手握风云

目录

一、排序的概念及引用

1.1. 排序的概念

1.2. 排序的应用

1.3. 常见的排序算法

二、常见排序算法的实现

2.1. 直接插入排序

2.2. 希尔排序


一、排序的概念及引用

1.1. 排序的概念

        所谓排序,就是使⼀串记录,按照其中的某个或某些关键字的⼤⼩,递增或递减的排列起来的操作。

        假定在待排序的记录序列中,存在多个具有相同的关键字的记录,若经过排序,这些记录的 相对次序保持不变,即在原序列中,r[i]=r[j],且r[i]在r[j]之前,而在排序后的序列中,r[i]仍在r[j]之 前,则称这种排序算法是稳定的;否则称为不稳定的

        内部排序:数据元素全部放在内存中的排序。

        外部排序:数据元素太多不能同时放在内存中,根据排序过程的要求不断的在内外存之间移动数据的排序。

1.2. 排序的应用

1.3. 常见的排序算法

二、常见排序算法的实现

2.1. 直接插入排序

        类比一下,在玩扑克牌的时候,从牌堆中拿取一张牌进行排序,就用到了插入排序。插入排序的过程:让i从第二个元素为起始位置,j=i-1,当arr[j]>arr[i],用tmp接收i下标的值,让j下标的元素向前移,然后让j--,如果tmp的值大于j下标的值,就把tmp的插入j的后面;如果tmp一直比j下标的值小,就让j一直--,当j<0时,那么tmp的值就插入第一个位置。上述过程就能保证i下标之前的元素保持是有序的。当i走到最后一个元素时,就完成了这个插入排序

        for (int i = 1; i < array.length; i++) {
            int tmp = array[i];
            for (int j = i - 1; j >= 0; j--) {
                if (array[j] > tmp) {
                    array[j + 1] = array[j];
                } else {
                    array[j + 1] = tmp;
                }
            }
        }

        上述代码我们还可以进行优化,如果数组本身就是有序的,再来一个更大的数,不用再让j向前移动了,直接break就可以了。如果j走到了-1的位置,就说第一个元素已经挪开了,就不会再走for循环了,我们再把tmp的值放回来。

public class Sort {
    public void InsertSort(int[] array) {
        for (int i = 1; i < array.length; i++) {
            int tmp = array[i];
            int j = i - 1;
            for (; j >= 0; j--) {
                if (array[j] > tmp) {
                    array[j + 1] = array[j];
                } else {

                    break;
                }
            }
            array[j + 1] = tmp;
        }
    }
}
import java.util.Arrays;

public class Solution {
    public static void main(String[] args) {
        int[] array = {87, 5, 32, 55, 43, 26};
        Sort sort = new Sort();
        sort.InsertSort(array);
        System.out.println(Arrays.toString(array));
    }
}

        我们来分析一下时间复杂度:当i为1的时候,j最多回退1次;当i为2的时候,j最多回退2次,那么时间复杂度的计算为1+2++(n-1)=1/2(n-1)(n-2),时间复杂度为O(n) = n^{2},这是最坏情况下。如果数组本身就是有序的,那么时间复杂度为O(n)=n。空间上,我们没有使用额外的数组,空间复杂度为O(n)=1。所以插入排序适合应用于数组本身就趋向于有序的情况。

        我们再思考一下插入排序的稳定性,上面的代码中,array[j] > tmp,才会执行,两个数相等则不会。如果我们改成array[j]>=tmp,就会变得不稳定。所以,如果排序本身就是稳定的,可以调整为不稳定的;如果排序本身就是不稳定的,则是无法变为稳定的。

        我们要想更直观的看到运行消耗的时间,我们可以写出一个方法,来计算我们运行的时间:

import java.util.Arrays;
import java.util.Random;

/**
 * @author: gao
 * @create-date: 2025/3/7 12:47
 */

public class Solution {

    public static void Order(int[] array) {
        for (int i = 0; i < array.length; i++) {
            array[i] = i;
        }
    }

    public static void ReverseOrder(int[] array) {
        for (int i = 0; i < array.length; i++) {
            array[i] = array.length - i;
        }
    }

    public static void DisOrder(int[] array) {
        Random ran = new Random();
        for (int i = 0; i < array.length; i++) {
            array[i] = ran.nextInt(array.length);
        }
    }

    public static void TestInsertSort(int[] array) {
        long StartTime = System.currentTimeMillis();
        Sort sort = new Sort();
        sort.InsertSort(array);
        long EndTIme = System.currentTimeMillis();
        System.out.println("直接插入排序耗时:" + (EndTIme - StartTime));
    }
    
    public static void main(String[] args) {
        int[] array = new int[10_000];
        ReverseOrder(array);
        TestInsertSort(array);
    }
}

2.2. 希尔排序

        希尔排序又称“缩小增量排序”,可以看作是直接插入排序的优化。所谓“缩小增量”,就是采用分组的思想,在组内进行插入排序。比如说,我们可以5个一组进行连续地划分(如下图所示),有的人可能会说,进行跳跃式地分组,但这样分组会出现的一种情况,就是尽可能把大的数据往后放,小的数据往前放。很明显第一种分组明显优于第二种。

        当gap > 1时都是预排序,⽬的是让数组更接近于有序。当gap == 1时,数组已经接近有序的了,这样就会很快。这样整体而言,可以达到优化的效果。我们实现后可以进⾏性能测试的对比。

        上面我们采用的是gap=5,接下来缩小间距,gap=2划分区间。这里不能写成i+=gap,这样会导致有些组没有参与排序。i++才能让每一组进行交互式排序。所以说不管gap为几,本质上还是执行插入排序。

        我们接下来思考的问题是为什么要缩小增量。组数越大。组内数据越少;组数越小。组内数据越多,效率就越低。在增量缩小的过程中,数组就趋向于有序。

public class Sort {
    public void ShellSort(int[] array) {
        int gap = array.length / 2;
        while (gap > 0) {
            Shell(array, gap);
            gap /= 2;
        }
    }

    public void Shell(int[] array, int gap) {
        for (int i = gap; i < array.length; i++) {
            int tmp = array[i];
            int j = i - gap;
            for (; j >= 0; j-= gap) {
                if (array[j] > tmp) {
                    array[j + gap] = array[j];
                } else {

                    break;
                }
            }

            array[j + gap] = tmp;
        }
    }
}
import java.util.Arrays;

public class Solution {
    public static void main(String[] args) {
        int[] array = {5, 11, 7, 2, 3, 17};
        Sort sort = new Sort();
        sort.ShellSort(array);
        System.out.println(Arrays.toString(array));
    }
}

        希尔排序是不稳定的,不同组交换期间很可能导致元素的相对位置发生改变。空间上,没有申请额外的内存空间,空间复杂度为O(n)=1。时间复杂度上,我们假设有n个数据,每次除2,都会n/2+n/4+n/8……当gap=1时,循环结束,所以外循环的时间复杂度O(n) = logn。希尔排序的时间复杂度的分析至今还非常困难,难度在于弄清比较次数和移动对象与增量选择的关联,并给出完整的数学分析。如果gap非常大的时候,那么j回退的次数就越少,几乎可以认为是常数。当gap非常小的时候,j需要分的组也很小,整体时间复杂度也为1。

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

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

相关文章

1.2TypeScript 类型系统在前端的革命性意义

文章目录 **一、前端开发的类型觉醒&#xff08;历史背景&#xff09;****二、类型系统的核心价值****三、类型系统与现代框架的化学反应****四、高级类型编程实战****五、工程化影响深度解析****六、生态系统的蝴蝶效应****七、企业级应用实践数据****八、类型系统的局限性***…

K8S学习之基础十九:k8s的四层代理Service

K8S四层代理Service 四层负载均衡Service 在k8s中&#xff0c;访问pod可以通过ip端口的方式&#xff0c;但是pod是由生命 周期的&#xff0c;pod在重启的时候ip地址往往会发生变化&#xff0c;访问pod就需要新的ip地址&#xff0c;这样就会很麻烦&#xff0c;每次pod地址改变就…

Varlens(手机上的单反)Ver.1.9.3 高级版.apk

Varlens 是一款专业级手机摄影软件&#xff0c;旨在通过丰富的功能和高自由度参数调节&#xff0c;让手机拍摄效果媲美微单相机。以下是核心功能总结&#xff1a; 一、核心功能 专业拍摄模式 支持手动/自动/程序模式&#xff0c;可调节ISO、快门速度、EV、白平衡等参数27 提供…

用Deepseek写一个 HTML 和 JavaScript 实现一个简单的飞机游戏

大家好&#xff01;今天我将分享如何使用 HTML 和 JavaScript 编写一个简单的飞机游戏。这个游戏的核心功能包括&#xff1a;控制飞机移动、发射子弹、敌机生成、碰撞检测和得分统计。代码简洁易懂&#xff0c;适合初学者学习和实践。 游戏功能概述 玩家控制&#xff1a;使用键…

物联网IoT系列之MQTT协议基础知识

文章目录 物联网IoT系列之MQTT协议基础知识物联网IoT是什么&#xff1f;什么是MQTT&#xff1f;为什么说MQTT是适用于物联网的协议&#xff1f;MQTT工作原理核心组件核心机制 MQTT工作流程1. 建立连接2. 发布和订阅3. 消息确认4. 断开连接 MQTT工作流程图MQTT在物联网中的应用 …

在Rocky Linux上安装Redis(DNF和源码安装)

一.前言 Redis 是一款高性能的 NoSQL 数据库&#xff0c;被广泛用于缓存、消息队列等场景。本教程将手把手教你如何在 Rocky Linux 上安装 Redis&#xff0c;包括使用 DNF 进行安装和源码编译安装的两种方式。 二. 使用 DNF 安装 Redis 1.安装redis sudo dnf -y install red…

江科大51单片机笔记【10】蜂鸣器(上)

一、蜂鸣器 1.原理 蜂鸣器是一种将电信号转换为声音信号的器件&#xff0c;常同来产生设备的按键音、报警音等提示信号蜂鸣器按驱动方式可分为有源蜂鸣器和无源蜂鸣器&#xff08;外观基本一样&#xff09;有源蜂鸣器&#xff1a;内部自带振荡源&#xff0c;将正负极接上直流…

Android设备是如何进入休眠的呢?

首先我们手机灭屏后&#xff0c;一般需要等一段时间CPU才真正进入休眠。即Android设备屏幕暗下来的时候&#xff0c;并不是立即就进入了休眠模式&#xff1b;当所有唤醒源都处于de-avtive状态后&#xff0c;系统才会进入休眠。在手机功耗中从灭屏开始到CPU进入休眠时间越短&…

011---UART协议的基本知识(一)

1. 摘要 文章为学习记录。主要介绍 UART 协议的概述、物理层、协议层、关键参数。 2. UART概述 通用异步收发传输器&#xff08;Universal Asynchronous Receiver/Transmitter&#xff09;&#xff0c;通常称作UART&#xff08;串口&#xff09;&#xff0c;是一种异步****串…

共绘智慧升级,看永洪科技助力由由集团起航智慧征途

在数字化洪流汹涌澎湃的当下&#xff0c;企业如何乘风破浪&#xff0c;把握转型升级的黄金机遇&#xff0c;已成为所有企业必须直面的时代命题。由由集团&#xff0c;作为房地产的领航者&#xff0c;始终以前瞻视野引领变革&#xff0c;坚决拥抱数字化浪潮&#xff0c;携手数字…

【leetcode100】组合总和Ⅱ

1、题目描述 给定一个候选人编号的集合 candidates 和一个目标数 target &#xff0c;找出 candidates 中所有可以使数字和为 target 的组合。 candidates 中的每个数字在每个组合中只能使用 一次 。 注意&#xff1a;解集不能包含重复的组合。 示例 1: 输入: candidates…

【cocos creator】热更新

一、介绍 试了官方的热更新功能&#xff0c;总结一下 主要用于安卓包热更新 参考&#xff1a; Cocos Creator 2.2.2 热更新简易教程 基于cocos creator2.4.x的热更笔记 二、使用软件 1、cocos creator v2.4.10 2、creator热更新插件&#xff1a;热更新manifest生成工具&…

open webui-二次开发-源码启动前后端工程-【超简洁步骤】

参考资料 openwebui docs 获取源码 git clone https://github.com/open-webui/open-webui && cd open-webui启动后端服务 cd backend conda create --name open-webui python3.11 conda activate open-webui pip install -r requirements.txt -U sh dev.sh没有cond…

软件工程笔记下

从程序到软件☆ 章节 知识点 概论☆ 软件的定义&#xff0c;特点&#xff0c;生存周期。软件工程的概论。软件危机。 1.☆软件&#xff1a;软件程序数据文档 &#xff08;1&#xff09;软件&#xff1a;是指在计算机系统的支持下&#xff0c;能够完成特定功能与性能的包括…

Manus AI Agent 技术解读:架构、机制与竞品对比

目录 1. Manus 是什么&#xff1f; 1.1 研发背景 1.2 技术特点 1.3 工具调用能力 1.4 主要应用场景 2. Manus 一夜爆火的原因何在&#xff1f; 2.1 技术突破带来的震撼 2.2 完整交付的产品体验 2.3 生态与开源策略 3. Manus 与其他 AI Agent 的对比分析 3.1 技术架构…

深入探讨 Docker 层次结构及其备份策略20250309

深入探讨 Docker 层次结构及其备份策略 本文将深入探讨 Docker 层次结构 以及在 不同场景下应选择哪种备份方式。通过本文的介绍&#xff0c;您将对如何高效地管理和迁移 Docker 容器有更深的理解。 &#x1f4cc; 什么是 Docker 层次结构&#xff1f; Docker 镜像采用了 分…

Rust语言:开启高效编程之旅

目录 一、Rust 语言初相识 二、Rust 语言的独特魅力​ 2.1 内存安全:消除隐患的护盾​ 2.2 高性能:与 C/C++ 并肩的实力​ 2.3 强大的并发性:多线程编程的利器​ 2.4 跨平台性:适配多环境的优势​ 三、快速上手 Rust​ 3.1 环境搭建:为开发做准备​ 3.2 第一个 R…

邮件发送器:使用 Python 构建带 GUI 的邮件自动发送工具

在本篇博客中&#xff0c;我们将深入解析一个使用 wxPython 构建的邮件发送器 GUI 程序。这个工具能够自动查找指定目录中的文件作为附件&#xff0c;并提供邮件发送功能。本文将从功能、代码结构、关键技术等方面进行详细分析。 C:\pythoncode\new\ATemplateFromWeekReportByM…

JavaWeb-HttpServletRequest请求域接口

文章目录 HttpServletRequest请求域接口HttpServletRequest请求域接口简介关于请求域和应用域的区别 请求域接口中的相关方法获取前端请求参数(getParameter系列方法)存储请求域名参数(Attribute系列方法)获取客户端的相关地址信息获取项目的根路径 关于转发和重定向的细致剖析…

IO多路复用实现并发服务器

一.select函数 select 的调用注意事项 在使用 select 函数时&#xff0c;需要注意以下几个关键点&#xff1a; 1. 参数的修改与拷贝 readfds 等参数是结果参数 &#xff1a; select 函数会直接修改传入的 fd_set&#xff08;如 readfds、writefds 和 exceptfds&#xf…