手把手教你 ,带你彻底掌握八大排序算法【数据结构】

news2024/12/25 0:18:16

文章目录

  • 插入排序
    • 直接插入排序
    • 希尔排序
  • 选择排序
    • 选择排序
    • 堆排序
        • 升序
  • 交换排序
    • 冒泡排序
    • 快速排序
      • 递归
        • hoare版本
        • 挖坑法
        • 前后指针版本
      • 三数取中法选key
      • 递归到小的子区间时,可以考虑使用插入排序
  • 归并排序
    • 递归实现
    • 非递归实现
  • 排序算法复杂度以及稳定性

在这里插入图片描述

插入排序

直接插入排序

直接插入排序是一种简单的插入排序法,其基本思想:是把待排序的记录按其关键码值的大小逐个插入到一个已经排好序的有序序列中,直到所有的记录插入完为止,得到一个新的有序序列
可以理解为一遍摸扑克牌,一边进行排序

在待排序的元素中,假设前面n-1(其中n>=2)个数已经是排好顺序的,现将第n个数插到前面已经排好的序列中,然后找到合适自己的位置,使得插入第n个数的这个序列也是排好顺序的。
按照此法对所有元素进行插入,直到整个序列排为有序的过程,称为插入排序

  • 当插入第n个元素时,前面的n-1个数已经有序
  • 用第n个数与前面的n-1个数比较,我们将第n个数假设为tmp ,也就是说将tmp插入到[0 ,end] 的区间中
    第一种情况:如果tmp比end大就放在end后面
    第二种情况:如果tmp比end小但是比数组前面的几个数据大,插入之后,tmp之后的数据就往后挪动
    第三种情况:如果tmp比数组所有元素都小,所有数据都需要挪动,end就得挪动到数组下标为-1的位置才结束

在这里插入图片描述

在这里插入图片描述

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

void InsertSort(int* a , int n )
{
	for (int i = 1; i <n ; i++)
	{
		//一趟插入排序 

		int tmp = a[i];
		int end = i-1; //end是数组下标
		while (end >= 0)
		{
			//如果tmp比end小但是比数组前面的几个数据大,插入之后,tmp之后的数据就往后挪动
			if (tmp < a[end])
			{
				a[end + 1] = a[end];//往后挪动
				end--;
			}
			else
			{
				break;
			}

		}
		//如果tmp比数组所有元素都小,所有数据都需要挪动,end就得挪动到数组下标为-1的位置才结束
			//如果tmp比end大就放在end后面
		a[end + 1] = tmp;//包含上述两种情况
   }
}

时间复杂度:O(N^2)
空间复杂度:O(1)

希尔排序

第一步先选定个小于n的数字作为gap,将所有距离为gap的数分为一组进行预排序,预排序的目的就是使数组接近有序,与直接插入排序相同,直接插入排序的间隔为1,预排序的间隔变为gap了
再选一个小于gap的数作为新的gap,重复第一步的操作
当gap的大小减到1时,就相当于整个序列被分到一组,进行一次直接插入排序,排序完成

举例分析一下:

在这里插入图片描述

我们用序列长度的一半作为第一次排序时gap的值,此时相隔距离为5的元素被分为一组(共分了5组,每组有2个元素),然后分别对每一组进行直接插入排序

在这里插入图片描述

gap的值折半,此时相隔距离为2的元素被分为一组(共分了2组,每组有5个元素),然后再分别对每一组进行直接插入排序

在这里插入图片描述

gap的值再次减半,此时gap减为1,即整个序列被分为一组,进行一次直接插入排序。

为什么是选一个小于gap的数作为新的gap,也就是要gap由大变小?
gap越大,数据跳跃的幅度越大,数据挪动得越快;gap越小,数据跳跃的幅度越小,数据挪动得越慢。前期让gap较大,可以让数据更快得移动到自己对应的位置附近,减少挪动次数,提高算法效率

