常见排序算法实现

  💕"每一天都是值得被热爱的"💕

作者:Mylvzi 

 文章主要内容:常见排序算法实现 

1.排序的概念

  所谓排序,就是按照特定顺序重新排列序列的操作

排序的稳定性:

当一个序列中存在相同的元素时  排序过后  他们出现的顺序和原来序列的顺序一致就说明此排序稳定;否则,就不稳定

内部排序:

  • 内部排序是指对数据集合完全加载到内存中进行排序的过程。
  • 这种排序方法适用于数据量较小,可以在计算机内存中容纳整个数据集的情况。
  • 常见的内部排序算法包括冒泡排序、选择排序、插入排序、快速排序、归并排序等。
  • 内部排序的优点是速度较快,因为所有数据都在内存中,不需要频繁的读取和写入操作。

外部排序:

  • 外部排序是指对大规模数据集合进行排序的方法,数据量太大,无法一次性加载到内存中。
  • 这种排序方法涉及将数据分割成适当大小的块,然后在内存中对这些块进行排序,最后将排序后的块写回磁盘或其他存储介质,并合并这些块以得到最终的排序结果。
  • 外部排序常用于需要处理大量数据的场景,比如数据库系统中对大型表进行排序或外部存储设备上的数据排序。
  • 常见的外部排序算法包括归并排序、多路归并排序等,这些算法允许对大规模数据进行高效排序,因为它们能够有效地利用磁盘I/O操作。

常见的排序算法

1.插入排序

  插入排序是一种非常简单的排序  比如在玩扑克牌时  在发牌阶段  我们每抽取一张牌  都要从后往前去比较大小  把抽取的牌插入到合适的位置

  所以,插入排序就是将待排序的元素(抽取的牌)插入到一个已经有序的序列之中

代码实现

/**
     * 插入排序  在一个已经存在的序列中  插入到合适位置
     * 时间复杂度:
     *          最好情况:O(N)
     *          最坏情况:O(N^2)
     * 空间复杂度:
     *          O(1)
     * 稳定性:是一个稳定的排序
     * 所以对于一个有序的序列来说  插入排序就是最快的
     */

    public static void insertSort(int[] arr) {

        for (int i = 1; i < arr.length; i++) {
            int tmp = arr[i];
            int j = i-1;
            for (; j >= 0; j--) {
                // >tmp  j向后挪动
                if(arr[j] > tmp) {
                    arr[j+1] = arr[j];
                }else {
                    // 要插入的元素已经是最大的了  不需要再去比较了
                    //arr[j+1] = tmp;
                    break;
                }
            }

            // 跳出循环有两种情况  1.tmp是最小的需要插入到第一个元素 此时j=-1  结束条件是j不>=0了   2.else语句中的break;
            arr[j+1] = tmp;
        }
    }

 2.希尔排序(缩小增量排序)

是根据插入排序的优点来进行优化的插入排序

我们知道,插入排序对于有序化高的序列来说速度是更快的,也就是说一个序列有序化越高,使用插入排序的时间复杂度就越低,速度就越快  

所以,对于一大堆的数据来说,我们可以先进行“预排”,使其有序化程度越来越高,从而实现效率更高

设置gap  利用插入排序的思想  分组进行优化  组数不断降低  直到最后为1  最后一个进行排序时  序列的有序化程度已经很高  速度很快  

希尔排序看似繁琐  实则提高了效率  虽然要进行多次插入排序  但时间优化了很多  主要原因在于以下几个方面:

1.分组会使得待排序的数据量减小  每次排序的数据量少  时间快

2.当gap = 1时,也就是要对整个序列进行排序  虽然数据量很大  但是有序化程度高  时间快

希尔排序的分析过

  代码实现

