【数据结构】常见八大排序算法总结

目录

前言

1.直接插入排序

2.希尔排序

3.选择排序

4.堆排序

5.冒泡排序

6.快速排序

6.1Hoare版本

6.2挖坑法

6.3前后指针法

6.4快速排序的递归实现 

6.5快速排序的非递归实现

7.归并排序

8.计数排序(非比较排序)

9.补充:基数排序

10.总结:排序算法的复杂度及稳定性分析


前言

排序 :排序就是使一串记录按照其中某个或某些关键字的大小,递增或者递减的排列起来的操作

内部排序:数据元素全部存放在内存中的排序

外部排序:数据元素太多而不能同时放在内存中,根据排序过程的要求不断在内外存之间移动数据的排序

常见的排序算法:

以上排序算法都是比较排序,还有计数排序这类非比较排序算法,一下我们对各个排序算法进行代码实现

1.直接插入排序

直接插入排序是一种简单的插入排序算法,其基本思想是:把待排序的记录按其关键码值的大小逐个插入到一个已经排好序的有序序列中,直到所有的记录插入完为止,得到一个新的有序序列

过程:当插入第i(i>=1)个元素时,前面的array[0],...,array[i-1]已经有序,此时用array[i]的排序码与array[i-1],array[i-2],...的排序码依次比较,找到插入位置后将原来位置上的元素顺序后移,将array[i]插入

📖Note:

对于数组中的第一个数据,不需要进行比较,因此第一步操作的是a[1]处的数据,区间[0,0]上的数据即a[0]这一个数据是有序的,因此外层循环只需要对n-1次,对a[1]到a[n-1]共n-1个数据进行处理

对于要处理的a[i],依次与a[i-1],a[i-2]比较,如果大于a[i]则交换(升序排列的情况)

优化:备份i处的数据,依次与a[i-1],a[i-2]比较,大于a[i]则向后移动一个位置,否则在小于a[i]的数据元素的后一个位置插入a[i]

时间复杂度与空间复杂度:

插入排序是在存放原数据的数组上进行操作,所以直接插入排序算法的空间复杂度是O(1)

1️⃣当原数据的序列是逆序时,为最坏情况,此时直接插入排序算法的时间复杂度是O(N^2)

如上图,序列逆序时,数据的挪动次数为1+2+3+...+n-1 = n(n-1)/2

所以时间复杂度为O(N^2)

2️⃣当原数据的序列有序时,为最好情况,此时直接插入排序算法的时间复杂度是O(N)

如上图:序列顺序排列时,总共进行了n-1次比较,没有数据的挪动

因此时间复杂度为O(N)

总结:元素集合越接近有序,直接插入排序算法的时间效率越高

//直接插入排序
void InsertSort(int* a, int n)
{
	for (int i = 0; i < n - 1; ++i)
	{
		// [0,end]有序,end+1处的数据找到正确位置后,[0, end+1]有序
		int end = i;
		int tmp = a[end + 1];
		while (end >= 0)
		{
			//升序排列
			if (a[end] > tmp)
			{
				a[end + 1] = a[end];
				--end;
			}
			else
			{
				break;
			}
		}
		//此时,a[end]<=tmp
		a[end + 1] = tmp;
	}
}

2.希尔排序

希尔排序又称为缩小增量排序,希尔排序的基本思想:先选定一个整数gap,把待排序文件中的所有记录分成n/gap个组,每一组内的记录进行排序,然后更新(缩小)gap,重复上述分组和排序的操作,当gap=1时,所有记录在同一组内排序,即使所有记录有序

以下以初始增量为3为例

第一次分组如下图

每一组内的记录进行排序,使用直接插入排序算法

缩小gap=1,即对序列:5 1 2 5 6 3 8 7 4 9进行排序

每一组记录使用直接插入排序算法,当gap不为1时,每组记录的数据元素并不是连续排列的,而是间隔gap,因此需要对直接插入排序算法进行改造

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

优化:for循环调整部分为i++时,每次直接插入排序排一组记录,当改为i+=gap时,可以实现多组记录同时排序

📖Note:

希尔排序是对直接插入排序的优化,当gap>1时都是预排序,目的是使数据集合接近有序,当数据集合接近有序时,直接插入排序的时间效率较高

gap的选择:

gap的取法有多种,最初Shell提出取gap = n/2,gap = gap/2,直到gap = 1;后来Knuth提出取gap = gap/3 + 1。我们采用Knuth提出的方式取值。

gap越大,大数可以越快的跳到后面;gap越小,跳的越慢,但数据集合越接近有序

时间复杂度和空间复杂度:

希尔排序算法不额外开辟空间,其空间复杂度为O(1)

对希尔排序的时间复杂度分析很困难,在特定情况下可以准确估算关键码的比较次数和对象移动次数,但想要弄清楚关键码的比较次数和对象移动次数与增量选择之间的依赖关系,目前还没有较完整的数学分析,Knuth在《计算机程序设计技巧》中的结论是:在n很大时,关键码的平均比较次数和对象平均移动次数大约在n^1.25到1.6*n^1.25范围内,这是在利用直接插入排序作为子序列排序方法的情况下得到的。

//希尔排序
void ShellSort(int* a, int n)
{
	int gap = n;
	while (gap > 1)
	{
		gap = gap / 3 + 1;//更新增量
		//for (int i = 0; i < n - gap; ++i)
		for (int i = 0; i < n - gap; i += gap)
		{
			int end = i;
			int tmp = a[end + gap];
			while (end >= 0)
			{
				if (a[end] > tmp)
				{
					a[end + gap] = a[end];
					end -= gap;
				}
				else
				{
					break;
				}
			}
			a[end + gap] = tmp;
		}
	}
}

3.选择排序

选择排序的基本思想:每一次从待排的数据元素中选出最小或最大的一个元素,存放在序列的起始位置,直到所有待排序的数据元素排完

直接选择排序的步骤

1️⃣在元素集合array[i]到array[n-1]中选择关键码最大(小)的数据元素

2️⃣若它不是这组元素中的最后一个或第一个元素,则将它与这组元素中的最后一个或第一个元素交换

3️⃣在剩余的array[i]到array[n-2](array[i+1]到array[n-1])集合中,重复上述步骤,直到集合剩余一个元素

简单来说直接选择排序将数据集合分成两部分,有序部分和无序部分

以排升序为例,每次选取待排数据集合即无序部分中的最大数放到无序部分的第一个位置即有序部分之后的第一个位置,就完成了一个数据元素的排序

优化:选择元素时选取最大元素和最小元素的操作同时进行,一趟比较选出最大的元素放在a[end]位置,选出最小的元素放在a[begin]位置,begin++,end--,重复上述操作

优化之后数组的两端有序部分,中间为无序部分

时间复杂度和空间复杂度:

直接选择排序算法不额外开辟空间,其空间复杂度为O(1)

1️⃣当原数据的序列是逆序时,为最坏情况,此时选择排序算法的时间复杂度是O(N^2)

遍历待排数据元素集合,共n个数据,选出最大值和最小值,需要比较n-1次

遍历待排数据元素集合,共n-2个数据,选出最大值和最小值,需要比较n-3次

综上所述,当原序列的数据元素逆序时,总共比较的次数为

(n-1)+(n-3)+...+1 = (n/2) *n  / 2

所以直接选择排序的时间复杂度为O(N^2)

2️⃣当原数据的序列有序时,为最好情况,此时直接插入排序算法的时间复杂度是O(N^2)

对于直接选择排序算法来说,逆序和顺序的时间复杂度相同,因为进行数据选择的操作是相同的,都是进行遍历选数,因此效率不高】

void Swap(int* p1, int* p2)
{
	int tmp = *p1;
	*p1 = *p2;
	*p2 = tmp;
}
//选择排序
void SelectSort(int* a, int n)
{
	int begin = 0, end = n - 1;
	while (begin < end)
	{
		// 选出最小的放begin位置
		// 选出最大的放end位置
		int mini = begin, maxi = begin;
		for (int i = begin + 1; i <= end; ++i)
		{
			if (a[i] > a[maxi])
			{
				maxi = i;
			}

			if (a[i] < a[mini])
			{
				mini = i;
			}
		}

		Swap(&a[begin], &a[mini]);
		// 修正maxi:特殊情况begin和maxi重叠时,执行一次交换,maxi记录的是最小值
		if (maxi == begin)
			maxi = mini;

		Swap(&a[end], &a[maxi]);
		++begin;
		--end;
	}
}

