数据结构-八大排序

news2024/12/23 15:02:52

在这里插入图片描述

八大排序

  • 一,直接插入排序
  • 二,希尔排序
  • 三,选择排序
  • 四,堆排序
  • 五,冒泡排序
  • 六,快速排序
    • 1,递归版本
      • (1)hoare法
      • (2)挖坑法
      • (3)前后指针法(推荐)
    • 2,非递归版本
    • 3,快排的优化
      • (1)三数取中
      • (2)小区间优化
      • (3)三路划分
  • 七,归并排序
    • 1,递归实现
    • 2,非递归实现
  • 八大排序稳定性总结

一,直接插入排序

在这里插入图片描述

思路:
在已经有序的数据基础上再插入一个新的数据,已经有序的数据最有一个数据的下标为end,将要插入的数据与end下标的数据比较,如果小于end小标的数据,那么end小标的数据就移动到end+1小标的位置,同时–end。同时,还要注意当end减小到-1时,就要停止比较直接插入到下标为0的位置。
在这里插入图片描述
下面看代码:

void InsertSort(int* a, int size)
{
	for (int i = 0; i < size - 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;
	}
}

选择排序的时间复杂度:O(N^2)
空间复杂度:O(1)

二,希尔排序

在这里插入图片描述

希尔排序是直接插入排序的升级版本,由于当插入的值较小时,直接插入排序需要移动大量的数据,希尔排序对其做出的改进就是增加了多组预排序,将每隔gap间距的数据归为一组,一共可以分成gap组,对这gap组数据进行预排序时,在需要挪动数据的时候,数据跳动的步长大,不再像直接插入排序一样一步一步的挪动。
在这里插入图片描述
所以,希尔排序的单趟排序与直接插入排序十分类似,
下面看单趟排序的代码:

for(int j = 0;j < gap; j++)
{
 for (int i = j; i < size - gap; i+=gap)
	{
		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;
	}
}

上面这种写法,是每排完一组后,再排另一组,我们可以对其简化一下,直接进行多组并排:

for (int i = 0; i < size - 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;
		}

下面是希尔排序的完整代码:

void ShellSort(int* a, int size)
{
	int gap=size;
	while (gap > 1)
	{
		gap = gap / 3 + 1;
		for (int i = 0; i < size - 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;
		}
	}
}

gap再逐渐的减小,当减小到1的时候就是直接插入排序,但此时与直接插入排序相比,数据已经大部分有序了。

希尔排序是一个较为优秀的排序
时间复杂度:O(N^1.3)
空间复杂度:O(1)

三,选择排序

在这里插入图片描述

选择排序的思路是:
每次遍历数组标记出最大数据的小标与最小数据的下标,分别将其与最有一个数据和开头数据进行交换。
下面看代码:

void SelectSort(int* a, int size)
{
	int begin = 0;
	int end = size - 1;
	while (begin < end)
	{
		int mini = begin;
		int maxi = begin;
		for (int i = begin + 1; i <= end; i++)
		{
			if (a[i] > a[maxi])
			{
				maxi = i;
			}
			if (a[i] < a[mini])
			{
				mini = i;
			}
		}
		swep(&a[begin], &a[mini]);
		if (maxi == begin)
		{
			maxi = mini;
		}
		swep(&a[maxi], &a[end]);
		++begin;
		--end;
	}
}

这种一次循环找出最大值与最小值的下标的写法,会存在一个小问题,就是maxi与begin重合的情况,当我们对mini与begin数据交换后,此时的begin下标位置的数据就已经不是最大的数据了,所以在交换end下标与maxi下标位置的数据前要先做判断。
在这里插入图片描述
选择排序的时间复杂度:O(N^2)
空间复杂度:O(1)

四,堆排序

在这里插入图片描述

堆排序的详细过程已在之前博客讲过:
链接: 堆排序博客

void AdjustDown(int* a, int size, int parent)
{
	int child = parent * 2 + 1;
	while (child < size)
	{
		if (child+1<size&&a[child + 1] > a[child])
		{
			child++;
		}
		if (a[child] > a[parent])
		{
			swep(&a[child], &a[parent]);
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			break;
		}
	}
}
void HeapSort(int* a, int size)
{
	for (int i = (size - 2) / 2; i >= 0; i--)
	{
		AdjustDown(a, size, i);
	}
	int end = size - 1;
	while (end > 0)
	{
		swep(&a[0], &a[end]);
		AdjustDown(a, end, 0);
		--end;
	}
}

