数据结构与算法—冒泡排序快速排序

news2024/11/28 12:56:02

目录

一、交换排序

二、冒泡排序 

时间复杂度 

三、快速排序

1、三种一次划分操作 

Hoare法

挖洞法

前后指针法 

三种方法总结: 

2、改进划分效率 

3、递归实现快速排序

4、非递归实现快速排序

 栈的函数:

非递归排序函数:

5、时间复杂度 

 完整代码:

 声明头文件:

 排序函数:

 测试函数:


 

 

一、交换排序

基本思想:所谓交换,就是根据序列中两个记录键值的比较结果来对换这两个记录在序列中的位置,交换排序的特点是:将键值较大的记录向序列的尾部移动,键值较小的记录向序列的前部移动。
主要包括:冒泡排序和快速排序

二、冒泡排序 

void BubbleSort(int* a, int n)
{
	for (int j = 0; j < n; ++j)
	{
		bool exchange = false;
		for (int i = 1; i < n - j; i++)
		{
			if (a[i - 1] > a[i])
			{
				int tmp = a[i];
				a[i] = a[i - 1];
				a[i - 1] = tmp;

				exchange = true;
			}
		}

		if (exchange == false)
		{
			break;
		}
	}
}

冒泡排序使用了两个循环,外层循环控制排序的轮数,内层循环控制每轮排序的次数。每轮排序都会将未排序部分的最大值交换到未排序部分的最后面,因此每轮排序都会将未排序部分的长度减1

  • 代码中的变量 j 表示已排序部分的长度,初始值为0,每轮排序结束后 j 的值加1。
  • 内层循环从第一个元素开始,依次比较相邻的两个元素,如果它们的顺序错误就交换它们的位置。
  • 如果在一轮排序中没有发生任何交换,说明已经排好序了,可以直接退出循环。

时间复杂度 

这个算法的时间复杂度是O(n^2),因为它需要进行n轮排序,每轮排序需要比较n-j-1次相邻的元素,因此总共需要比较(n-1)+(n-2)+...+1=n*(n-1)/2次。 

三、快速排序

快速排序是一种交换排序方法,由Hoare于1962年提出。它使用二叉树结构来进行排序,

基本思想:

  • 从待排序元素序列中任选一个元素作为基准值,按照该元素的排序码将待排序集合分割成两个子序列。
  • 左子序列中的所有元素都小于基准值,右子序列中的所有元素都大于基准值。
  • 然后,对左右子序列分别重复该过程,直到所有元素都排列在相应位置上为止。

 我们以数组第一个元素6为基准值key,通过动图来体会一下划分一次的过程。

 在每次分割过程中,选择一个基准值,将序列中的元素按照基准值的大小分割成两个子序列。然后,对这两个子序列分别进行递归排序,最终将它们合并起来,就可以得到一个有序的序列。

1、三种一次划分操作 

接下来我们对三种快排的方法进行讲解:

Hoare法

通过动图来体会一下Hoare法划分一次的过程。

void Swap(int* p1, int* p2)
{
	int tmp = *p1;
	*p1 = *p2;
	*p2 = tmp;
}

