【数据结构与算法】JavaScript实现排序算法

这篇具有很好参考价值的文章主要介绍了【数据结构与算法】JavaScript实现排序算法。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

一、大O表示法

大O表示法:

  • 在计算机中采用粗略的度量来描述计算机算法的效率,这种方法被称为 “大O”表示法
  • 数据项个数发生改变时,算法的效率也会跟着改变。所以说算法A比算法B快两倍,这样的比较是没有意义的。
  • 因此我们通常使用算法的速度随着数据量的变化会如何变化的方式来表示算法的效率,大O表示法就是方式之一。

常见的大O表示形式

符号 名称
O(1) 常数
O(log(n)) 对数
O(n) 线性
O(nlog(n)) 线性和对数乘积
O(n²) 平方
O(2n) 指数

不同大O形式的时间复杂度:

【数据结构与算法】JavaScript实现排序算法,数据结构与算法,排序算法,javascript,算法,数据结构

可以看到效率从大到小分别是:O(1)> O(logn)> O(n)> O(nlog(n))> O(n²)> O(2n)

推导大O表示法的三条规则:

  • 规则一:用常量1取代运行时间中所有的加法常量。如7 + 8 = 15,用1表示运算结果15,大O表示法表示为O(1);
  • 规则二:运算中只保留最高阶项。如N^3 + 3n +1,大O表示法表示为:O(N3);
  • 规则三:若最高阶项的常数不为1,可将其省略。如4N2,大O表示法表示为:O(N2);

二、排序算法

这里主要介绍几种简单排序和高级排序:

  • 简单排序: 冒泡排序、选择排序、插入排序;
  • 高级排序: 希尔排序、快速排序;