/**
     * 希尔排序  优化的插入排序
     * 先进行预排序  跳跃式进行分组  分的组数逐渐减少  直到组数为1
     * 分组优化
     * 时间复杂度:O(N^1.3)
     * 空间复杂度:O(1)
     * 稳定性:不稳定
     */

    public static void shellSort(int[] arr) {
        int gap = arr.length;
        while (gap > 1) {
            gap /= 2;
            shell(arr,gap);
        }
    }

    private static void shell(int[] arr,int gap) {
        for (int i = gap; i < arr.length; i++) {
            int tmp = arr[i];
            int j = i-gap;
            for (; j >= 0; j-= gap) {
                // >tmp  j向后挪动
                if(arr[j] > tmp) {
                    arr[j+gap] = arr[j];
                }else {
                    // 要插入的元素已经是最大的了  不需要再去比较了
                    //arr[j+1] = tmp;
                    break;
                }
            }

            arr[j+gap] = tmp;
        }

    }

3.选择排序

  选择排序也是一个比较简单的排序  其核心思想在于每次都要选择一个最小的/最大的元素位于最左边

选择排序无论你的顺序如何  都要遍历整个数组去寻找最小值/最大值  所以对于初始顺序不敏感

代码实现

    public static void selectSort(int[] arr) {
        for (int i = 0; i < arr.length; i++) {
            int minIndex = i;
            for (int j = i+1; j < arr.length; j++) {
                if(arr[j] < arr[minIndex]) {
                    minIndex = j;
                }
            }

            swap(arr,i,minIndex);
        }
    }

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

4.堆排

堆排 就是利用堆的特性进行排序的一种方式  

思路:

1.看条件创建堆  升序--》大根堆   降序--》小根堆

2.交换首元素和末元素  向下调整

代码实现:

    /**
     * 堆排  从小到大
     * 1.创建大根堆
     * 2.交换  向下调整
     */

    public static void heapSort(int[] arr) {
        creatBigHeap(arr);
        int end = arr.length-1;
        while(end > 0) {
            swap(arr,0,end);
            shiftDown(arr,0,end);
            end--;
        }
    }

    private static void creatBigHeap(int[] arr) {
        for (int parent = (arr.length-1-1)/2; parent >= 0; parent--) {
            shiftDown(arr,parent,arr.length);
        }
    }

    private static void shiftDown(int[] arr, int parent, int end) {
        int child = 2*parent+1;
        while (child < end) {
            // 判断左右孩子谁是最大的
            if(child+1 < end && arr[child] < arr[child+1]) {
                child++;
            }

            if(arr[child] > arr[parent]) {
                swap(arr,child,parent);
                parent = child;
                child = 2*parent+1;
            }else {
                break;
            }
        }
    }

5.快速排序

  核心思路:分而治之

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

快速排序  本质上是一个递归的过程  有序时  会出现单叉树  此时时间复杂度最高  达到0(N^2)

  

代码实现

    /**
     * 快速排序
     *时间复杂度:O(N*logN);  每次分而治之需要遍历的数字都是N  最好情况是一颗完全二叉树  高度为logN  所以时间复杂度是N*logn
     *
     * 容易溢出  占用内存大
     * 三种写法:hore法  挖坑法(推荐) 前后指针法
     * 递归方法  最好的情况是一种完全二叉树
     */
    public static void quickSort(int[] arr) {
        quick(arr,0,arr.length-1);
    }

    private static void quick(int[] arr,int start,int end) {
        if(start >= end) return;

        if(end - start + 1 > 7) {
            insertSortRange(arr,start,end);
            return;
        }

        midOfThree(arr,start,end);



        // 获得按照规则交换后的基准值的下标
        int pivot = parttion(arr,start,end);

        // 遍历左子树  分而治之
        quick(arr,start,pivot-1);

        // 遍历右子树  分而治之
        quick(arr,pivot+1,end);

    }

    public static void insertSortRange(int[] arr,int begin,int end) {

        for (int i = 1; i <= end; i++) {
            int tmp = arr[i];
            int j = i-1;
            for (; j >= begin; j--) {
                // >tmp  j向后挪动
                if(arr[j] > tmp) {
                    arr[j+1] = arr[j];
                }else {
                    // 要插入的元素已经是最大的了  不需要再去比较了
                    //arr[j+1] = tmp;
                    break;
                }
            }

            // 跳出循环有两种情况  1.tmp是最小的需要插入到第一个元素 此时j=-1  结束条件是j不>=0了   2.else语句中的break;
            arr[j+1] = tmp;
        }
    }

    private static void midOfThree(int[] arr, int left, int right) {
        int mid = (left + right) / 2;

        if(arr[left] > arr[right]) swap(arr,left,right);// 保证左边元素是较小值
        if(arr[mid] > arr[right]) swap(arr,mid,right);// 保证中间元素是较小值
        if(arr[mid] > arr[left]) swap(arr,mid,left);// 此时保证left下标的值是中间大小


    }


    private static int parttionHoare(int[] arr,int left,int right) {
        int i = left;
        // 每次都选取第一个元素为基准值
        int key = arr[left];

        // 遍历交换
        // left  从左往右  找比Key大的
        // right 从右往左  找比key小的
        while (left < right) {
            /**
             * 为什么先走右边
             * 先走right保证他们相遇时  一定是比key值小的数据
             * 如果先走left 相遇时碰到的一定是比key大的  此时再交换  则key的左边存在比key大的数据了
             */
            // 先从右边找
            while (left < right && arr[right] >= key) {  // 等号必须要取  万一两个都是6  会陷入死循环
                right--;
            }
            // 此时right下标的元素比key小

            while (left < right && arr[left] <= key) {
                left++;
            }

            swap(arr,left,right);
        }

        // 使基准值位于中间(即左边都比key小  右边都比key大)
        swap(arr,i,left);
        return left;
    }


    /**
     * 快排的优化
     * 1.三数取中法:解决特殊情况 --》第一个数字是最小的  或者是最大的 减少了树的高度  开辟的空间更小
     * 2.小区间内采用插入排序  减少递归的次数(但时间有可能会增加)  降低了内存的要求
     */