堆排序也是一个比较优秀的排序:
时间复杂度:O(N*logN)
空间复杂度:O(1)

五,冒泡排序

在这里插入图片描述

不得不说,冒泡排序绝对是非常经典的排序,大量出现在教学中。
思路:
前后数据对比,若前面数据大于后面的数据就交换,这样经过第一轮的排序,最大值就被换到了末尾,这样以此往复,第二大的数据被放到倒数第二的位置…

void BubbleSort(int* a, int size)
{
	for (int i = 0; i < size; i++)
	{
		int flag = 1;
		for (int j = 0; j < size - 1 - i; j++)
		{
			if (a[j] > a[j + 1])
			{
				flag = 0;
				swep(&a[j], &a[j + 1]);
			}
		}
		if (flag)
		{
			break;
		}
	}
}

冒泡排序也可以进行一个小的优化,定义一个flag标识符,在进行一趟的比较中,如果没有进行过数据的交换,那么就证明此时这组数据已经有序了,那么就直接break跳出循环即可。

虽然,冒泡排序大量用在教学中,但其的性能不高,
时间复杂度:O(N^2)
空间复杂度:O(1)

六,快速排序

实际中,快速排序是被应用最广泛的排序,敢叫快速排序绝不是浪得虚名哟~
快速排序的单趟排序思路:首先选一个key位置,将key下标的数据调整到它在整个数据中的正确位置,并且将key位置的左右区间拆分成与其类似的子问题。

1,递归版本

(1)hoare法

在这里插入图片描述

hoare法:
1,选取key位置,通常选在数据的一个个位置也就是left位置。
2,如果key选在left位置,那么right指针先动,找到比key位置数据小的数据时停住。
3,left指针再动,找到比key位置数据大的数据后停住
4,交换left,right位置的数据,以此往复前面的操作,直到left,right相遇位置。
5,相遇后,交换key位置与相遇位置的数据,此时,相遇位置的左边的数据小于等于相遇位置的数据,右边的数据大于等于相遇位置的数据。所以,这个数据就调整到了它的正确位置。

int PartSort1(int* a, int begin, int end)
{
	int keyi = begin;
	int left = begin;
	int right = end;
	while (left < right)
	{
		//找小
		while (left < right && a[right] >= a[keyi])
		{
			--right;
		}
		//找大
		while (left < right && a[left] <= a[keyi])
		{
			++left;
		}
		swep(&a[left], &a[right]);
	}
	swep(&a[keyi], &a[left]);
	keyi = left;
	return keyi;
}

此时,肯定有人会有疑问?为什么相遇位置的数据一定小于等于key位置的数据。
首先,相遇无非两种情况:
1,right指针找到left指针
2,left指针找到right指针
先分析1:
当right指针移动的时候,说明之前已经发生过交换,交换后left指向的一定是比key位置数据小的数据。即使是right指针第一次移动时,直接移动到了数据的最左端与left相遇,那此时的相遇位置的数据与key位置的数据相同。
在这里插入图片描述
再分析2:
left指针移动前,right指针肯定移动过(因为规定right指针先移动),那么此时right指向的数据一定比key位置的数据小,所以left与right相遇时,相遇的位置的数据比key位置的数据要小。

(2)挖坑法

在这里插入图片描述

hoare法出现后,小的细节比较多,后面也出现了许多新的放法,比如:挖坑法
1,把数组left位置的数据赋值给key,形成了第一个坑位hole就是left位置。
注意:此时的key不再是数组的下标
2,还有right指针先动,找到比key小的数据停止,将数据填到坑位中,将此时的right赋值给hole形成新的坑位。
3,left指针后动,找到别key数据大的数据停止,将其填到坑位中,将left赋值给hole,形成新的坑位。
4,right与left相遇时停止,将key填入坑位中。此时,key数据就调整到了它的正确位置。

int PartSort2(int* a, int begin, int end)
{
	int key = a[begin];
	int hole = begin;
	int left = begin;
	int right = end;
	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;
}

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

在这里插入图片描述

定义一个key是数组第一个元素的下标,prev指向第一个元素,cur指向第二个元素。
1,cur指针移动,找到比key为下标的数小的时候停止。
2,++prev,交换prev与cur位置的数据。
3,当cur指向数组最有一个位置的下一个位置时,循环停止。
4,交换key下标与prev下标的数据。