4.堆排序

冒泡排序详解可以参考:CSDN

5.冒泡排序

冒泡排序详解可以参考:CSDN

6.快速排序

快速排序是Hoare于1962年提出的一种二叉树结构的交换排序方法,快速排序的基本思想为:任取待排元素序列中的某个元素作为基准值,按照该排序码将待排序集合分割成两个子序列,左子序列中的所有元素均小于基准值,右子序列中的所有元素均大于基准值,然后左右序列重复该过程,直到所有元素都排列在相应的位置上为止

快速排序递归实现与二叉树的前序遍历规则类似,需要注意的是如何按照基准值来对区间中数据进行划分,常见的方式有以下几种:

6.1Hoare版本

单趟排序:选定一个基准值key,一般情况下为第一个或最后一个元素,设置两个变量left和right分别指向数据元素集合的头和尾,left向后找大数,right向前找小数,找到则交换,直到二者相遇,将相遇位置的数与key交换。

单趟排序结束后,数据元素的排列为:小数  key  大数,其中小(大)数是指小(大)于key的数。key的位置已经确定,不需要再更改,分割出了两个子区间,若这两个子区间有序,则整体有序,因此递归对两个子区间进行排序即可使整体有序。

以下为单趟排序的参考代码:

int keyi = left;//选取第一个数作为关键值
	while (left < right)
	{
		//right找小数
		while (left < right && a[right] >= a[keyi])
		{
			--right;
		}
		//left找大数
		while(left < right && a[left] <= a[keyi])
		{
			++left;
		}
		//判断left和right的关系,防止错过
		if (left < right)
		{
			Swap(&a[left], &a[right]);
		}
	}
	//left和right相遇,交换相遇位置的值和关键值
	Swap(&a[left], &a[keyi]);

📖Note:

1️⃣与关键值比较大小时,需要注意与关键值相等的特殊情况

对left,找大数,遇到小于key的值时继续向后走;遇到等于key的值时,也应该向后继续寻找,如果不过滤等于key的值,则会产生死循环

当数据元素集合中为单值时,如果不过滤等于的情况,right找小数的循环将会是一个死循环;或者当key == a[right]时,也会陷入死循环

2️⃣当选取第一个数据元素作为key,left与right相遇时,交换相遇位置的数据与key,如何保证相遇的位置比key小?(key是第一个数据元素,属于小数区间)

left和right相遇有两种情况:

  1. right停下来,left撞到right相遇,相遇位置比key小:right找小数,所以right停下来的位置为小于key的数,即相遇位置为小于key的数,属于小数区间。因此若选取第一个数据元素做key,则right先走
  2. left停下来,right撞到left相遇,相遇位置比key大:left找大数,所以left停下来的位置为大于key的数,即相遇位置为大于key的数,属于大数区间。因此若选取最后一个数据元素做key,则left先走
// Hoare
int PartSort1(int* a, int left, int right)
{
	//取第一个数据元素为关键值
	int keyi = left;
	while (left < right)
	{
		// R找小
		while (left < right && a[right] >= a[keyi])
		{
			--right;
		}

		// L找大
		while (left < right && a[left] <= a[keyi])
		{
			++left;
		}

		if (left < right)
			Swap(&a[left], &a[right]);
	}
	//相遇位置为meeti
	int meeti = left;
	Swap(&a[meeti], &a[keyi]);

	return meeti;
}

快速排序的优化:主要是对关键值key选取的优化

key的选择有三种方法:

  1. 随机选key
  2. 针对有序序列,先key为最中间位置的数据
  3. 三数取中:即第一个元素,中间位置元素,最后一个位置元素三数取中位数

三数取中的函数接口如下:

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

对key的选择优化之后,可以减少递归的层数,有效避免栈溢出

关键值key的选择优化之后,Hoare版本的单趟排序可以优化:通过三数取中的方法确定关键值key后,将key与第一个数据交换,此时关键值key仍为第一个数据,因此其他地方不需要修改

// Hoare
int PartSort1(int* a, int left, int right)
{
	// 三数取中
	int mid = GetMidIndex(a, left, right);
	Swap(&a[left], &a[mid]);

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

		// L找大
		while (left < right && a[left] <= a[keyi])
		{
			++left;
		}

		if (left < right)
			Swap(&a[left], &a[right]);
	}
	//相遇位置为meeti
	int meeti = left;
	Swap(&a[meeti], &a[keyi]);

	return meeti;
}

