【五一创作】排序篇:冒泡排序,快速排序的递归与非递归实现(C语言)

news2024/11/17 18:21:28

目录

前言:

一:冒泡排序

基础思路

完整排序

时间复杂度分析

二:递归实现快速排序

基础思路

单趟排序

(1)双向扫描法

(2)挖坑法

(3)前后指针法(推荐这种)

完整排序

时间复杂度分析

优化

(1)三数取中

(2)小区间优化

三:非递归实现快速排序

基础思路

完整排序

四:效率对比


前言:

本文的排序都是升序,降序反过来就行

交换排序

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

一:冒泡排序

基础思路

1.从第一个元素开始,一直到最后一个元素,重复以下的步骤:

2.每次比较相邻的两个元素,如果第一个元素大于第二个元素,则交换它们的位置;

3.经过一轮遍历之后,最后一个元素一定是当前未排序部分中最大的元素,因此可以减少下一轮遍历的比较次数;

4.重复上述步骤,直到所有元素都按照升序排列。

完整排序

代码:

//冒泡排序
void BubbleSort(int* a, int n)
{
	int i = 0;
	int j = 0;
	for (i = 0; i < n; i++)
	{
		//设计一个标志,如果没有进入循环说明后面已经有序,跳出循环
		int exchange = 0;
		for (j = 1; j < n - i; j++)
		{
			if (a[j] < a[j - 1])
			{
				swap(&a[j], &a[j - 1]);
				exchange = 1;
			}
		}
		if (exchange == 0)
		{
			break;
		}	
	}
}

图解:

 

时间复杂度分析

在冒泡排序过程中,需要进行n-1次遍历

每次遍历需要比较相邻的两个元素并可能交换位置,也就是说每次遍历会固定一个最大(小)元素的位置,因此最多需要遍历n-1次,而每次遍历最多需要比较n-1次

因此比较次数为:(n-1) + (n-2) + ... + 2 + 1 = n(n-1)/2

因此时间复杂度为O(n^2)

需要注意的是,在最好情况下,也就是所有元素已经按照升序排列,在第一次遍历时就可以确定所有元素的位置,因此只需要进行一次遍历,时间复杂度为O(n)

而在最坏情况下,也就是所有元素已经按照降序排列,在每次遍历时都需要进行比较和交换,时间复杂度为O(n^2)。

二:递归实现快速排序

基础思路

1.选择一个基准值(key),将数组或序列分成两部分

2.将小于基准值的元素放在基准值的左侧,大于基准值的元素放在右侧,等于基准值的元素可以放在左侧或右侧。

3.递归地对左右两侧进行排序,直到排序完成。

(简单的来说,快速排序的核心思路就是把每一个数据放到属于它的位置)

单趟排序

我们看下面这个数组:

 我们选取最左边的数据为基准值key(key的选取没有规定,按个人需求来就行),采用交换而不是覆盖的方法,我们保存key的下标keyi。如果采用覆盖的方法,我们保存值key。

 要让小于key的值全都移到左边,大于key的值全都移到右边(相等无所谓),我们一共有三种方法。

单次排序完成后我们要让程序知道是那个数据已经确定好了位置,所以每一组方法最后都要返回确定好的位置下标。

(1)双向扫描法

基础思路:

选择一个基准值(key),从左到右和从右到左分别进行扫描

小于基准值的元素交换到左侧,大于基准值的元素交换到右侧,直到两个扫描相遇

最后将基准值插入到左右两侧之间

选最左边为key,右边先出发,这样可以保证最后相遇的的位置值小于key。

图解:

 

相反,选右边为key,左边先出发,可以保证相遇位置大于key

图解:

 

代码(这里选最左为key):

