【数据结构】详解七大排序算法(有源码)

news2024/11/26 16:38:05

目录

  • ☀️直接插入排序
  • ☀️希尔排序
  • ☀️直接选择排序
  • ☀️堆排序
  • ☀️冒泡排序
  • ☀️快速排序
  • ☀️归并排序
  • ☀️排序算法复杂度及稳定性分析


在这里插入图片描述

☀️直接插入排序

1、基本思想

   把待排序的数按其关键码值的大小逐个插入到一个已经排好序的有序序列中,直到所以的记录插入完为止,得到一个新的有序序列。

   实际中我们玩扑克牌时,就用到了插入排序的思想在这里插入图片描述
基本步骤:
   当插入第i个元素时,前面的arr[0]、arr[2]…arr[n-1]已经排好序,此时用arr[i]待排序的值与前面的数进行比较,找到插入的位置,将arr[i]插入,原来位置上的元素依次向后移动。

在这里插入图片描述
2、代码实现

void insertSort(int* a, int n)
{
	for (int i = 0; i < n - 1; i++)
	{
		int end = i;
		int tmp = a[end + 1];
		while (end >= 0)
		{
			if (tmp < a[end])
			{
				a[end + 1] = a[end];
				end--;
			}
			else
			{
				break;
			}
		}
		a[end + 1] = tmp;
	}
}

插入排序特性总结:

  1. 元素集合越接近有序,直接插入排序算法的时间效率越高。
  2. 时间复杂度:O(N^2)
  3. 空间复杂度:O(1),是一种稳定的排序算法。
  4. 稳定性:稳定

☀️希尔排序

1、基本思想
希尔排序又称缩小增量法,其基本思想:

  • 先选定一个整数gap,把待排序的数列分成gap组,对每一组内的元素进行排序
  • 然后逐渐减小gap,重复排序
  • 直到gap=1,再进行直接插入排序,完成排序

第一步进行预排序,分组排序,把大的排后面,小的排前面
第二步进行直接插入排序

在这里插入图片描述

动态演示:
在这里插入图片描述

2、代码实现

void shellSort(int* a, int n)
{
	int gap = n;
	while (gap > 1)
	{
		gap = gap / 3 + 1;
		for (int i = 0; i < n - gap; i++)
		{
			int end = i;
			int tmp = a[end + gap];
			while (end >= 0)
			{
				if (tmp < a[end])
				{
					a[end + gap] = a[end];
					end -= gap;
				}
				else
				{
					break;
				}
			}
			a[end + gap] = tmp;
		}
	}
}

希尔排序特性总结:

  1. 希尔排序是对直接插入排序的优化
  2. 当gap>1时都是预排序,目的是让数列更接近有序。当gap==1时,数列就已经接近有序,再进行直接插入排序就会很快,进而到达优化效果
  3. 由于gap取值不唯一,希尔排序的时间复杂度不好计算,因此在希尔排序的时间复杂度不固定。
  4. 时间复杂度:O(N^1.3)
  5. 稳定性:不稳定

☀️直接选择排序

1、基本思想

  • 每次从待排序的数列中选出最小(或最大)的一个元素,存放在数列的起始位置,直到全部待排序的元素排完。

基本步骤:

  1. 在待排序的数列中选择最小(或最大)的一个元素。
  2. 若它不是这组待排序的数列中的第一个(或最后一个)元素,则将它与这组数列的第一个(或最后一个)元素交换。
  3. 对剩余的数据,重复上述操作,直到数列排完。
    在这里插入图片描述
    2、代码实现
void Swap(int* p1, int* p2)
{
	int tmp = *p1;
	*p1 = *p2;
	*p2 = tmp;
}
void SelectSort(int* a, int n)
{
	int begin = 0;
	int end = n - 1;
	while (begin < end)
	{
		int min = begin;
		int max = begin;
		for (int i = begin + 1; i <= end; i++)
		{
			if (a[i] < a[min])
			{
				min = i;
			}
			if (a[i] > a[max])
			{
				max = i;
			}
		}
		Swap(&a[begin], &a[min]);
		if (max == begin)
		{
			max = min;
		}
		Swap(&a[end], &a[max]);
		begin++;
		end--;
	}
}

