获取中...

-

Just a minute...

冒泡排序,选择排序,插入排序,希尔排序,归并排序,快速排序,堆排序,计数排序,桶排序,基数排序


** 稳定:** 如果a原本在b前面,而a=b,排序之后a仍然在b的前面;
** 不稳定:** 如果a原本在b的前面,而a=b,排序之后a可能会出现在b的后面;
** 内部排序:** 所有排序操作都在内存中完成;
** 外部排序:** 由于数据太大,因此把数据放在磁盘中,而排序通过磁盘和内存的数据传输才能进行;
** 时间复杂度:** 一个算法执行所耗费的时间。
** 空间复杂度:** 运行完一个程序所需内存的大小
** In-place: ** 占用常数内存,不占用额外内存
** Out-place: ** 占用额外内存

时间复杂度:

1.平方阶 (O(n2)) 排序 各类简单排序:直接插入、直接选择和冒泡排序。
2.线性对数阶 (O(nlog2n)) 排序 快速排序、堆排序和归并排序;
3.O(n1+§)) 排序§ 是介于 0 和 1 之间的常数。 希尔排序
4.线性阶 (O(n)) 排序 基数排序,此外还有桶、箱排序。

关于稳定性:

1.稳定的排序算法:冒泡排序、插入排序、归并排序和基数排序。
2.不是稳定的排序算法:选择排序、快速排序、希尔排序、堆排序。
找了几张动图感觉不错😀

冒泡排序

图解冒泡排序
** 冒泡排序的基本思想是:每次比较两个相邻的元素,如果它们的顺序错误就把它们交换过来。**
如果有 n 个数进行排序,只需将 n-1 个数归位,也就是说要进行 n-1 趟操作。而每一趟都需要从第 1 位开始进行相邻两个数的比较,将较小的一个数放 在后面,比较完毕后向后挪一位继续比较下面两个相邻数的大小,重复此步骤,直到后一个尚未归位的数,已经归位的数则无需再进行比较

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include<stdio.h>
int main()
{
int a[100],i,j,t,n;
scanf("%d",&n);
for(i=1;i<=n;i++)
{
scanf("%d",&a[i]);
}
for(i=1;i<=n-1;i++)
{
for(j=1;j<=n-i;j++)
{
if(a[j]<a[j+1])
{
t=a[j];
a[j]=a[j+1];
a[j+1]=t;
}
}
}
for(i=1;i<=n;i++)
{
printf("%d ",a[i]);
}
return 0;
}

冒泡排序的核心部分是双重嵌套循环。冒泡排序的时间复杂度是 O(N 2)。这是 一个非常高的时间复杂度。

选择排序

图解选择排序
选择排序是一种简单直观的排序算法。它的工作原理是每一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,然后,再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。以此类推,直到全部待排序的数据元素排完。 选择排序是不稳定的排序方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#include <iostream>
using namespace std;
int main()
{
int a[1000];
int n;
scanf("%d",&n);
for(int i = 0; i < n; i++)
{
cin>> a[i];
}
for(int i = 0; i < n-1; i++)
{
int p = i;
for(int j = i+1; j < n; j++)
{
if(a[p] > a[j])
{
p = j;
}
}
if(p != i)
{
int temp= a[p];
a[p] = a[i];
a[i] = temp;
}
}
for(int i = 0; i < n; i ++)
{
cout<< a[i]<<' ';
}
return 0;
}

插入排序

图解插入排序
** 算法思路:**
从第一个元素开始,该元素可以认为已经被排序。
取出下一个元素,在已经排序的元素序列中从后向前扫描。
如果该元素(已排序)大于新元素,将该元素移到下一位置。
重复步骤3,直到找到已排序的元素小于或者等于新元素的位置。
将新元素插入到该位置后。
重复步骤2~5。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include<stdio.h>
int main()
{
int a[1000],n,i,j,t;
scanf("%d",&n);
for(i=0;i<n;i++)
{
scanf("%d",&a[i]);
}
for(i=1;i<n;i++)
{
t=a[i];
for(j=i-1;j>=0&&a[j]<t;j--)
{
a[j+1]=a[j];
}
a[j+1]=t;
}
for(i=0;i<n;i++)
{
printf("%d ",a[i]);
}
return 0;
}

希尔排序