parttion的第二种写法:挖坑法

 // 挖坑法
    private static int parttion2(int[] arr,int left,int right) {
        // 每次都选取第一个元素为基准值
        int key = arr[left];


        // 遍历交换
        // left  从左往右  找比Key大的
        // right 从右往左  找比key小的
        while (left < right) {
            // 先从右边找
            while (left < right && arr[right] >= key) {  // 等号必须要取  万一两个都是6  会陷入死循环
                right--;
            }
            // 此时right下标的元素比key小
            arr[left] = arr[right];

            while (left < right && arr[left] <= key) {
                left++;
            }
            arr[right] = arr[left];
        }

        // 使基准值位于中间(即左边都比key小  右边都比key大)
        arr[left] = key;
        return left;
    }

parttion的第三种写法:前后指针法

    private static int parttion(int[] arr,int left,int right) {
        // 前后指针法
        int prev = left;
        int cur = left+1;

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

            cur++;
        }

        // 遍历完整个数组了
        swap(arr,left,prev);
        return prev;
    }

注意:

1.parttion一共有三种写法,推荐以挖坑法为先  前后指针或者Hoare法次之

2.为什么快速排序要先走right?因为这样能够保证left和right最后相遇的位置的元素是比key小的元素交换过后仍满足条件

快速排序的非递归写法

// 快排的非递归写法
    public static void quickSortNor(int[] arr) {
        Stack<Integer> stack = new Stack<>();
        int left = 0;
        int right = arr.length-1;
        int pivot = parttion(arr,left,right);

        if(pivot-1 > left) {
            stack.push(left);
            stack.push(pivot-1);
        }

        if (pivot + 1 < right) {
            stack.push(pivot+1);
            stack.push(right);
        }

        while(!stack.isEmpty()) {
            right = stack.pop();
            left = stack.pop();

            pivot = parttion(arr,left,right);

            if(pivot-1 > left) {
                stack.push(left);
                stack.push(pivot-1);
            }

            if (pivot + 1 < right) {
                stack.push(pivot+1);
                stack.push(right);
            }
        }

    }

 6.冒泡排序法(不做讲解)

/**
     *  冒泡排序
     *  时间复杂度:O(N^2)  最好(加了优化)O(N)
     *  空间复杂度:O(1)
     *  稳定性好
     */

    public static void bubbleSort(int[] arr) {
        for (int i = 0; i < arr.length; i++) {
            boolean flg = false;
            for (int j = 0; j < arr.length-1-i; j++) {
                if(arr[j] > arr[j+1]) {
                    swap(arr,j,j+1);
                    flg = true;
                }
            }

            if(!flg) {
                return;
            }
        }
    }