6.2挖坑法

挖坑法是对Hoare版本的改进,确定关键值key之后,将其备份,并将其作为一个坑位(数据集合中的第一个元素),right先走找小数,找到则将该小数填到坑位中,并更新该小数的位置为新的坑位,left再走找大数,找到后将该大数填到坑位中,并更新该大数的位置为新的坑位,重复上述操作,直到left和right相遇,相遇位置为一个坑位,填入关键值key

具体过程如下图所示;

// 挖坑法
int PartSort2(int* a, int left, int right)
{
	// 三数取中
	int mid = GetMidIndex(a, left, right);
	Swap(&a[left], &a[mid]);

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

6.3前后指针法

前后指针法也是对Hoare版本的改进,确定关键值key(数据元素集合的第一个元素)并备份,设置两个指针,prev和cur,prev初始化为第一个数据元素,cur初始化为prev的下一个数据元素,当cur没有越界时,cur向后找小数,找到则交换prev指向的数据和cur指向的元素,prev++,cur++,重复上述操作,直到cur越界时,交换prev指向的数据元素和关键值key

📖Note:prev++的操作在交换操作之前,cur++的操作在交换操作之后

在cur找到小数时,prev指向的也为小数,其下一个位置为大数,所以prev需要先加1再交换

具体过程如下图所示:

特殊情况:++prev == cur时,会产生自己和自己交换的情况,因此需要过滤

// 前后指针法
int PartSort3(int* a, int left, int right)
{
	// 三数取中
	int mid = GetMidIndex(a, left, right);
	Swap(&a[left], &a[mid]);

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

		++cur;
	}

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

	return prev;
}

6.4快速排序的递归实现 

快速排序的递归实现:

单趟排序结束后,数据元素的排列为:小数  key  大数

小数区间为[begin ,keyi -1],大数区间为[keyi+1,end]

按照单趟排序的方法递归对这两个区间排序即可

递归结束的条件:区间中只有一个值时递归调用结束,begin==end;或者begin>end,区间无效时递归调用结束

//快速排序
void QuickSort(int* a, int begin, int end)
{
	if (begin >= end)
	{
		return;
	}
    //每趟排序区间的划分:Hoare,挖坑法,前后指针法
    //调用相应接口即可
	int keyi = PartSort1(a, begin, end);

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

6.5快速排序的非递归实现

函数的调用需要在开辟栈帧,如果递归调用的层数太深,可能会造成栈溢出

我们可以借助数据结构中的栈来优化快速排序,数据结构中的栈是在堆区开辟空间,堆区的空间要远远大于栈区空间

实际上,快速排序的非递归是借助数据结构中的栈模拟递归的过程

非递归实现思路:每次将区间的左右区间压栈,当栈不为空时,取栈顶两个元素分别作为一趟排序的区间的左右端点进行排序

📖Note:

如果先定义right,则需要区间左端点begin要先入栈,区间右端点end后入栈,这样才能保证right被区间右端点赋值,left被区间左端点赋值

//快速排序的非递归实现
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);
        //右区间
		if (keyi + 1 < right)
		{
			StackPush(&st, keyi + 1);
			StackPush(&st, right);
		}
        //左区间
		if (left < keyi - 1)
		{
			StackPush(&st, left);
			StackPush(&st, keyi - 1);
		}
	}

	StackDestroy(&st);
}

7.归并排序

归并排序是建立在归并操作上的一种有效的排序算法,该算法是采用分治法的典型应用。将已经有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序,若将两个有序表合并成一个有序表,称为二路归并。

二路归并排序的步骤如下图:

由如下动态图,可以更好的理解二路归并排序的过程

归并排序的递归实现类似于二叉树的后续遍历,采用二叉树的后续遍历框架,递归结束的条件是begin>=end;区间的划分为 [begin, mid] 和 [mid+1, end],mid为中间位置

归并排序的时间复杂度和空间复杂度:

归并排序需要额外开辟一个长度为N的数组,因此其空间复杂度为O(N)