图解希尔排序
** 希尔排序 **是插入排序的一种又称缩小增量排序,是直接插入排序算法的一种更高效的改进版本。希尔排序是非稳定排序算法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#include<stdio.h>
int main()
{
int i,j,t,n,gap;
int a[1000];
scanf("%d",&n);
for(i=0;i<n;i++)
{
scanf("%d",&a[i]);
}
gap=n/2;
while(gap>0)
{
for(i=gap+1;i<=n;i++)
{
j=i-gap;
while(j>0)
{
if(a[j]>a[j+gap])
{
t=a[j];
a[j]=a[j+gap];
a[j+gap]=t;
j=j-gap;
}
else
{
j=0;
}
}
}
gap=gap/2;
}
for(i=0;i<n;i++)
{
printf("%d ",a[i]);
}
return 0;
}

https://blog.csdn.net/qq_39207948/article/details/80006224这篇挺通俗易懂的

归并排序

图解归并排序
归并排序是一个典型的基于分治的递归算法。它不断地将原数组分成大小相等的两个子数组(可能相差1),最终当划分的子数组大小为1时,将划分的有序子数组合并成一个更大的有序数组。
** 归并排序的递归公式:** T(N) = 2T(N/2) + O(N)
** 归并排序算法复杂度分析:** 归并排序中,用到了一个临时数组,故空间复杂度为O(N),由归并排序的递归公式:T(N) = 2T(N/2) + O(N) 可知时间复杂度为O(NlogN)
数组的初始顺序会影响到排序过程中的比较次数,但是总的而言,对复杂度没有影响。平均情况 or 最坏情况下 它的复杂度都是O(NlogN)
** 算法思路:**(从小到大):
1、对于一组数据a[N],申请临时空间,temp[N],用于临时存放数据,划分为两个序列
2、设置两个指针分别指向两个序列的首部,其中中间数据mid=(start+end)/2划分到前一个序列当中
3、比较两个指针所指向的数据,选择相对小的元素放入到合并空间,并移动指针到下一位置
4、重复步骤3,直到这两个指针的某个指针超出自身所指向序列
5、将另外一个序列全部依次放入到临时数组中(合并空间)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
#include<stdio.h>
// 一个递归函数
void mergesort(int *num,int start,int end);
// 这个函数用来将两个排好序的数组进行合并
void merge(int *num,int start,int middle,int end);
int main()
{
int num[1000],n,i;
scanf("%d",&n);
// 排序之前
for (i=0; i<n; i++)
{
scanf("%d",&num[i]);
}
// 进行合并排序
mergesort(num,0,n-1);
// 排序之后
for (i=0; i<n; i++)
{
printf("%d ",num[i]);
}
return 0;
}
//这个函数用来将问题细分
void mergesort(int *num,int start,int end)
{
int middle;
if(start<end)
{
middle=(start+end)/2;
// 归并的基本思想
// 排左边
mergesort(num,start,middle);
// 排右边
mergesort(num,middle+1,end);
// 合并
merge(num,start,middle,end);
}
}
//这个函数用于将两个已排好序的子序列合并
void merge(int *num,int start,int middle,int end)
{
int n1=middle-start+1;
int n2=end-middle;
// 动态分配内存,声明两个数组容纳左右两边的数组
int *L=new int[n1+1];
int *R=new int[n2+1];
int i,j=0,k;
//将新建的两个数组赋值
for (i=0; i<n1; i++)
{
*(L+i)=*(num+start+i);
}
//元素
*(L+n1)=1000000;
for (i=0; i<n2; i++)
{
*(R+i)=*(num+middle+i+1);
}
*(R+n2)=1000000;
i=0;
// 进行合并
for (k=start; k<=end; k++)
{
if(L[i]<=R[j])
{
num[k]=L[i];
i++;
}
else
{
num[k]=R[j];
j++;
}
}
delete [] L;
delete [] R;
}

快速排序