int PartSort1(int* a, int left, int right)
{
	int keyi = left;
	while (left < right) {
        //右边找小
		while (left < right && a[right] >= a[keyi]) {
			right--;
		}
        //左边找大
		while (left < right && a[left] <= a[keyi]) {
			left++;
		}
		Swap(&a[left], &a[right]);
	}

	Swap(&a[keyi], &a[left]);
    
    return left;
}

  1. 函数中使用了两个指针 left 和 right,它们分别指向区间的左右两端。
  2. 首先将基准值keyi赋值为left。

    代码首先判断left是否小于right,如果不是,则说明当前子序列已经比较完毕,可以退出循环。

    接着,代码判断a[right]是否大于等于a[keyi],如果是,则说明a[right]比关键元素大或者相等,需要继续向左查找比关键元素小的元素。因此,right指针向左移动一位,继续比较下一个元素。

  3. 在内层循环中,首先从右侧开始,代码先判断left是否小于right,如果不是,则说明当前子序列已经比较完毕,可以退出循环。接着,代码判断a[right]是否大于等于a[keyi],如果是,则说明a[right]比关键元素大或者相等,需要继续向左查找比关键元素小的元素。
  4. 如果找到第一个小于基准值 a[keyi] 的元素,从左侧开始,继续找到第一个大于基准值的元素。接着,交换这两个元素的位置,使得左侧元素小于等于基准值,右侧元素大于等于基准值。重复这个过程,直到 left 和 right 指针相遇。 
  5. 如果left和right相遇,则退出循环,将关键元素 a[keyi] 与 left 指针所指向的元素交换位置,使得基准值位于分区的中间位置。这样,分区函数 PartSort1 就完成了它的任务。
  6. 最后,交换a[keyi]和a[left]的值,返回基准值 a[keyi] 排序后的位置 left 。
  • 需要注意的是,函数 PartSort1 返回了 left 指针的值,这个值表示基准值在分区后的位置。在快速排序算法中,这个值会被用来确定下一次分区的区间范围。
  • 另外,函数中使用了一个 Swap 函数,用于交换两个指针所指向的元素的值。这个函数的作用是使代码更加简洁和易读,避免了重复的代码。

挖洞法

通过动图来体会一下挖洞法划分一次的过程。

int PartSort2(int* a, int left, int right)
{
	int key = a[left];
	int hole = left;
	while (left < right) {
		while (left < right && a[right] >= key) {
			right--;
		}
		a[hole] = a[right];
		hole = right;
		while (left < right && a[left] <= key) {
			left++;
		}
		a[hole] = a[left];
		hole = right;
	}
	a[hole] = key;
	return hole;
}

 

  1. 首先,定义了一个名为PartSort2的函数,该函数接受三个参数:一个整型数组a,以及左右两个下标left和right,表示要排序的数组a的左右边界。
  2. 定义了一个名为key的变量,用于存储左边界位置的元素值,即a[left]的值。定义了一个名为hole的变量,用于记录当前空缺的位置,初始值为left。
  3. 进入while循环,当left小于right时,执行以下操作:
  4. 在循环中,先从右边开始找到第一个小于key的元素,将其下标赋值给right。将a[right]的值赋值给a[hole],即将右边界位置的元素放到空缺的位置上。将hole的值更新为right,即将空缺位置的下标更新为右边界位置的下标。
  5. 接着,从左边开始找到第一个大于key的元素,将其下标赋值给left。将a[left]的值赋值给a[hole],即将左边界位置的元素放到空缺的位置上。将hole的值更新为left,即将空缺位置的下标更新为左边界位置的下标。
  6. 循环结束后,将key的值赋值给a[hole],即将key放到空缺的位置上。
  7. 最后,返回hole的值,即返回key在数组a中的位置。

前后指针法 

通过动图来体会一下前后指针法划分一次的过程。

 

 思路:

  1. 最开始prev和cur相邻的,
  2. 当cur遇到比key的大的值以后,cur移动prev不动,他们之间的值都是比key大的值
  3. cur找小,找到小的以后,跟prev位置的值交换,然后prev后移一位。
  4. 相当于把大翻滚式往右边推,同时把小的换到左边