//单次快速排序,双向扫描法
int partion1(int* a, int left, int right)
{
	//关键字下标
	int keyi = left;

	while (right > left)
	{
		//右边先走,找比key小的
		//加上right>left是为了让相遇的两者停下
		//a[right]>=a[keyi]的目的是如果相同也跳过
		//不然可能导致死循环,这里相遇的位置就是9
		// right和left一直不进入循环,死循环
		// 9, 3, 5, 2, 7, 8, 6, -1, 9, 4, 0
		while (right > left && a[right] >= a[keyi])
		{
			right--;
		}
		//左边走,找比key大的
		while (right > left && a[left] <= a[keyi])
		{
			left++;
		}
		//交换
		swap(&a[left], &a[right]);
	}

	//最后相遇进行交换
	swap(&a[left], &a[keyi]);
	return left;
}

(2)挖坑法

基础思路:

①选择一个基准值(key),设计一个变量pivot来记录坑位,坑位的初始值为key下标一致,从左到右和从右到左分别进行扫描

小于基准值的元素交换到左侧坑位,自己形成新的坑位;大于基准值的元素交换到右侧坑位,自己形成新的坑位,直到两个扫描相遇

最后将基准值插入到左右两侧之间

和前面不同,挖坑法会覆盖数据,我们选择保存值而不是下标。

选最左边为key,右边先出发,这样可以保证最后相遇的的位置值小于key。

图解:

 

相反,选右边为key,左边先出发,可以保证相遇位置大于key

图解:

 

代码:

//挖坑法单趟
int partion2(int* a, int left, int right)
{
	int key = a[left];
	int pivot = left;
	while (left < right)
	{
		//右边先走,找比key小的
		while (right > left && a[right] >= key)
		{
			right--;
		}
		// 小的放到左边的坑里,自己形成新的坑位
		a[pivot] = a[right];
		pivot = right;
		//左边走,找比key大的
		while (right > left && a[left] <= key)
		{
			left++;
		}
		// 大的放到右边的坑里,自己形成新的坑位
		a[pivot] = a[left];
		pivot = left;
	}
	a[pivot] = key;
	return pivot;
}

(3)前后指针法(推荐这种)

基础思路:

定义两个指针prev和cur,prev表示左侧已处理区域的末尾,cur表示右侧未处理区域的起始

从cur开始向右扫描,如果扫描到的元素小于基准值,就将其与prev后面的元素交换,prev指针右移一位。

最后将基准值插入到prev位置。

(这个过程其实相当于把比key小的值全都赶到左边)

选最左边为key,prev位置的值最后一定小于key。

图解:

代码:

//前后指针法
int partion3(int* a, int left, int right)
{
	int keyi = left;
	int prev = left;
	int cur = left + 1;
	while (cur <= right)
	{
		//++prev和cur相等,无效交换,不换
		if (a[cur] < a[keyi] && ++prev!= cur)
		{
			swap(&a[prev], &a[cur]);
		}
		cur++;
	}

	swap(&a[keyi], &a[prev]);
	return prev;
}

完整排序

完整排序的核心在于分治思想。

要排序整个数组,我们可以先确定key的位置,以这个位置为界分出左右两个区域

排序左区域,我们先确定key的位置,以这个位置为界分出两个区域。

………………

一直细分到区域不存在(right<left)或者只有一个元素(right==left,相当于有序)。

然后用同样的方法排序右区域。

代码(未优化):

//单次快速排序,双向扫描法
int partion1(int* a, int left, int right)
{
	//关键字下标
	int keyi = left;

	while (right > left)
	{
		//右边先走,找比key小的
		//加上right>left是为了让相遇的两者停下
		//a[right]>=a[keyi]的目的是如果相同也跳过
		//不然可能导致死循环,这里相遇的位置就是9
		// right和left一直不进入循环,死循环
		// 9, 3, 5, 2, 7, 8, 6, -1, 9, 4, 0
		while (right > left && a[right] >= a[keyi])
		{
			right--;
		}
		//左边走,找比key大的
		while (right > left && a[left] <= a[keyi])
		{
			left++;
		}
		//交换
		swap(&a[left], &a[right]);
	}

	//最后相遇进行交换
	swap(&a[left], &a[keyi]);
	return left;
}