假如我 们的计算机每秒钟可以运行 10亿次,那么对 1亿个数进行排序,桶排序只需要 0.1秒,而冒 泡排序则需要 1千万秒,既不浪费空间又可以快 一点的排序算法那就是快速排序啦啦。
** 例:**
对 ** 6 1 2 7 9 3 4 5 10 8 ** 这 10个数进行排序。
首先在这个序列中随便找一个数作为基准数,假设是6,接下来,需要将这个序列中 所有比基准数大的数放在 6的右边,比基准数小的数放在6的左边
3 1 2 5 4 6 9 7 10 8
在初始状态下,数字 6在序列的第 1位。我们的目标是将 6挪到序列中间的某个位置, 假设这个位置是 k。现在就需要寻找这个 k,并且以第 k位为分界点,左边的数都小于等于 6,右边的数都大于等于6
** 分别从初始序列“6 1 2 7 9 3 4 5 10 8”两端开始。先从右往左找一个小于 6的数,再从左往右找一个大于 6的数,然后交换它们。这里可以用两个 变量 i和 j,分别指向序列左边和右边。刚开始i指向序列的左边(即 i=1),指向数字 6。让j指向序列的右边(即 j=10),指向数字 8就行了 **

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
#include<stdio.h>
int a[101],n;//定义全局变量,这两个变量需要在子函数中使用
void quicksort(int left,int right)
{
int i,j,t,temp;
if(left>right)
{
return;
}
temp=a[left]; //temp中存的就是基准数
i=left;
j=right;
while(i!=j)
{
//顺序很重要,要先从右往左找
while(a[j]>=temp && i<j)
{
j--;
}
//再从左往右找
while(a[i]<=temp && i<j)
{
i++;
}
//交换两个数在数组中的位置
if(i<j)//当i和j没有相遇时
{
t=a[i];
a[i]=a[j];
a[j]=t;
}
}
//终将基准数归位
a[left]=a[i];
a[i]=temp;
quicksort(left,i-1);//继续处理左边的,这里是一个递归的过程
quicksort(i+1,right);//继续处理右边的,这里是一个递归的过程
}
int main()
{
int i,j,t; //读入数据
scanf("%d",&n);
for(i=1;i<=n;i++)
{
scanf("%d",&a[i]);
}
quicksort(1,n); //快速排序调用
//输出排序后的结果
for(i=1;i<=n;i++)
{
printf("%d ",a[i]);
}
return 0;
}

堆排序

图解堆排序
** 堆排序 ** 就是把最大堆堆顶的最大数取出,将剩余的堆继续调整为最大堆,再次将堆顶的最大数取出(最大堆调整的递归运算),这个过程持续到剩余数只有一个时结束。
** 算法思路:**
1.将无需序列构建成一个堆,根据升序降序需求选择大顶堆或小顶堆; 
2.将堆顶元素与末尾元素交换,将最大元素”沉”到数组末端;
3.重新调整结构,使其满足堆定义,然后继续交换堆顶元素与当前末尾元素,反复执行调整+交换步骤,直到整个序列有序。
** 例:{ 1, 3, 4, 5, 2, 6, 9, 7, 8, 0 } **

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
#include <stdio.h>
#include <stdlib.h>

void swap(int *arr,int i, int k)
{
int temp = arr[i];
arr[i] = arr[k];
arr[k] = temp;
}


void max_heapify(int *arr, int start, int end)
{
//建立父节点下标和子节点下标
int dad = start;

int son = dad * 2 + 1;

while (son <= end)
{ //若子节点下标在范围内才做比较
if (son + 1 <= end && arr[son] < arr[son + 1]) //先比较两个子节点大小,选择最大的
{
son++;
}

if (arr[dad] > arr[son]) //如果父节点大于子节点代表调整完毕,直接跳出
{
return;
}
else
{ //否则交换父子节点的值再继续左右子节点值得比较
swap(arr,dad, son);
printf("dad=%d--son=%d\n",dad,son);
dad = son;
son = dad * 2 + 1;
}

}
}

void heap_sort(int *arr, int len)
{
int i;
//初始化,i从最后一个父节点开始调整
for (i = len / 2 - 1; i >= 0; i--)
{
max_heapify(arr, i, len - 1);
}
for (i = len - 1; i > 0; i--)
{
swap(arr,0, i);
max_heapify(arr, 0, i - 1);
}
}

int main(int argc, char const *argv[])
{

int arr[] = {5,4,3,2,1};
int len = sizeof(arr) / sizeof(int);
heap_sort(arr, len);
for (int i = 0; i < len; i++)
{
printf("%d ", arr[i]);
}
printf("\n");
return 0;
}

https://www.cnblogs.com/chengxiao/p/6129630.html

计数排序

