【Java数据结构】排序

news2024/12/24 21:38:01

排序

    • 插入排序
    • 希尔排序
    • 选择排序
    • 堆排序
    • 冒泡排序
    • 快速排序
      • 序列的分割
        • Hoare法
        • 挖坑法
        • 快慢指针法
      • 优化1 - 三数取中
      • 优化2- 数据规模小时的插入
    • 归并排序

插入排序

直接插入排序是一种简单的插入排序法,其基本思想是:

把待排序的记录按其关键码值的大小逐个插入到一个已经排好序的有序序列中,直到所有的记录插入完为止,得到一个新的有序序列 。

实际中我们玩扑克牌时,就用了插入排序的思想。

实现

/**
     * 插入排序
     * 时间复杂度:
     * 最好情况下:O(n)  ->  数据有序的情况下  1 2 3 4 5
     * 最坏情况下:O(n^2) -> 数据逆序的情况下  5 4 3 2 1
     * 空间复杂度:O(1)
     * 稳定性:稳定的排序
     * 当数据越有序的时候  直接插入排序的效率越高
     */
    public static void insertSort(int[] array) {
        for (int i = 1; i < array.length; i++) {
            int j = i - 1;
            int temp = array[i];
            while (j >= 0 && temp < array[j]) {
                array[j + 1] = array[j];
                array[j] = temp;
                j--;
            }
        }
    }

在这里插入图片描述

希尔排序

希尔排序可以看作是插入排序的一种优化

希尔排序法又称缩小增量法。希尔排序法的基本思想是:先选定一个整数,把待排序文件中所有记录分成多个组,所有距离为的记录分在同一组内,并对每一组内的记录进行排序。然后,取,重复上述分组和排序的工作。当到达=1时,所有记录在统一组内排好序。

在这里插入图片描述

/**
     * 希尔排序
     * 每组进行插入排序
     * 时间复杂度:O(n^1.25 - n^1.5)  -> n^1.3不确定
     * 空间复杂度:O(1);
     * 稳定性:不稳定;
     */
    private static void shell(int[] array, int gap) {
    //插入排序
        for (int i = gap; i < array.length; i++) {
            int temp = array[i];
            int j = i - gap;
            while (j >= 0 && temp < array[j]) {
                array[j + gap] = array[j];
                array[j] = temp;
                j -= gap;
            }
        }
    }

    //分组操作
    public static void shellSort(int[] array) {
    //分组操作
        int gap = array.length;
        while (gap > 1) {
            gap /= 2;
            shell(array, gap);
        }
    }

图片: Alt

选择排序

每一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,直到全部待排序的数据元素排完 。

/**
     * 选择排序
     * 时间复杂度:O(N*logN) 对数据不敏感 无论数据原本是有序还是无序 都是这个表达式
     * 空间复杂度:O(1)
     * 稳定性:不稳定
     */

    public static void selectSort1(int[] array) {
        for (int i = 0; i < array.length; i++) {
            int minIndex = i;
            for (int j = i + 1; j < array.length; j++) {
                if (array[j] < array[minIndex]) {
                    minIndex = j;
                }
            }
            swap(array, minIndex, i);
        }
    }

Alt

优化 可以再一次遍历中找到最大的和最小的这样效率可以快一倍

public static void selectSort2(int[] array) {
        int left = 0;
        int right = array.length - 1;
        while (left < right) {
            int minIndex = left;
            int maxIndex = right;
            for (int i = left + 1; i <= right; i++) {
                if (array[i] < array[minIndex]) {
                    minIndex = i;
                }
                if (array[i] > array[maxIndex]) {
                    maxIndex = i;
                }
            }
            swap(array, minIndex, left);
            //此时特殊处理首元素是最大的元素
            if (maxIndex == left) {
                maxIndex = minIndex;
            }
            swap(array, maxIndex, right);
            left++;
            right--;
        }
    }

堆排序

利用堆的性质 如果从小到大排序使用大根堆 如果从大到小排序 使用小根堆

大根堆的堆顶元素是最大的 将这个元素和最后一个元素交换 此时最后一个元素确定位置 隔离最后一个元素 向下调整堆 重复上述操作 即将元素从小到大排序