int PartSort3(int* a, int left, int right)
{
	int prev = left;
	int cur = left + 1;
	int keyi = left;
	while (cur <= right){
		if (a[cur] < a[keyi] && ++prev != cur){
			Swap(&a[prev], &a[cur]);
		}
		++cur;
	}
	Swap(&a[prev], &a[keyi]);
	keyi = prev;
	return keyi;
}

  1. 首先,定义了一个名为PartSort3的函数,该函数接受三个参数:一个整型数组a,以及左右两个下标left和right,表示要排序的数组a的左右边界。
  2. 定义了三个变量:prev、cur和keyi,分别表示当前处理的位置、当前遍历的位置和关键元素的位置,初始值均为left。
  3. 进入while循环,当cur小于等于right时,执行以下操作:
  4. 如果a[cur]小于a[keyi],则将prev的值加1,并将a[prev]和a[cur]交换位置,即将小于关键元素的元素放到前面。
  5. 将cur的值加1,继续遍历数组。
  6. 循环结束后,将a[prev]和a[keyi]交换位置,即将关键元素放到正确的位置上。
  7. 将keyi的值更新为prev,即将关键元素的位置更新为当前的位置。
  8. 最后,返回keyi的值,即返回关键元素在数组a中的位置。

 三种方法总结: 

  • PartSort1函数:该函数采用的是左右指针法,即从左右两端开始遍历数组,将小于关键元素的元素放到左边,大于关键元素的元素放到右边,最后将关键元素放到正确的位置上。该函数的时间复杂度为O(nlogn)

  • PartSort2函数:该函数采用的是挖坑填数法,即先将关键元素挖出来,然后从右边开始找到第一个小于关键元素的元素,将其放到左边的空位上,再从左边开始找到第一个大于关键元素的元素,将其放到右边的空位上,最后将关键元素放到正确的位置上。该函数的时间复杂度为O(nlogn)

  • PartSort3函数:该函数采用的是前后指针法,即从左到右遍历数组,将小于关键元素的元素放到前面,大于关键元素的元素放到后面,最后将关键元素放到正确的位置上。该函数的时间复杂度为O(n)

总体来说,这三种函数都是快速排序算法中的重要组成部分,它们的实现方式不同,但本质上都是通过将小于关键元素的元素放到前面,大于关键元素的元素放到后面,来实现对数组的排序。在实际应用中,可以根据具体情况选择不同的函数来实现快速排序算法。

2、改进划分效率 

但是这样每次都已区间最左位置的元素为基准值 ,可能会导致排序效率很低

这是因为在某些情况下,选择的基准值可能会导致划分后的两个子序列长度差别很大,从而使得快速排序算法的时间复杂度退化为O(n^2)

例如:如果待排序的数组已经是有序而且是升序的,而每次选择的基准值都是区间最左位置的元素,那么每次划分后的左子序列都为,右子序列都包含了原数组中的所有元素,这样就会导致快速排序算法的时间复杂度退化为O(n^2)

为了避免这种情况,可以采用一些优化策略,如三数取中法、随机化等。

  • 三数取中法是指在区间的左、中、右三个位置分别取出一个数,然后将它们排序,取中间值作为基准值。这样可以避免选择到极端值作为基准值,从而提高快速排序算法的效率。
  • 随机化则是指在待排序的数组中随机选择一个元素作为基准值,这样可以使得每次划分后的两个子序列长度差别更小,从而提高快速排序算法的效率。

实际中我们更推荐三数取中法,它的效率会更好一点。 

通过该代码实现找到区间中的中位数, 代码如下:

int GetMidIndex(int* a, int left, int right)
{
	int mid = (left + right) / 2;
	if (a[left] < a[mid])
	{
		if (a[mid] < a[right])
		{
			return mid;
		}
		else if (a[left] < a[right])
		{
			return right;
		}
		else
		{
			return left;
		}
	}
	else // a[left] > a[mid]
	{
		if (a[mid] > a[right])
		{
			return mid;
		}
		else if (a[left] > a[right])
		{
			return right;
		}
		else
		{
			return left;
		}
	}
}

接下来将中位数运用到Hoare法函数PartSort3中:(挖洞和Hoare方法操作相同)

int PartSort3(int* a, int left, int right)
{
    //获取中位数
	int midi = GetMidIndex(a, left, right);
	//将中位数的值与left交换
    Swap(&a[left], &a[midi]);

	int prev = left;
	int cur = left + 1;
	int keyi = left;
	while (cur <= right){
		if (a[cur] < a[keyi] && ++prev != cur){
			Swap(&a[prev], &a[cur]);
		}
		++cur;
	}
	Swap(&a[prev], &a[keyi]);
	keyi = prev;
	return keyi;
}

 3、递归实现快速排序

 排序一个区间的函数PartSort1我们写完后,应该将它运用起来,通过递归思想对每个划分区间进行排序,从而实现快速排序。