图解计数排序
** 计数排序 ** 计数排序是一种非常快捷的稳定性强的排序方法,时间复杂度O(n+k),其中n为要排序的数的个数,k为要排序的数的组大值。计数排序对一定量的整数排序时候的速度非常快,一般快于其他排序算法。但计数排序局限性比较大,只限于对整数进行排序。计数排序是消耗空间发杂度来获取快捷的排序方法,其空间发展度为O(K)同理K为要排序的最大值。
** 计数排序的基本思想 ** 为一组数在排序之前先统计这组数中其他数小于这个数的个数,则可以确定这个数的位置。例如要排序的数为 7 4 2 1 5 3 1 5;则比7小的有7个数,所有7应该在排序好的数列的第八位,同理3在第四位,对于重复的数字,1在1位和2位(暂且认为第一个1比第二个1小),5和1一样位于6位和7位。
** 计数排序的核心 ** 在于将输入的数据值转化为键存储在额外开辟的数组空间中。作为一种线性时间复杂度的排序,计数排序要求输入的数据必须是有确定范围的整数
** 算法步骤:**
1.根据待排序集合中最大元素和最小元素的差值范围,申请额外空间;
2.遍历待排序集合,将每一个元素出现的次数记录到元素值对应的额外空间内;
3.对额外空间内数据进行计算,得出每一个元素的正确位置;
4.对待排序集合每一个元素移动到计算得出的正确位置上。
** 算法分析 **
由算法示例可知,计数排序的时间复杂度为 O(N+K),因为算法过程中需要申请一个额外空间和一个与待排序集合大小相同的已排序空间,所以空间复杂度为 O(N+K),所以计数排序只适用于元素值较为集中的情况,若集合中存在最大最小元素值相差甚远的情况,则计数排序开销较大、性能较差。通过额外空间的作用方式可知,额外空间存储元素信息是通过计算元素与最小元素值的差值作为下标来完成的,若待排序集合中存在元素值为浮点数形式或其他形式,则需要对元素值或元素差值做变换,以保证所有差值都为一个非负整数形式
** 计数排序的实现办法:**
首先需要三个数组,第一个数组记录A要排序的数列大小为n,第二个数组B要记录比某个数小的其他数字的个数所以第二个数组的大小应当为K(数列中最大数的大小),第三个数组C为记录排序好了的数列的数组,大小应当为n。
接着需要确定数组最大值并确定B数组的大小。并对每个数由小到大的记录数列中每个数的出现次数。因为是有小到大通过出现次数可以通过前面的所有数的出现次数来确定比这个数小的数的个数,从而确定其位置。
对于重复的数,每排好一个数则对其位置数进行减减操作,以此对完成其余相同的数字进行排位。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#include<iostream>
#include<stdlib.h>

using namespace std;

int main()
{
int n;
cin >> n;
int *a = new int[n];
int *c = new int[n];
memset(a, 0, n*sizeof(int));
memset(c, 0, n*sizeof(int));
int max = 0;
for (int i = 0; i < n; i++)
{
cin >> a[i];
max = a[i]>max ? a[i] : max;
}
int *b = new int[max+1];
memset(b, 0, (max+1)*sizeof(int));
for (int i = 0; i < n; i++)
{
b[a[i]]++;
}
for (int i = 1; i < max + 1; i++)
{
b[i] = b[i] + b[i - 1];
}
for (int i = 0; i < n; i++)
{
b[a[i]]--;
c[b[a[i]]] = a[i];
}
for (int i = 0; i < n; i++)
cout << c[i] << endl;
delete[]a;
delete[]b;
delete[]c;

return 0;
}

一篇关于计数排序的漫画:https://www.sohu.com/a/258882297_478315

桶排序

图解桶排序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include<stdio.h>
int main()
{
int a[1001], i, j, t,n;
for (i = 0; i <= 1000; i++) //m次
{
a[i] = 0;
}
scanf("%d", &n);
for (i = 1; i <= n; i++) //n次
{
scanf("%d", &t);
a[t]++;
}
for (i = 1000; i >= 0; i--)
{
for (j = 1; j <=a[i]; j++) //m+n次
{
printf("%d ", i);
}
}
getchar();
getchar();
return 0;
}

** 整个排序算法一共执行了 m+n+m+n次,因此该算法的时间复杂度是 O(m+n+m+n)即
O(2x(m+n)) 。我们在说时间复杂度的时候可以忽略较小的常数,所以桶排序的时间复杂度为 O(m+n) **
但是桶排的 ** 恶心之处 ** 就在于 ** 浪费空间 ** 例如需 要排序数的范围是 02100000000之间,那你则需要申请 2100000001个变量,也就是说要写 成 int a[2100000001]。因为我们需要用 2100000001个“桶”来存储 02100000000之间每一 个数出现的次数。即便只给你 5个数进行排序(例如这 5个数是 1、1912345678、2100000000、 18000000和 912345678),你也仍然需要 2100000001个“桶”,这真是太浪费空间了!还有, 如果现在需要排序的不再是整数而是一些小数,比如将 5.56789、2.12、1.1、3.123、4.1234 ,那就更恶心了。。。还是冒泡好。。。

基数排序

图解基数排序
** 基数排序 ** 也可以称为多关键字排序,同计数排序类似,也是一种非比较性质的排序算法。将待排序集合中的每个元素拆分为多个总容量空间较小的对象,对每个对象执行桶排序后,则完成排序过程。