归并排序的时间复杂度为O(N*logN)

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

	int mid = (end + begin) / 2;

	_MergeSort(a, begin, mid, tmp);
	_MergeSort(a, mid + 1, end, tmp);

	// 两两归并成有序序列:取小的尾插
	// [begin, mid] [mid+1, end]
	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++];
		}
		else
		{
			tmp[i++] = a[begin2++];
		}
	}
	//左区间中还有数据
	while (begin1 <= end1)
	{
		tmp[i++] = a[begin1++];
	}
	//右区间中还有数据
	while (begin2 <= end2)
	{
		tmp[i++] = a[begin2++];
	}

	// 拷贝回原数组:归并哪部分就拷贝哪部分回去
	memcpy(a + begin, tmp + begin, (end - begin + 1) * sizeof(int));
}

void MergeSort(int* a, int n)
{
	int* tmp = (int*)malloc(sizeof(int) * n);
	if (tmp == NULL)
	{
		perror("malloc fail");
		return;
	}

	_MergeSort(a, 0, n - 1, tmp);

	free(tmp);
	tmp = NULL;
}

8.计数排序(非比较排序)

计数排序又称为鸽巢原理,是对哈希定址法的直接应用

计数排序的操作步骤:

  1. 统计相同元素出现的次数
  2. 根据统计的结果将序列回收到原来的序列中

📖Note:

计数排序中的相对映射

1.统计个数,得到个数记录数组C

2.将数组C转换成C[i]中存放的是值小于等于i的数据的个数

3.为A数组从前向后的每个元素找到对应的B中的位置,每次从A中复制一个元素到B中,C中相应的计数减一

4.当A中的所有数据都复制到B之后,B中存放的就是有序的数据

总结:计数排序在数据范围集中时,效率很高,但其适用范围有限

void CountSort(int* a, int n)
{
	int max = a[0], min = a[0];
	for (int i = 1; i < n; i++)
	{
		if (a[i] > max)
		{
			max = a[i];
		}
		if (a[i] < min)
		{
			min = a[i];
		}
	}

	int range = max - min + 1;
	//统计计数

	int* countA = (int*)malloc(sizeof(int) * range);
	if (countA == NULL)
	{
		perror("malloc fail");
		return;
	}
	memset(countA, 0, sizeof(int) * range);

	for (int i = 0; i < n; i++)
	{
		countA[a[i] - min]++;//映射的相对位置
	}

	//排序
	int j = 0;
	for (int i = 0; i < range; i++)
	{
		while (countA[i]--)
		{
			a[j] = i + min;
			j++;
		}
	}
}

9.补充:基数排序

基数排序也属于非比较排序

基数排序的操作步骤:

  1. 分发数据
  2. 回收数据

多关键字排序有两种方式:MSD(最高位优先)和LSD(最低位优先)

基数排序的步骤如下图:

10.总结:排序算法的复杂度及稳定性分析

稳定性:假定在待排序的记录序列中,存在多个具有相同的关键字的记录,若经过排序,这些记录的相对次序保持不变,即在原序列中,a[i] = a[j],且a[i] 在 a[j]之前,而在排序后的序列中,a[i] 仍在 a[j]之前,则称这种排序算法是稳定的,否则称之为不稳定的

1️⃣直接插入排序:稳定

关键码相同则不调整,继续向后排序

2️⃣希尔排序:不稳定

预排序时,相同的数据可能分到不同的组,不能保证稳定性

3️⃣选择排序:不稳定

4️⃣堆排序不稳定

当一个堆为单值时,向下调整会影响该值的稳定性,因此堆排序不稳定

5️⃣冒泡排序:稳定

关键码相同则不调整,继续向后排序

6️⃣快速排序:不稳定

7️⃣归并排序:稳定

关键码相同则不调整,继续向后排序

时间复杂度与空间复杂度总结:

算法平均情况最好情况最坏情况辅助空间稳定性
冒泡排序O(N^2)O(N)O(N^2)O(1)稳定
简单选择排序O(N^2)O(N^2)O(N^2)O(1)不稳定
直接插入排序O(N^2)O(N)O(N^2)O(1)稳定
希尔排序O(N*logN)~O(N^2)O(N^1.3)O(N^2)O(1)不稳定
堆排序O(N*logN)O(N*logN)O(N*logN)O(1)不稳定
归并排序O(N*logN)O(N*logN)O(N*logN)O(N)稳定
快速排序O(N*logN)O(N*logN)O(N^2)O(logN)~O(N)不稳定

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Web3解密:区块链技术如何颠覆传统互联网