void QuickSort(int* a, int begin, int end)
{
	if (begin >= end)
		return;

	int keyi = PartSort1(a, begin, end);
	//每次划分范围 [begin, keyi-1] keyi [keyi+1, end]

	QuickSort(a, begin, keyi - 1);
	QuickSort(a, keyi + 1, end);
}
  1. QuickSort函数是快速排序算法的入口函数,它接受一个整型数组a、数组的起始下标begin和结束下标end作为参数。
  2. 在函数内部,首先判断递归结束条件—begin和end的大小关系,如果begin大于等于end,分别代表“区间不存在”和”区间只有一个值“,则直接结束当前递归,
  3. 否则调用PartSort1函数对数组进行一次划分操作,将数组a中的元素分为两部分,一部分小于等于a[keyi],另一部分大于a[keyi],并返回a[keyi]在排序后的位置。
  4. 然后,递归调用QuickSort函数对左半部分和右半部分进行排序,直到整个数组有序。

这个递归版本的快速排序算法。与非递归版本相比,它的实现更加简单,但是可能会因为递归调用的深度过大而导致栈溢出的问题。 

接下来我们来看看非递归如何实现快速排序: 

4、非递归实现快速排序

我们将待排序数组的起始位置和结束位置入栈,然后不断地从栈中取出两个位置,对这两个位置之间的元素进行分区操作,得到基准元素的位置,然后将基准元素左右两边的子数组的起始位置和结束位置入栈,以便后续处理。这样就可以避免递归调用带来的额外开销,提高了快速排序的效率。 

 栈的函数:

#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>

typedef int STDataType;
typedef struct Stack
{
	STDataType* a;
	int top;
	int capacity;
}ST;

void STInit(ST* pst);
void STDestroy(ST* pst);
void STPush(ST* pst, STDataType x);
void STPop(ST* pst);
STDataType STTop(ST* pst);
bool STEmpty(ST* pst);
int STSize(ST* pst);

void STInit(ST* pst)
{
	assert(pst);
	pst->a = NULL;

	pst->top = 0;   
	pst->capacity = 0;
}

void STDestroy(ST* pst)
{
	assert(pst);

	free(pst->a);
	pst->a = NULL;
	pst->top = pst->capacity = 0;
}

void STPush(ST* pst, STDataType x)
{
	if (pst->top == pst->capacity)
	{
		int newCapacity = pst->capacity == 0 ? 4 : pst->capacity * 2;
		STDataType* tmp = (STDataType*)realloc(pst->a, newCapacity * sizeof(STDataType));
		if (tmp == NULL)
		{
			perror("realloc fail");
			return;
		}

		pst->a = tmp;
		pst->capacity = newCapacity;
	}

	pst->a[pst->top] = x;
	pst->top++;
}

void STPop(ST* pst)
{
	assert(pst);
	assert(!STEmpty(pst));

	pst->top--;
}

STDataType STTop(ST* pst)
{
	assert(pst);
	assert(!STEmpty(pst));

	return pst->a[pst->top - 1];
}

bool STEmpty(ST* pst)
{
	assert(pst);

	return pst->top == 0;
}

int STSize(ST* pst)
{
	assert(pst);

	return pst->top;
}

非递归排序函数:

void QuickSortNonR(int* a, int begin, int end)
{
	ST st;
	STInit(&st);
	STPush(&st, end);
	STPush(&st, begin);

	while (!STEmpty(&st))
	{
		int left = STTop(&st);
		STPop(&st);

		int right = STTop(&st);
		STPop(&st);

		//PartSort3也可以,讲解基于PartSort1
        //int keyi = PartSort3(a, left, right);
		int keyi = PartSort1(a, left, right);

		if (keyi + 1 < right)
		{
			STPush(&st, right);
			STPush(&st, keyi + 1);
		}

		if (left < keyi-1)
		{
			STPush(&st, keyi-1);
			STPush(&st, left);
		}
	}

	STDestroy(&st);
}

 下面是对left到key部分(即第一个基准值key的左部分)进行操作的过程: 