基数排序在桶排序的基础上做了优化,桶排序需要选择适当的映射规则,来完成集合中元素到多个桶的映射,也可以称之为值域划分。但是当集合中元素跨度很大时,映射规则的设计比较困难,若规则设计的宽泛一些,则桶的个数较少,随便避免了许多空桶的情况,但是可能会存在元素分布不均,桶排序则演变为普通的比较性质排序;若规则设计的较为精确,则桶的个数较多,可能会存在大部分桶都是空桶的情况,存在较大空间浪费。

桶排序之所以存在上述问题,原因在于算法中对待排序元素的属性选择所致。排序过程选择使用了元素本身的 “大小” 属性,所以算法处理的元素集合就是这个 “大小” 空间。例如,若待排序元素为整型,而整型数字在 “大小” 方面可以是无限大或者无限小的;若待排序元素为字符串,而字符串在 “长度” 方面是无限大的。而桶排序又是一种对元素总容量敏感的排序算法,所以存在使用限制。

基数排序过程中也使用了桶排序操作,不过对于桶排序面向的对象进行了优化。例如,若元素是整数类型,则选择元素的每位数字作为排序对象,因为每个数字的容量空间大小只是 10;同理若元素是字符串,则选择元素的每位字符,因为每个字符的容量空间大小为 26。所以在基数排序过程中,给其中的桶排序操作选择了容量空间有限的排序对象。
基数排序中的桶排序操作具有一点特殊性,即每个桶的宽度,或者称为值域跨度为一,所以将待排序集合中所有元素移动到各个桶上之后,不需要再对每个桶进行排序
** 算法思路:**
1.根据待排序元素的类型申请桶空间,并从待排序集合中计算出元素的最大位数;
2.从右向左,根据元素当前位数的值,将所有元素移动到对应的桶中;
3.将所有桶中元素移动回原始集合中;
4.重复步骤 2, 3,直到遍历完所有位数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<assert.h>
#include<malloc.h>
#include<unistd.h>

int GetMaxwidth(int *s, int len) //获得数组中的最大元素,并获得其长度
{
int i = 1;
int max = s[0];
int count = 0;
for (; i<len; i++)
{
if (max<s[i])
{
max = s[i];
}
}
while (max>0)
{
max /= 10;
count++;
}
return count;
}

int GetRadix(int a, int m) //获得数据在当前位数下的数字
{
int n;
int i;
for (i = 0; i<m; i++)
{
n = a % 10;
a /= 10;
}
return n;
}

void CardinalSort(int *s, int len)
//基数排序
{
int width = GetMaxwidth(s, len);
int i = 0;
int j = 0;
int radix;
Que que[10];
for (i = 0; i<10; i++) //初始化队列
{
que[i].data = (int *)malloc(sizeof(int)* len);
que[i].head = que[i].tail = 0;
}

for (i = 1; i <= width; i++) //大循环条件
{
for (j = 0; j<len; j++)
{
radix = GetRadix(s[j], i); //获得当前位数下数据
que[radix].data[(que[radix].tail)++] = s[j];
//将数据存入到对应的下标的队列中
}

int count = 0;
for (j = 0; j<10; ++j) //按照队列个数来遍历数据
{
while (que[j].head != que[j].tail) //如果队列不为空的情况下
{
s[count++] = que[j].data[(que[j].head)++];
//将队列中的数据出队列放入到原数组中
}
que[j].head = que[j].tail = 0;
//置队列的头和尾为空
}
}
}

void Print(int *s, int len) //打印数据
{
int i;
for (i = 0; i<len; i++)
{
printf("%d ", s[i]);
}
printf("\n");
}

int main()
{
int s[] = { 25, 78, 58, 99, 105, 254, 763, 365, 47, 33, 10, 87 };
int len = sizeof(s) / sizeof(s[0]);

CardinalSort(s, len);
Print(s, len);
return 0;
}
相关文章
评论
分享
  • 迷宫问题

    迷宫问题 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960...

    迷宫问题
  • 网鼎杯部分wp

    pwnboom1分析远程已经打不通了,远程的偏移和本地的偏移不一样,只能复现一下本地的了。 首先看到流程图,代码量很大,有很大的switch语句和嵌套结构,可能是虚拟机或者是解析器。 从下图看出是一个C语言的解析器。 然后看了...

    网鼎杯部分wp
  • 网络设备配置与管理

    Linux网络设备与管理大作业 下图为某企业网络拓扑图,接入层采用二层交换机2960,汇聚和核心层使用了一台三层交换机3560 24PS,局域网边缘采用一台路由器LanRouter用于连接到外部网络的Isp Router两台路由器...

    网络设备配置与管理
Please check the parameter of comment in config.yml of hexo-theme-Annie!