直接选择排序特性总结:

  1. 直接选择排序效率不好,实际中很少使用
  2. 时间复杂度:O(N^2)
  3. 空间复杂度:O(1)
  4. 稳定性:不稳定

☀️堆排序

1、基本思想
堆排序分两个步骤:

  1. 建堆
      升序:建大堆
      降序:建小堆
  2. 利用堆删除思想来进行排序
    把堆顶数据和最后一个数据进行交换,把最后一个数不看做堆里面的,相当于n-1个数,向下调整,选出次大的数。

2、代码如下

void Swap(int* p1, int* p2)
{
	int tmp = *p1;
	*p1 = *p2;
	*p2 = tmp;
}
//向下调整
void AdjustDown(int* a, int n, int parent)
{
	int child = parent * 2 + 1;
	while (child < n)
	{
		// 确认child指向大的那个孩子
		if (child + 1 < n && a[child + 1] > a[child])
		{
			++child;
		}

		// 1、孩子大于父亲,交换,继续向下调整
		// 2、孩子小于父亲,则调整结束
		if (a[child] > a[parent])
		{
			Swap(&a[child], &a[parent]);
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			break;
		}
	}
}
void HeapSort(int* a, int n)
{
	for (int i = (n - 1 - 1) / 2; i >= 0; --i)
	{
		AdjustDown(a, n, i);
	}
	int end = n - 1;
	while (end > 0)
	{
		Swap(&a[0], &a[end]);
		AdjustDown(a, end, 0);
		--end;
	}
}

堆排序特性总结:

  1. 堆排序效率比直接选择排序高
  2. 时间复杂度:O(N*logN)
  3. 空间复杂度:O(1)
  4. 稳定性:不稳定

☀️冒泡排序

1、基本思想
将待排序数列中的元素两两进行比较,直到将最大的数移动到末尾,一共进行n-1趟。
在这里插入图片描述
2、代码实现

void Swap(int* pa, int* pb)
{
	int tmp = *pa;
	*pa = *pb;
	*pb = tmp;
}
void bubbleSort(int* a, int n)
{
	for (int i = 0; i < n; i++)
	{                          
		int exchange = 0;
		for (int j = 1; j < n - i; j++)
		{
			if (a[j - 1] > a[j])
			{
				Swap(&a[j - 1], &a[j]);
				exchange = 1;
			}
		}
		//一趟冒泡过程中,没有发生交换,说明已经有序了,不需要再处理
		if (exchange == 0)
		{
			break;
		}
	}
}

冒泡排序特性总结:

  1. 冒泡排序是一种非常容易理解的排序
  2. 时间复杂度:O(N^2)
  3. 空间复杂度:O(1)
  4. 稳定性:稳定

☀️快速排序

1、基本思想
快速排序是Hoare于1962年提出的一种二叉树结构的交换排序方法,其基本思想:

  • 任意取待排序数列中的某一元素key,按照所取元素key值将待排数列分割成两个子数列
  • 左子数列小于所取元素key值,右子数列大于所取元素key值
  • 然后左右子数列重复该过程,直到排序完成

基本步骤:

  1. 首先进行单趟排序,左边比key小,右边比key大
  2. 然后递归排序左右两边子数列

全程图解:
在这里插入图片描述

将区间按照所取元素划分为左右两部分的常见方式有三种

第一种:hoare版
在这里插入图片描述
若左边做key,右边先走,保证相遇位置比key小
若右边做key,左边先走,保证相遇位置比key大
相遇的两种情况:
1、R停住,L遇到R,相遇的位置就是R停住的位置
2、L挺住,R遇到L,相遇的位置就是L停住的位置

第二种:挖坑版
在这里插入图片描述

第三种:前后指针版
在这里插入图片描述
2、代码实现及优化
第一种:hoare版

void Swap(int* p1, int* p2)
{
	int tmp = *p1;
	*p1 = *p2;
	*p2 = tmp;
}
//hoare版
int PartSort1(int* a, int begin, int end)
{
	int left = begin;
	int right = end;
	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[left], &a[keyi]);
	keyi = left;
	return keyi;
}
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);
}

