深入刨析数据结构之排序(下)

目录

1.内部排序

1.5选择排序

1.5.1简单选择排序

1.5.2树形选择排序

1.6堆排序

1.7归并排序

1.7.1递归归并

1.7.2非递归归并

1.8计数排序

1.9基数排序

常见内部排序的总结:


1.内部排序

1.5选择排序

   选择排序(Selection Sort)的基本思想是:每一趟在n-i+1(i=1,2,⋯,n-1)个记录中选取关键字最小的记录作为有序序列中第i个记录。其中最简单且为读者最熟悉的是简单选择排序(Simple Selection Sort)。


1.5.1简单选择排序

   一趟简单选择排序的操作为:通过n-i次关键字间的比较,从n-i+1个记录中选出关键字最小的记录,并和第i(1≤i≤n)个记录交换之。
   显然,对L.r[1..n]中记录进行简单选择排序的算法为:令i从1至n-1,进行n-1趟选择操作,容易看出,简单选择排序过程中,所需进行记录移动的操作次数较少,其最小值为“0”,最大值为3(n-1)。然而,无论记录的初始排列如何,所需进行的关键字间的比较次数相同,均为n(n-1)/2。因此,总的时间复杂度也是O(n^2)。

具体过程太过于抽象,我们来看看选择排序的动图加以理解:

步骤及思路:(按照升序排序)

1.设置外层循环,将循环次数设置为0-n-2次,因为我们还需要留出最后一个数进行比较

2.设置内层循环,从当前下标的下一个元素开始寻找比这个元素小的元素

3.如果较小元素与参照元素下标不相同,就进行交换

下面是代码实现:

void SelectSort1(int* arr, int n)
{int i = 0;for (i = 0; i < n - 1; i++){int min = i;int j = 0;for (j = i + 1; j < n; j++){if (arr[j] < arr[min]){min = j;}}if (min != i){int tmp = arr[min];arr[min] = arr[i];arr[i] = tmp;}}
}

   那么,能否加以改进呢?
   从上述可见,选择排序的主要操作是进行关键字间的比较,因此改进简单选择排序应从如何减少“比较”出发考虑。显然,在n个关键字中选出最小值,至少进行n-1次比较,然而,继续在剩余的n-1个关键字中选择次小值就并非一定要进行n-2次比较,若能利用前n-1次比较所得信息,则可减少以后各趟选择排序中所用的比较次数。因此我们来介绍一下树形选择排序

1.5.2树形选择排序

   树形选择排序(Tree Selection Sort),又称锦标赛排序(Tournament Sort),是一种按照锦标赛的思想进行选择排序的方法。首先对n个记录的关键字进行两两比较,然后在其中[n/2]个较小者之间再进行两两比较,如此重复,直至选出最小关键字的记录为止,这个过程可用一棵有n个叶子结点的完全二叉树表示。例如,图10.9(a)中的二叉树表示从8个关键字中选出最小关键字的过程。8个叶子节点依次存放排序之前的8个关键字,每个非终端结点中的关键字均等于其左、右孩子结点中较小的关键字,则根结点中的关键字即为叶子节点中的最小关键字。在输出最小关键字之后,根据关系的可传递性,欲选出次小关键字,仅需将叶子结点中的最小关键字(13)改为“最大值”,然后从该叶子结点开始。和其左(或右)兄弟的关键字进行比紋,修改从叶子节点到根的路径各个结点的关键字,则根结点的关键字即为次小关键字。同理,可依次选出从小到大的所有关键字(参见图10.9(b)和(c))。由于含有n个叶子结点的完全二叉树的深度为[logn]+1,则在树形选择排序中,除了最小关键字之外,每选择一个次小关键字仅需进行[logn]次比较,因此,它的时间复杂度为O(nlogn)。但是,这种排序方法尚有辅助存储空间较多、和“最大值”进行多余的比较等缺点。为了弥补,威洛姆斯(J.willioms)在1964年提出了另一种形式的选择排序堆排序