ST是一个栈的结构体,STInit、STPush、STPop和STEmpty分别是初始化栈、入栈、出栈和判断栈是否为空的函数。PartSort1是快速排序中的分区函数,用于将数组分成两个部分。具体来说,PartSort1函数选择数组的最后一个元素作为基准元素,然后将小于基准元素的元素放在数组的左边,大于基准元素的元素放在数组的右边,最后返回基准元素的位置。

  1. 初始化一个空栈,将待排序数组的开始和结束位置入栈。
  2. 进入一个循环,条件是栈不为空。在每次循环中:
  3. 从栈顶取出一个元素,作为当前子数组的开始位置,再从栈顶取出一个元素,作为当前子数组的结束位置。
  4. 调用PartSort1函数,对当前子数组进行一次快速排序的划分,返回划分元素的位置。这个函数的作用是找到一个元素(划分元素),使得它左边的所有元素都小于它,它右边的所有元素都大于它。
  5. 如果划分元素的右边还有元素(即keyi + 1 < right),则将右边子数组的开始和结束位置入栈。
  6. 如果划分元素的左边还有元素(即left < keyi-1),则将左边子数组的开始和结束位置入栈。
  7. 当栈为空时,所有子数组都已经排序完成,算法结束,销毁栈。

5、时间复杂度 

递归和非递归版本的快速排序算法的时间复杂度是一样的,都是在平均情况下O(n log n),在最坏情况下O(n^2)。

这是因为每次划分都将数组分成了两个部分,每个部分的长度大约是原数组长度的一半,因此需要进行log n次划分才能完成排序。在每次划分中,需要遍历整个数组,时间复杂度是O(n)。因此,快速排序的平均时间复杂度是O(n log n)。 

 完整代码:

 声明头文件:

#include<stdio.h>
#include<stdlib.h>
#include<stdbool.h>

void PrintArray(int* a, int n);
void BubbleSort(int* a, int n);
void QuickSort(int* a, int begin, int end);
void QuickSortNonR(int* a, int begin, int end);

 排序函数:

#include "sort.h"

void PrintArray(int* a, int n)
{
	for (int i = 0; i < n; i++) {
		printf("%d ", a[i]);
	}
	printf("\n");
}

void BubbleSort(int* a, int n)
{
	for (int j = 0; j < n; ++j)
	{
		bool exchange = false;
		for (int i = 1; i < n - j; i++)
		{
			if (a[i - 1] > a[i])
			{
				int tmp = a[i];
				a[i] = a[i - 1];
				a[i - 1] = tmp;

				exchange = true;
			}
		}

		if (exchange == false)
		{
			break;
		}
	}
}

void Swap(int* p1, int* p2)
{
	int tmp = *p1;
	*p1 = *p2;
	*p2 = tmp;
}

int GetMidIndex(int* a, int left, int right)
{
	int mid = (left + right) / 2;
	if (a[left] < a[mid]) {
		if (a[mid] < a[right]) {
			return mid;
		}
		else if (a[left] < a[right]) {
			return right;
		}
		else {
			return left;
		}
	}
	else {//a[left]>a[mid]
		if (a[mid] > a[right]) {
			return mid;
		}
		else if (a[left] < a[right]) {
			return left;
		}
		else {
			return right;
		}
	}
}

// hoare
// [left, right]
int PartSort1(int* a, int left, int right)
{
	int midi = GetMidIndex(a, left, right);
	Swap(&a[left], &a[midi]);

	int keyi = left;
	while (left < right) {
		//右边找小
		while (left < right && a[right] >= a[keyi]) {
			right--;
		}
		while (left < right && a[left] <= a[keyi]) {
			left++;
		}

		Swap(&a[left], &a[right]);
	}

	Swap(&a[keyi], &a[left]);

	return left;
}