快排在理想状态下,时间复杂度O(N*logN)
最坏情况下,时间复杂度O(N^2)
为解决最坏情况,可以对快排进行优化,即三数取中
加入三数取中后,快排瞬间从最坏变成最好,快排几乎不会出现最后情况
代码如下:

void Swap(int* p1, int* p2)
{
	int tmp = *p1;
	*p1 = *p2;
	*p2 = tmp;
}
//三数取中
int GetMidIndex(int* a, int begin, int end)
{
	int mid = (begin + end) / 2;
	if (a[begin] < a[mid])
	{
		if (a[mid] < a[end])
		{
			return mid;
		}
		else if (a[begin] > a[end])
		{
			return begin;
		}
		else
		{
			return end;
		}
	}
	else
	{
		if (a[mid] > a[end])
		{
			return mid;
		}
		else if (a[begin] < a[end])
		{
			return begin;
		}
		else
		{
			return end;
		}
	}
}
int PartSort1(int* a, int begin, int end)
{
	int mid = GetMidIndex(a, begin, end);
	Swap(&a[begin], &a[mid]);
	int left = begin;
	int right = end;
	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[left], &a[keyi]);
	keyi = left;

	return keyi;
}
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);
}

还有一种优化方式,即递归到小的区间时,有直接插入排序,减少递归调用次数
代码如下:

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

	if ((end - begin + 1) < 15)
	{
		//小区间有直接插入排序
		insertSort(a + begin, end - begin + 1);
	}
	else
	{
		int keyi = PartSort1(a, begin, end);

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

第二种:挖坑版

//挖坑版
int PartSort2(int* a, int begin, int end)
{
	int mid = GetMidIndex(a, begin, end);
	Swap(&a[begin], &a[mid]);

	int left = begin;
	int right = end;
	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 = left;
	}
	a[hole] = key;
	return hole;
}

第三种:前后指针版