7.归并排序

   归并排序(MERGE-SORT)是建立在归并操作上的一种有效的排序算法,该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使 子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。 归并排序核心步骤:

分解左边   分解右边   合并

代码实现

/**
     * 归并排序:
     * 时间复杂度:O(n*logN)
     * 空间复杂度:O(N);
     * 稳定
     *
     * 分解左边  分解右边  归并(合并两个有序数组)
     *
     * 稳定的排序:冒泡  插入  归并
     */

    public static void mergeSort(int[] arr) {
        mergeSortFunc(arr,0,arr.length-1);
    }

    private static void mergeSortFunc(int[] arr, int left, int right) {
        if(left >= right) return;

        int mid = (left+right) / 2;
        mergeSortFunc(arr,left,mid);
        mergeSortFunc(arr,mid+1,right);
        merge(arr,left,mid,right);

    }

    private static void merge(int[] arr, int left, int mid, int right) {
        // 这里边的思路相当于是 合并两个有序序列
        int[] tmpArr = new int[right-left+1];
        int k = 0;

        // 分别定义两个需要合并的数组的首元素的下标
        int s1 = left;
        int s2 = mid+1;

        // 遍历两个数组
        while(s1 <= mid && s2 <= right) {
            if(arr[s1] < arr[s2]) {
                tmpArr[k++] = arr[s1++];
            }else {
                tmpArr[k++] = arr[s2++];
            }
        }

        // 出循环证明有一个数组不为空
        while(s1 <= mid) {
            tmpArr[k++] = arr[s1++];
        }

        while (s2 <= right) {
            tmpArr[k++] = arr[s2++];
        }

        // 将排序好的元素返回给原数组
        for (int i = 0; i < tmpArr.length; i++) {
            arr[i+left] = tmpArr[i];
        }
    }

非递归写法

// 归并排序的非递归写法
    public static void mergeSortNor(int[] arr) {
        int gap = 1;
        while(gap < arr.length) {
            for (int i = 0; i < arr.length; i+= 2*gap) {
                int left = i;
                int mid = i+gap-1;
                int right = mid+gap;

                if(mid >= arr.length) {
                    mid = arr.length-1;
                }
                if(right >= arr.length) {
                    right = arr.length-1;
                }

                merge(arr,left,mid,right);
            }

            gap *= 2;
        }
    }

8.计数排序

  设置一个计数数组  用于记录待排序数组中相应元素出现的次数  并按照下标排列  最后返回给原数组

   /**
     * 计数排序
     * 时间复杂度:O(N+范围)
     * 空间复杂度:O(范围)
     * 适用于数据范围集中  数据量较小的情况
     * 稳定性好
     */

    public static void countSort(int[] arr) {
        // 1.得到数组的最大值和最小值(数组的下标只能从0开始)
        int minVal = arr[0];
        int maxVal = arr[0];
        for (int i = 0; i < arr.length; i++) {
            if (arr[i] < minVal) {
                minVal = arr[i];
            }

            if (arr[i] > maxVal) {
                maxVal = arr[i];
            }
        }

        //2.遍历arr  并在计数数组中存放对应的次数
        int[] count = new int[maxVal - minVal + 1];
        for (int i = 0; i < arr.length; i++) {
            count[arr[i] - minVal]++;
        }

        //3.重新写入到原数组
        int index = 0;
        for (int i = 0; i < count.length; i++) {
            while(count[i] > 0) {
                arr[index] = i + minVal;
                index++;
                count[i]--;
            }
        }
    }

总结:

1. 稳定的排序:插入排序  冒泡排序  归并排序

2.希尔排序的时间复杂度很复杂,到现在也没有一个具体的结论

3.初始序列顺序对排序的影响 

选择排序无论你的顺序如何  都要遍历整个数组去寻找最小值/最大值  所以对于初始顺序不敏感

4.表格汇总 

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

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

相关文章

1、NPC 三电平SVPWM simulink仿真