int PartSort3(int* a, int begin, int end)
{
	int keyi = begin;
	int prev = begin;
	int cur = begin + 1;
	while (cur <= end)
	{
		if (a[cur] < a[keyi]&&++prev!=cur)
		{
			swep(&a[prev], &a[cur]);
		}
		++cur;
	}
	swep(&a[prev], &a[keyi]);
	keyi = prev;
	return keyi;
}

这段代码中,可能有人会对if条件判断语句产生疑问,为什么这样写呢?
下面通过图示来解答:
在这里插入图片描述
上图就是,单趟循环的整个过程,我们发现前几次cur与prev指向的是同一个位置,所以就没必要进行交换。

下面看递归版本的完整代码:

int PartSort3(int* a, int begin, int end)
{
	int keyi = begin;
	int prev = begin;
	int cur = begin + 1;
	while (cur <= end)
	{
		if (a[cur] < a[keyi]&&++prev!=cur)
		{
			swep(&a[prev], &a[cur]);
		}
		++cur;
	}
	swep(&a[prev], &a[keyi]);
	keyi = prev;
	return keyi;
}
void QuickSort1(int* a, int begin, int end)
{
	if (begin >= end)
	{
		return;
	}
	swep(&a[begin], &a[mid]);
	int keyi = PartSort3(a, begin, end);
	QuickSort1(a, begin, keyi - 1);
	QuickSort1(a, keyi+1, end);
}

2,非递归版本

递归的写法虽然简单,但是当递归深度太深的话,就会出现问题,所以就出现了非递归的写法:
非递归是借助数据结构的栈来实现的
因为,快排是针对同一个数组的不同区间进行调整的,所以把要调整的区间存到栈中,然后每次取出栈顶的区间进行调整,调整完后将形成新的两段区间压栈。
注意:为了模拟递归的过程,要先压有段区间,再压左端区间。还有如果产生的两段区间中数据个数小于两个时,就不需要压栈了。

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);
		int keyi = left;
		int cur = left + 1;
		int prev = left;
		while (cur <= right)
		{
			if (a[cur] < a[keyi]&&++prev!=cur)
			{
				swep(&a[prev], &a[cur]);
			}
			++cur;
		}
		swep(&a[prev], &a[keyi]);
		keyi = prev;
		if (keyi + 1 < right)
		{
			STPush(&st, end);
			STPush(&st, keyi + 1);
		}
		if (left < keyi - 1)
		{
			STPush(&st, keyi - 1);
			STPush(&st, begin);
		}
	}
	STDestroy(&st);
}

3,快排的优化

在这里插入图片描述
当每次选的key最终都被调整到数组的中间位置时,那么快排就类似一个二分结构,时间复杂度是O(N*logN)。

(1)三数取中

当数组本身就有序或者接近有序的时候,每次key都被调整到最开始或最末尾的位置,导致快排达不到一个二分的结构,此时的时间复杂度就为O(N^2)。
为了防止这种现象的出现,采取三数取中的方式,在begin,end,一个随机位置中选出第二大的数据与begin数据做交换,在在begin位置标记为key。
这样就不会存在上述问题了

int GetMidIndex(int* a, int begin, int end)
{
	int mid = begin + rand() % (end - begin);
	if (a[begin] > a[end])
	{
		if (a[end] > a[mid])
		{
			return end;
		}
		else if (a[mid] > a[begin])
		{
			return begin;
		}
		else
		{
			return mid;
		}
	}
	else
	{
		if (a[mid] < a[begin])
		{
			return begin;
		}
		else if (a[mid] > a[end])
		{
			return end;
		}
		else
		{
			return mid;
		}
	}
}

(2)小区间优化

正常的快排类似于一个二分的结构,
在这里插入图片描述
最后一层的数量要占到总数的1/2左右,最后两层占到总数的3/4左右。
由于,递归是有消耗的,所以当区间内数据量小于10个的时候,我们就用插入排序来代替快速排序,这样减少递归的消耗。

void QuickSort1(int* a, int begin, int end)
{
	if (begin >= end)
	{
		return;
	}
	if ((end - begin + 1) < 10)
	{
		InsertSort(a, (end - begin + 1));
	}
	int mid = GetMidIndex(a, begin, end);
	swep(&a[begin], &a[mid]);
	int keyi = PartSort3(a, begin, end);
	QuickSort1(a, begin, keyi - 1);
	QuickSort1(a, keyi+1, end);
}

(3)三路划分