/**
     * 堆排序
     * 时间复杂度 O(N*logN)对数据不敏感  不管有序无序都是这个表达式
     * 空间复杂度 O(1)
     * 稳定性 不稳定排序
     */
    public static void heapSort(int[] array) {
        createBigHeap(array);
        //构建大根堆
        int end = array.length - 1;
        //标记末尾位置
        while (end > 0) {
            swap(array, 0, end);
            shiftDown(array, 0, end);
            //此时向下调整不包含最后一个元素
            end--;
        }
    }

    private static void createBigHeap(int[] array) {
        for (int i = (array.length - 1 - 1) / 2; i >= 0; i--) {
            shiftDown(array, i, array.length);
        }
    }

    private static void shiftDown(int[] array, int parent, int len) {
        int child = 2 * parent + 1;
        while (child < len) {
            if (child + 1 < len && array[child] < array[child + 1]) {
                child = child + 1;
            }
            if (array[child] > array[parent]) {
                swap(array, parent, child);
                parent = child;
                child = 2 * parent + 1;
            } else {
                break;
            }
        }
    }

冒泡排序

**相邻元素两两比较交换 **

/**
     * 冒泡排序
     * 时间复杂度:O(N^2) 对数据不敏感  有序 无序都是这个复杂度!
     * 空间负责度:O(1)
     * 稳定性:稳定的排序
     * 加了优化之后,时间复杂度可能会变成O(n)
     */

    public static void bubbleSort(int[] array) {
        for (int i = 0; i < array.length - 1; i++) {
            boolean flag = false;
            for (int j = 0; j < array.length - 1 - i; j++) {
                if (array[j] > array[j + 1]) {
                    swap(array, j, j + 1);
                    flag = true;
                }
            }
            if (!flag) {
                //如果没有进行swap操作 说明数据已经有序
                break;
            }
        }
    }

Alt

快速排序

快速排序是Hoare于1962年提出的一种二叉树结构的交换排序方法,其基本思想为:任取待排序元素序列中的某元素作为基准值,按照该排序码将待排序集合分割成两子序列,左子序列中所有元素均小于基准值,右子序列中所有元素均大于基准值,然后最左右子序列重复该过程,直到所有元素都排列在相应位置上为止。

第一步: **找到基准值 **
第二步: 将大于基准值的元素放在基准值后 小于基准值的元素放在基准值之前

序列的分割

我们在找到基准值key后 要将大于key的数据放在key之后 小于key的元素放在key之前 那么我们应该如何进行操作呢?

Hoare法

这个方法的基本思想是**定义一个前后指针left和right 一个指向序列头 一个指向序列尾 指针移动 当right指针指向小于key值的元素时停下 left指针指向大于key值时停下 此时让left指针和right指针指向的元素交换 知道left指针遇到right指针时停下 **

public static int parttion1(int[] array, int left, int right) {
        int key = left;
        int temp = array[left];
        while (left < right) {
            while (left < right && array[right] >= temp) {
                right--;
            }
            while (left < right && array[left] <= temp) {
                left++;
            }
            swap(array, left, right);
        }
        swap(array, key, left);
        return left;
    }

挖坑法

挖坑法的基本思路是还是定义前后指针left和right 一个指向序列头 一个指向序列尾 记录下left指向的元素(将此处看作一个坑) 移动right元素 直到遇到小于key值的元素 此时将right指向的元素放在坑中 此时这个位置就变成了一个坑 在移动left指针 直到遇到大于key的元素 此时再把left指向的元素放在坑中 直到left和right相遇 最后把key值放在最后的坑中

//2.挖坑法
    public static int parttion2(int[] array, int left, int right) {
        int temp = array[left];
        //先在第一个位置挖一个坑 temp保存这个坑的元素
        while (left < right) {
            while (left < right && array[right] >= temp) {
                right--;
            }
            array[left] = array[right];//把小的放进坑里 此时right位置就是另一个坑
            while (left < right && array[left] <= temp) {
                left++;
            }
            array[right] = array[left];
        }
        array[left] = temp;//把最后一个坑填起来
        return left;
    }

快慢指针法