void ShellSort(int* a, int n) // 希尔排序 
{
	int gap = n;
	while (gap > 1)
	{
		//一趟排序
		gap /= 2;
		for (int i = gap; i < n; i += gap)
		{

			int end = i - gap;  //有序序列最后一个元素的下标
			int tmp = a[end + gap];
			while (end >= 0)
			{
				//如果tmp比end小但是比数组前面的几个数据大,插入之后,tmp之后的数据就往后挪动
				if (tmp < a[end])
				{
					a[end + gap] = a[end]; //往后挪动 
					end -= gap;
				}
				else
				{
					break;
				}

			}
			//tmp比数组中所有数据都小  ,一直往后挪动 ,end挪到了-1
			//tmp >a[end] 
			a[end + gap] = tmp;

		}
	}
}

选择排序

选择排序

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

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

若它不是这组元素中的最后一个(第一个)元素,则将它与这组元素中的最后一个(第一个)元素交换;

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

注意特殊情况 :如果Maxi在left位置,当a [ Mini ] 和 a [ left ] 互换时,此时Maxi的位置就变成了Mini, 我们添加一个判断条件 ,判断left == Maxi , 修正Maxi ,防止Maxi更改
在这里插入图片描述

以升序为例做分析:

在这里插入图片描述

经过第一躺交换,最小的元素排在了数组的第一个位置

在这里插入图片描述

经过第二趟交换 ,第二小的元素已经到了数组第二个元素位置

在这里插入图片描述

经过第三趟排序 ,第三小的元素已经排在了第三个位置

在这里插入图片描述

最后一趟排序 ,数组已经有序了

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

代码实现:

void SelectSort(int* a, int n)//选择排序
{
	//升序 
	int left = 0, right = n - 1;
	while (left < right)
	{
		

	//选出最大最小数的下标 
	
		int Mini = left;
		int Maxi = left;
		//一趟 
		for (int i = left+1 ; i <= right; i++)
		{
			if (a[i] < a[Mini])
			{
				Mini = i; // 更新Mini
			}
			if (a[i] > a[Maxi])
			{
				Maxi = i; //更新Maxi 
			}
		}
	
		// Mini和左边交换
		Swap(&a[left], &a[Mini]);
		// Maxi 在左边  ,Maxi 被换成Mini, 修正Maxi
		if (left == Maxi)
		{
			Maxi = Mini;  
		}
		//Maxi与右边交换
		Swap(&a[right], &a[Maxi]);
		left++;
		right--;
	}
}

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

堆排序

排降序 建立小堆
排升序 建立大堆

升序

向上调整建堆,时间复杂度为O(N* longN)

使用向上调整算法建大堆,将数组建成大堆后,此时堆顶元素是最大的 ,将堆顶元素和最后一个元素进行交换,这样最大的元素就到了数组最后一个元素,对剩下的元素使用向下调整 , 当下一次向下调整时,我们不管这个处在数组最后一个位置的最大元素(有点类似堆的删除 ),此时第二大的元素来到的堆顶 ,堆顶元素继续与最后一个元素进行交换,(注意第一个交换过去的最大的元素已经不在范围内了) ,依次类推 ,升序就完成了

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

 void HeapSort(int* a, int n)
 {
	 //向上调整建堆
	 for (int i = 0; i < n; i++)
	 {
		 AdjustUp(a, i);
	}
	 //向下调整排序
	 int end = n - 1;// end 是最后一个元素的下标
	 while (end > 0)
	 {
		 Swap(&a[0], &a[end]);
		 AdjustDown(a, end, 0);
		 end--;
	 }
 }
 int main()
 {
	 int a[10] = { 2, 1, 5, 7, 6, 8, 0, 9, 4, 3 };
	 HeapSort(a, 10);
	 return 0; 
 }