//挖坑法单趟
int partion2(int* a, int left, int right)
{
	int key = a[left];
	int pivot = left;
	while (left < right)
	{
		//右边先走,找比key小的
		while (right > left && a[right] >= key)
		{
			right--;
		}
		// 小的放到左边的坑里,自己形成新的坑位
		a[pivot] = a[right];
		pivot = right;
		//左边走,找比key大的
		while (right > left && a[left] <= key)
		{
			left++;
		}
		// 大的放到右边的坑里,自己形成新的坑位
		a[pivot] = a[left];
		pivot = left;
	}
	a[pivot] = key;
	return pivot;
}

//前后指针法
int partion3(int* a, int left, int right)
{
	int keyi = left;
	int prev = left;
	int cur = left + 1;
	while (cur <= right)
	{
		//++prev和cur相等,无效交换,不换
		if (a[cur] < a[keyi] && ++prev!= cur)
		{
			swap(&a[prev], &a[cur]);
		}
		cur++;
	}
	swap(&a[keyi], &a[prev]);
	return prev;
}

//快速排序
void QuickSort(int* a, int left, int right)
{
	//如果left>right,区间不存在
	//left==right,只有一个元素,可以看成是有序的
	if (left >= right)
	{
		return;
	}

	//单次排序
	int keyi = partion3(a, left, right);

	//分成左右区间,排序左右区间
	QuickSort(a, left, keyi - 1);
	QuickSort(a, keyi + 1, right);
}

图解:

 

时间复杂度分析

假设有N个数,快速排序的时间复杂度一般在O(N*logN)

图解:

 

大多数情况都是上面这种情况,但也存在特殊的情况会使快速排序的效率降低,比如原数组有序(后面优化可以应对这种情况)或者数组中存在很多相同的数据这个时候时间复杂度为O(N^2)

 

优化

(1)三数取中

基础思路:

①既然有序会降低快排的排序效率,那我们可以自己打乱数组

②取到该区间的最左边数据和最右数据以及中间数据,取到中间值

③将这个中间值与我们选定的key交换

(这样每次key最后位置都接近中位数)

代码:

// 三数取中
int GetMidIndex(int* a, int left, int right)
{
	int midi = left + (right - left) / 2;

	if (a[midi] > a[right])
	{
		if (a[midi] < a[left])
			return midi;
		else if (a[right] > a[left])
			return right;
		else
			return left;
	}
	else // a[right] > a[mid]
	{
		if (a[midi] > a[left])
			return midi;
		else if (a[left] < a[right])
			return left;
		else
			return right;
	}
}


//单次快速排序,双向扫描法
int partion1(int* a, int left, int right)
{
    //三数取中,打乱顺序
	int midi = GetMidIndex(a, left, right);
	swap(&a[midi], &a[left]);

	//关键字下标
	int keyi = left;

	while (right > left)
	{
		//右边先走,找比key小的
		//加上right>left是为了让相遇的两者停下
		//a[right]>=a[keyi]的目的是如果相同也跳过
		//不然可能导致死循环,这里相遇的位置就是9
		// right和left一直不进入循环,死循环
		// 9, 3, 5, 2, 7, 8, 6, -1, 9, 4, 0
		while (right > left && a[right] >= a[keyi])
		{
			right--;
		}
		//左边走,找比key大的
		while (right > left && a[left] <= a[keyi])
		{
			left++;
		}
		//交换
		swap(&a[left], &a[right]);
	}

	//最后相遇进行交换
	swap(&a[left], &a[keyi]);
	return left;
}

(2)小区间优化

就现在编译器的优化来说,只要控制得当,递归与非递归效率几乎无异

递归最大的缺点在于如果递归深度太深,可能会导致栈溢出

小区间优化的目的就是将递归的最后几层(最后几层占到递归次数的90%)使用其它排序完成,这样就一定程度减少了栈溢出的可能(依然可能溢出)。

至于排序的选择,因为数据量很少,可以采用比较简单的排序,而且快排的过程区间不断接近有序,用直接插入排序是比较合适的。

代码:


//直接插入排序
void InsertSort(int* a, int n)
{
	//断言,不能传空指针
	assert(a);

	for (int i = 0; i < n - 1; i++)
	{
		int end = i;
		int x = a[end + 1];
		//如果end < 0就代表这个数据是最小的
		while (end >= 0)
		{
			//如果大于就向后覆盖,用x保存
			if (a[end] > x)
			{
				a[end + 1] = a[end];
				end--;
			}
			//如果小于就确定了插入位置
			else
			{
				break;
			}
		}
		a[end + 1] = x;
	}
}


//快速排序
void QuickSort(int* a, int left, int right)
{
	//如果left>right,区间不存在
	//left==right,只有一个元素,可以看成是有序的
	if (left >= right)
	{
		return;
	}

	//优化,减少递归次数(差不多三层),效率提升几乎没有
	//小于10的区间(区间大小自己控制)用直接插入排序
	if (right - left + 1 < 10)
	{
		InsertSort(a + left, right - left + 1);
		return;
	}
	else
	{
		//单次排序
		int keyi = partion3(a, left, right);

		//分成左右区间,排序左右区间
		QuickSort(a, left, keyi - 1);
		QuickSort(a, keyi + 1, right);
	}
}

三:非递归实现快速排序

基础思路

1.非递归实现的思想和递归一致,需要借助栈来模拟函数栈帧的建立销毁,将整个序列视为一个待排序的区间,并使用左右指针指向区间的两端;

2.将区间左右指针指向的范围分别作为待排序序列的左区间和右区间,并将区间信息保存到栈中。这一步相当于对待排序序列进行了压栈操作;

3.当栈不为空时,从栈顶弹出一个区间,并以该区间的左右指针为基准值进行快速排序。具体而言,对该区间进行划分,选出一个基准值,将数组分为两部分,并将左右区间分别压入栈中,以便之后进行排序;

4.重复执行步骤 3,直到栈为空为止。

完整排序

图解:

 

对栈的实现和性质有疑问的可以点下面的链接,这里直接给出代码

栈的实现:https://blog.csdn.net/2301_76269963/article/details/129823215?spm=1001.2014.3001.5502

代码:

//栈的代码
//重定义数据类型,方便更改
typedef int STDataType;

typedef struct stack 
{
	//存储数据
	STDataType* a;
	//栈顶(位置)
	int top;
	//容量
	int capacity;
}ST;

//初始化
void StackInit(ST* ps)
{
	//断言,不能传空指针进来
	assert(ps );
	//一开始指向NULL
	ps->a = NULL;
	//把栈顶和容量都置为空
	ps->top = ps->capacity = 0;
}

//判断栈是否为空
bool StackEmpty(ST* ps)
{
	//断言,不能传空指针进来
	assert(ps);
	//依据top来判断
	/*if (ps->top == 0)
		return true;
	return false;*/
	//更简洁的写法,一个判断语句的值要么为true,要么false
	return ps->top == 0;
}

//销毁
void StackDestroy(ST* ps)
{
	//断言,不能传空指针进来
	assert(ps );
	//栈顶和容量置为空
	ps->top = ps->capacity = 0;
	//释放空间
	free(ps->a);
	ps->a = NULL;
}

//入栈
void StackPush(ST* ps, STDataType x)
{
	//断言,不能传空指针进来
	assert(ps);
	//先判断是否扩容
	if (ps->top == ps->capacity)
	{
		int newcapacity = ps->capacity == 0 ? 4 : (ps->capacity) * 2;
		//扩容
		STDataType* tmp = 
		(STDataType*)realloc(ps->a, sizeof(STDataType) * newcapacity);
		//扩容失败
		if (tmp == NULL)
		{
			printf("realloc error\n");
			exit(-1);
		}
		//更新
		ps->capacity = newcapacity;
		ps->a = tmp;
	}
	//存储数据
	ps->a[ps->top] = x;
	ps->top++;
}


//出栈(删除)
void StackPop(ST* ps)
{
	//断言,不能传空指针进来
	assert(ps);
	//如果栈为空,不能出栈
	assert(!StackEmpty(ps));
	ps->top--;
}