前后指针法的核心思想是用一对快慢指针维护一个小于key的区间,有点滑动窗口的意味在里面。虽然代码量是最短的,但是理解起来也并不困难

fast一直往后遍历,当fast遇到小于key的元素时,则将fast处的元素与slow+1处的元素进行交换
为什么是slow + 1处的元素呢?因为pre以内的数据(除了头为key)都满足小于等于key的条件
当cur遍历结束时pre左边的数据都是小于等于key的,右边都是大于等于key的。最后还需要做的操作是将pre处的数据与left处的数据进行交换,这样就确定了key应该在的位置

public static int parttion3(int[] array, int left, int right) {
        int slow = left;
        int fast = left + 1;
        //定义快慢指针 并初始化
        while (fast <= right) {
            //当快指针为最后一个元素时循环结束
            if (array[fast] < array[left] && array[++slow] != array[fast]) {
                //第一个条件保证快指针fast指向的元素小于我们的key元素
                //第二个条件和++slow交换是因为 slow之前的元素都是小于key的
                //当第一个条件成立时 第二个条件不执行 也就是在这次循环中 slow不进行++操作
                //当两个条件都成立时 此时slow的下一个元素 就是一个大于key的元素 但是这个元素还不可以是fast指向的元素
                //即if条件的原因
                swap(array, fast, slow);
                //交换元素
            }
            fast++;
        }
        swap(array, left, slow);
        //此时key的位置已经确定 即slow此时指向的位置
        //因为slow之前的元素都是小于key slow之后的元素都是大于key
        return slow;

    }

优化1 - 三数取中

快排在操作时 如果元素本身有序 则会出现以下情况
在这里插入图片描述
我们每次找到的基准值都是这段序列中最小的元素 此时 基准值key在调整后 依旧是序列的第一个元素

此时我们递归基准值的左边和右边 此时 左边没有元素 只需要递归右边

如果我们这段序列有n个元素 那么我们就需要递归n次 则递归的深度就是O(N) 此时非常容易发生栈溢出

根据这个情况 我们可以采用三数取中的方法来减少递归次数

  1. 求出序列第一个元素 中间元素 最后一个元素的中间值
  2. 把这个中间值放在 序列的开头
  3. 在对序列进行调整 把大于key的元素放在key左边 小于k的元素放在k右边

**此时因为我们将中间大小的元素 放在了序列的开头 让元素基本平均放在key值的左右 此时就可以减少递归的次数 **

优化2- 数据规模小时的插入

我们的 快排的递归调用 可以看作是一颗二叉树 找到key并且调整完序列后 对key的左序列和右序列进行递归

此时我们可以想到越靠近数的叶子节点 递归的 数量越多 假如二叉树是一颗完全二叉树则
第一层1次
第二层2次
第三层4次
第四层8次