随着区块链技术的崛起&#xff0c;Web3正逐渐成为新一代互联网的代名词。它不再依赖中心化的权威机构&#xff0c;而是通过去中心化、透明、安全的特性&#xff0c;为用户带来更为开放和公正的互联网体验。本文将深入解密Web3&#xff0c;揭示区块链技术如何颠覆传统互联网的基…

新品发布 | 多通道总线记录仪TLog1004,是你期待的吗?

新品发布 2024年1月12日&#xff0c;同星智能又发布一款多通道 CAN &#xff08;FD&#xff09;总线、LIN 总线接口logger设备&#xff0c;此款产品在TLog1002基础上进行了升级&#xff0c;同时内置 3 路数字输入和 2 路数字输出&#xff0c;便于多种信号测量和系统集成。可以满…

Python + Selenium —— 网页元素定位之Xpath定位!

前面讲的定位方式&#xff0c;都能够很方便的定位到网页元素。但是这些属性并非所有的网页元素都具备&#xff0c;可以这么说&#xff0c;绝大部分情况下都很难保证元素具备这些属性。 也就是很多时候需要使用其他的方式来定位&#xff0c;在 WebDriver 中提供了 Xpath 和 Css…

【Linux】进程间通信——system V 共享内存、消息队列、信号量

需要云服务器等云产品来学习Linux的同学可以移步/–>腾讯云<–/官网&#xff0c;轻量型云服务器低至112元/年&#xff0c;优惠多多。&#xff08;联系我有折扣哦&#xff09; 文章目录 写在前面1. 共享内存1.1 共享内存的概念1.2 共享内存的原理1.3 共享内存的使用1.3.1 …

Linux/Traceback

Enumeration nmap 使用nmap初步扫描发现只开放了22和80端口&#xff0c;端口详细扫描情况如下 先看看web是什么样子的&#xff0c;打开网站发现有一条留言&#xff0c;显示该站点已经被黑了&#xff0c; 并且留下了后门 查看源代码&#xff0c;可以看到下面的注释 <!--So…

(C语言)编译和链接

前言͟͟͞͞&#x1f48c;&#xff1a;对于现在的各种编译器而言许多都是好多个功能的集成&#xff0c;那么我们的代码到底是如何去实现的呢&#xff1f;难道我们的计算机可以直接读懂我们所写的代码&#xff0c;并运行吗&#xff1f;对于很多细心的小伙伴们可能会想这样的问题…

Arduino开发实例-INA219 电流传感器驱动

INA219 电流传感器驱动 文章目录 INA219 电流传感器驱动1、INA219 电流传感器介绍2、硬件准备及接线3、代码实现1、INA219 电流传感器介绍 INA219 模块用于同时测量电流和电压。 该模块使用 I2C 通信传输电压和电流数据。 其他特性: 测量精度:1%最大测量电压:26V最大测量电…

解决小程序字体在最左上角问题

问题如下 原因&#xff1a; 出现这种现象的原因是项目默认开启了Skyline渲染模式&#xff0c;因为Skyline渲染模式不支持原生导航栏&#xff0c;所以在json文件中设置的导航栏失效&#xff0c;文字就会向上移动&#xff0c;如果想要使用原生的导航栏&#xff0c;可以将app.jso…

C#,入门教程(20)——列表(List)的基础知识

上一篇&#xff1a; C#&#xff0c;入门教程(19)——循环语句&#xff08;for&#xff0c;while&#xff0c;foreach&#xff09;的基础知识https://blog.csdn.net/beijinghorn/article/details/124060844 List顾名思义就是数据列表&#xff0c;区别于数据数组&#xff08;arr…

centos7安装Redis7.2.4

文章目录 下载Redis解压Redis安装gcc依赖&#xff08;Redis是C语言编写的&#xff0c;编译需要&#xff09;编译安装src目录下二进制文件安装到/usr/local/bin修改redis.conf文件启动redis服务外部连接测试 参考&#xff1a; 在centos中安装redis-5.0.7 Memory overcommit must…