//取顶部数据
STDataType StackTop(ST* ps)
{
	//断言,不能传空指针进来
	assert(ps);
	//如果栈为空,不能进行访问
	assert(!StackEmpty(ps));
	//返回栈顶数据
	return ps->a[ps->top-1];
}

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

	int keyi = left;
	int prev = left;
	int cur = left + 1;
	while (cur <= right)
	{
		//++prev和cur相等,无效交换,不换
		if (a[cur] < a[keyi] && ++prev!= cur)
		{
			swap(&a[prev], &a[cur]);
		}
		cur++;
	}
	swap(&a[keyi], &a[prev]);
	return prev;
}

//非递归快排
void QuickSortNonR(int* a, int left, int right)
{
	ST s;
	StackInit(&s);
	StackPush(&s, right);
	StackPush(&s, left);

	//栈不为空,循环继续
	while (!StackEmpty(&s))
	{
		int begin = StackTop(&s);
		StackPop(&s);
		int end = StackTop(&s);
		StackPop(&s);
		int keyi = partion3(a, begin, end);

		//要先排序左区间,先入右区间,后进先出
		//不满足条件代表区间不存在或者区间元素个数为1
		if (keyi + 1 < end)
		{
			StackPush(&s, end);
			StackPush(&s, keyi + 1);
		}
		if (begin < keyi - 1)
		{
			StackPush(&s, keyi-1);
			StackPush(&s, begin);
		}
	}
}

四:效率对比

代码:

// 测试排序的性能对比
void TestOP()
{
	srand(time(0));
	//随机生成十万个数
	const int N = 100000;
	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);
	//5和6是给快速排序和归并排序的
	int* a5 = (int*)malloc(sizeof(int) * N);
	int* a6 = (int*)malloc(sizeof(int) * N);
	int* a7 = (int*)malloc(sizeof(int) * N);

	if (a1==NULL || a2==NULL )
	{
		printf("malloc error\n");
		exit(-1);
	}
	if (a3 == NULL || a4 == NULL)
	{
		printf("malloc error\n");
		exit(-1);
	}
	if (a5 == NULL || a6 == NULL || a7 == NULL)
	{
		printf("malloc error\n");
		exit(-1);
	}

	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];
	}

	//clock函数可以获取当前程序时间
	int begin1 = clock();
	InsertSort(a1, N);
	int end1 = clock();

	int begin2 = clock();
	ShellSort(a2, N);
	int end2 = clock();

	int begin3 = clock();
	SelectSort(a3, N);
	int end3 = clock();

	int begin4 = clock();
	HeapSort(a4, N);
	int end4 = clock();

	int begin5 = clock();
	QuickSort(a5, 0, N - 1);
	int end5 = clock();

	int begin6 = clock();
	MergeSort(a6, N);
	int end6 = clock();

	int begin7 = clock();
	BubbleSort(a7, N);
	int end7 = clock();
	
	printf("InsertSort:%d\n", end1 - begin1);
	printf("ShellSort:%d\n", end2 - begin2);
	printf("SelectSort:%d\n", end3 - begin3);
	printf("HeapSort:%d\n", end4 - begin4);
	printf("QuickSort:%d\n", end5 - begin5);
	printf("MergeSort:%d\n", end6 - begin6);
	printf("BubbleSort:%d\n", end7 - begin7);

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


int main()
{
	//测试效率
	TestOP();
}

 

 

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

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

相关文章

基于Vue3+TS+Vite+Cesium创建项目

基于Vue3TSViteCesium创建项目 基于Vite构建项目安装配置Cesium创建Cesium三维视图运行结果 随着近几年社会的发展&#xff0c;人们对三维可视化的需求也是越来越多&#xff0c;三维GIS如今真的越来越火了&#xff0c;对于做GIS前端开发的人员来说&#xff0c;Cesium开发是绝对…

PLC模糊PID(模糊规则表)