对于这种情况 我们可以对数据规模较小的数据采用插入排序来减少递归的调用 以此来完成对快排的优化

    /**
     * 时间复杂度:
     *     O(n*logN)[最好情况了]   O(N^2)[数据是有序的 或者是逆序的 ]
     *
     * 空间复杂度:O(logN)[好的情况]  O(n) [不好的情况]
     * 稳定性:不稳定排序
     */
    //划分元素方法
    //1.hoare法
    public static int parttion1(int[] array, int left, int right) {
        int key = left;
        int temp = array[left];
        while (left < right) {
            while (left < right && array[right] >= temp) {
                right--;
            }
            while (left < right && array[left] <= temp) {
                left++;
            }
            swap(array, left, right);
        }
        swap(array, key, left);
        return left;
    }

    //2.挖坑法
    public static int parttion2(int[] array, int left, int right) {
        int temp = array[left];
        //先在第一个位置挖一个坑 temp保存这个坑的元素
        while (left < right) {
            while (left < right && array[right] >= temp) {
                right--;
            }
            array[left] = array[right];//把小的放进坑里 此时right位置就是另一个坑
            while (left < right && array[left] <= temp) {
                left++;
            }
            array[right] = array[left];
        }
        array[left] = temp;//把最后一个坑填起来
        return left;
    }

    //3.双指针法
    public static int parttion3(int[] array, int left, int right) {
        int slow = left;
        int fast = left + 1;
        //定义快慢指针 并初始化
        while (fast <= right) {
            //当快指针为最后一个元素时循环结束
            if (array[fast] < array[left] && array[++slow] != array[fast]) {
                //第一个条件保证快指针fast指向的元素小于我们的key元素
                //第二个条件和++slow交换是因为 slow之前的元素都是小于key的
                //当第一个条件成立时 第二个条件不执行 也就是在这次循环中 slow不进行++操作
                //当两个条件都成立时 此时slow的下一个元素 就是一个大于key的元素 但是这个元素还不可以是fast指向的元素
                //即if条件的原因
                swap(array, fast, slow);
                //交换元素
            }
            fast++;
        }
        swap(array, left, slow);
        //此时key的位置已经确定 即slow此时指向的位置
        //因为slow之前的元素都是小于key slow之后的元素都是大于key
        return slow;

    }

    /**
     * 优化1:对key值的取法
     * 三数取中法
     */
    private static int threeNumber(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[right]) {
                return right;
            } else if (array[mid] > array[left]) {
                return left;
            } else {
                return mid;
            }
        }
    }

    /**
     * 优化2:对规模较小时的优化
     * 对规模较小时可以采用插入排序来操作
     */
    private static void insertSort2(int[] array, int left, int right) {
        for (int i = left + 1; i <= right ; i++) {
            int temp = array[i];
            int j = i - 1;
            while(j >= left && array[j] > temp){
                array[j + 1] = array[j];
                array[j] = temp;
                j--;
            }
        }

    }

    private static void quick(int[] array, int start, int end) {
        if (start >= end) {
            return;
        }
        if(end - start + 1 <= 20){
            insertSort2(array,start,end);
            return;
        }
        int mid = threeNumber(array,start,end);
        swap(array,mid,start);
        int pivot = parttion2(array, start, end);
        quick(array, start, pivot - 1);
        quick(array, pivot + 1, end);
    }

    public static void quickSort(int[] array) {
        quick(array, 0, array.length - 1);
    }


    /**
     * 非递归实现快排
     */
    public static void quickSort1(int[] array){
        Stack<Integer> stack = new Stack<>();
        int start = 0;
        int end = array.length - 1;
        if(end - start <= 20){
            //直接插入排序
            insertSort2(array,start,end);
            return;
        }

        //三数取中
        int mid = threeNumber(array,start,end);
        //把中间值交换到key位置
        swap(array,mid,start);
        int pivot = parttion1(array,start,end);
        //已经排好序的一个元素
        if(pivot > start + 1){
            stack.push(start);
            stack.push(pivot - 1);
        }
    }

归并排序

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

/**
     * 归并排序
     * 时间复杂度:O(N*logN)
     * 空间复杂度:O(N)
     * 稳定性:稳定的排序
     */
    private static void merge(int[] array, int left,int mid, int right){
        int s1 = left;
        int e1 = mid;
        int s2 = mid + 1;
        int e2 = right;
        int[] tempArr = new int[right - left + 1];
        int k = 0;
        //记录tempArr数组的下标
        while (s1 <= e1 && s2 <= e2){
            if(array[s1] <= array[s2]){
                tempArr[k++] = array[s1++];
            }else{
                tempArr[k++] = array[s2++];
            }
        }
        while (s1 <= e1){
            tempArr[k++] = array[s1++];
        }
        while (s2 <= e2){
            tempArr[k++] = array[s2++];
        }
        for (int i = 0; i < k; i++) {
            array[i + left] = tempArr[i];
        }
    }
    private static void mergeSortFunc(int[] array, int left, int right){
        if(left >= right){
            return;
        }
        int mid = (left + right) / 2;
        mergeSortFunc(array,left,mid);
        mergeSortFunc(array,mid + 1,right);
        //合并
        merge(array,left,mid,right);
    }

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

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

相关文章

Jetson Orin环境安装Opencv+Cuda以及vscode环境配置

文章目录 一&#xff1a;Opencv Cuda源码的下载、编译1.卸载jetson上自带的无cuda加速Opencv2.安装Opencv依赖库3.下载 OpenCV 和 opencv_contrib 源码4.编译安装 OpenCV、opencv_contrib 二&#xff1a;Opencv 的环境配置三&#xff1a;Vscode 中的Opencv环境配置四&#xff…