// 挖坑法
// [left, right]
int PartSort2(int* a, int left, int right)
{
	int midi = GetMidIndex(a, left, right);
	Swap(&a[left], &a[midi]);

	int key = a[left];
	int hole = left;
	while (left < right) {
		while (left < right && a[right] >= key) {
			right--;
		}
		a[hole] = a[right];
		hole = right;
		while (left < right && a[left] <= key) {
			left++;
		}
		a[hole] = a[left];
		hole = right;
	}
	a[hole] = key;
	return hole;
}

// 前后指针法
// [left, right]
int PartSort3(int* a, int left, int right)
{
	int midi = GetMidIndex(a, left, right);
	Swap(&a[left], &a[midi]);

	int prev = left;
	int cur = left + 1;
	int keyi = left;
	while (cur <= right){
		if (a[cur] < a[keyi] && ++prev != cur){
			Swap(&a[prev], &a[cur]);
		}
		++cur;
	}
	Swap(&a[prev], &a[keyi]);
	keyi = prev;
	return keyi;
}

// 时间复杂度: O(logN*N)
// 空间复杂度:O(logN)
void QuickSort(int* a, int begin, int end)
{
	if (begin >= end)
		return;

	int keyi = PartSort3(a, begin, end);
	// [begin, keyi-1] keyi [keyi+1, end]

	QuickSort(a, begin, keyi - 1);
	QuickSort(a, keyi + 1, end);
}

测试函数:

#include"Sort.h"
#include<time.h>

//测试排序
void TestBubbleSort()
{
	int a[] = { 4,7,1,9,3,6,5,8,3,2,0 };
	PrintArray(a, sizeof(a) / sizeof(int));
	BubbleSort(a, sizeof(a) / sizeof(int));
	PrintArray(a, sizeof(a) / sizeof(int));
}

void TestQuickSort()
{
	//int a[] = { 4,7,1,9,3,6,5,8,3,2,0 };
	int a[] = { 6,1,2,7,9,3,4,5,10,8 };

	PrintArray(a, sizeof(a) / sizeof(int));
	QuickSort(a, 0, sizeof(a) / sizeof(int) - 1);
	PrintArray(a, sizeof(a) / sizeof(int));
}

//测试运行效率
void TestOP()
{
	srand(time(0));
	const int N = 1000000;
	int* a1 = (int*)malloc(sizeof(int) * N);
	int* a2 = (int*)malloc(sizeof(int) * N);
	int* a3 = (int*)malloc(sizeof(int) * N);
	int* a4 = (int*)malloc(sizeof(int) * N);
	int* a5 = (int*)malloc(sizeof(int) * N);
	int* a6 = (int*)malloc(sizeof(int) * N);
	int* a7 = (int*)malloc(sizeof(int) * N);


	for (int i = 0; i < N; ++i)
	{
		a1[i] = rand();
		a2[i] = a1[i];
		a3[i] = a1[i];
		a4[i] = a1[i];
		a5[i] = a1[i];
		a6[i] = a1[i];
		a7[i] = a1[i];
	}

	int begin1 = clock();
	BubbleSort(a1, N);
	int end1 = clock();

	int begin2 = clock();
	QuickSort(a2, 0, N - 1);
	int end2 = clock();

	printf("BubbleSort:%d\n", end1 - begin1);
	printf("QuickSort:%d\n",  end2 - begin2);

	free(a1);
	free(a2);
	free(a3);
	free(a4);
	free(a5);
	free(a6);
	free(a7);
}

int main()
{
	//TestBubbleSort();

	//TestQuickSort();

	//estOP();

	return 0;
}

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

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

相关文章

tensorboard玩耍手册