向下调整建堆的前提是左右子树都是堆 ,从倒数第一个非叶子节点开始倒着调整,如何找到倒数第一个非叶子节点?通过最后一个节点的父节点来找到 , 那为什么要找倒数第一个非叶子节点? 因为倒数第一个非叶子节点的左右子树都满足大堆或小堆的条件

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

 void HeapSort(int* a, int n)
 {
	 //向下调整建堆
	 for (int i = (n-1-1)/ 2; i >= 0; i--) // n-1是最后一个节点的下标,(n-1-1)/2 通过下标找到最后一个节点的父节点
	 {
		 AdjustDown(a,n , i);
	 }
	 //向下调整排序
	 int end = n - 1; //end 是最后一个元素的下标 
	 while (end >=0)
	 {
		 Swap(&a[0], &a[end]);
		 AdjustDown(a, end, 0);
		 end--;
	 }

 }
 int main()
 {
	 int a[10] = { 2, 1, 5, 7, 6, 8, 0, 9, 4, 3 };
	 HeapSort(a, 10);
	 return 0; 
 }

交换排序

冒泡排序

以升序为例

冒泡排序,该排序的命名非常形象,即一个个将气泡冒出。冒泡排序一趟冒出一个最大(或最小)值
如果前一位的数字大于后一位的,那么这两个数字交换位置,因此,最大的数字在第一轮循环中不断像一个气泡一样向上冒

在这里插入图片描述

动图演示:
在这里插入图片描述
代码实现 :

void BubbleSort(int* a, int n)
{
	for (int i = 0; i < n-1; i++)
	{
		//一趟 
		for (int j = 0; j < n-1 - i; j++)
		{
			assert(j < n-1);
			if (a[j] > a[j + 1])
			{
				assert(j + 1 < n);
				Swap(&a[j], &a[j + 1]);
				
			}

		}
	}
}

冒泡排序:
时间复杂度 :O(N^2)

快速排序

递归

hoare版本

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

单趟排序 : 一趟下来的结果是让Key左边的数据全部都小于Key,Key右边的数据全部都大于Key, 就相当于Key这个基准值被排好序了,快速排序的单趟排序本质上就是在排一个数的顺序

步骤:

1 选最左边的或者最右边的当作基准值Key
2 定义一个Left和一个Right,Right从右向左走, 若Right遇到小于Keyi的数,则停下,Left从左向右开始走,直到Left遇到一个大于Keyi的数时,将Left和Right的内容交换 ,Right再次开始走,如此进行下去,直到Left和Right最终相遇,此时将相遇点的内容与key交换即可
需要注意的是:若选择最左边的数据作为基准值Key,则需要Right先走;若选择最右边的基准值数据作为基准值Key,则需要Left先走

注意:Left 和Right相遇位置一定比Key小

3 然后我们再将Key的左序列和右序列再次进行这种单趟排序,如此反复操作下去 ,其实就是一个递归

40a86189240a9f74809.png)

如果选择最左边的数据作为基准值Key ,为什么不能Left先走呢?

在这里插入图片描述

在这里插入图片描述

快排hoare版本 单趟动图演示:
在这里插入图片描述
代码实现:

void QuickSort(int* a, int left , int right ) // Hoare 版本 
{
	int begin = left;
	int end = right;
	
	
	//升序 
	  // 从左向右走 , 从右向左走 
	int Keyi = left; // 最左边为基准值 
	if (left >= right)
		return;   


	while (left <right )
	{
		//单趟排序 
		
		 //右边找小 
		while (right > left && a[right] >= a[Keyi])
		{

			right--;
			assert(right > 0);
		}
		 //左边找大
		while (left < right && a[left] <= a[Keyi])
		{
			left++;
			assert(left <= right);
	     }
		Swap(&a[left], &a[right]);
		
	}
	Swap(&a[Keyi], &a[left]); //Left和Right最终相遇,此时将相遇点的内容与key交换即可

	//递归 类似二叉树的前序遍历 
	  // [begin , Keyi-1 ]  Keyi  [Keyi+1 , end] 
	
	Keyi = left;
	assert(Keyi - 1 >= 0);

 QuickSort( a,  begin, Keyi -1);
 QuickSort(a, Keyi+1, end );

}

快排时间复杂度 :
如果每趟排序所选的Key都正好是该序列的中间值,即单趟排序结束后Key位于序列正中间,那么快速排序的时间复杂度就是O(NlogN)