系统分析师---系统建模相关高频考试知识点

系统规划---成本效益分析 评价信息系统经济效益常用的方法主要有成本效益分析法,投入产出分析法和价值工程方法。盈亏平衡法常用于销售定价; 可行性分析 系统规划是信息系统生命周期的第一个阶段,其任务是对企业的环境、目标以及现有系统的状况进行初步调查,根据企业目标…

张正友相机标定原理

相机标定 记录1.1 张正友相机标定相关 参考 记录 最小二乘法&#xff1a;A^T A x 0 奇异值分解的办法求解最小二乘法 因为可以假设标定板平面在世界坐标系Z0的平面上&#xff0c; 1.1 张正友相机标定相关 单目相机标定实现–张正友标定法(包含具体的实现以及C代码&#xff0…

《花雕学AI》ChatGPT Shortcut Chrome 扩展:让生产力和创造力加倍的 ChatGPT 快捷指令库

你是否想要与一个智能的对话伙伴聊天&#xff0c;或者让它帮你完成各种任务&#xff0c;如写作、编程、摘要、翻译等&#xff1f;如果是的话&#xff0c;你可能会对 ChatGPT 感兴趣。ChatGPT 是一个基于 GPT-3.5 的对话式人工智能&#xff0c;可以与用户进行自然、流畅、有趣的…

文件看不见了,内存还占着容量的找回教程

U盘文件突然不见了但还占用内存空间的解决方法 如果文件看不见了但内存占用仍然存在&#xff0c;可能是因为以下原因&#xff1a; 文件被隐藏。某些操作系统允许隐藏文件&#xff0c;这些文件只能在文件浏览器中被找到。 文件被损坏。如果文件损坏&#xff0c;它可能不会显示在…

Python图形化编程开源项目拼码狮PinMaShi

开源仓库 #项目地址 https://github.com/supercoderlee/pinmashi https://gitee.com/supercoderlee/pinmashiPinMaShi采用electron开发&#xff0c;图形化拖拽式编程有效降低编程难度&#xff0c;对Python编程的初学者非常友好&#xff1b;积木式编程加快Python程序的开发&…

黑马Redis笔记-高级篇

黑马Redis笔记-高级篇 1、Redis持久化&#xff08;解决数据丢失&#xff09;1.1 RDB持久化1.1.1 定义1.1.2 异步持久化bgsave原理 1.2 AOF持久化1.3 RDB和AOF比较 2、Redis主从&#xff08;解决并发问题&#xff09;2.1 搭建主从架构2.2 主从数据同步原理2.2.1 全量同步2.2.2 增…

基于哈里斯鹰算法优化的核极限学习机(KELM)分类算法 -附代码

基于哈里斯鹰算法优化的核极限学习机(KELM)分类算法 文章目录 基于哈里斯鹰算法优化的核极限学习机(KELM)分类算法1.KELM理论基础2.分类问题3.基于哈里斯鹰算法优化的KELM4.测试结果5.Matlab代码 摘要&#xff1a;本文利用哈里斯鹰算法对核极限学习机(KELM)进行优化&#xff0c…

【小梦C嘎嘎——启航篇】基本语法格式:namespace ?

基本语法格式&#xff1a;namespace &#xff1f;&#x1f60e; 前言&#x1f64c;namespace 是什么&#xff1f;namespace 的意义何在&#xff1f; 总结撒花&#x1f49e; &#x1f60e;博客昵称&#xff1a;博客小梦 &#x1f60a;最喜欢的座右铭&#xff1a;全神贯注的上吧&…

springboot + vue 部署 阿里云云服务器 ECS

安装所需文件 安装mysql5.7 下载MySQL的yum源配置 wget http://dev.mysql.com/get/mysql57-community-release-el7-11.noarch.rpm安装MySQL的yum源 yum -y install mysql57-community-release-el7-11.noarch.rpm使用yum方式安装MySQL5.7&#xff08;下载需要点时间&#xf…