from torch.utils.tensorboard import SummaryWriter 来自官网的示例&#xff1a;TensorBoard: TensorFlow可视化 查看 event 文件 文件名含义 根据参考[1]文档的来说&#xff1a;文件名含义如下&#xff1a; <WORKING_DIR>/runs/<DATETIME>_<MACHINE_NAME>…

YOLOv8-Seg改进: 分割小目标系列篇 | 多头分割器,提升分割小目标和弱小分割精度

🚀🚀🚀本文改进:YOLOv8-Seg有3个分割头,能够多尺度分割,但对小目标分割可能存在分割能力不佳的现象,因此添加一个微小物体的分割头,能够大量涨点,map提升明显,特别是在处理低分辨率图像和分割小目标等更困难的任务时。 🚀🚀🚀多头分割器 分割小目标检测首选…

【Unity程序小技巧】如何消除多次Destory带来的性能消耗

&#x1f468;‍&#x1f4bb;个人主页&#xff1a;元宇宙-秩沅 &#x1f468;‍&#x1f4bb; hallo 欢迎 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! &#x1f468;‍&#x1f4bb; 本文由 秩沅 原创 &#x1f468;‍&#x1f4bb; 收录于专栏&#xff1a;Uni…

如何优化负载均衡?一文讲懂

当web应用程序增长到单服务器无法承受的地步&#xff0c;企业就面临着优化负载均衡的需求。简而言之&#xff0c;企业需要实现流量重定向&#xff0c;就需要从业务可靠性的需求出发&#xff0c;寻找一套可行的负载均衡方案&#xff0c;那么常用的负载均衡方案有哪些&#xff1f…

Jenkins CICD过程常见异常