当要排序的一组数据中,所有数据或者说绝大多数数据相等时,也会极大的降低快排的效率。
1,left和right分别指向数组的首个元素和末尾元素,将首个元素赋值给key。
2,cur指针指向首个元素的下一个元素。
3,当cur指向的数据小于key时,交换left和cur的数据,并且left++,cur++
4,当cur指向的数据大于key时,交换right和cur的数据,并且right–。
5,当cur指向的数据等于key时,cur++。
6,当cur与right错过时,停止循环。
在这里插入图片描述

将数组拆分为三段,begin-left-1是小于key的数据
left到right是等于key的数据
right+1到end是大于key的数据

void QuickSort2(int* a, int begin, int end)
{
	if (begin >= end)
	{
		return;
	}
	if ((end - begin + 1) < 10)
	{
		InsertSort(a, (end - begin + 1));
	}
	int mid = GetMidIndex(a, begin, end);
	swep(&a[begin], &a[mid]);
	int left = begin;
	int right = end;
	int key = a[begin];
	int cur = begin + 1;
	while (cur <= right)
	{
		if (a[cur] < key)
		{
			swep(&a[left], &a[cur]);
			++left;
			++cur;
		}
		else if (a[cur] > key)
		{
			swep(&a[right], &a[cur]);
			--right;
		}
		else
		{
			++cur;
		}
	}
	QuickSort2(a, begin, left-1);
	QuickSort2(a, right+1, end);
}

七,归并排序

1,递归实现

在这里插入图片描述

