【排序】快速排序(C语言实现)

文章目录

  • 前言
  • 1. Hoare思想
  • 2. 挖坑法
  • 3. 前后指针法
  • 4. 三路划分
  • 5. 快速排序的一些小优化
    • 5.1 三数取中
      • 常规的三数取中
      • 伪随机的三数取中
    • 5.2 小区间优化
  • 6. 非递归版本的快排
  • 7. 快速排序的特性总结




前言


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

本章会重点讲解快速排序的四种思想:

  1. Hoare思想
  2. 挖坑法
  3. 前后指针法
  4. 三路划分

以及快速排序的一些小优化,和递归与非递归的实现方式。

阅读本章建议有一点二叉树的基础,如果对二叉树不太了解的可以看看:《初阶二叉树》这篇文章


1. Hoare思想


我们先看一下Hoare的思想是怎么样实现的:
请添加图片描述
这里来解释一下这个思想的意思:

  1. 首先我们将数组起始位置的数据定义成key
  2. 我们从右边开始往前找一个比key的数
  3. 找到比key小的数,我们在从左边开始找一个比key的数
  4. 做完上面两个步骤以后,当R与L相遇时,我们再将key放到R和L相遇的位置上,此刻key已经排到了它所应该在的位置(就是排好序以后的位置),我们介绍的这几种方法本质都是将key放到它所应该在的位置

注意⚠️:
在实现这个代码之前,我们要注意几个点:

  1. 如果是排升序的话,一定是要先从右边开始找,再从左边找,这样就能确保找到的数,一定是小于key的,具体原因如下:
    在这里插入图片描述
  2. 我们要注意如果数组中所有数都比key大,那么我们往右边找比key小的数的时候,控制不当就会导致越界;同理,如果数组中所有的数都比key要小,那么我们往左边找比key大的数的时候,控制不当也会导致越界。
  3. 我们在交换R和L的值的时候,可能会遇到R和L遇到等于key的数,如果不做处理的话,这样R和L就会一直交换,导致死循环。

说了那么多,我们一起来看看Hoare版的快排用代码是怎么实现的:

int PartSort_Hoare(int* a, int begin, int end)
{int left = begin;int right = end - 1;// 在这块将key定义成下标,// 因为我们在后面的过程中会涉及将key与我们锁定的位置的数交换位置,// 如果写成int key = a[left]就不方便后面的交换int key_i = left;while (left < right){// 先走右边,再走左边while (left < right && a[right] >= a[key_i]) // 防止越界 && 找小{--right;}while (left < right && a[left] <= a[key_i]) // 防止越界 && 找大 {++left;}// 找到大的数和小的数,将它们交换位置Swap(&a[left], &a[right]);}// 出循环以后此刻left=right// 将在left位置和key_i位置的数交换一下位置Swap(&a[left], &a[key_i]);return left;
}void Quick_Sort(int* a, int begin, int end)
{if (begin >= end)return;int key = PartSort_Hoare(a, begin, end); // 区间分为: [begin, key) key[key + 1, end), 这里要注意, 我们给的区间是左闭右开的Quick_Sort(a, begin, key);Quick_Sort(a, key + 1, end);}

这里有一个细节不知道大家有没有发现
在这里插入图片描述
我在处理这两个地方的时候是先判断left是否小于right,这样处理的好处是有效的防止了我们对数组的越界访问,如果是后判断left是否小于right,那么在进行a[right] >= a[key_i]这步操作的时候,我们可能会进行越界访问数组的操作。


2. 挖坑法


我们先看一下挖坑法是怎么样实现的:
请添加图片描述

这里来解释一下这个思想的意思:

  1. 先将第一个数据存放在一个临时变量key中,然后形成一个坑位
  2. 右边开始找一个key小的数,将这个数放进坑位中,然后又将这个位置设置成坑位
  3. 左边开始找一个key大的数,将这个数放进坑位中,然后又将这个位置设置成坑位
  4. 当L和R相遇时,此时坑位一定是在L和R的交点处,我们将前面保存的临时变量key放入坑位中,此刻key已经排到了它所应该在的位置(就是排好序以后的位置)

我们来看一下挖坑法实现快排用代码是怎么实现的:

int PartSort_Hole(int* a, int begin, int end)
{int left = begin;int right = end - 1;// 这里不用像Hoare法那样设置成下标,// 因为我们在最后是直接将数据放入坑位中int key = a[begin];int hole = left; // 设置坑位while (left < right){// 先从右边走while (left < right && a[right] >= key)  //  防止越界 && 找小{--right;}//找到小于key的数了,将该数放在hole中,再将该位置设置为holea[hole] = a[right];hole = right;while (left < right && a[left] <= key)  // 防止越界 && 找大{++left;}//找到小于key的数了,将该数放在hole中,再将该位置设置为holea[hole] = a[left];hole = left;}a[hole] = key;return hole;
}void Quick_Sort(int* a, int begin, int end)
{if (begin >= end)return;int key = PartSort_Hole(a, begin, end);// 区间分为: [begin, key) key[key + 1, end), 这里要注意, 我们给的区间是左闭右开的Quick_Sort(a, begin, key);Quick_Sort(a, key + 1, end);
}


3. 前后指针法


我们先看一下前后指正法是怎么样实现的:
请添加图片描述
这里来解释一下这个思想的意思:

  1. 我们先定义两个指针prevcurkeyprevkey指向数组的起始位置,cur指向prev的下一个位置
  2. 我们先移动cur,找到比key小的数,就将cur位置的数据和 prev+1位置的数据交换位置,但这里出于考虑省略一些交换数据的消耗,我们可以判断prev+1位置的数据是否等于cur位置的数据,如过a[cur] < cur && a[prev+1] != a[cur],就将两个位置的数据进行交换
  3. 直到cur指针越界,我们这个过程就结束了,然后再将key的值放到prev的位置,此刻key已经排到了它所应该在的位置(就是排好序以后的位置)

我们来看一下前后指针实现快排用代码是怎么实现的:

int PartSort_PC_Point(int* a, int begin, int end)
{// 创建两个变量来指向数组int prev = begin;int cur = prev + 1;// 这里将key设置成int key_i = begin;while (cur < end){if (a[cur] < a[key_i] && ++prev != cur) // 找比key小的值 && 防止与自身交换{Swap(&a[cur], &a[prev]);}cur++;}// 此时cur已经比right大了,将prev与key_i位置的数换位置Swap(&a[key_i], &a[prev]);return prev;
}void Quick_Sort(int* a, int begin, int end)
{if (begin >= end)return;int key = PartSort_PC_Point(a, begin, end);// 区间分为: [begin, key) key[key + 1, end), 这里要注意, 我们给的区间是左闭右开的Quick_Sort(a, begin, key);Quick_Sort(a, key + 1, end);
}


4. 三路划分


三路划分这个思想用来处理大量重复数据的时候,效率特别高。

我们先看一下三路划分是怎样实现的:
请添加图片描述
这里来解释一下这个思想的意思:

  1. 我们先定义一个key等于数组起始位置的数据 ,之后在数组起始位置定义一个L指针,在L的下一个位置定义一个cur指针,最后在末尾位置定义一个R指针
  2. 我们从cur位置开始,找一个比key小的数,找到以后,交换curL的位置的值,并且++cur++L
  3. 如果cur等于key,++cur
  4. 如果cur遇到一个比key大的数,交换交换curR的位置的值,并且--R,这里要注意不能动cur的值,因为从R位置交换过来的值,我们没办法判断它与key的大小关系,所以保持cur的位置不变,再继续将cur位置的值与key做比较
  5. cur > R的时候,这个过程就结束了

在实现这个代码的过程中,我们需要返回的是一个范围:[begin,L)[R + 1,end)

为什么是这样呢?
在这里插入图片描述
因为我们在进行一次三路划分过后,已经将key与key相等的数排好位置了,剩下的就只剩[begin,L)[R + 1,end)需要排序(这里我是设置成左闭右开区间,大家写的时候,也可以自己设置成左闭右闭区间),因为实现三路划分需要得到一个范围,所以写成函数不太好控制,所以我就直接将这个过程写在Quick_Sort函数中了

我们来看一下三路划分法实现快排用代码是怎么实现的:

void Quick_Sort(int* a, int begin, int end)
{if (begin >= end)return;int left = begin;int right = end - 1;int cur = left + 1;int key = a[left];while (cur <= right) // cur > right 循环才结束{if (a[cur] < key) // cur位置的值小于key{// 交换cur和L的位置的值,并且++cur ,++leftSwap(&a[cur], &a[left]);++cur;++left;}else if (a[cur] > key) // cur位置的值大于key{// 交换cur和L的位置的值,并且--rightSwap(&a[cur], &a[right]);--right;}else // cur位置的值等于key{++cur;}}// [begin, left) 和 [right + 1, end)Quick_Sort(a, begin, left);Quick_Sort(a, right + 1, end);
}


5. 快速排序的一些小优化


我们在上面实现排序的时候,面对一些情况我们写的快排也还是有些吃力,所以我们也还需要对一些情况做一些处理

这里有一点,可以注意一下:
就是我在上面写4种思想实现快排的时候,前三种思想我是将递归过程单独写成子函数来调用的,这样做的目的是使我写这篇博客时的可读性提高。但如果是我们自己写的时候,也可以不用单独写成一个子函数来调用,可以直接将这些思想的过程写进函数体里面。毕竟函数调用时,还是会有一点消耗的,当然这些消耗可以忽略不计

5.1 三数取中

三数取中有两种取法(当然不止两种),这里我要介绍的是两种思想:

常规的三数取中

这个思想主要是:我们通过比较数组起始位置和中间位置还有末尾位置的数据,第二大的数作为key

具体我们写代码来看一下:

int GetMidi(int* a, int begin, int end) // 三数取中
{int left = begin;int right = end - 1;int mid = (left + right) / 2;if (a[left] < a[mid])                // a[left] < a[mid]{if (a[mid] < a[right])           // a[left] < a[mid] < a[right]return mid;else if (a[left] < a[right])     // a[mid] >= a[right] > a[left]return right;else                             // a[mid] > a[left] >= a[right]return left;}else                                 // a[left] >= a[mid]{if (a[left] < a[right])          // a[mid] <= a[left] < a[right]return left;else if (a[right] > a[mid])      // a[left] >= a[right] > a[mid]return right;else                             // a[left] > a[mid] >= a[right]return mid;}
}

伪随机的三数取中

这个思想主要是:我们通过比较数组起始位置和数组中一个随机的位置,和他们中间位置的数据,第二大的数作为key

具体实现过程如下:

int GetMidi_random(int* a, int begin, int end) 
{int left = begin;int right = begin + rand() % (end - begin); // 后面加的数是整个数组的范围int mid = (left + right) / 2;if (a[left] < a[mid])                // a[left] < a[mid]{if (a[mid] < a[right])           // a[left] < a[mid] < a[right]return mid;else if (a[left] < a[right])     // a[mid] >= a[right] > a[left]return right;else                             // a[mid] > a[left] >= a[right]return left;}else                                 // a[left] >= a[mid]{if (a[left] < a[right])          // a[mid] <= a[left] < a[right]return left;else if (a[right] > a[mid])      // a[left] >= a[right] > a[mid]return right;else                             // a[left] > a[mid] >= a[right]return mid;}
}int main()
{srand((unsigned)time(NULL)); // 生成随机数种子return 0;
}

我们在用三数取中时,获取到的是想要的数的位置,然后将这个位置的数和数组起始位置的数交换一下,这样就方便我们定义key了。具体实现方式,我写在了本小节的最后。

5.2 小区间优化

在这里插入图片描述

我们在学过二叉树以后,应该知道,完全二叉树,倒数第二层和最后一层加起来占了整个节点75%的节点,而我们在处理大量数据使用快排实现递归时当只有10个数据时,还会递归多次:
在这里插入图片描述
而在处理这些大量数据,我们递归下来时,无疑会产生大量范围为10及10以下的多组数据,如果我们此时继续递归,无疑会产生很多组小范围的数据。所以范围小于等于10的时候,就没必要使用快排继续递归了,这里可以考虑使用直接插入排序来排每组范围小于等于10的数组。这样就大大降低了递归时开辟栈帧的消耗了。在使用插入排序时,我们必须要弄清楚所需排序的范围:
在这里插入图片描述

下面我以三路划分的思想写一个我上述优化后的排序,里面有用到直接插入排序,这个不是本章的重点,不了解的可以看看这篇博客:《简单排序》:

int GetMidi_random(int* a, int begin, int end) 
{int left = begin;int right = begin + rand() % (end - begin); // 后面加的数是整个数组的范围int mid = (left + right) / 2;if (a[left] < a[mid])                // a[left] < a[mid]{if (a[mid] < a[right])           // a[left] < a[mid] < a[right]return mid;else if (a[left] < a[right])     // a[mid] >= a[right] > a[left]return right;else                             // a[mid] > a[left] >= a[right]return left;}else                                 // a[left] >= a[mid]{if (a[left] < a[right])          // a[mid] <= a[left] < a[right]return left;else if (a[right] > a[mid])      // a[left] >= a[right] > a[mid]return right;else                             // a[left] > a[mid] >= a[right]return mid;}
}void Quick_Sort(int* a, int begin, int end)
{if (begin >= end)return;if (end - begin > 10)  // 小区间优化{int left = begin;int right = end - 1;int cur = left + 1;int mid = 0;if (left < end){mid = GetMidi(a, left, end); //三数取中,获取我们要找的数Swap(&a[mid], &a[left]); // 将三数取中得到的数换到第一个位置}int key = a[left];while (cur <= right){if (a[cur] < key){Swap(&a[cur], &a[left]);++left;++cur;}else if (a[cur] > key){Swap(&a[cur], &a[right]);--right;}else++cur;}// 分区间进行递归Quick_Sort(a, begin, left);Quick_Sort(a, right + 1, end);}else{// 使用插入排序时,要注意使用的范围InsertSort(a + begin, end - begin);}
}



6. 非递归版本的快排


在我们使用快排时,其实用递归也递归不了多少层。所以在平时我们要使用快排时,使用递归版的完全够用了。但由于现在还在学习阶段,所以掌握一下非递归版的快排还是有必要的。

我们实现非递归版本的快排时,用到的思想是我上面介绍的几种思想,只不过接下来讲的不是用递归去完成这个过程,这里我们要使用一个工具——,能够看到这里的我相信大家都对栈有一定的了解了,所以我在下面代码中,就将直接使用我写的栈了。不太了解栈的也可以看看这篇文章:《栈和队列》

void QuickSortNonR(int* a,int begin, int end)
{int left = begin;int right = end;Stack stack; // 创建一个栈的数据结构StackInit(&stack); // 将这个栈初始化一下StackPush(&stack, left); // 插入最开始的数StackPush(&stack, right); // 插入最后一位数的下一位,因为我们上面实现的三种方法都是左闭右开的while (!StackEmpty(&stack)){right = StackTop(&stack); // 我们是先插入最右边的数,所以我们应该先拿出最右边的数StackPop(&stack); // 栈顶元素删除left = StackTop(&stack); // 在上面删除right时,栈顶元素就是left了StackPop(&stack); // 栈顶元素删除int key = PartSort_Hoare(a, left, right); // 将这段区间进行排序// [left,key) key [key+1,right)if (right > key + 1) // 如果这个区间存在,就将这个区间表示的范围入栈(这里要注意入栈顺序和出栈顺序){StackPush(&stack, key + 1);StackPush(&stack, right);}if (left < key) // 如果这个区间存在,就将这个区间表示的范围入栈(这里要注意入栈顺序和出栈顺序){StackPush(&stack, left);StackPush(&stack, key);}}// 销毁栈StackDestroy(&stack);
}

有人可能会问我们递归时用到的栈帧和我们自己做的栈,有什么区别呢?

因为我自己做的栈是在堆上开辟的空间,而如果使用递归版本的栈帧是在栈上开辟的空间。32位机器下,栈上的空间只有8MB左右,而堆上的空间有4G左右。所以这里选择在堆上开辟空间,可以很好的节约空间,也能减少递归时产生的消耗。


7. 快速排序的特性总结


  1. 快速排序整体的综合性能和使用场景都是比较好的,所以才敢叫快速排序

  2. 时间复杂度:O(N*logN)
    在这里插入图片描述

  3. 空间复杂度:O(logN)

    • 快速排序的空间复杂度取决于递归深度和每个递归函数栈空间的大小。
    • 在最坏情况下,快速排序的空间复杂度为O(n);在平均情况下,快速排序的空间复杂度为O(logn)1。
    • 快速排序是一种原地排序算法,它的工作原理是选择一个基准值,将待排序的数组划分为两个子数组,一个小于等于基准值,一个大于基准值,然后对两个子数组递归进行快速排序,直到整个数组有序。
  4. 稳定性:不稳定

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

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

相关文章

【开源项目】轻量元数据管理解决方案——Marquez

大家好&#xff0c;我是独孤风。 又到了本周的开源项目推荐。最近推荐的元数据管理项目很多&#xff0c;但是很多元数据管理平台的功能复杂难用。 那么有没有轻量一点的元数据管理项目呢&#xff1f; 今天为大家推荐的开源项目&#xff0c;就是一个轻量级的元数据管理工具。虽然…

Linux入门攻坚——12、Linux网络属性配置相关知识2

CentOS 7网络属性配置&#xff1a; 传统命名机制&#xff1a;以太网eth[0,1,2,...]&#xff0c;wlan[0,1,2...] 可预测功能的命名机制&#xff1a; udev支持多种不同的命名方案&#xff1a; Firmware &#xff0c;拓扑结构 在对待设备文件这块&#xff0c;Linux改…

人生重开模拟器(c语言)

前言&#xff1a; 人生重开模拟器是前段时间非常火的一个小游戏&#xff0c;接下来我们将一起学习使用c语言写一个简易版的人生重开模拟器。 网页版游戏&#xff1a; 人生重开模拟器 (ytecn.com) 1.实现一个简化版的人生重开模拟器 &#xff08;1&#xff09; 游戏开始的时…

云HIS系统源码,基层卫生HIS系统,云端SaaS模式多医院版

系统介绍&#xff1a; 基层卫生健康云HIS系统采用云端SaaS服务的方式提供&#xff0c;使用用户通过浏览器即能访问&#xff0c;无需关注系统的部署、维护、升级等问题&#xff0c;系统充分考虑了模板化、配置化、智能化、扩展化等设计方法&#xff0c;覆盖了基层医院的主要工作…

基于OpenMV与STM32的数据通信项目(代码开源)

前言&#xff1a;本文为手把手教学 OpenMV 与 STM32 的数据通信项目教程&#xff0c;本教程使用 STM32F103C8T6 与 OpenMV 进行操作。 OpenMV 是非常强大的计算机视觉实现工具&#xff0c;自身提供了非常多的视觉项目案例&#xff0c;编程与使用门槛极低。为了进一步增强作品的…

Camunda Rest API

客户端像调用本地方法一样调用引擎中的接口。 https://docs.camunda.org/manual/7.17/reference/rest/ 一&#xff1a;pom.xml <dependency><groupId>org.camunda.community.rest</groupId><artifactId>camunda-platform-7-rest-client-spring-boot-…

解决百度网盘限速问题(不用会员)

不想冲网盘会员的友友可以看一下这个方法 1.需要下载Motrix(因为我是Mac所以用的这个,Win用户可以试试别的) 相关软件下载 | 油小猴 2.打开这个网站 就是加速 用户操作演示面板 3.勾选后可以直接发送到Motrix 还可以使用的方法

CES2024:智能戒指、全息技术、AI家居机器人等有趣的小工具

在CES2024的展会上上&#xff0c;我们见证了一系列充满创意和未来感的科技产品。从智能戒指到全息技术&#xff0c;再到AI家居机器人&#xff0c;这些有趣的小工具不仅展现了技术的进步&#xff0c;更预示着未来生活的可能性。现在就来给大家介绍九个实用有趣的小工具。 1、华…

[AutoSar]基础部分 RTE 03 C/S Port 同步/异步

目录 关键词平台说明一、C/S port interface 定义1.1在Davinci developer中的创建 二、同步调用和异步调用2.1 同步2.1.1同步code2.1.2同步处理时序图 2.2 异步2.2.1异步code2.2.2异步处理时序图2.2.2.1 poling2.2.2.2 waiting2.2.2.3none 三、server端的mapping到task详解 关键…

CSS3新增文本样式-text-shadow属性

文本样式 概念:在CSS3中&#xff0c;增加了丰富的文本修饰效果&#xff0c;使得页面更加美观舒服。 常用的文本样式属性 属性说明text-shadow文本阴影text-stroke文本描边text-overflow文本溢出word-wrap强制换行font-face嵌入字体 W3C坐标系 我们日常生活使用最多的是数学坐…

三菱plc学习入门(三,FB模块)

小编很抱歉&#xff0c;因为小编是以基恩士&#xff0c;三菱的plc一起学习并找发现不同&#xff01;&#xff01;&#xff01;并结合工作的案例来进行学习&#xff0c;所以内容上与系统的学习还是存在差异。如果只是单独的学习此篇文章&#xff0c;如果对您有帮助&#xff0c;欢…

蓝桥杯省赛无忧 STL 课件11 pair

01 pair的定义和结构 在C中&#xff0c;pair是一个模板类&#xff0c;用于表示一对值的组合&#xff0c;它位于头文件中。 pair类的定义如下: template<class T1,class T2>struct pair{T1 first;//第一个值T2 second;//第二个值// 构造函数pair();pair(const T1& X…

Deep Reinforment Learning Note 1

文章目录 Terminology Terminology st : stateot : observationat : action π θ ( a t ∣ o t ) \pi_\theta (a_t | o_t) πθ​(at​∣ot​) : policy π θ ( a t ∣ s t ) \pi_\theta (a_t | s_t) πθ​(at​∣st​) : policy (fully observed) Observation result from…

STM32--基于STM32F103的MAX30102心率血氧测量

本文介绍基于STM32F103ZET6MAX30102心率血氧测量0.96寸OLED&#xff08;7针&#xff09;显示&#xff08;完整程序代码见文末链接&#xff09; 一、简介 MAX30102是一个集成的脉搏血氧仪和心率监测仪生物传感器的模块。它集成了一个红光LED和一个红外光LED、光电检测器、光器…

程序员有哪些接单的渠道?

这题我会&#xff01;程序员接单的渠道那可太多了&#xff0c;想要接到合适的单子&#xff0c;筛选一个合适的平台很重要。如果你也在寻找一个合适的接单渠道&#xff0c;可以参考以下这些方向。 首先&#xff0c;程序员要对接单有一个基本的概念&#xff1a;接单渠道可以先粗略…

【排序】归并排序(C语言实现)

文章目录 1. 递归版的归并排序1.1 归并排序的思想2. 递归版的归并排序的实现 2. 非递归版的归并排序 1. 递归版的归并排序 1.1 归并排序的思想 归并排序&#xff08;MERGE - SORT&#xff09;是建立在归并操作上的一种有效的排序算法, 该算法是采用分治法&#xff08;Divide a…

25考研数学备考计划

今天要给大家分享的是考研数学的一些备考经验。 一般理工科&#xff1a;数一、二&#xff1b;经济类&#xff1a;数三。 题型分值 学/专硕考试范围 另外&#xff0c;我也为大家找到了考研数学教材范围及重点&#xff0c;大家V..X关注&#xff1a;ZL研知己&#xff0c;回复&…

听歌识曲(UPC练习)

题目描述 洛洛有一份私人歌单&#xff0c;歌单里面塞满了他喜欢的歌曲&#xff0c;像夏恋、雨道、彩月、幻昼……整整有好几百首。洛洛每天都要把他的歌单听一遍&#xff0c;以致于他都能知道在什么时候放的是什么歌。 洛洛在向你推荐了他的歌单之后&#xff0c;决定考考你&am…

Redis Zset类型

Redis Zset类型 Zset&#xff08;有序集合&#xff09;它是集合的一种&#xff0c;不仅可以保存元素&#xff0c;还可以为每个元素关联一个 double 类型的分数&#xff08;score&#xff09;&#xff0c;Redis 正是通过分数来为集合中的元素进行从小到大的排序。在 Zset 中&am…

Docker 方式安装 HertzBeat

一、安装docker docker安装参考https://www.runoob.com/docker/docker-tutorial.html curl -fsSL https://get.docker.com -o get-docker.shsudo sh get-docker.sh二、拉取docker镜像 https://hertzbeat.com/zh-cn/docs/start/docker-deploy部署HertzBeat您可能需要掌握的几条…