模糊PID的模糊化相关内容,请参看下面的博客文章: PLC模糊控制模糊PID(梯形图实现+算法分析)_RXXW_Dor的博客-CSDN博客博途PLC的模糊PID控制详细内容请查看下面的博客文章:Matlab仿真+博途PLC模糊PID控制完整SCL源代码参考(带模糊和普通PID切换功能)_博途怎么实现模糊pid_…

linux驱动开发 - 08_内核定时器

文章目录 1 Linux 时间管理和内核定时器简介1.1 内核时间管理简介1.2 内核定时器简介1、init_timer 函数2、add_timer 函数3、del_timer 函数4、del_timer_sync 函数5、mod_timer 函数 1.3 Linux 内核短延时函数 2 实验程序编写2.1 定时器驱动程序编写2.2 编写测试 APP 3 编译驱…

【Linux】管道

目录 一、前言 二、管道 1、匿名管道 1.1、基本原理 1.2、代码实现 1.3、管道的特点 1.4、基于管道的简单设计 2、命名管道 2.1、匿名管道与命名管道的区别 2.2、代码实现命名管道通信 一、前言 为了满足各种需求&#xff0c;进程之间是需要通信的。进程间通信的主要目…

【VM服务管家】专题_7.5 异常收集

目录 5.1 信息收集&#xff1a;异常报错信息收集的方法5.2 日志等级&#xff1a;日志等级调低的方法 5.1 信息收集&#xff1a;异常报错信息收集的方法 描述 环境&#xff1a;VM4.0以上VS2013及以上 现象&#xff1a;未知问题、偶发问题、崩溃问题如何收集信息提供给研发排查。…

【Linux】基础IO——库函数与系统调用的关系

目录 引言1.文件操作语言方案&#xff08;C的文件操作接口-库函数&#xff09;打开文件、关闭文件——fopen、fclose打开文件的模式 写入——fput、printf读取——fgets 2.文件操作系统方案&#xff08;系统的文件操作接口-系统调用&#xff09;打开文件、关闭文件——open、cl…

【游戏逆向】Lua游戏逆向及破解方法介绍

前言 随着手游的发展&#xff0c;越来越多的Cocos-lua端游开发者转移到手游平台。Lua脚本编写逻辑的手游也是越来越多&#xff0c;如梦幻西游、刀塔传奇、开心消消乐、游龙英雄、奇迹暖暖、疾风猎人、万万没想到等手游。随着Lua手游的增加&#xff0c;其安全性更值得关注&…

python基本数据类型---数字字符串

引入 在内存中存储的数据可以是不同的数据类型。比如名字可以使用字符串存储&#xff0c;年龄可以使用数字存储&#xff0c;python有6种基本数据类型&#xff0c;用于各种数据的存储&#xff0c;分别是&#xff1a;numbers(数字类型)、string(字符串)、List(列表)、Tuple(元组…

哈希表企业应用-DNA的字符串检测

DNA的字符串检测-引言 若干年后, ikun DNA 检测部成立,专门对 这些ikun的解析检测 突然发现已经完全控制不了 因为学生已经会了 而且是太会了 所以DNA采用 以下视频测试: ikun必进曲 ikun必经曲 ikun必阶曲 如何感受到了吧!,如果你现在唱跳并且还Rap 还有打篮球 还有铁山靠 那…

自动化运维工具Ansible之playbooks剧本

目录 一、playbooks 1、playbooks简述 2、playbooks剧本格式 3、playbooks组成部分 4、playbooks启动及检测 5、playbooks模块实战实例1 6、vars模块实战实例2 7、when模块实战实例3 8、with_items循环模块实战实例4 9、template模块实战实例5 10、tags模块实战实例…

有研究员公开了一个解析并提取 Dell PFS BIOS 固件的工具(上)

导语&#xff1a;研究员公开了一个解析并提取 Dell PFS BIOS 固件的工具。 Dell PFS BIOS提取器 介绍 解析 Dell PFS BIOS 映像并提取其 SPI/BIOS/UEFI 固件组件。它支持所有Dell PFS 修订版和格式&#xff0c;包括最初在 ThinOS 包中LZMA压缩、ZLIB压缩或拆分成块的格式。输出…