基于JavaWeb+SSM+Vue停车场微信小程序系统的设计和实现

基于JavaWebSSMVue停车场微信小程序系统的设计和实现 滑到文末获取源码Lun文目录前言主要技术系统设计功能截图订阅经典源码专栏Java项目精品实战案例《500套》 源码获取 滑到文末获取源码 Lun文目录 目录 1系统概述 1 1.1 研究背景 1 1.2研究目的 1 1.3系统设计思想 1 2相关…

Linux命令手册

简介 Multics&#xff08;大而全&#xff09;项目失败&#xff0c;吸取教训启动Unix&#xff08;小而精&#xff09;&#xff0c;Linus Benedict Torvalds受Unix启发开发初始版本Linux内核&#xff0c;Git也由其开发&#xff0c;目的是为了更好的管理Linux内核开发。Unix是商业…

有线桥接|Wifi隔了一堵墙就没信号?房间的网线口利用起来,让房间死角也有网!

前言 本篇文章是路由器有线桥接主路由&#xff0c;起到AP&#xff08;热点&#xff09;的效果 上次发布的无线桥接&#xff0c;使用的前提是需要把旧路由放置在主路由的信号范围内&#xff0c;这极大限制了桥接路由器的放置位置。 如果隔了一堵墙基本上就无法连接Wifi&#x…

供应链安全项目in-toto开源框架详解

引言&#xff1a;in-toto 是一个开源框架&#xff0c;能够以密码学的方式验证构件生产路径上的每个组件和步骤。它可与主流的构建工具、部署工具进行集成。in-toto已经被CNCF技术监督委员会 (Technical Oversight Committee&#xff0c;TOC)接纳为CNCF孵化项目。 1. 背景 由于…

微信小程序之WXML 模板语法之数据绑定、事件绑定、wx:if和列表渲染

学习的最大理由是想摆脱平庸&#xff0c;早一天就多一份人生的精彩&#xff1b;迟一天就多一天平庸的困扰。各位小伙伴&#xff0c;如果您&#xff1a; 想系统/深入学习某技术知识点… 一个人摸索学习很难坚持&#xff0c;想组团高效学习… 想写博客但无从下手&#xff0c;急需…

一键搭建你的知识库

效果 说明 由于安装包安装需要glibc>2.7 我就不尝试了 因为glib升级是一个繁琐的过程 没有升级的意义 只是为了体验知识库 没必要浪费时间 1.1docker compose部署trilium 1.1.创建目录 mkdir -p /opt/triliumcd /opt/trilium 1.2.编写docker-comppose.yml文件 vim dock…

使用STM32的GPIO口实现LED闪烁

✅作者简介&#xff1a;热爱科研的嵌入式开发者&#xff0c;修心和技术同步精进 代码获取、问题探讨及文章转载可私信。 ☁ 愿你的生命中有够多的云翳,来造就一个美丽的黄昏。 &#x1f34e;获取更多嵌入式资料可点击链接进群领取&#xff0c;谢谢支持&#xff01;&#x1f447…

JavaWeb-Cookie与Session

一、概念 是否还记得我们在HTTP概念中提到&#xff1a;HTTP的一大特点是无状态&#xff0c;这意味着多次HTTP请求之间是无法共享数据的。而在请求之间共享一些数据又是我们期望达到的效果。&#xff08;例如登录的记住我功能&#xff09;于是便有了会话跟踪技术&#xff0c;而…

Qt拖拽组件与键盘事件

1.相关说明 1.设置widget或view的拖拽和放置模式函数setDragDropMode参数说明&#xff0c;NoDragDrop(无拖拽和放置)、DragOnly(只允许拖拽)、DropOnly(只允许放置)、DragDrop(允许拖拽和放置)、InternalMove(只移动不复制) 2.设置widget或view的放置动作函数setDefaultDropAct…

python 实现大语言模型中的概率论:两人轮流出手对决时取胜概率的推导

假设你跟朋友通过打赌投篮来打赌一万块。你们找到一个篮球框&#xff0c;然后约定轮流投篮&#xff0c;谁先投进谁赢。假设你投进的概率是 p&#xff0c;也就是投不进的概率是 1-p&#xff0c;你对手投进的概率是 q,投不进的概率是 1-q&#xff0c;如果由你先投&#xff0c;那么…