挖坑法

以升序为例

单趟排序:一趟下来的结果是让Key左边的数据全部都小于Key,Key右边的数据全部都大于Key

步骤(在最左边挖坑为例) :

1 选出最左边或者最右边的一个数 ,存放在Key变量中,并且在该数据位置形成一个坑

2 定义一个Left和一个Right,Left从左向右走,找比Key大的数 ,Right从右向左走,找比Key小的数。(注意: 如果是在最左边挖坑,则需要Right先走;如果是在最右边挖坑,则需要Left先走

3 因为是以最左边挖坑为例 ,所以是Right 先走 , 在Left和Right 遍历的过程中,如果Right遇到小于Key的数,则将该数放到最左边的坑位,并在Right当前位置形成新的坑位,这时Left再向右边走,若遇到大于Key的数,则将其抛入到刚才在Right位置形成的一个坑位,然后在Left 当前位置形成一个坑位,如此循环下去,直到最终Left和Right相遇,而且Left和Right相遇一定相遇在坑位处 ,这时将Key填在坑位即可。

Key的左序列和右序列再次进行这种单趟排序,如此反复操作下去,直到左右序列只有一个数据,或是左右序列不存在时,便停止操作

单趟动图演示:
在这里插入图片描述
代码实现:

//快排挖坑法
void QuickSort(int* a, int left, int right)
{
			int begin = left;
		    int end = right;
			if (left >= right)
				return;   

	 // 升序	,最左边挖坑 ,右边先走
	//选最左边为 Key,并形成hole 
	int hole=left;
	
	int Key = a[left];


	while (left < right)
	{
		//单趟排序 
		
		//right 找小 
		while (left < right && a[right] > Key)
		{
			right--;
		}
		//找到比Key小的数 ,放进最左边的坑位 ,并在right当前位置形成新的坑位
		a[hole] = a[right];
		hole = right;
		assert(hole >= 0);

		//left 找 大  
		while (right > left && a[left] < Key)
		{
			left++;
		}
		// 找到比Key大的数 ,放进right当时形成的坑位 ,并在Left当前位置形成新的坑位 
		a[hole] = a[left];
		hole = left;
		assert(hole >= 0);

	}

	//直到最终Left和Right相遇,这时将Key填在坑位即可。
	assert(hole >= 0);
	a[hole] = Key;
	//递归 

	
		//递归 类似二叉树的前序遍历 
	  // [begin , Key-1 ]  Key  [Key+1 , end] 
	
	Key = left;
	assert(Key - 1 >= 0);

 QuickSort( a,  begin, Key -1);
 QuickSort(a, Key+1, end );

}

前后指针版本

以升序为例

单趟排序:一趟下来的结果是让Key左边的数据全部都小于Key,Key右边的数据全部都大于Key

步骤(以最左边为Key 为例) :

1 选最左边的或者最右边的当作基准值Key
2 定义一个prev 和 cur ,prev指针指向序列开头 ,prev 从左往右遍历 ,cur指针指向prev+1, cur从左往右遍历 。
3、若cur指向的内容小于Key,则prev先++,然后交换prev和cur的值,然后cur++;若cur的值大于key,则cur指针直接++。如此进行下去,直到cur指针越界,此时将Key和prev指针指向的内容交换即可。

然后我们再将Key的左序列和右序列再次进行这种单趟排序,如此反复操作下去 ,其实就是一个递归

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

代码实现:

int  PartSort2(int* a, int left, int right)  //前后指针法
{

	int begin = left;
	int end = right;
	//升序 
	// 单趟排序
	int prev = left;
	int cur = prev + 1;

	int Keyi = left; //选最左边为Key 

	//cur 找比Key小的 
	while (cur <= right)  //cur未越界就继续 
	{
		//如果cur找到比Key小 ,往后++
		if (a[cur] < a[Keyi] && ++prev != cur) //++prev !=cur , 有可能数组前几个元素比Key小,但是没必要交换
		{
			Swap(&a[cur], &a[prev]); //交换 
		 }
		cur++;  
	}
	assert(cur <= right+1); 
	Swap(&a[prev], &a[Keyi]);
	
	/*递归 类似二叉树的前序遍历 
   [begin , Keyi-1 ]  Keyi  [Keyi+1 , end] */

	Keyi = prev;
	assert(Keyi - 1 >= 0);


	
	return prev;
}
void QuickSort(int* a, int left, int right)
{
   //递归 
	if (left >= right)
		return;

	int keyi = PartSort2(a, left, right);


	QuickSort(a, left, keyi - 1);
	QuickSort(a, keyi + 1, right);
}

三数取中法选key

如果这个序列是非常无序,快速排序的效率是非常高的 ,一旦序列有序 ,每次选取最左边或是最右边的数作为基准值Key,时间复杂度就会从O(N*logN)变为O(N^2),这样快排的效率极低

在这里插入图片描述

也就是说影响快排时间复杂度就是基准值Key的选取,如果选取的Key离中间位置越近,则效率越高

为了解决这个问题 ,也就出现了三数取中 :
三数取中,当中的三个数指的是:最左边的数、最右边的数以及中间位置的数
三数取中就是取这三个数当中,值的大小居中的那个数作为该趟排序的Key ,因为它离中间位置最近。这就确保了我们所选取的数不会是序列中的最左边的数、最右边的数

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

//三数取中  ,得到中间元素的下标
int GetMiddleIndexi(int* a, int left, int right)
{
	int mid = (left + right) / 2;
	if (a[left] < a[mid])
	{
		if (a[right] > a[mid])
		{
			return mid;// a[right] > a[mid] > a[left]
		}
		// a[mid]>=a[right] ,此时a[mid]是最大的 ,只需要比较a[left] ,a[right] 
		else if (a[left] > a[right])// a[mid] > a[left] > a[right] 
		{
			return left;
		}
		else //a[left] < a[right]
		{
			return right; // a[mid] > a[right] > a[left]
		}

	}
	else //a[left] >= a[mid]
	{
		if (a[mid] > a[right])
		{
			return mid;
		 }
		//a[mid] <= a[right]  ,此时a[mid]已经是最小的 只需要比较a[left] 和a[right]
		else if (a[left] > a[right]) //  a[left] > a[right] > a[mid] 
		{
			return right; 
		}
		else // a[left] <= a[right]
		{
			return left;   //a[right] > a[left] > a[mid ]
		}
	}

}

递归到小的子区间时,可以考虑使用插入排序

归并排序

归并排序是采用分治法的一个非常典型的应用。其基本思想是:将已有序的子序合并,从而得到完全有序的序列,即先使每个子序有序,再使子序列段间有序
在这里插入图片描述

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

递归实现

核心步骤:

分解得到子序列
如何得到有序的子序列 ? 序列分解到只有一个元素或是没有元素时,就可以认为是有序了
然后合并两个有序的子序列 ,思路与Leetcode. 88合并两个有序数组 相似 ,依次比较 ,较小值尾插到tmp数组中
创建一个与待排序列大小相同的tmp数组 ,合并完毕后再将tmp数组里的数据拷贝回原数组 ,最后将tmp 数组释放 。
代码实现 :

void _MergeSort(int* a, int left, int right,  int * tmp)
{
	int mid = (left + right) / 2;

	//分割
	 // [begin , mid]  [mid+1 , end ]
	int begin = left;
	int end = right; 
	if (begin >= end)//序列分解到只有一个元素或是没有元素时,就可以认为是有序了
		return;

	_MergeSort(a, begin, mid, tmp); //end-begin+1 是元素个数 
	_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;  //不能取0 , 两段有序区间不一定是从头开始的区间 
	
	while (begin1 <= end1 && begin2 <=end2 ) 
	{
		if (a[begin1] < a[begin2])
		{
			tmp[i++] = a[begin1++];
		}
		else
		{
			tmp[i++] = a[begin2++];

		}
	}
	//begin1 先走完 ,begin2还没有走完,begin2尾插到tmp数组后面
	while (begin2 <= end2 )
	{
		tmp[i++] = a[begin2++];
	}
	//begin2 先走完 ,begin1还没有走完, begin1尾插到tmp数组后面 
	while (begin1 <= end1)
	{
		tmp[i++] = a[begin1++];
	}
	//将tmp数组拷贝到原数组中 
	memcpy(a+begin, tmp+begin, sizeof(int*) * ( right- left +1));
}
void MergeSort(int* a , int left , int right , int *tmp  )
{
	//申请一个与原数组大小的tmp数组 
   tmp = (int*)malloc(sizeof(int) *  (right -left +1 ) );
	
	//归并
	
	_MergeSort( a,  left,  right , tmp);

		//释放tmp 数组 
	free(tmp);

}

tmp 和 a的后面为什么都要加begin ?
如图所示:
在这里插入图片描述

非递归实现

核心:控制每次参与合并的元素个数,就能使序列变为有序

在这里插入图片描述

如果排序的数是奇数,就会出现一些越界的情况需要特殊处理
end1越界不需要归并
在这里插入图片描述

begin2 越界不需要归并
在这里插入图片描述

end2 越界,需要归并,修正end2
在这里插入图片描述

void MergeSort(int* a,int  n )
{
	int* tmp = (int*)malloc(sizeof(int) * (n ) );
	if (tmp == NULL)
	{
		printf("malloc fail");
		exit(-1);
	}
	int gap = 1;
	
	while (gap<n)
	{
		//归并
		for (int i = 0; i < n; i += 2 * gap)
		{
			//归并 ——类似合并两个有序数组 ,[begin , end1]  [begin2 , end2 ]
			int begin1 = i, end1 = i + gap - 1;
			int begin2 = i + gap - 1 + 1, end2 =i+ 2 * gap - 1;  //从图中分析可得

			int index= i;
			printf("[%d %d] [%d %d] ", begin1, end1, begin2, end2);
			/*end1 和begin2越界*/
			if (end1 >=n || begin2 >=n)
			{
				break;
			}
			//end2越界,修正
			if (end2 >=n)
			{
				end2 = n - 1;
			}
			while (begin1 <= end1 && begin2 <= end2)
			{
				if (a[begin1] < a[begin2])
				{
					tmp[index++] = a[begin1++];
				}
				else
				{
					tmp[index++] = a[begin2++];

				}
			}
			//begin1 先走完 ,begin2还没有走完,begin2尾插到tmp数组后面
			while (begin2 <= end2)
			{
				tmp[index++] = a[begin2++];
			}
			//begin2 先走完 ,begin1还没有走完, begin1尾插到tmp数组后面 
			while (begin1 <= end1)
			{
				tmp[index++] = a[begin1++];
			}
			//将tmp数组拷贝到原数组中 
			memcpy(a + i, tmp + i, sizeof(int) * (end2 - i + 1));
		}
		
		gap *= 2;
	}
	free(tmp);
}

归并排序时间复杂度:

每一层归并排序的消耗是O(N) ,有 log层 , 所以归并排序的时间复杂度 O(N * logN)

排序算法复杂度以及稳定性

在这里插入图片描述
在这里插入图片描述

稳定的排序有:直接插入排序、冒泡排序、归并排序
不稳定的排序有:希尔排序、选择排序、堆排序、快速排序、计数排序

在这里插入图片描述

如果你觉得这篇文章对你有帮助,不妨动动手指给点赞收藏加转发,给鄃鳕一个大大的关注
你们的每一次支持都将转化为我前进的动力!!!

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

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

相关文章

计算机操作系统学习-引论

本专栏是对计算机操作系统学习的记录&#xff1a;《现代操作系统 第四版》&#xff0c;电子版的可以在评论区自取。 1 计算机硬件简介 操作系统与运行该操作系统的计算机硬件密切相关。如图1所示&#xff0c;我们可以将自己的计算机抽象为&#xff0c;CUP&#xff0c;内存和I/…

【数学建模】Day01——层次分析法

文章目录 1. 引出层次分析法1.1 思考问题1.2 平台借力1.3 分而治之的思想1.4 一致矩阵1.5 一致性检验1.6 一致矩阵计算权重1.7 判断矩阵求权重 2. 层次分析法2.1 定义2.2 具体步骤2.3 局限性 1. 引出层次分析法 1.1 思考问题 我们评价的目标是什么&#xff1f;我们为了达到这…

C语言:指针详解【进阶】后篇

目录 函数指针函数指针数组指向函数指针数组的指针回调函数 前言&#xff1a; 在C语言&#xff1a;指针详解【进阶】前篇中我们深入学习了字符指针&#xff0c;数组指针&#xff0c;指针数组以及数组传参和指针传参。我们对指针的应用有了较为深刻的认识&#xff0c;今天这里我…

BusterNet网络Python模型实现学习笔记之二

文章目录 一、squeeze函数的用法二、nn.CrossEntropyLoss函数三、isinstance函数四、定义冻结层 freeze_layers五、SummaryWriter 基础用法六、Python 基础语法1.变量嵌入到字符串2. enumerate() 函数3. 进度条库tqdm4. 字典&#xff08;dict&#xff09;展开为关键字参数&…

TAPFixer总结

相关工作 Menshen 检测属性用户写 et al检测属性就简单三个 未来工作&#xff1a; liveness; implicit; 数据集&#xff1b; 抽象方式合并&#xff1b;抽象规则配置&#xff1b;缓解谓词爆炸&#xff1b;concurrency的说明; 代码简化工作&#xff1b;给出能修复的漏洞种类 …

《基于光电容积法和机器学习的冠状动脉疾病患者出血风险预测》阅读笔记

目录 一、论文摘要 二、论文十问 三、论文亮点与不足之处 四、与其他研究的比较 五、实际应用与影响 六、个人思考与启示 参考文献 一、论文摘要 在冠状动脉疾病&#xff08;CAD&#xff09;患者的抗血栓治疗过程中&#xff0c;出血事件是关注的主要焦点。本研究旨在探讨…

浅谈一下布隆过滤器的设计之美

1 缓存穿透 2 原理解析 3 Guava实现 4 Redisson实现 5 实战要点 6 总结 布隆过滤器是一个非常有用的数据结构。它可以在大规模数据中高效地判断某个元素是否存在。布隆过滤器的应用非常广泛&#xff0c;不仅在搜索引擎、防垃圾邮件等领域中经常用到&#xff0c;而且在许多…

R语言单因素方差分析

R中的方差分析 介绍用于比较独立组的不同类型的方差分析&#xff0c;包括&#xff1a; 单因素方差分析&#xff1a;独立样本 t 检验的扩展&#xff0c;用于在存在两个以上组的情况下比较均值。这是方差分析检验的最简单情况&#xff0c;其中数据仅根据一个分组变量&#xff0…

【数据结构】七大排序总结

目录 &#x1f33e;前言 &#x1f33e; 内部排序 &#x1f308;1. 直接插入排序 &#x1f308;2. 希尔排序 &#x1f308;3. 直接选择排序 &#x1f308;4. 堆排序 &#x1f308;5. 归并排序 &#x1f308;6. 冒泡排序 &#x1f308;7. 快速排序 &#x1f33e;外部排序 &…

4 月份 火火火火 的开源项目

盘点 4 月份 GitHub 上 Star 攀升最多的开源项目&#xff0c;整个 4 月份最火项目 90% 都是 AI 项目&#xff08;准确的说&#xff0c;最近半年的热榜都是 AI 项目&#xff09; 本期推荐开源项目目录&#xff1a; 1. AI 生成逼真语音 2. 复旦大模型 MOSS&#xff01; 3. 让画中…

万万没想到在生产环境翻车了,之前以为很熟悉 CountDownLatch

前言 需求背景 具体实现 解决方案 总结 前言 之前我们分享了CountDownLatch的使用。这是一个用来控制并发流程的同步工具&#xff0c;主要作用是为了等待多个线程同时完成任务后&#xff0c;在进行主线程任务。然而&#xff0c;在生产环境中&#xff0c;我们万万没想到会…

【LeetCode】583. 两个字符串的删除操作

583. 两个字符串的删除操作&#xff08;中等&#xff09; 思路 这道题的状态定义和 1143. 最长公共子序列 相同&#xff0c;「定义一个 dp 数组&#xff0c;其中 dp[i]表示到位置 i 为止的子序列性质&#xff0c;并不是必须以 i 结尾」&#xff0c;此时 dp 数组的最后一位即为…

富士康终于醒悟了,重新加码中国制造,印度制造信不过

4月25日富士康在郑州揭牌新事业总部&#xff0c;显示出在扰攘了数年之后&#xff0c;富士康再度加强郑州富士康的发展力度&#xff0c;这应该是富士康在印度努力数年之后终于清醒了&#xff0c;印度制造终究不如中国制造可靠。 一、苹果和富士康在印度发展的教训 这两年苹果和富…

智能算法系列之基于粒子群优化的模拟退火算法

文章目录 前言1. 算法结合思路2. 问题场景2.1 Sphere2.2 Himmelblau2.3 Ackley2.4 函数可视化 3. 算法实现代码仓库&#xff1a;IALib[GitHub] 前言 本篇是智能算法(Python复现)专栏的第四篇文章&#xff0c;主要介绍粒子群优化算法与模拟退火算法的结合&#xff0c;以弥补各自…

【unity项目实战】3DRPG游戏开发07——其他详细的设计

敌人动画设计 新增图层动画,把权重设为1 在新图层默认新建一个空状态Base State,实现怪物默认动画播放Base State,因为Base State是空动画,所以默认会找上一个层的动画,这样就实现了两个图层动画的切换,也可以选择修改权重的方式实现 敌人随机巡逻 显示敌人巡逻的范…

网络字节序和主机字节序详解(附代码)

一、网络字节序和主机字节序 网络字节序和主机字节序是计算机网络中常用的两种数据存储格式。 主机字节序&#xff1a; 指的是在计算机内部存储数据时采用的字节排序方式。对于一个长为4个字节的整数&#xff0c;若采用大端字节序&#xff0c;则该整数在内存中的存储顺序是&a…

AppScan-被动手动扫描

被动扫描是针对性的扫描&#xff0c;浏览器代理到AppScan&#xff0c;然后进行手工操作&#xff0c;探索产生出的流量给AppScan进行扫描。这样可以使得扫描足够精准&#xff0c;覆盖率更加高&#xff0c;还能减少不必要的干扰 &#xff08;一&#xff09;环境准备 1、火狐安装…

SAP UI5 之Controls (控件) 笔记三

文章目录 官网 Walkthrough学习-Controls控件1.0.1 在index.html中使用class id 属性控制页面展示的属性1.0.2 我们在index.js文件中引入 text文本控制1.0.3打开浏览器查看结果 官网 Walkthrough学习-Controls控件 Controls控件 在前面展示在浏览器中的Hello World 是在Html …

Presto 之Hash Join的Partition

一. 前言 在Presto中&#xff0c;当两表Join为Hash Join并且join_distribution_type为PARTITIONED的时候&#xff0c;Presto会将Build表分区&#xff08;Partition&#xff09;后再进行Join操作。在Presto中的Join操作中&#xff0c;对表的分区有两级&#xff0c;第一级是将Has…

超简单搭建一个自用的ChatGPT网站(支持给网站添加访问密码)

前言&#xff1a; 有小伙伴留言想在自己的服务器搭建上图所示的ChatGPT网站&#xff0c;那么今天就是教大家如何在自己的服务器搭建像上图所示的ChatGPT网站 准备条件&#xff1a; 1&#xff09;一台服务器(这里用centos7) 2&#xff09;ChatGPT的API-KEY 一、Docker环境部署…