1、SVPWM时间计算函数&#xff0c;是从matlab的SVPWM3L_TimingCalculation.p文件中反汇编出来的函数&#xff1a; function [TgABC_On ,TgABC_Off ,Sn ]SVPWM3L_TimingCalculation_frompfile (Vref ,DeltaVdc ,Fsw ) %#codegen %coder .allowpcode (plain ); TgABC_On [0 ,0 ,…

Genio 700安卓核心板-MT8390安卓核心板规格参数

Genio 700(MT8390)安卓核心板是一款专门针对智能家居、互动零售、工业和商业应用的高性能边缘人工智能物联网平台。它集成了高度响应的边缘处理、先进的多媒体功能、各种传感器和连接选项&#xff0c;并支持多任务操作系统。 )安卓核心板采用高效的芯片内人工智能多处理器(APU)…

计算机毕业设计 基于SpringBoot的销售项目流程化管理系统的设计与实现 Java实战项目 附源码+文档+视频讲解

博主介绍&#xff1a;✌从事软件开发10年之余&#xff0c;专注于Java技术领域、Python人工智能及数据挖掘、小程序项目开发和Android项目开发等。CSDN、掘金、华为云、InfoQ、阿里云等平台优质作者✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精…

Python+Qt多点最短路径(最优路径)算法实现

程序示例精选 PythonQt多点最短路径(最优路径)算法实现 如需安装运行环境或远程调试&#xff0c;见文章底部个人QQ名片&#xff0c;由专业技术人员远程协助&#xff01; 前言 这篇博客针对《PythonQt多点最短路径(最优路径)算法实现》编写代码&#xff0c;代码整洁&#xff0…

SDL2 播放视频文件(MP4)

1.简介 这里引入FFmpeg库&#xff0c;获取视频流数据&#xff0c;然后通过FFmpeg将视频流解码成YUV原始数据&#xff0c;再将YUV数据送入到SDL库中实现视频播放。 2.FFmpeg的操作流程 注册API&#xff1a;av_register_all()构建输入AVFormatContext上下文&#xff1a;avform…

vscode+python开发之虚拟环境和解释器切换

需求情景&#xff1a; 现在我们要开发多个项目比如&#xff1a;项目A&#xff0c;项目B、项目C&#xff0c;他们每个项目需要依赖不同的库。每个项目依赖的解释器也不一样怎么办&#xff1f; 项目A&#xff1a;需要在python3.7环境运行 依赖aadd3.2库 项目B、需要在python3.11…

取消Element UI响应式设计——打造固定布局的菜单

引言 在当今的Web开发中&#xff0c;响应式设计已经成为了一个不可或缺的部分。然而&#xff0c;有时候我们可能需要取消这种响应式特性&#xff0c;尤其是对于一些特定的界面元素&#xff0c;如导航菜单。在Element UI框架中&#xff0c;导航菜单&#xff08;el-menu&#xff…

arcgis--填充面域空洞

方法一&#xff1a;使用【编辑器】-【合并工具】进行填充。首选需要在相同图层中构造一个填充空洞的面域&#xff0c;然后利用【合并】工具进行最后填充。 打开一幅含有空洞的矢量数据&#xff0c;如下&#xff1a; 打开【开始编辑】-【构造工具】-【面】进行覆盖空洞的面域的…

基于鸟群算法优化概率神经网络PNN的分类预测 - 附代码

基于鸟群算法优化概率神经网络PNN的分类预测 - 附代码 文章目录 基于鸟群算法优化概率神经网络PNN的分类预测 - 附代码1.PNN网络概述2.变压器故障诊街系统相关背景2.1 模型建立 3.基于鸟群优化的PNN网络5.测试结果6.参考文献7.Matlab代码 摘要&#xff1a;针对PNN神经网络的光滑…

机器视觉系统中工业光源选型避坑指南

光源的作用&#xff1a; 照亮目标&#xff0c;提高目标亮度 形成有利于图像处理的效果&#xff0c;提升对比度 克服环境光干扰&#xff0c;保证图像的稳定性 光源的选型思路&#xff1a; ①颜色 ②外形  ③打光方式  ④亮度 选颜色 通过选择合适颜色的光源&#xff0c;…