思路:归并排序(MERGE-SORT)是建立在归并操作上的一种有效的排序算法,该算法是采用分治法(Divide and
Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有
序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。 归并排序核心步骤:

(1)拆分:

在这里插入图片描述
(2)归并
在这里插入图片描述

递归代码实现:

void _MergeSort(int* a, int begin, int end,int* tmp)
{
	if (begin >= end)
	{
		return;
	}
	int mid = begin + (end - begin) / 2;
	_MergeSort(a, begin, mid,tmp);
	_MergeSort(a, mid+1, end,tmp);

	int begin1 = begin, end1 = mid;
	int begin2 = mid + 1, end2 = end;
	int i = begin;
	
	while (begin1 <= end1 && begin2 <= end2)
	{
		if (a[begin1] <= a[begin2])
		{
			tmp[i++] = a[begin1];
			++begin1;
		}
		else
		{
			tmp[i++] = a[begin2];
			++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 size)
{
	int* tmp = (int*)malloc(sizeof(int) * size);
	if (!tmp)
	{
		perror("malloc fail");
		exit(-1);
	}
	_MergeSort(a, 0, size - 1,tmp);
	free(tmp);
	tmp = NULL;
}

2,非递归实现

递归调用占用堆栈的空间,每递归一次都会新开辟一块空间,这样当递归的深度太深的时候,可能会栈溢出。
由于,归并的第一步是拆分,将一个n个数据的数组,拆分成n个一个数据的数组,再归并tmp数组中,再将tmp数组拷贝回去。设定一个rangeN表示两两数组归并时,数组中的数据个数。
在这里插入图片描述
上面是理想状态
当数据个数不是2的整数倍的时候,会出现越界的情况,需要我们对区间进行控制。
(1)end1越界
在这里插入图片描述
(2)begin2越界
在这里插入图片描述
(3)end2越界
在这里插入图片描述
对上述情况做出处理:
在这里插入图片描述

void MergeSortNonR(int* a, int size)
{
	int* tmp = (int*)malloc(sizeof(int) * size);
	if (!tmp)
	{
		perror("malloc fail");
		exit(-1);
	}
	int rangeN = 1;
	while (rangeN < size)
	{
		for (int j = 0; j < size; j += rangeN * 2)
		{
			int begin1 = j;
			int end1 = j + rangeN - 1;
			int begin2 = j + rangeN;
			int end2 = j + rangeN * 2 - 1;
			if (end1 >= size)
			{
				end1 = size - 1;
				begin2 = size;
				end2 = size - 1;
			}
			else if (begin2 >= size)
			{
				begin2 = size;
				end2 = size - 1;
			}
			else if (end2 >= size)
			{
				end2 = size - 1;
			}
			int i = j;
			while (begin1 <= end1 && begin2 <= end2)
			{
				if (a[begin1] <= a[begin2])
				{
					tmp[i++] = a[begin1];
					++begin1;
				}
				else
				{
					tmp[i++] = a[begin2];
					++begin2;
				}
			}
			while (begin1 <= end1)
			{
				tmp[i++] = a[begin1++];
			}
			while (begin2 <= end2)
			{
				tmp[i++] = a[begin2++];
			}
		}
		memcpy(a, tmp, sizeof(int) * size);
		rangeN *= 2;
	}
}

归并排序时间复杂度:O(N*logN)
空间复杂度:O(N)

八大排序稳定性总结

稳定排序:冒泡排序,插入排序,归并排序
非稳定排序:选择排序,希尔排序,堆排序,快速排序

非稳定排序的例子
1,选择排序
在这里插入图片描述
2,希尔排序由于进行多组预排序,当相同的数据被分配到不同组时,并且在不同组内的相对顺序不同时,就会使排序不稳定。
3,堆排序
在这里插入图片描述
这是一个大堆当第一个8与堆的最后一个元素交换后,将堆的节点数-1,向下调整后再与堆的最有一个元素交换时,就会破坏稳定性。
(4)快速排序
在这里插入图片描述

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

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

相关文章

5 年 Python ,总结的 10 条 Python 使用技巧

今天给大家分享 10 个我平时整理非常实用的 Python 开发小技巧&#xff0c;内容目录如下&#xff1a; 值得一提的是&#xff0c;这 10 个技巧全部收录在我自己写的 《Python黑魔法指南》里。 1. 如何在运行状态查看源代码&#xff1f; 查看函数的源代码&#xff0c;我们通常…

Apache Drill的学习

Drill的下载地址&#xff1a; Index of /dist/drill 上传安装包后&#xff0c;解压&#xff0c;测试环境中&#xff0c;我直接放到/root目录下了 tar -zxvf apache-drill-1.11.0.tar.gz 进入/root/apache-drill-1.11.0/bin 启动命令&#xff1a; ./sqlline -u jdbc:drill:…

VMware创建Linux虚拟机之(四)ZooKeeperHBase完全分布式安装

Hello&#xff0c;world&#xff01; &#x1f412;本篇博客使用到的工具有&#xff1a;VMware16 &#xff0c;Xftp7 若不熟悉操作命令&#xff0c;推荐使用带GUI页面的CentOS7虚拟机 我将使用带GUI页面的虚拟机演示 虚拟机&#xff08;Virtual Machine&#xff09; 指通过软…

C语言基础

文章目录二. 前言1.vscode 搭建C语言开发环境2.编程语言的演变2-1.计算机语言2-2 解释型vs编译型2-2 面向过程vs面向对象三. C语言基础0.数据存储范围1.变量与常量2.数据类型2-1 整型数据2-2 浮点型数据2-3 字符型数据2-4 格式化输出2-5 输入框语句2-5 字符输入函数2-6 字符输出…

硬件内存模型

Hardware Memory Models 这是Go语言作者之一的rsc语言内存模型相关博文之一&#xff1b;硬件内存模型的笔记。硬件内存模型对应的是汇编指令的执行&#xff0c;可以认为每一条指令原子执行。 Sequential Consistency 顺序一致性模型 多个进程的操作按照一定顺序执行&#xf…

(附源码)ssm小米购物网站 毕业设计 261624

基于ssm小米购物网站 摘 要 近年来&#xff0c;随着移动互联网的快速发展&#xff0c;电子商务越来越受到网民们的欢迎&#xff0c;电子商务对国家经济的发展也起着越来越重要的作用。简单的流程、便捷可靠的支付方式、快捷畅通的物流快递、安全的信息保护都使得电子商务越来越…

[附源码]计算机毕业设计宁财二手物品交易网站Springboot程序

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

数据结构(13)最小生成树JAVA版:prim算法、kruskal算法

目录 13.1.概述 13.2.prim算法 13.2.1.概述 13.2.2.代码实现 13.3.kruskal算法 13.3.1.概述 13.3.2.代码实现 13.1.概述 最小生成树&#xff0c;包含图的所有顶点的一棵树&#xff0c;树的边采用包含在图中的原有边中权重和最小的边。翻译成人话就是遍历一遍全图所有顶点…

Java基于J2EE的流浪动物收容与领养管理系统

随着城市饲养宠物日益增加&#xff0c;流浪动物也越来越多&#xff0c;本文对流浪动物出现的原因&#xff0c;引发的社会问题以及流浪动物的保护等方面进行思考阐述,以期唤醒人们对动物福利的关注和对生命的珍爱。 通过以上的调研研究发现&#xff0c;如此多的流浪动物是如此的…

肠道菌群代谢组学之粪便微生物移植治疗原发性硬化性胆管炎

​ The American Journal of GASTROENTEROLOGY (IF10.241) 10位原发性硬化性胆管炎患者的粪便微生物移植&#xff1a;一个试点的临床试验 研究背景 百趣代谢组学分享&#xff0c;原发性硬化性胆管炎&#xff08;Primary sclerosing cholangitis&#xff0c;PSC&#xff09;是…

【Java语言】— 快速入门

Java背景知识 Java是美国sun公司在1995年推出的一门计算机高级编程语言。 Java早期称为Oak&#xff08;橡树&#xff09;&#xff0c;后改为Java。 Java之父:詹姆斯高斯林。 2009年sun公司被Oracle公司收购。 为什么用Java 世界上最流行的编程语言之一&#xff0c;在国内使用…

生成式AI结合3D、XR怎么玩?NVIDIA、Niantic等公司已入局

最近生成式AI风头有点大&#xff0c;这种技术只需要用文字就能作画&#xff0c;而且效果惊艳&#xff0c;堪比专业画师的作品。其中一些热门的方案包括DALL-E 2、Midjourney、BariumAI、D-ID AI、Stable Diffusion等等&#xff0c;这些工具简单、好玩&#xff0c;已经被无数网友…

BYD精制项目除铜工艺去除铜离子

某精细化工公司BYD精制项目 工艺选择 过滤系统螯合树脂除铜系统合格品回收箱 工艺原理 在不应该1,4丁炔二醇的情况下去除铜离子 项目背景 1,4-丁炔二醇BYD&#xff08;but-2-yne-1,4-diol&#xff09;是一种重要的中间体化工原料&#xff0c;广泛应用于生产丁二醇及其下游产…

2022CTF培训(五)字符串混淆进阶代码自解密

附件下载链接 复杂的字符串混淆 原理 之前的字符串混淆是一次性解密的&#xff0c;找到解密函数即可获得所有字符串&#xff0c;同时执行解密函数后内存中也可直接获得所有字符串。 因此对抗人员升级了混淆技术&#xff0c;使得解密仅在使用时发生&#xff0c;从而避免了全部…

微机原理不挂科

微机原理1.计算机基础1.1数制码值转换1.2码制1.3微机组成2.8088/8086微处理器2.1CPU内部结构2.2寄存器2.3存储器分段和地址空间2.4堆栈2.5 8086/8088CPU引脚2.6 时序与总线操作3.指令系统3.2寻址方式3.3语法规则3.4数据传送指令3.5算术运算指令3.6逻辑运算与移位指令3.7串操作指…

(二十) 共享模型之工具【JUC】【线程安全集合类】

一、线程安全集合类概述 线程安全集合类可以分为三大类&#xff1a;&#xff08;1&#xff09;遗留的线程安全集合如 Hashtable &#xff0c; Vector&#xff08;2&#xff09;使用 Collections 装饰的线程安全集合&#xff0c;如&#xff1a; 1️⃣Collections.synchronizedCo…

[附源码]计算机毕业设计JAVA游戏账号交易平台

[附源码]计算机毕业设计JAVA游戏账号交易平台 项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybati…

Redis单机集群

先放张图 上图就是典型的哨兵模式 salve&#xff1a;从服务器&#xff0c;需要进行同步主服务器的数据 master&#xff1a;主服务器&#xff0c;负责执行客户端的请求&#xff0c;将数据更新信息发送给从服务器&#xff0c;保持数据一致 哨兵&#xff1a;接受客户端请求&…

【前端】前端监控体系

文章目录一、所需的数据1.1、生命周期数据1.2、HTTP测速数据1.3、系统异常数据1.4、用户行为数据1.5、用户日志二、埋点与收集2.1、数据埋点2.1、数据上报2.3、数据监控对于一个应用来说&#xff0c;除了前期的开发和设计&#xff0c;在项目上线后端维护很重要&#xff0c;其中…

Docker 讲解与基本操作

哈喽~大家好&#xff0c;这篇来看看Docker 讲解与基本操作。 &#x1f947;个人主页&#xff1a;个人主页​​​​​ &#x1f948; 系列专栏&#xff1a;【微服务】 &#x1f949;与这篇相关的文章&#xff1a; SpringCloud Sentinel 使用Spr…