1.6堆排序

   堆排序(Heap Sort)只需要一个记录大小的辅助空间,每个待排序的记录仅占有一个存储空间。

   什么是堆?堆的定义如下:n个元素的序列{k₁,k₂,…,kₙ}当且仅当满足下关系时,称之为堆。       

若将和此序列对应的一维数组(即以一维数组作此序列的存储结构)看成是一个完全二叉树,则堆的含义表明,完全二叉树中所有非终端结点的值均不大于(或不小于)其左、右孩子结点的值。由此,若序列{k₁,k₂,…, kn} 是堆,则堆顶元素(或完全二叉树的根)必为序列中 n个元素的最小值(或最大值)。例如,下列两个序列为堆,对应的完全二叉树如图10.10所示。

若在输出堆顶的最小值之后,使得剩余n-1个元素的序列重又建成一个堆,则得到n个元素中的次小值。如此反复执行,便能得到一个有序序列,这个过程称之为堆排序。由此,实现堆排序需要解决两个问题:

(1)如何由一个无序序列建成一个堆?

(2)如何在输出堆顶元素之后,调整剩余元素成为一个新的堆?

下面先讨论第二个问题。例如,图10.11(a)是个堆,假设输出堆顶元素之后,以堆中最后一个元素替代之,如图10.11(b)所示。此时根结点的左、右子树均为堆,则仅需自上至下进行调整即可。首先以堆顶元素和其左、右子树根结点的值比较之,由于右子树根结点的值小于左子树根结点的值且小于根结点的值,则将27和97交换之;由于97 替代了27之后破坏了右子树的“堆”,则需进行和上述相同的调整,直至叶子结点,调整后的状态如图10.11(c)所示,此时堆顶为 n-1个元素中的最小值。重复上述过程,将堆顶元素27和堆中最后一个元素97 交换且调整,得到如图10.11(d)所示新的堆。

我们称这个自堆顶至叶子的调整过程为“筛选”。

从一个无序序列建堆的过程就是一个反复“筛选”的过程。若将此序列看成是一个完全二叉树,则最后一个非终端结点是第[n/2]个元素,由此“筛选”只需从第[n/2]个元素开始。例如,图10.12(a)中的二叉树表示一个有8个元素的无序序列

{49,38,65,97,76,13,27,49}

则筛选从第 4个元素开始,再于97>49,则交换之,交换后的序列如图10.12(b)所示,同理、在第3个元素65被筛选之后序列的状态如图10.12(c)所示,由于第2个元素38不大于其左、右子树根的值,则筛选后的序列不变。图10.12(e)所示 筛选根元素49之后建成的堆:

步骤及思路:(按照升序排序)

1.我们先来进行建堆的操作,我们有两种方法可以选择,向上建堆和向下建堆

2.然后我们可以定义end来代表数组最后一个元素的下标,在end>=1的条件下设置循环

3.循环内部先交换数组第一个元素和最后一个元素,因为第一个元素是堆中最大的元素,将则个元素放到最后一个,再重新建堆,就可以形成升序数组,重复这个过程就可以完成排序

向上和向下建堆:

1.向上建堆:传入孩子结点的下标,根据父亲结点=(孩子结点-1)/2,我们将父亲结点与孩子结点进行比较,如果父亲结点小于孩子结点,交换他们,将父亲结点作为新的孩子结点,继续比较,直到不再小于孩子结点为止。

2.向下建堆:传入父亲结点的下标,和数组的总元素个数,根据左孩子结点=父亲结点*2+1,我们先将左孩子结点与右孩子结点的数据进行比较,选出较大的孩子结点,与父亲结点比较,同样,如果父亲结点小于孩子结点,交换他们,将孩子结点作为新的父亲结点,继续比较,直到不再小于孩子结点为止或是孩子结点的下标大于元素个数为止。

下面是代码实现:

void AdjustUp(int* arr, int child)
{int parent = (child - 1) / 2;while (child > 0){if (arr[child] > arr[parent]){swap(&arr[child], &arr[parent]);child = parent;parent = (child - 1) / 2;}else{break;}}
}
void AdjustDown(int* arr, int n, int parent)
{int child = parent * 2 + 1;while (child < n){if (child + 1 < n && arr[child] < arr[child + 1]){child++;}if (arr[child] > arr[parent]){swap(&arr[parent], &arr[child]);parent = child;child = parent * 2 + 1;}else{break;}}
}
//最坏:o(nlogn)
void HeapSort(int* arr, int n)
{int i = 0;for (i = 0; i < n; i++){AdjustUp(arr, i);}int end = n - 1;while (end > 0){//将最大值与最后一个交换一下,end--,继续排前面的元素swap(&arr[0], &arr[end]);AdjustDown(arr, end, 0);end--;}
}

堆排序对记录数较少的文件并不值得提倡,但对n较大的文件还是很有效的,因为其运行时间主要耗费在建初始堆和调整建新堆时进行的反复“筛选”上,由此,堆排序在最坏的情况下,其时间的复杂度为O(nlogn),相对于快速排序来说,这是堆排序的最大优点,此外,堆排序仅需一个记录大小供交换用的辅助空间。

1.7归并排序

   归并排序(Merging Sort)是又一类不同的排序方法。“归并”的含义是将两个或两个以上的有序表组合成一个新的有序表。它的实现方法早已为读者所熟悉,无论是顺序存储结构还是链表存储结构,都可在(的时间量级上实现。利用归并的思想容易实现排序。

   假设初始序列含有n个记录,则可看成是n个有序的子序列,每个子序列的长度为1,然后两两归并,得到 个长度为2 或1的有序子序列;再两两归并,……,如此重复,直至得到一个长度为n的有序序列为止,这种排序方法称为2-路归并排序。例如图10.13为2-路归并排序的一个例子。

1.7.1递归归并

步骤及实现:(按照升序排序)

1.先开辟一个与原数组所占空间相同的新数组,(建议使用malloc函数),传入第一个元素下标left,最后一个元素下标right

2.取数组的中间下标数mid,调用递归,将left和mid传入,再次调用递归,传入mid和right

3.类似于后序遍历,先调用递归两次,再进行归并,将数组划分为begin和mid,mid+1和end,其中begin=left,end=right,begin和mid作为第一个归并数组的begin1和end1,mid+1和end作为第二个归并数组的begin2和end2

4.在begin1<=end1&&begin2<=end2的条件下归并,之后再分别合并

5.使用memcpy将排好序的数据拷贝回原数组

下面是代码实现:

void _MergeSort(int* arr, int left,int right,int* tmp)
{if (left >= right)return;int mid = (left + right) / 2;//类似于后序遍历_MergeSort(arr, left, mid, tmp);_MergeSort(arr, mid + 1, right, tmp);int begin1 = left;int end1 = mid;int begin2 = mid + 1;int end2 = right;int i = left;while (begin1 <= end1 && begin2 <= end2){if (arr[begin1] < arr[begin2]){tmp[i++] = arr[begin1++];}else if (arr[begin1] > arr[begin2]){tmp[i++] = arr[begin2++];}else{tmp[i++] = arr[begin1++];tmp[i++] = arr[begin2++];}}while (begin1 <= end1){tmp[i++] = arr[begin1++];}while (begin2 <= end2){tmp[i++] = arr[begin2++];}memcpy(arr + left, tmp + left, sizeof(int) * (right - left + 1));
}
//N*logN
void MergeSort(int* arr, int n)
{int* tmp = (int*)malloc(sizeof(int) * n);if (tmp == NULL){perror("malloc failed");return;}_MergeSort(arr, 0, n - 1,tmp);free(tmp);tmp = NULL;
}

1.7.2非递归归并

步骤及思路(按照升序排序):

1.还是先开辟出一块空间与原数组空间大小相同,做好拷贝排序好的数据的准备

2.设置一个gap,使gap的初始值为1,每次循环后*=2,这样就可以实现从小区间归并到大区间归并

3.设置内层循环,设置两个归并数组的begin和end分别为i和i+gap-1,i+gap和i+2*gap-1,这里我们对边界值进行一些处理:如果end1或者begin2越界就退出循环,不进行拷贝,如果是end2越界,就修改为n-1,再进行与递归归并相同的操作

4.将有序的数据拷贝回原数组

下面是代码实现:

void MergeSortNonR(int* arr, int n)
{int* tmp = (int*)malloc(sizeof(int) * n);if (tmp == NULL){perror("malloc failed");return; }int gap = 1;while (gap < n){int i = 0;for (i = 0; i < n; i += 2 * gap){int begin1 = i;int end1 = i + gap - 1;int begin2 = i + gap;int end2 = i + 2 * gap - 1;int j = i;//画图理解,边界值的处理if (end1 >= n || begin2 >= n){break;}if (end2 >= n){end2 = n - 1;}while (begin1 <= end1 && begin2 <= end2){if (arr[begin1] < arr[begin2]){tmp[j++] = arr[begin1++];}else if (arr[begin1] > arr[begin2]){tmp[j++] = arr[begin2++];}else{tmp[j++] = arr[begin1++];tmp[j++] = arr[begin2++];}}while (begin1 <= end1){tmp[j++] = arr[begin1++];}while (begin2 <= end2){tmp[j++] = arr[begin2++];}memcpy(arr + i , tmp + i, sizeof(int) * (end2-i+1));//这里排完数据就拷贝,不要到时候一把梭哈}gap *= 2;}free(tmp);tmp = NULL;
}

1.8计数排序

步骤及思路:(按照升序排序)

1.这里我们先遍历一遍数组,选出最大值和最小值

2.通过最大值和最小值开辟出范围数组

3.再次遍历原数组,采用相对映射,将数组中的数据-最小值对应的下标++,这样新开辟的数组记录的是相对映射数据出现的次数

4.遍历新数组对原数组进行还原

下面是代码实现:

void CountSort(int* arr, int n)
{int max = arr[0];int min = arr[0];int i = 0;for (i = 0; i < n; i++){if (arr[i] > max){max = arr[i];}if (arr[i] < min){min = arr[i];}}int range = max - min + 1;int* countA = (int*)calloc(1,sizeof(int) * (range));if (countA == NULL){perror("malloc failed");return;}for (i = 0; i < n; i++){countA[(arr[i] - min)]++;}int j = 0;for (i = 0; i < range; i++){while (countA[i] > 0){arr[j++] = i + min;countA[i]--;}}free(countA);countA = NULL;
}

1.9基数排序

   基数排序(Radix Sorting)是和前面所述各类排序方法完全不相同的一种排序方法。
   从前几节的讨论可见,实现排序主要是通过关键字间的比较和移动记录这两种操作,而实现基数排序不需要进行记录关键字间的比较。基数排序是一种借助多关键字排序的思想对单逻辑关键字进行排序的方法。

书上对于多关键字的排序介绍:

有点抽象,我们直接来看例子:

步骤及实现:

1.这里需要用到队列这一数据结构,由于已经介绍过,这里就直接引用,我们建立一个队列数组,队列数组的下标就代表每一次基数排序的关键字

2.先将队列数组初始化,先统计数据,分别入队列,注意这里循环的次数,是按照数据的最高位的位数来确定的,使用collect和destribute分别来收集,分发数据

3.按照最低位优先原则,使用GetKey函数来获得数据指定位数的数字,来入对应数字的队列

4.分发数据,完成第一次基数排序,重复这个过程

下面来看代码实现:

int Getkey(int num, int index)
{int ret = 0;while (index > 0){ret = num % 10;num /= 10;index--;}return ret;
}
void Collect(Queue* quarr, int* arr, int index, int sz)
{int i = 0;for (i = 0; i < sz; i++){int judge = Getkey(arr[i], index);QueuePush(&(quarr[judge]), arr[i]);}
}
void Destribute(Queue* quarr, int* arr, int sz)
{int i = 0;for (i = 0; i < sz; i++){int j = 0;int put = 0;for (j = 0; j < 10; j++){if (!QueueEmpty(&quarr[j])){put = QueueFront(&quarr[j]);QueuePop(&quarr[j]);break;}}arr[i] = put;}
}
//基数排序,按个位十位百位分别入队列,出队列循环排序
void RadixSort(int* arr, int n)
{Queue quarr[10];int i = 0;for (i = 0; i < 10; i++){QueueInit(&quarr[i]);}for (i = 1; i <= 3; i++){Collect(quarr, arr, i, n);//统计数据,分别入队列Destribute(quarr, arr, n);//按照队列标号从小到大依次出队列,直到队列为空}for (i = 0; i < 10; i++){QueueDestroy(&quarr[i]);}
}

常见内部排序的总结:

(1)从平均时间性能而言,快速排序最佳,其所需时间最省,但快速排序在最坏情况下的时间性能不如堆排序和归并排序。而后两者相比较的结果是,在n较大时,归并排序所需时间较堆排序省,但它所需的辅助存储量最多。
(2)上表中的“简单排序”包括除希尔排序之外的所有插人排序,起泡排序和简单选择排序,其中以直接插入排序为最简单,当序列中的记录“基本有序”或n值较小时,它是最佳的排序方法,因此常将它和其他的排序方法,诸如快速排序、归并排序等结合在一起使用。
(3)基数排序的时间复杂度也可写成O(d•n)。因此,它最适用于n值很大而关键宇较小的序列。若关键字也很大,而序列中大多数记录的“最高位关键字”均不同,则亦可先按“最高位关键字”不同将序列分成若干“小”的子序列,而后进行直接插入排序
(4)从方法的稳定性来比较,基数排序是稳定的内排方法,所有时间复杂度为O(n^2)的简单排序法也是稳定的,然而,快速排序、堆排序和希尔排序等时间性能较好的排序方法都是不稳定的。一般来说,排序过程中的“比较”是在“相邻的两个记录关键字”间进行的排序方法是稳定的。值得提出的是,稳定性是由方法本身决定的,对不稳定的排序方法而言,不管其描述形式如何,总能举出一个说明不稳定的实例来。反之,对稳定的排序方法,总能找到一种不引起不稳定的描述形式。由于大多数情况下排序是按记录的主关键字进行的,则所用的排序方法是否稳定无关紧要。若排序按记录的次关键字进行,则应根据问题所需慎重选择排序方法及其描述算法。

1.稳定的排序方法有冒泡排序,直接插入排序,归并排序,其他都是不稳定的,如:选择排序,5,2,5...........2,会将2的顺序颠倒,希尔排序,相同数值的数据可能会被分到不同组,堆排序,堆顶元素会被换到数组末尾,顺序颠倒,快速排序,key的元素会和left的元素交换,有可能会颠倒顺序

2.在数组接近有序的时候,这时最最有效的排序方法是直接插入排序,快速排序会退化成O(n^2)

3.在空间复杂度中,归并排序需要O(n)的辅助空间来用开辟新数组,来拷贝排序好的数据,快速排序需要一个栈空间,如果对数组进行相对有序的划分时,则需要logn的辅助空间,而关键字为第一个元素或者为最后一个元素时,则需要n的辅助空间,其他排序方法都只需要单个辅助空间即可。

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

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

相关文章

RocketMQ场景问题

1.消息丢失 有这么一个场景&#xff0c;就是订单支付完成之后&#xff0c;订单系统会进行发送消息给RocketMQ集群&#xff0c;下游会有积分系统进行监听这个消息&#xff0c;进行消费然后给用户发放积分。在下面的这个场景中&#xff0c;通过查询日志发现了订单系统发送订单支付…

cordova项目环境搭建 hello

环境准备&#xff1a; 1.下载nodejs并安装配置。 直接官网下载最新版本。此次我是下载的 Node.js v22.12.0。 1.1安装好后配置镜像。 输入&#xff1a;node -v // 显示node.js版本 npm -v // 显示npm版本 1.2环境配置 &#xff08;1&#xff09;找到安装的目录&am…

LE Audio 初探

LE Audio 架构 一.LE Audio profile框架 profile初识&#xff1a; BAP&#xff1a; BAP通常在手机端实现&#xff0c;它允许配置编解码器&#xff0c;配置QoS(质量服务)&#xff0c;控制流媒体&#xff0c; PACS&#xff1a; Published Audio Capabilities Service&#xff0c;…

Ⅱ.INTRODUCTION TO CUDA C

前言 上一节环境配置好了&#xff0c;我们开始吧&#xff01; 一、A First Program 1. Hello, World! 我们先写一个C语言的 Hello, World! 作为对比 int main(void){printf("Hello, World!\n");return 0; }大家应该知道这个代码运行在CPU上吧&#xff0c;我们CP…

域上的多项式环,整除,相通,互质

例1.已知 (R,,x)为域&#xff0c;请选出正确的说法:(A)(R,,x)也是整区; ABCD (B)R中无零因子; C)R在x运算上满足第一、二、三指数律; (D)R只有平凡理想; (E)R只有平凡子环。 域的特征&#xff1a; 域中&#xff0c;非0元素的加法周期 思考、在模7整数环R,中&#xff0c;…

【0x0014】HCI_Read_Local_Name命令详解

目录 一、命令概述 二、命令格式 三、返回事件及参数说明 3.1. HCI_Command_Complete 事件 3.2. Status 3.3. Local_Name 四、命令执行流程 4.1. 命令发送 4.2. 控制器接收并处理命令 4.3. 控制器返回结果 4.4. 主机接收并解析事件包 4.5. 示例代码 五、应用场景 …

【AI日记】25.01.04 kaggle 比赛 3-3 | 王慧玲与基层女性

【AI论文解读】【AI知识点】【AI小项目】【AI战略思考】【AI日记】 工作 参加&#xff1a;kaggle 比赛 Forecasting Sticker Sales时间&#xff1a;6 小时 读书 书名&#xff1a;基层女性时间&#xff1a;3 小时原因&#xff1a;虽然我之前就知道这个作者&#xff0c;因为我…

【从零开始入门unity游戏开发之——C#篇40】C#特性(Attributes)和自定义特性

文章目录 前言一、特性&#xff08;Attributes&#xff09;基本概念二、自定义特性1、自定义特性代码示例&#xff1a;2、应用自定义特性&#xff1a;3、解释3.1 **AttributeUsage 特性**3.2 特性的命名3.3 **构造函数**&#xff1a;3.4 **属性**&#xff1a; 4、使用反射获取特…

【Python学习(六)——While、for、循环控制、指数爆炸】

Python学习&#xff08;六&#xff09;——While、for、循环控制、指数爆炸 本文介绍了While、for、循环控制、指数爆炸&#xff0c;仅作为本人学习时记录&#xff0c;感兴趣的初学者可以一起看看&#xff0c;欢迎评论区讨论&#xff0c;一起加油鸭~~~ 心中默念&#xff1a;Py…

基于PyQt5的UI界面开发——图像与视频的加载与显示

介绍 这里我们的主要目标是实现一个基于PyQt5和OpenCV的图像浏览和视频播放应用。用户可以选择本地的图像或视频文件夹&#xff0c;进行图像自动播放和图像切换以及视频播放和调用摄像头等操作&#xff0c;并且支持图像保存功能。项目的核心设计包括文件路径选择、图像或视频的…

云手机+Facebook:让科技与娱乐完美结合

移动互联网时代&#xff0c;Facebook作为全球最大的社交媒体平台之一&#xff0c;早已成为企业、品牌和组织竞相角逐的营销阵地。而云手机的出现&#xff0c;则为Facebook营销注入了新的活力&#xff0c;其独特的优势让营销活动更加高效、精准且灵活。本文将深入探讨云手机在Fa…

全新免押租赁系统打造便捷安全的租赁体验

内容概要 全新免押租赁系统的推出&#xff0c;标志着租赁行业的一次重大变革。这个系统的最大特点就是“免押金”&#xff0c;大大减轻了用户在租赁过程中的经济负担。从此&#xff0c;不再需要为一部手机或其他商品支付高昂的押金&#xff0c;用户只需通过简单的信用评估&…

postman在软件测试中的应用

postman工具概述 Postman 是一款功能强大的 API 开发和测试工具&#xff0c;在软件开发和测试领域应用广泛。开发阶段&#xff0c;可以通过工具进行mock数据测试&#xff0c;方便开发&#xff0c;联调&#xff1b;测试阶段&#xff0c;可以通过不同环境&#xff0c;不同数据进…

电子电气架构 --- 安全相关内容汇总

我是穿拖鞋的汉子,魔都中坚持长期主义的汽车电子工程师。 老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师: 所谓鸡汤,要么蛊惑你认命,要么怂恿你拼命,但都是回避问题的根源,以现象替代逻辑,以情绪代替思考,把消极接受现实的懦弱,伪装成乐观面对不幸的…

探索Wiki:开源知识管理平台及其私有化部署

在如今的信息时代&#xff0c;企业和团队的知识管理变得愈发重要。如何有效地存储、整理、共享和协作&#xff0c;是提高团队效率和创新能力的关键因素之一。今天&#xff0c;我要为大家介绍一款非常有用的github上开源知识管理工具——Wiki&#xff0c;并分享它的私有化部署方…

一份完整的软件测试报告如何编写?

在软件开发的过程中&#xff0c;测试是必不可少的环节。然而&#xff0c;测试报告往往是最被忽视的部分。你是否也曾在忙碌的测试工作后&#xff0c;面对一份模糊不清的测试报告感到头疼&#xff1f;一份清晰、完整且结构合理的测试报告&#xff0c;能够帮助团队快速了解软件的…

OpenEuler22.03 LTS SP3 系统优化

OpenEuler22.03 LTS SP3 系统优化 1、关闭selinux setenforce 0 sed -i "s#SELINUXenforcing#SELINUXdisabled#g" /etc/selinux/config 2、禁用swap swapoff -a sed -ri s/.*swap.*/#&/ /etc/fstab 3、公有云机器&#xff0c;必须安全加固 &#xff08;1&…

logback之自定义过滤器

logback有两种过滤器&#xff0c;一种是context中的过滤器叫TurboFilter&#xff0c;是一个全局的过滤器&#xff0c;会影响所有的日志记录。另一种是Appender中的过滤器&#xff0c;只对所在的append有效。两者大同小异&#xff0c;这里我们以Appender的过滤器为例。 &#x…

HackMyVM-Airbind靶机的测试报告

目录 一、测试环境 1、系统环境 2、使用工具/软件 二、测试目的 三、操作过程 1、信息搜集 2、Getshell 3、提权 使用ipv6绕过iptables 四、结论 一、测试环境 1、系统环境 渗透机&#xff1a;kali2021.1(192.168.101.127) 靶 机&#xff1a;debian(192.168.101.11…

前端,npm install安装依赖卡在sill idealTree buildDeps(设置淘宝依赖)

输入npm i后&#xff0c;一直卡在sill idealTree buildDeps&#xff0c;一动不动 cnpm可以安装成功&#xff0c;但使用cnpm不会生成package-lock.json文件 设置淘宝依赖&#xff0c;依然卡住&#xff0c;挂梯子也不行 解决方法&#xff1a; // 取消ssl验证 set strict-ssl …