【 断电延时继电器 电源监视 导轨安装 JOSEF约瑟 HJZS-E202 AC220V】

品牌&#xff1a;JOSEF约瑟型号&#xff1a;HJZS-E202名称&#xff1a;断电延时继电器额定电压&#xff1a;110、220VDC/AC&#xff1b;100VAC触点容量&#xff1a;250V/5A功率消耗&#xff1a;≤4.2W返回系数&#xff1a;10%额定电压 系列型号&#xff1a; HJZS-E202断电延时…

4.4 栈实现及其应用

目录 栈 顺序栈 创建栈: 清空栈: 判断栈是否空 &#xff1a; 进栈 : 出栈 : 取栈顶元素: 栈 栈是限制在一端进行插入操作和删除操作的线性表&#xff08;俗称堆栈&#xff09; 允许进行操作的一端称为“栈顶” 另一固定端称为“栈底” 当栈中没有元素时称为“空栈”…

Robbin负载均衡详解及实践---SpringCloud组件(三)

Robbin负载均衡详解及实践 一 为什么使用Robbin&#xff1f;二 Robbin概念三 负载均衡实践1.启动eureka客户端2.启动多个provider服务&#xff0c;注册到eureka3.在consumer端配置负载均衡参数 四 Robbin源码剖析 一 为什么使用Robbin&#xff1f; 在Eureka详解及实践—Spring…

SAS初识

1、SAS常用工作窗口 “结果”&#xff08;Result&#xff09;窗口——管理SAS程序的输出结果&#xff1b; “日志”&#xff08;Log&#xff09;窗口——记录程序的运行情况&#xff1b; “SAS资源管理器”&#xff08;Explore&#xff09;窗口&#xff1b; “输出”&#xff0…

详解vue中的Object.defineProperty

如果想要age遍历的话 就设置属性 打印出来 发现有可以枚举的属性age 参考课程&#xff1a; 011_尚硅谷Vue技术_Object.defineProperty_哔哩哔哩_bilibili // 1.Vue中的数据代理&#xff1a; // 通过Vm对象来代理data对象中属性的操作&#xff08;读/写&#xff09; // 2…

STL容器 —— list 了解、接口使用,以及模拟实现list(部分常用接口)

注意 &#xff1a; 以下所有文档都来源此网站 &#xff1a; http://cplusplus.com/ 一、vector的介绍及使用 list文档的介绍&#xff1a;https://cplusplus.com/reference/list/list/ 1. vector 的介绍 1. list是可以在常数范围内在任意位置进行插入和删除的序列式容器&…

python自动化测试工具selenium使用指南 ,绝对能帮到你

目录 概述 pythonselenium环境安装 使用selenium启动浏览器 selenium页面加载等待和检测 使用time.sleep()等待 使用implicitly_wait设置最长等待时间 使用WebDriverWait设置等待条件 检测document是否加载完成 selenium元素定位和读取 查找元素 dom元素交互 查找元…

地址族和数据序列

IP地址 为使计算机连接到网络并收发数据,必需向其分配IP地址。 IP地址分为两类。 IPv4 (Internet Protocol version 4)4字节地址族 IPv6 (Internet Protocol version 6)16字节地址族 IPv4与IPv6的差别主要是表示IP地址所用的字节数,目前通用的地址族为IPv4。 IPv6是为了应对20…

全面接入!ChatGPT杀进15个商业应用,让AI替你打工

ChatGPT API开放60多天&#xff0c;世界已经不是两个月前的样子了。 微软联合创始人比尔盖茨称GPT是“40多年来最革命性的技术进步”&#xff0c;英伟达创始人黄仁勋高呼&#xff1a;“我们正处于AI的iPhone时刻”&#xff0c;阿里董事会主席兼CEO张勇也说&#xff1a;“所有行…

经验总结:(Git 版本控制工具快速入门)

Git学习笔记 版本控制(版本迭代) 什么是版本控制 版本迭代&#xff0c;开发过程中&#xff0c;对项目各个阶段的版本的控制。 在开发过程中用于管理我们对文件、目录或工程等内容的修改历史&#xff0c;方便查看更改历史记录&#xff0c;备份以便恢复以前的版本。 多人开发…