Vue 注册组件介绍

Vue组件的基本概念 Vue组件是一种可复用的Vue实例&#xff0c;用于封装可重用的HTML元素、JavaScript代码和CSS样式。它可以让开发者更好地组织和复用代码&#xff0c;使Web应用程序更加可维护和可扩展 Vue组件通常由三部分组成&#xff1a;模板&#xff08;template&#xf…

NLP实战:快递单信息抽取-基于ERNIE1.0 预训练模型

目录 一、介绍 二、代码 2.1前期准备 2.2加载自定义数据集 2.3数据处理 2.4数据读入 2.5PaddleNLP一键加载预训练模型 2.6设置Fine-Tune优化策略&#xff0c;模型配置 2.7模型训练与评估 ​编辑 2.8模型预测 三、总结 原文&#xff1a; 一、介绍 命名实体识别&…

网易云音乐开发--主页静态页面搭建

如何用VScode来开发小程序 wxml和wxss来高亮小程序 窗口设置 轮播图制作 就是通过swiper来设置轮播图 iconfont字体图标使用 这里要借助阿里的iconfonticonfont-阿里巴巴矢量图标库 找到自己喜欢的图标&#xff0c;添加到购物车 添加到项目 这样就可以统一的管理图标的库 …

Windows环境安装Elasticsearch和Kibana

文章目录 1 Elasticsearch1.1 下载1.2 解压并添加环境变量1.3 访问1.4 cmd命令1.5 中文分词器1.5.1 下载1.5.2 安装1.5.2.1 命令安装1.5.2.2 手动安装1.5.2.3 验证分词 1.6 使用curl批量导入 2 安装 kibana2.1 下载kibana2.2 中文界面2.3 操作索引2.3.1 增加索引2.3.1.1 单条新…

电商--订单支付中存在的问题以及思考

文章目录 前言背景订单支付大致流程订单支付流程中的注意细节防止订单重复创建为何会出现重复创建订单处理措施 掉单导致的重复支付为何会出现这种场景处理措施 已支付流水退款为何会出现这种场景 前言 最近感觉应该把自己在工作中遇到的一些比较有意思的核心流程进行总结以此…

win10远程桌面控制Ubuntu服务器 - 内网穿透实现公网远程

文章目录 前言视频教程1. ubuntu安装XRDP2.局域网测试连接3. Ubuntu安装cpolar内网穿透4.cpolar公网地址测试访问5.固定域名公网地址 转载自远程穿透文章&#xff1a;Windows通过RDP异地远程桌面Ubuntu【内网穿透】 前言 XRDP是一种开源工具&#xff0c;它允许用户通过Windows…

软件测试之测试的分类(重点:黑盒测试、白盒测试、单元测试、集成测试、系统测试)

文章目录 1. 按照测试对象进行划分1&#xff09;界面测试2&#xff09;可靠性测试3&#xff09;容错性测试4&#xff09;文档测试5&#xff09;兼容性测试6&#xff09;易用性测试7&#xff09;软件安装卸载的测试8&#xff09;安全测试9&#xff09;性能测试10&#xff09;内存…

代码随想录算法训练营第五十天| 123.买卖股票的最佳时机III、188.买卖股票的最佳时机IV

文章目录 123.买卖股票的最佳时机III188.买卖股票的最佳时机IV:star: 123.买卖股票的最佳时机III 至多买卖两次 分清楚动态规划所有状态至关重要&#xff0c;这是求dp数组的前提 和之前买卖股票问题解题思路相似&#xff0c;只是多增加了第二天的状态 总结&#xff1a;买卖股票…

Docker代码环境打包

1. 介绍 Docker是一种开源的容器化平台&#xff0c;它可以在操作系统级别运行应用程序。通过将应用程序及其依赖项封装成一个可移植的容器&#xff0c;Docker使得应用程序可以在任何环境中轻松部署、运行和管理。使用Docker&#xff0c;开发人员可以避免在不同环境中出现的配置…