腾讯云CVM服务器5年可选2核4G和4核8G配置

腾讯云服务器网整理五年云服务器优惠活动 txyfwq.com/go/txy 配置可选2核4G和4核8G&#xff0c;公网带宽可选1M、3M或5M&#xff0c;系统盘为50G高性能云硬盘&#xff0c;标准型S5实例CPU采用主频2.5GHz的Intel Xeon Cascade Lake或者Intel Xeon Cooper Lake处理器&#xff0c;…

Android JNI静态和动态注入方法

作者&#xff1a;MiniCode Android调用C/C的代码目前比较流行的方式之一便是通过JNI&#xff0c;其中按本地方法的实现有两种方式&#xff1a;静态和动态 创建一个C项目或者C的Module&#xff1a; 创建成功之后会生成如下文件&#xff08;CMakeLists.txt、nativelib.cpp&#…

Centos7下mbr主引导记录演示

linux mbr主引导记录演示 dd if/dev/sda ofmbr.bin bs446 count1 dd if/dev/sda ofmbr.bin bs446 count1hexdump -C mbr.bin[rootlocalhost ~]# cd /boot/grub2 [rootlocalhost grub2]# ls [rootlocalhost grub2]# grub2-editenv list #默认引导内核查看 [rootlocalhost g…

生产环境中的面试问题,实时链路中的Kafka数据发现某字段值错误,怎么办?...

大家好呀&#xff0c;今天分享的是一个生产环境中遇到的问题。也是群友遇到的一个面试问题。 原问题是&#xff1a; 早晨8点之后发现kafka的record中某个字段的值出现了错误&#xff0c;现在已经10点了&#xff0c;需要对kafka进行数据订正&#xff0c;怎么样定位和解决这个问题…

降低城市内涝风险,万宾科技内涝积水监测仪的作用

频繁的内涝会削弱和损坏城市的关键基础设施&#xff0c;包括道路、桥梁和公用设施。城市内涝风险降低可以减少交通中断事件&#xff0c;也可以保护居民安全并降低路面维修等成本&#xff0c;进一步确保城市基本服务继续发挥作用。对城市可持续发展来讲有效减少内涝的风险是重要…

根据数组数组,实现上一页下一页功能

<span click"prePage"><i class"el-icon-back"></i></span><span click"nextPage"><i class"el-icon-right"></i></span> this.typeList&#xff1a;最终显示页面的数组 this.typeNe…

C#中.NET Framework4.8 Windows窗体应用通过EF访问数据库并对数据库追加、删除记录

目录 一、应用程序设计 二、应用程序源码 三、生成效果 前文作者发布了在.NET Framework4.8 控制台应用中通过EF访问已有数据库&#xff0c;事实上在.NET Framework4.8 Windows窗体应用中通过EF访问已有数据库也是一样的。操作方法基本一样&#xff0c;数据库EF模型和上下文…

MySQL时间戳2038年灾难:你的数据还能撑过去吗?

点击上方蓝字关注我 Timestamp 类型在MySQL中通常用于存储日期和时间。然而&#xff0c;Timestamp类型的一个限制是其存储范围&#xff0c;它使用4字节&#xff08;32位&#xff09;整数来表示秒数&#xff0c;从而导致在2038年01月19日03:14:07之后无法正确存储时间戳。这是因…

Android设计模式--工厂模式

一&#xff0c;定义 工厂模式与Android 设计模式--单例模式-CSDN博客&#xff0c;Android设计模式--Builder建造者模式-CSDN博客&#xff0c;Android设计模式--原型模式-CSDN博客 一样&#xff0c;都是创建型设计模式。 工厂模式就是定义一个用于创建对象的接口&#xff0c;让…

[当人工智能遇上安全] 10.威胁情报实体识别 (1)基于BiLSTM-CRF的实体识别万字详解

您或许知道&#xff0c;作者后续分享网络安全的文章会越来越少。但如果您想学习人工智能和安全结合的应用&#xff0c;您就有福利了&#xff0c;作者将重新打造一个《当人工智能遇上安全》系列博客&#xff0c;详细介绍人工智能与安全相关的论文、实践&#xff0c;并分享各种案…