此处创建一个列表类ArrayList并添加一些属性和方法,用于存放这些排序方法:

    //创建列表类
    function ArrayList() {
      //属性
      this.array = []

      //方法
      //封装将数据插入到数组中方法
      ArrayList.prototype.insert = function(item){
        this.array.push(item)
      }

      //toString方法
      ArrayList.prototype.toString = function(){
        return this.array.join('-')
      }

      //交换两个位置的数据
      ArrayList.prototype.swap = function(m, n){
        let temp  = this.array[m]
        this.array[m] = this.array[n]
        this.array[n] = temp
      }
1.冒泡排序

冒泡排序的思路:

  • 对未排序的各元素从头到尾依次比较相邻的两个元素大小关系;
  • 如果左边的人员高,则将两人交换位置。比如1比2矮,不交换位置;
  • 右移动一位,继续比较2和3,最后比较 length - 1 和 length - 2这两个数据;
  • 当到达最右端时,最高的人一定被放在了最右边
  • 按照这个思路,从最左端重新开始时,只需要走到倒数第二个位置即可;

【数据结构与算法】JavaScript实现排序算法,数据结构与算法,排序算法,javascript,算法,数据结构

实现思路:

两层循环:

  • 外层循环控制冒泡趟数:
    • 第一次:j = length - 1,比较到倒数第一个位置 ;
    • 第二次:j = length - 2,比较到倒数第二个位置 ;
  • 内层循环控制每趟比较的次数:
    • 第一次比较: i = 0,比较 0 和 1 位置的两个数据;
    • 最后一次比较:i = length - 2,比较length - 2和 length - 1两个数据;

详细过程如下图所示:

【数据结构与算法】JavaScript实现排序算法,数据结构与算法,排序算法,javascript,算法,数据结构

动态过程:

【数据结构与算法】JavaScript实现排序算法,数据结构与算法,排序算法,javascript,算法,数据结构

代码实现:

      //冒泡排序
      ArrayList.prototype.bubblesor = function(){
        //1.获取数组的长度
        let length = this.array.length

        //外层循环控制冒泡趟数
        for(let j = length - 1; j >= 0; j--){
          //内层循环控制每趟比较的次数
          for(let i = 0; i < j; i++){
          if (this.array[i] > this.array[i+1]) {
            //交换两个数据
            let temp  = this.array[i]
        	this.array[i] = this.array[i+1]
        	this.array[i+1] = temp
          }
        }
        }
      }

测试代码:

    //测试类
    let list = new ArrayList()

    //插入元素
    list.insert(66)
    list.insert(88)
    list.insert(12)
    list.insert(87)
    list.insert(100)
    list.insert(5)
    list.insert(566)
    list.insert(23)
    
    //验证冒泡排序
    list.bubblesor()
    console.log(list);

测试结果:
【数据结构与算法】JavaScript实现排序算法,数据结构与算法,排序算法,javascript,算法,数据结构

冒泡排序的效率:

  • 上面所讲的对于7个数据项,比较次数为:6 + 5 + 4 + 3 + 2 + 1;
  • 对于N个数据项,比较次数为:(N - 1) + (N - 2) + (N - 3) + … + 1 = N * (N - 1) / 2;如果两次比较交换一次,那么交换次数为:N * (N - 1) / 4;
  • 使用大O表示法表示比较次数和交换次数分别为:O( N * (N - 1) / 2)和O( N * (N - 1) / 4),根据大O表示法的三条规则都化简为:O(N^2);
2.选择排序

选择排序改进了冒泡排序:

  • 交换次数O(N^2)减小到O(N)
  • 但是比较次数依然是O(N^2)

选择排序的思路:

  • 选定第一个索引的位置比如1,然后依次和后面的元素依次进行比较
  • 如果后面的元素,小于索引1位置的元素,则交换位置到索引1处;
  • 经过一轮的比较之后,可以确定一开始指定的索引1位置的元素是最小的
  • 随后使用同样的方法除索引1意外逐个比较剩下的元素即可;
  • 可以看出选择排序,第一轮会选出最小值第二轮会选出第二小的值,直到完成排序。

【数据结构与算法】JavaScript实现排序算法,数据结构与算法,排序算法,javascript,算法,数据结构

实现思路:

两层循环:

  • 外层循环控制指定的索引:
    • 第一次:j = 0,指定第一个元素 ;
    • 最后一次:j = length - 1,指定最后一个元素 ;
  • 内层循环负责将指定索引(i)的元素与剩下(i - 1)的元素进行比较;

动态过程:

【数据结构与算法】JavaScript实现排序算法,数据结构与算法,排序算法,javascript,算法,数据结构

代码实现:

      //选择排序
      ArrayList.prototype.selectionSort = function(){
        //1.获取数组的长度
        let length = this.array.length

        //2.外层循环:从0开始获取元素
        for(let j = 0; j < length - 1; j++){
          let min = j
          //内层循环:从i+1位置开始,和后面的元素进行比较
        for(let i = min + 1; i < length; i++){
          if (this.array[min] > this.array[i]) {
            min = i
          }
        }
        this.swap(min, j)
        }
      }

测试代码:

    //测试类
    let list = new ArrayList()

    //插入元素
    list.insert(66)
    list.insert(88)
    list.insert(12)
    list.insert(87)
    list.insert(100)
    list.insert(5)
    list.insert(566)
    list.insert(23)
    
    //验证选择排序
    list.selectionSort()
    console.log(list);

测试结果:

【数据结构与算法】JavaScript实现排序算法,数据结构与算法,排序算法,javascript,算法,数据结构

选择排序的效率:

  • 选择排序的比较次数为:N * (N - 1) / 2,用大O表示法表示为:O(N^2);
  • 选择排序的交换次数为:(N - 1) / 2,用大O表示法表示为:O(N);
  • 所以选择排序的效率高于冒泡排序;
3.插入排序

插入排序是简单排序中效率最高的一种排序。

插入排序的思路:

  • 插入排序思想的核心是局部有序。如图所示,X左边的人称为局部有序
  • 首先指定一数据X(从第一个数据开始),并将数据X的左边变成局部有序状态;
  • 随后将X右移一位,再次达到局部有序之后,继续右移一位,重复前面的操作直至X移至最后一个元素。

【数据结构与算法】JavaScript实现排序算法,数据结构与算法,排序算法,javascript,算法,数据结构

插入排序的详细过程:

【数据结构与算法】JavaScript实现排序算法,数据结构与算法,排序算法,javascript,算法,数据结构

动态过程:

【数据结构与算法】JavaScript实现排序算法,数据结构与算法,排序算法,javascript,算法,数据结构

代码实现:

      //插入排序
      ArrayList.prototype.insertionSort = function(){
        //1.获取数组的长度
        let length = this.array.length

        //2.外层循环:从第二个数据开始,向左边的已经局部有序数据进行插入
        for(let i = 1; i < length; i++){
          //3.内层循环:获取i位置的元素,使用while循环(重点)与左边的局部有序数据依次进行比较
          let temp = this.array[i]
          let j = i
          while(this.array[j - 1] > temp && j > 0){
            this.array[j] = this.array[j - 1]//大的数据右移
            j--
          }
          //4.while循环结束后,index = j左边的数据变为局部有序且array[j]最大。此时将array[j]重置为排序前的数据array[i],方便下一次for循环
          this.array[j] = temp
        }
      }

测试代码:

   //测试类
    let list = new ArrayList()

    //插入元素
    list.insert(66)
    list.insert(88)
    list.insert(12)
    list.insert(87)
    list.insert(100)
    list.insert(5)
    list.insert(566)
    list.insert(23)
    // console.log(list);

    //验证插入排序
    list.insertionSort()
    console.log(list);

测试结果:

【数据结构与算法】JavaScript实现排序算法,数据结构与算法,排序算法,javascript,算法,数据结构

插入排序的效率:

  • 比较次数: 第一趟时,需要的最大次数为1;第二次最大为2;以此类推,最后一趟最大为N-1;所以,插入排序的总比较次数为N * (N - 1) / 2;但是,实际上每趟发现插入点之前,平均只有全体数据项的一半需要进行比较,所以比较次数为:N * (N - 1) / 4
  • 交换次数:指定第一个数据为X时交换0次,指定第二个数据为X最多需要交换1次,以此类推,指定第N个数据为X时最多需要交换N - 1次,所以一共需要交换N * (N - 1) / 2次,平局次数为N * (N - 1) / 2
  • 虽然用大O表示法表示插入排序的效率也是O(N^2),但是插入排序整体操作次数更少,因此,在简单排序中,插入排序效率最高
4.希尔排序

希尔排序插入排序的一种高效的改进版,效率比插入排序要

希尔排序的历史背景:

  • 希尔排序按其设计者希尔(Donald Shell)的名字命名,该算法由1959年公布
  • 希尔算法首次突破了计算机界一直认为的**算法的时间复杂度都是O(N^2)**的大关,为了纪念该算法里程碑式

的意义,用Shell来命名该算法;

插入排序的问题:

  • 假设一个很小的数据项很靠近右端的位置上,这里本应该是较大的数据项的位置
  • 将这个小数据项移动到左边的正确位置,所有的中间数据项都必须向右移动一位,这样效率非常低;
  • 如果通过某种方式,不需要一个个移动所有中间的数据项,就能把较小的数据项移到左边,那么这个算法的执行速度就会有很大的改进。

希尔排序的实现思路:

  • 希尔排序主要通过对数据进行分组实现快速排序;
  • 根据设定的增量(gap)将数据分为gap个组(组数等于gap),再在每个分组中进行局部排序;

假如有数组有10个数据,第1个数据为黑色,增量为5。那么第二个为黑色的数据index=5,第3个数据为黑色的数据index = 10(不存在)。所以黑色的数据每组只有2个,10 / 2 = 5一共可分5组,即组数等于增量gap

  • 排序之后,减小增量,继续分组,再次进行局部排序,直到增量gap=1为止。随后只需进行微调就可完成数组的排序;

具体过程如下:

  • 排序之前的,储存10个数据的原始数组为:

【数据结构与算法】JavaScript实现排序算法,数据结构与算法,排序算法,javascript,算法,数据结构

  • 设初始增量gap = length / 2 = 5,即数组被分为了5组,如图所示分别为:[8, 3]、[9, 5]、[1, 4]、[7, 6]、[2, 0]:

【数据结构与算法】JavaScript实现排序算法,数据结构与算法,排序算法,javascript,算法,数据结构

  • 随后分别在每组中对数据进行局部排序,5组的顺序如图所示,变为:[3, 8]、[5, 9]、[1, 4]、[6, 7]、[0, 2]:

【数据结构与算法】JavaScript实现排序算法,数据结构与算法,排序算法,javascript,算法,数据结构

  • 然后缩小增量gap = 5 / 2 = 2,即数组被分为了2组,如图所示分别为:[3,1,0,9,7]、[5,6,8,4,2]:

【数据结构与算法】JavaScript实现排序算法,数据结构与算法,排序算法,javascript,算法,数据结构

  • 随后分别在每组中对数据进行局部排序,两组的顺序如图所示,变为:[0,1,3,7,9]、[2,4,5,6,8]:

【数据结构与算法】JavaScript实现排序算法,数据结构与算法,排序算法,javascript,算法,数据结构

  • 然后然后缩小增量gap = 2 / 1 = 1,即数组被分为了1组,如图所示为:[0,2,1,4,3,5,7,6,9,8]:

【数据结构与算法】JavaScript实现排序算法,数据结构与算法,排序算法,javascript,算法,数据结构

  • 最后只需要对该组数据进行插入排序即可完成整个数组的排序:

【数据结构与算法】JavaScript实现排序算法,数据结构与算法,排序算法,javascript,算法,数据结构

动态过程:

【数据结构与算法】JavaScript实现排序算法,数据结构与算法,排序算法,javascript,算法,数据结构

图中d表示增量gap。

增量的选择:

  • 原稿中希尔建议的初始间距为N / 2,比如对于N = 100的数组,增量序列为:50,25,12,6,3,1,可以发现不能整除时向下取整。
  • Hibbard增量序列: 增量序列算法为:2^k - 1,即1,3,5,7… …等;这种情况的最坏复杂度为 O(N3/2)* ,平均复杂度为*O(N5/4) 但未被证明;
  • Sedgewcik增量序列:

【数据结构与算法】JavaScript实现排序算法,数据结构与算法,排序算法,javascript,算法,数据结构

以下代码实现中采用希尔排序原稿中建议的增量即N / 2

代码实现:

      //希尔排序
      ArrayList.prototype.shellSort = function(){
        //1.获取数组的长度
        let length = this.array.length

        //2.初始化增量
        let gap = Math.floor(length / 2)

        //3.第一层循环:while循环(使gap不断减小)
        while(gap >= 1 ){
          //4.第二层循环:以gap为增量,进行分组,对分组进行插入排序
          //重点为:将index = gap作为选中的第一个数据
          for(let i = gap; i < length; i++){
            let temp = this.array[i]
            let j = i
            //5.第三层循环:寻找正确的插入位置
            while(this.array[j - gap] > temp && j > gap - 1){
              this.array[j] = this.array[j - gap]
              j -= gap
            }
          //6.将j位置的元素设置为temp
          this.array[j] = temp
          }

          gap = Math.floor(gap / 2)
        }
      }

这里解释一下上述代码中的三层循环:

  • 第一层循环: while循环,控制gap递减到1;
  • 第二层循环: 分别取出根据g增量gap分成的gap组数据:将index = gap的数据作为选中的第一个数据,如下图所示,gap=5,则index = gap的数据为3,index = gap - 1的数据为8,两个数据为一组。随后gap不断加1右移,直到gap < length,此时实现了将数组分为5组。

【数据结构与算法】JavaScript实现排序算法,数据结构与算法,排序算法,javascript,算法,数据结构

  • 第三层循环: 对每一组数据进行插入排序;

测试代码:

   //测试类
    let list = new ArrayList()

    //插入元素
    list.insert(66)
    list.insert(88)
    list.insert(12)
    list.insert(87)
    list.insert(100)
    list.insert(5)
    list.insert(566)
    list.insert(23)
    // console.log(list);

    //验证希尔排序
    list.shellSort()
    console.log(list);

测试结果:

【数据结构与算法】JavaScript实现排序算法,数据结构与算法,排序算法,javascript,算法,数据结构

希尔排序的效率:

  • 希尔排序的效率和增量有直接关系,即使使用原稿中的增量效率都高于简单排序。
5.快速排序

快速排序的介绍:

  • 快速排序可以说是目前所有排序算法中,最快的一种排序算法。当然,没有任何一种算法是在任意情况下都是最优的。但是,大多数情况下快速排序是比较好的选择。
  • 快速排序其实是冒泡排序的升级版;

快速排序的核心思想是分而治之,先选出一个数据(比如65),将比其小的数据都放在它的左边,将比它大的数据都放在它的右边。这个数据称为枢纽

和冒泡排序的不同:

  • 我们选择的65可以一次性将它放在最正确的位置,之后就不需要做任何移动;
  • 而冒泡排序即使已经找到最大值,也需要继续移动最大值,直到将它移动到最右边;

【数据结构与算法】JavaScript实现排序算法,数据结构与算法,排序算法,javascript,算法,数据结构

快速排序的枢纽:

  • 第一种方案: 直接选择第一个元素作为枢纽。但是,当第一个元素就是最小值的情况下,效率不高;
  • 第二种方案: 使用随机数。随机数本身十分消耗性能,不推荐;
  • 优秀的解决方法:取index为头、中、位的三个数据排序后的中位数;如下图所示,按下标值取出的三个数据为:92,31,0,经排序后变为:0,31,92,取其中的中位数31作为枢纽(当(length-1)/2不整除时可向下或向上取整):

【数据结构与算法】JavaScript实现排序算法,数据结构与算法,排序算法,javascript,算法,数据结构

实现枢纽选择:

//交换两个位置的数据
let swap = function(arr, m, n){
    let temp  = arr[m]
    arr[m] = arr[n]
    arr[n] = temp
}

//快速排序
//1.选择枢纽
let median = function(arr){
  //1.取出中间的位置
  let center = Math.floor(arr.length / 2)
  let right = arr.length - 1 
  let left = 0

  //2.判断大小并进行交换
  if (arr[left] > arr[center]) {
    swap(arr, left, center)
  }
  if (arr[center] > arr[right]){
    swap(arr, center, right)
  }
  if (arr[left] > arr[right]) {
    swap(arr, left, right)
  }
  //3.返回枢纽
  return center
}

数组经过获取枢纽函数操作之后,选出的3个下标值对应的数据位置变为:

【数据结构与算法】JavaScript实现排序算法,数据结构与算法,排序算法,javascript,算法,数据结构

动态过程:

【数据结构与算法】JavaScript实现排序算法,数据结构与算法,排序算法,javascript,算法,数据结构

快速排序代码实现:

//2.快速排序
let QuickSort = function(arr){
  if (arr.length == 0) {
    return []
  }
  let center = median(arr)
  let c = arr.splice(center, 1)
  let l = []
  let r = []

  for (let i = 0; i < arr.length; i++) {
      if (arr[i] < c) {
        l.push(arr[i])
      }else{
        r.push(arr[i])
      }        
  }
  return QuickSort(l).concat(c, QuickSort(r))
}

算法的巧妙之处在于通过:

QuickSort(l).concat(c, QuickSort(r))

递归调用QuickSort函数实现了枢纽Center左边数据l和右边数据r的排序;

测试代码:

let arr = [0, 13, 81, 43, 31, 27, 56, 92]
console.log(QuickSort(arr));

测试结果

【数据结构与算法】JavaScript实现排序算法,数据结构与算法,排序算法,javascript,算法,数据结构

快速排序的效率:文章来源地址https://www.toymoban.com/news/detail-783849.html

  • 快速排序最坏情况下的效率:每次选择的枢纽都是最左边或最右边的数据,此时效率等同于冒泡排序,时间复杂度为O(n2)。可根据不同的枢纽选择避免这一情况;
  • 快速排序的平均效率:为O(N*logN),虽然其他算法效率也可达到O(N*logN),但是其中快速排序是最好的

到了这里,关于【数据结构与算法】JavaScript实现排序算法的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • JavaScript数据结构与算法整理------数组

            数组的标准定义: 一个存储元素的线性集合,元素可以通过索引来任意存取,索引通常是数字,用来计算元素之间存储位置的偏移量 ,几乎所有的编程语言都有类似的数据结构,而JavaScript的数组略有不同。         JavaScript中的数组是一种特殊的对象,用来表示偏

    2023年04月24日
    浏览(62)
  • 数据结构与算法--javascript(持续更新中...)

    1. 数据结构 队列: 一种遵循 先进先出 (FIFO / First In First Out) 原则的一组有序的项;队列在尾部添加新元素,并从头部移除元素。最新添加的元素必须排在队列的末尾。 (例如:去食堂排队打饭,排在前面的人先打到饭,先离开;排在后面的人后打到饭,后离开。) 栈: 一

    2024年02月16日
    浏览(38)
  • 数据结构与算法之查找: 顺序查找 (Javascript版)

    顺序查找 思路 遍历数组 找到跟目标值相等元素,就返回它的下标 没有找到,返回-1 算法实现 总结 非常低效,算是入门搜索 时间复杂度:O(n) 对于数组结构或链表结构而言,没什么太多可说的

    2024年02月05日
    浏览(49)
  • JavaScript(ES6)数据结构与算法之树

    6.1 概念 非线性结构 n(n=0)个节点构成的有限集合,n=0时称为空树 对于任一非空树 有一个根节点 其余节点可以构成子树 树的术语: 节点的度 :节点的子树个数 树的度 :树所有节点中最大的度数 叶节点 /叶子节点:度为零的节点 父节点:有子树的的节点是子树根节点的父节

    2024年02月04日
    浏览(41)
  • 【JavaScript数据结构与算法】数组类(电话号码的字符组合)

    个人简介 👀 个人主页: 前端杂货铺 🙋‍♂️ 学习方向: 主攻前端方向,也会涉及到服务端(Node.js) 📃 个人状态: 在校大学生一枚,已拿多个前端 offer(秋招) 🚀 未来打算: 为中国的工业软件事业效力 n 年 🥇 推荐学习:🍍前端面试宝典 🍉Vue2 🍋Vue3 🍓Vue2/3项目

    2024年02月07日
    浏览(43)
  • 【JavaScript数据结构与算法】字符串类(计算二进制子串)

    个人简介 👀 个人主页: 前端杂货铺 🙋‍♂️ 学习方向: 主攻前端方向,也会涉及到服务端(Node.js) 📃 个人状态: 在校大学生一枚,已拿多个前端 offer(秋招) 🚀 未来打算: 为中国的工业软件事业效力 n 年 🥇 推荐学习:🍍前端面试宝典 🍉Vue2 🍋Vue3 🍓Vue2/3项目

    2024年02月05日
    浏览(46)
  • 【JavaScript数据结构与算法】字符串类(反转字符串中的单词)

    个人简介 👀 个人主页: 前端杂货铺 🙋‍♂️ 学习方向: 主攻前端方向,也会涉及到服务端(Node.js) 📃 个人状态: 在校大学生一枚,已拿多个前端 offer(秋招) 🚀 未来打算: 为中国的工业软件事业效力 n 年 🥇 推荐学习:🍍前端面试宝典 🍉Vue2 🍋Vue3 🍓Vue2/3项目

    2023年04月09日
    浏览(91)
  • 数据结构实战:利用JavaScript和Python实现链表

    本实战通过JavaScript和Python两种编程语言分别实现单链表数据结构。首先,介绍了链表的基本概念,包括结点(包含数据域和指针域)和链表结构(由多个结点按一定顺序链接而成)。在JavaScript部分,创建了LinkedList.js文件,定义了Node类和LinkedList类,实现了链表的增删查改等

    2024年02月01日
    浏览(48)
  • 【数据结构与算法】归并排序详解:归并排序算法,归并排序非递归实现

    归并排序是一种经典的排序算法,它使用了分治法的思想。下面是归并排序的算法思想: 递归地将数组划分成较小的子数组,直到每个子数组的长度为1或者0。 将相邻的子数组合并,形成更大的已排序的数组,直到最终得到一个完全排序的数组。 归并排序的过程可以分为三

    2024年01月22日
    浏览(70)
  • 【数据结构与算法】:非递归实现快速排序、归并排序

    🔥 个人主页 : Quitecoder 🔥 专栏 :数据结构与算法 上篇文章我们详细讲解了递归版本的快速排序,本篇我们来探究非递归实现快速排序和归并排序 快速排序的非递归实现主要依赖于栈(stack)来模拟递归过程中的函数调用栈。递归版本的快速排序通过递归调用自身来处理子

    2024年03月24日
    浏览(54)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包