1 Status [126] Exception when publishing, exception message [Exec exit status not zero. Status [126] 1.1 报错日志 SSH: EXEC: STDOUT/STDERR from command [/app/***/publish.sh] ... bash: /app/***/publish.sh: Permission denied SSH: EXEC: completed after 200…

【ElasticSearch系列-06】Es集群架构的搭建以及集群的核心概念

ElasticSearch系列整体栏目 内容链接地址【一】ElasticSearch下载和安装https://zhenghuisheng.blog.csdn.net/article/details/129260827【二】ElasticSearch概念和基本操作https://blog.csdn.net/zhenghuishengq/article/details/134121631【三】ElasticSearch的高级查询Quer…

uniapp vue2 vuex 持久化

1.vuex的使用 一、uniapp中有自带vuex插件&#xff0c;直接引用即可 二、在项目中新建文件夹store,在main.js中导入 在根目录下新建文件夹store,在此目录下新建index.js文件 index.js import Vue from vueimport Vuex from vuexVue.use(Vuex)const store new Vuex.Store(…

客户服务质量提升的三种思路

客户服务质量是企业在市场竞争中立于不败之地的重要因素之一&#xff0c;优秀的客户服务不仅可以提高客户满意度&#xff0c;还可以提高客户黏度和回头率。随着经济的发展&#xff0c;客户服务行业也在不断发展壮大。在这个竞争激烈的行业中&#xff0c;企业如何提高客户服务质…

如何自己实现一个丝滑的流程图绘制工具(九) 自定义连接线

背景 产品又有更近的想法了&#xff0c;bpmn-js的连接线你用的时候是看不到的&#xff0c;也就是你从左侧点击连接线的没有线随鼠标移动. 但是产品想要看得见的连接线移动拖拽。 咩有办法&#xff0c;不能换框架&#xff0c;那就只能自己实现啦&#xff01; 思路&#xff1a; …

多维详述MediaBox互动直播AUI Kit低代码开发方案

本专栏将分享阿里云视频云MediaBox系列技术文章&#xff0c;深度剖析音视频开发利器的技术架构、技术性能、开发能效和最佳实践&#xff0c;一起开启音视频的开发之旅。本文为MediaBox最佳实践篇&#xff0c;重点从互动直播AUI Kit的核心能力、技术架构、快速集成等方面&#x…

【Word自定义配置,超简单,图文并茂】自定义Word中的默认配置,比如标题大小与颜色(参考科研作图配色),正文字体等

▚ 01 自定义样式Styles中的默认标题模板 &#x1f4e2;自定义标题的显示效果&#xff0c;如下图所示&#xff1a; 1.1 自定义标题的模板Normal.dotm 1.1.1 选择所需修改的标题 新建一个空白Word文档&#xff0c;依次选择菜单栏的开始Home&#xff0c;样式Styles&#xff0c;…

2023最新版本 FreeRTOS教程 -10-事件组(通过5种情况快速上手)

事件组对应单个事件触发或多个事件同时触发的场景 创建事件组函数 EventGroupHandle_t xEventGroupCreate( void );删除事件组函数 void vEventGroupDelete( EventGroupHandle_t xEventGroup )设置事件 在任务中使用xEventGroupSetBits() 在中断中使用xEventGroupSetBits…

注解和反射实现Excel导入导出

目录 使用实例 定义三个注解 /*** 设置允许导出*/ @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface EnableExport {String fileName();} /*** 设置该字段允许导出* 并且可以设置宽度*/ @Target(ElementType.FIELD) @Retention(RetentionP…

高级算法复习

时间代价 主定理 递归树 排序 贪心算法 贪心选择性&#xff08;Greedy-choice property&#xff09;&#xff1a; 通过做出局部最优&#xff08;贪婪&#xff09;选择&#xff0c;可以得出全局最优解——这是贪心算法可行的第一个基本要素&#xff0c;也是贪心算法与动态规划…

使用Nginx和Spring Gateway为SkyWalking的增加登录认证功能

文章目录 1、使用Nginx增加认证。2、使用Spring Gateway增加认证 SkyWalking的可视化后台是没有用户认证功能的&#xff0c;默认下所有知道地址的用户都能访问&#xff0c;官网是建议通过网关增加认证。 本文介绍通过Nginx和Spring Gateway两种方式 1、使用Nginx增加认证。 生…

用 winget 在 Windows 上安装 kubectl

目录 kubectl 是什么&#xff1f; 安装 kubectl 以管理员身份打开 PowerShell 使用 winget 安装 kubectl 测试一下&#xff0c;确保安装的是最新版本 导航到你的 home 目录&#xff1a; 验证 kubectl 配置 kubectl 是什么&#xff1f; kubectl 是 Kubernetes 的命令行工…

【C#】Mapster对象映射的使用

系列文章 【C#】编号生成器&#xff08;定义单号规则、固定字符、流水号、业务单号&#xff09; 本文链接&#xff1a;https://blog.csdn.net/youcheng_ge/article/details/129129787 【C#】日期范围生成器&#xff08;开始日期、结束日期&#xff09; 本文链接&#xff1a;h…

Sectigo SSL

Sectigo&#xff08;前身为ComodoCA&#xff09;是全球在线安全解决方案提供商和全球最大的证书颁发机构。为了强调其在SSL产品之外的扩张&#xff0c;Comodo在2018年更名为Sectigo。新名称减少了市场混乱&#xff0c;标志着公司向创新的全方位网络安全解决方案提供商过渡。 S…

openEuler 系统使用 Docker Compose 容器化部署 Redis Cluster 集群

openEuler 系统使用 Docker Compose 容器化部署 Redis Cluster 集群 Redis 的多种模式Redis-Alone 单机模式Redis 单机模式的优缺点 Redis 高可用集群模式Redis-Master/Slaver 主从模式Redis-Master/Slaver 哨兵模式哨兵模式监控的原理Redis 节点主客观下线标记Redis 节点主客观…

UnoCSS框架常用语法

文章目录 🍉vscode 开发插件🍉设置边框颜色🍉设置宽、高、背景色、外边距🍉设置flex🍉设置元素在滚动时固定在指定区域内🍉vscode 开发插件 vscode 开发建议安装 UnoCSS插件 🍉设置边框颜色 border-[color]: 设置边框的颜色,[color]可以是预设的颜色名称(如…