//前后指针版
int PartSort3(int* a, int begin, int end)
{
	int mid = GetMidIndex(a, begin, end);
	Swap(&a[begin], &a[mid]);

	int keyi = begin;
	int prev = begin;
	int cur = begin + 1;
	while (cur <= end)
	{
		// 找到比key小的值时,跟++prev位置交换,小的往前翻,大的往后翻
		if (a[cur] < a[keyi] && ++prev != cur)
		{
			Swap(&a[prev], &a[cur]);
		}
		cur++;
	}

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

若深度太深,就会造成溢出,快排的非递归就是次问题
代码如下:

//非递归快排
void QuickSortNonR(int* a, int begin, int end)
{
	//创建栈
	ST st;
	StackInit(&st);
	
	StackPush(&st, begin);
	StackPush(&st, end);

	while (!StackEmpty(&st))
	{
		int right = StackTop(&st);
		StackPop(&st);
		int left = StackTop(&st);
		StackPop(&st);

		int keyi = PartSort3(a, left, right);
		// [left, keyi-1] keyi [keyi+1, right]
		if (keyi+1 < right)
		{
			StackPush(&st, keyi + 1);
			StackPush(&st, right);
		}

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

快速排序特性总结:

  1. 快速排序整体的综合性能和使用场景都是比较好的
  2. 时间复杂度:O(N*logN)
  3. 空间复杂度:O(logN)
  4. 稳定性:不稳定

☀️归并排序

1、基本思想
归并排序是建立在归并操作上的一种排序算法,该算法是采用分治法的一个典型的应用。其基本思想:

  • 先使每个子序列有序,再使子序列段间有序
  • 将有序的子序列合并成一个有序序列

若将两个有序表合并成一个有序表,称为二路归并。

归并全过程如下图:
在这里插入图片描述
动态演示:
在这里插入图片描述
2、代码实现

void _MergeSort(int* a, int begin, int end, int* tmp)
{
	if (begin >= end)
		return;
	int mid = (begin + end) / 2;
	// [begin, mid] [mid+1, end] 递归让子区间有序
	_MergeSort(a, begin, mid, tmp);
	_MergeSort(a, mid + 1, end, tmp);
	// 归并[begin, mid] [mid+1, end]
	int begin1 = begin;
	int end1 = mid;
	int begin2 = mid + 1;
	int end2 = end;
	int i = begin;
	while (begin1 <= end1 && begin2 <= end2)
	{
		if (a[begin1] <= a[begin2])
		{
			tmp[i++] = a[begin1++];
		}
		else
		{
			tmp[i++] = a[begin2++];
		}
	}
	while (begin1 <= end1)
	{
		tmp[i++] = a[begin1++];
	}
	while (begin2 <= end2)
	{
		tmp[i++] = a[begin2++];
	}
	memcpy(a + begin, tmp + begin, sizeof(int) * (end - begin + 1));
}
void MergeSort(int* a, int n)
{
	int* tmp = (int*)malloc(sizeof(int) * n);
	if (tmp == NULL)
	{
		perror("malloc fail");
		exit(-1);
	}
	_MergeSort(a, 0, n - 1, tmp);
	free(tmp);
	tmp = NULL;
}

非递归实现:

void MergeSortNonR(int* a, int n)
{
	int* tmp = (int*)malloc(sizeof(int)*n);
	if (tmp == NULL)
	{
		perror("malloc fail");
		exit(-1);
	}

	// 归并每组数据个数,从1开始,因为1认为是有序的,可以直接归并
	int rangeN = 1;
	while (rangeN < n)
	{
		for (int i = 0; i < n; i += 2 * rangeN)
		{
			// [begin1,end1][begin2,end2] 归并
			int begin1 = i, end1 = i + rangeN - 1;
			int begin2 = i + rangeN, end2 = i + 2 * rangeN - 1;
			int j = i;
			if (end1 >= n)
			{
				break;
			}
			else if (begin2 >= n)
			{
				break;
			}
			else if (end2 >= n)
			{
				end2 = n - 1;
			}

			while (begin1 <= end1 && begin2 <= end2)
			{
				if (a[begin1] <= a[begin2])
				{
					tmp[j++] = a[begin1++];
				}
				else
				{
					tmp[j++] = a[begin2++];
				}
			}

			while (begin1 <= end1)
			{
				tmp[j++] = a[begin1++];
			}

			while (begin2 <= end2)
			{
				tmp[j++] = a[begin2++];
			}

			// 归并一部分,拷贝一部分
			memcpy(a + i, tmp + i, sizeof(int)*(end2 - i + 1));
		}

		rangeN *= 2;
	}
	free(tmp);
	tmp = NULL;
}

归并排序特性总结:

  1. 归并的缺点在于需要O(N)空间复杂度,归并排序的思考更多的是解决在磁盘中的外排序问题
  2. 时间复杂度:O(N*logN)
  3. 空间复杂度:O(N)
  4. 稳定性:稳定

☀️排序算法复杂度及稳定性分析

在这里插入图片描述
测试排序的性能对比,代码如下:

int main()
{
	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);
	int* a5 = (int*)malloc(sizeof(int) * N);
	int* a6 = (int*)malloc(sizeof(int) * N);
	int* a7 = (int*)malloc(sizeof(int) * N);
	int j = 0;
	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();
	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 begin7 = clock();
	BubbleSort(a5, N);
	int end7 = clock();

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

	int begin6 = clock();
	MergeSort(a7, N);
	int end6 = 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("BubbleSort:%d\n", end5 - begin5);
	printf("QuickSort:%d\n", end6 - begin6);
	printf("MergeSort:%d\n", end7 - begin7);
	free(a1);
	free(a2);
	free(a3);
	free(a4);
	free(a5);
	free(a6);
	free(a7);
	return 0;
}

性能测试如下:
在这里插入图片描述

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

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

相关文章

【设计模式】装饰者模式Decorator(Java)

文章目录1. 装饰者模式定义2. 类图3.Java实现3.1 饮料Beverage3.2 小料CondimentDecorator3.3 椰果Coconut3.4 红豆RedBean3.5 奶茶MikeTea3.6 果茶JuiceTea3.7 商店主方法StoreMain1. 装饰者模式定义 装饰者模式动态地将责任附加到对象上。若要扩展功能&#xff0c;装饰者提供…

享元模式

文章目录享元模式1.享元模式的本质2.何时选用享元模式3.优缺点4.享元模式的结构5.实现最初实现享元模式初步改造享元模式再改进享元模式再优化享元模式 享元模式最开始看就是类似缓存&#xff0c;缓存一些信息&#xff0c;节约查询时间&#xff0c;以空间换时间 但是再理解后才…

一行代码就能完成的事情,为什么要写两行

今天休息休息&#xff0c;复习一下使用的简洁运算方式以及常用的单行代码 三元运算符 用三元运算符代替简单的if else if (age < 18) {me 小姐姐; } else {me 老阿姨; } 改用三元运算符,一行就能搞定 me age < 18 ? 小姐姐 : 老阿姨; 复杂的判断三元运算符就有点…

这才是最适合新手的python教程(最新版python3.10)

前言 这几年&#xff0c;Python 凭借着语法简洁、跨平台、类库丰富、可扩展、开放源码等特点&#xff0c;成为了 AI 和机器学习时代的第一编程语言。甚至击破铁三角&#xff08; Java、C、C&#xff09;的架构&#xff0c;荣登 TIOBE 榜单的榜首。 身边有不少程序员都选择 Pyt…

LEADTOOLS 入门教程: 检测和提取条形码 - .NET Core

LEADTOOLS是一个综合工具包的集合&#xff0c;用于将识别、文档、医疗、成像和多媒体技术整合到桌面、服务器、平板电脑、网络和移动解决方案中&#xff0c;是一项企业级文档自动化解决方案&#xff0c;有捕捉&#xff0c;OCR&#xff0c;OMR&#xff0c;表单识别和处理&#x…

漏洞丨PDF Explorer 1.5.66.2 - Buffer Overflow

作者&#xff1a;黑蛋 一、漏洞简介 这是一个栈溢出漏洞&#xff0c;一个叫PDF Explorer的软件&#xff08;干嘛的咱没必要知道&#xff09;&#xff0c;他对于用户输入内容长度没有限制造成栈溢出漏洞。 二、漏洞环境 虚拟机 目标程序 调试器 win7x86 PDF Explorer x32…

NeurIPS 2022 | MoVQ: 基于Modulating Quantized Vectors的高保真图像生成

原文标题&#xff1a;MoVQ: Modulating Quantized Vectors for High-Fidelity Image Generation 一、问题提出 虽然两级Vector Quantized (VQ)【指VQVAE-2】生成模型允许合成高保真和高分辨率图像&#xff0c;但它们的量化操作符将图像中的相似patch编码到相同的索引中&#…

2023年MBA联考英语(二)大作文:关于某高校大学生的十大主题

2023年管理类联考倒计时9天&#xff01;在历年的联考英语二作文主题中&#xff0c;大学生群体是时长会出现一期的&#xff0c;这既是对联考中部分专业涉及大学生群体的一种反映&#xff0c;也是因为这个群体的话题对大多数考生来讲都相对熟悉&#xff0c;毕竟都是从这个阶段经历…

世界杯竞猜项目Dapp-第四章(subgraph)

subgraph 是什么 subgraph 索引协议作为 Dapp 领域最重要的基建之一&#xff08;如 uniswap、wave 等都在使用&#xff09;&#xff0c;主要用来做链上数据索引&#xff0c;即在链下对链上事件进行捕捉&#xff08;扫链、计算、存储&#xff09;&#xff0c;然后可对存储下来的…

CAN总线学习笔记 | CAN盒测试STM32的CAN中断接收

CAN基础知识介绍文中介绍了CAN协议的基础知识&#xff0c;以及STM32F4芯片的CAN控制器相关知识&#xff0c;下面将通过实例&#xff0c;利用STM32CubeMX图形化配置工具&#xff0c;并配合CAN盒&#xff0c;来实现CAN通讯的中断收发测试 一、STM32CubeMX配置 CAN是挂载在APB1总…

《钱进球场》:球场争锋·棒球1号位

动画《钱进球场》改编自森高夕次原作、足立刑事著同名体育漫画&#xff0c;于2017年3月宣布动画化 &#xff0c;由STUDIO DEEN负责动画制作&#xff0c;于2018年4月6日起播出。动画第二期于2018年10月5日开始播出。全24话。 中文名 钱进球场 原版名称 グラゼニ 动画制作 Stud…

pytorch 生成手写数字图像

生成对抗网络的概念 最基本的GAN模型由一个生成器 G 和判别器 D 组成。生成器用于生成假样本&#xff0c;判别器用于判断样本是真实的还是假的。 生成器(Generator)&#xff1a;通过机器生成数据&#xff08;大部分情况下是图像&#xff09;&#xff0c;目的是“骗过”判别器…

jQuery - AJAX 简介

什么是 AJAX&#xff1f; AJAX 异步 JavaScript 和 XML&#xff08;Asynchronous JavaScript and XML&#xff09;。 简短地说&#xff0c;在不重载整个网页的情况下&#xff0c;AJAX 通过后台加载数据&#xff0c;并在网页上进行显示。 使用 AJAX 的应用程序案例&#xff…

个人简介网页设计作业 静态HTML个人介绍网页作业 DW个人网站模板下载 WEB静态大学生简单网页 个人网页作品代码 个人网页制作 学生个人网页

&#x1f389;精彩专栏推荐&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb; ✍️ 作者简介: 一个热爱把逻辑思维转变为代码的技术博主 &#x1f482; 作者主页: 【主页——&#x1f680;获取更多优质源码】 &#x1f393; web前端期末大作业…

[附源码]Nodejs计算机毕业设计基于百度AI平台的财税报销系统Express(程序+LW)

该项目含有源码、文档、程序、数据库、配套开发软件、软件安装教程。欢迎交流 项目运行 环境配置&#xff1a; Node.js Vscode Mysql5.7 HBuilderXNavicat11VueExpress。 项目技术&#xff1a; Express框架 Node.js Vue 等等组成&#xff0c;B/S模式 Vscode管理前后端分…

Redis中BIO、NIO、IO多路复用

1 BIO(阻塞IO) 阻塞IO就是两个阶段都必须阻塞等待 通常IO操作都是阻塞I/O的&#xff0c;也就是说当你调用read时&#xff0c;如果没有数据收到&#xff0c;那么线程或者进程就会被挂起&#xff0c;直到收到数据。 read直到数据复制到应用进程的缓冲区或者发生错误才会返回&am…

【数据结构与算法】第十六篇:图论(基础篇)

知识导航图形结构的引进图&#xff08;Grapth&#xff09;1.图的概念与应用2.有向图入度&#xff0c;出度3.无向图4.完全图无向完全图有向完全图5.连通图6.连通分量强连通分量图的实现方案1.邻接矩阵实现法2.邻接表实现法3.两种方法对比分析图形结构的引进 &#x1f30e; 数据…

Linux基础-目录操作

该文章主要为完成实训任务及总结&#xff0c;详细实现过程及结果见【参考文章】 参考文章&#xff1a;https://howard2005.blog.csdn.net/article/details/126962205 文章目录一、常用权限操作1.1 常用权限操作1. chgrp命令2. chown命令3. chmod命令1.2 权限操作实战任务1 创建…

14、Redis_主从复制

文章目录14、Redis_主从复制14.1 是什么14.2. 能干嘛14.3 怎么玩&#xff1a;主从复制14.3.1 新建redis6379.conf&#xff0c;填写以下内容14.3.2 新建redis6380.conf&#xff0c;填写以下内容14.3.3 新建redis6381.conf&#xff0c;填写以下内容14.3.4 启动三台redis服务器14.…

java项目_第173期ssm高校二手交易平台_计算机毕业设计

java项目_第173期ssm高校二手交易平台_计算机毕业设计 【源码请到下载专栏下载】 今天分享的项目是《ssm高校二手交易平台》 该项目分为2个角色&#xff0c;管理员和用户。 用户可以浏览前台商品&#xff0c;并且进行购买商品&#xff0c;并在 个人后台查看自己的订单、查看商品…