常用排序算法(下)

news2025/1/29 13:56:01

目录

2.5 冒泡排序

2.6 快速排序

2.6 1 快速排序思路

详细步骤

2.6 2 快速排序递归实现 

2.6 3快速排序非递归:

快排非递归的优势

 非递归思路

1. 初始化栈

2. 将整个数组的起始和结束索引入栈

3. 循环处理栈中的子数组边界

4. 单趟排序

5. 处理分区后的子数组

6. 重复步骤3和步骤5

注意事项

2.7 归并排序

基本思想:

一、算法步骤

二、具体实现

归并排序递归实现:

归并排序非递归实现: 

三、性能分析

2.8 计数排序

计数排序概念:

一、计数排序的原理与步骤

二、计数排序的示例

 计数排序代码实现:

三、注意事项

四、计数排序的特点


2.5 冒泡排序

   基本思路

    冒泡排序的基本思路是通过重复遍历待排序的数列,比较每对相邻元素的值,若发现顺序错误则交换它们的位置。这个过程重复进行,直到没有需要交换的元素为止,此时数列就完成了排序。冒泡排序的名字是因为较小的元素会逐渐“冒泡”到数列的顶端,即前面,而较大的元素则会沉到数列的底部,即后面。
冒泡排序的基本步骤如下:
(1)比较相邻的元素。如果第一个比第二个大,就交换它们两个。
(2)对每一对相邻元素做同样的工作,从开始第一对到结尾的最后一对。这步做完后,最后的元素会是最大的数。
(3)针对所有的元素重复以上的步骤,除了最后一个。
(4)持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。
冒泡排序的时间复杂度在最坏情况下是O(n^2),其中n是数列的长度。尽管冒泡排序不是最高效的排序算法,但它的实现简单,对于小数据量的排序问题还是相当实用的。

  动图展示:

代码实现:

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

 问:程序中的flag的作用是什么?

答:冒泡排序时间复杂度是O(N^2),但是这个时间复杂度是由这个算法的最坏情况决定的,也就是当我们要排升序时,我们给了一个降序的数组,这时这个程序的时间复杂度就是O(N^2),而在这个组相对有序的情况下 ,我们可以使用一些手段来提高这个算法的效率,而这个flag就是我们优化这个算法的关键。

   我们知道,如果一个相对有序甚至有序组因该是比无序的数组排序的速度更快的,但是,我们需要判断何时有序,像这个程序中我们使用了两个for循环,如果不判断何时有序然后结束程序的话,它就会完完整整地将这两个循环走完,也就是说,无论是有序还是无序的一组数来排序它的时间复杂度都是O(N^2)。这时我们就可以定义一个flag变量,将它初始化为0,如果里面有数排序了,说明这个数组是无序的,将flag置为1,下一次循环又将flag置为0,我们在下面判断,如果flag为0的话,就说明这个数组没有元素交换过,也就是说这个数组是有序的,既然它是有序的,我们就马上结束这个程序,这样优化我们的冒泡排序就不会每次进入它的时间复杂度都是O(N^2)了,如果这个数组是有序的,那么只会遍历一遍数组就结束程序,此时它的速度是0(N)。

2.6 快速排序

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

2.6 1 快速排序思路

快速排序(Quick Sort)是一种高效的排序算法,它使用分治策略来对一个数组进行排序。以下是快速排序的基本思路:

  1. 选择基准
    从数组中选择一个元素作为基准(key)。这个基准可以是数组中的第一个元素、最后一个元素、中间元素,或者通过某种方式随机选择的元素。

  2. 分区
    重新排列数组,使得所有小于基准的元素都位于基准的左侧,所有大于基准的元素都位于基准的右侧。这个操作称为分区(partition)。

  3. 递归排序
    对基准左侧和右侧的子数组分别进行快速排序。这是一个递归的过程,直到子数组的大小为零或一(此时子数组已经是有序的)。

  4. 合并
    由于快速排序是原地排序,且通过递归已经保证了每个子数组的有序性,所以最终整个数组也会是有序的,不需要额外的合并步骤。

详细步骤

  1. 初始化

    • 设定数组的起始位置 left和结束位置 right
  2. 选择基准

    • 选择数组中的一个元素作为基准,通常选择 array[right](也可以选择其他元素)。
  3. 分区

    • 初始化一个指针 i,使其指向数组的起始位置 low
    • 遍历数组,从 left 到 right-1
      • 如果 array[i] <= key,则继续向右移动 i
      • 如果 array[i] > key,则交换 array[i] 和 array[righth-1](或者任何一个大于key 的未处理元素的位置),并将 right-1 向左移动一位(因为该位置已经处理过)。
    • 最后,将基准元素key 放到正确的位置上,即 array[i] 和 array[right] 之间(通常是通过交换 array[i] 和 array[right] 来完成)。
  4. 递归排序

    • 对基准左侧的子数组(left到 i-1)进行快速排序。
    • 对基准右侧的子数组(i+1 到 right)进行快速排序。
  5. 结束条件

    • 当子数组的大小为零或一时,递归结束,数组已经有序。

2.6 2 快速排序递归实现 

  到现在为止,我们已经了解了快速排序的大致思路,先来看递归实现的快速排序:

void Swap(int* p1, int* p2)
{
	int tmp = *p1;
	*p1 = *p2;
	*p2 = tmp;
}
int GetMiddle(int* a, int left, int right)
{
	int mid = left + right + 1;
	if (a[left] < a[mid])
	{
		if (a[right] > a[mid])
		{
			return mid;
		}
		else if (a[right] > a[left])
		{
			return right;
		}
		else
		{
			return left;
		}
	}
	else //a[left]>a[mid]
	{
		if (a[right] > a[left])
		{
			return left;
		}
		else if (a[right] > a[mid])
		{
			return right;
		}
		else
		{
			return mid;
		}
	}
}

void QuickSort(int* a, int left, int right)
{
	if (left >= right)
	{
		return;
	}
//当left大于或等于right时,说明数组已经有序

	if ((left + right + 1) < 10)
	{
		InsertSort(a + left, left + right + 1);
//假设快排递归将被排序数组分散成了
//一棵二叉树,我们知道二叉树的最后一层
//占了这棵树的大半结点,当左右加起来
//的数据少于10时,我们可以使用
//插入排序提高它的效率
	}
	else
	{
		//三数取中
		int mid = GetMiddle(a, left, right);
		Swap(&a[left], &a[mid]);
		int keyi = left;
		int begin = left, end = right;

		while (begin < end)
		{
			while (begin < end && a[end] >= a[keyi])
			{
				end--;
			}

			while (begin < end && a[begin] <= a[keyi])
			{
				begin++;
			}
			Swap(&a[begin], &a[end]);
		}

		Swap(&a[keyi], &a[begin]);
		keyi = begin;

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

使用一组无序的数来测试一下:

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

注意:此程序中的插入排序和三数取中算法都是为了优化快速排序,使它拥有更好的效率,快速排序的核心逻辑代码为:

void Swap(int* p1, int* p2)
{
	int tmp = *p1;
	*p1 = *p2;
	*p2 = tmp;
}
void QuickSort(int* a, int left, int right)
{
	if (left >= right)
	{
		return;
	}
	//当left大于或等于right时,说明数组已经有序
	int keyi = left;
	int begin = left, end = right;

	while (begin < end)
	{
		while (begin < end && a[end] >= a[keyi])
		{
			end--;
		}

		while (begin < end && a[begin] <= a[keyi])
		{
			begin++;
		}
		Swap(&a[begin], &a[end]);
	}

	Swap(&a[keyi], &a[begin]);
	keyi = begin;

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

2.6 3快速排序非递归:

  看到这里,小伙伴们不禁疑惑:我们既然可以使用递归实现快速排序,为什么还要使用非递归实现呢?

快排非递归的优势

快速排序非递归实现的重要性主要体现在以下几个方面:

  1. 避免递归调用开销:递归实现虽然直观易懂,但在一些编程语言中,递归调用会引入额外的函数调用栈的使用和维护开销。非递归实现通过显式地使用栈来管理状态,可以有效避免这些开销,从而提高性能。

  2. 防止栈溢出:在极端情况下,递归实现可能导致栈溢出,尤其是在处理大规模数据时。非递归实现使用显式的数据结构(如栈)来管理状态,不依赖于系统的调用栈,从而避免了栈溢出的风险。

  3. 降低空间复杂度:递归实现可能需要更多的内存空间,因为每个递归调用都需要在内存中保留一些信息。非递归实现通常使用更少的内存,只需维护一些必要的状态信息,有助于减少内存使用。

  4. 提升性能:在某些编程语言和环境中,递归调用的性能可能不如循环,因为每个递归调用都需要函数调用的开销。非递归实现可以更好地与一些编译器和优化器协同工作,从而提高性能。

  5. 便于优化:非递归实现更容易进行一些优化,例如通过使用迭代而不是递归的方式来访问数组,以更好地利用CPU缓存。这种优化可以进一步提升算法的执行效率。

  6. 适应特定场景:在一些对递归深度有限制的环境(如嵌入式系统)中,非递归实现是必须的,以确保算法能够正常运行。

 非递归思路

快速排序的非递归思路主要是通过手动管理一个栈来模拟递归过程中的函数调用栈,从而实现对数组的排序。以下是快速排序非递归思路的详细步骤:

1. 初始化栈

  • 创建一个空栈,用于保存接下来需要排序的子数组的边界。这个栈可以是任意类型的栈结构,但通常使用整型栈来保存子数组的起始索引(left)和结束索引(right)。

2. 将整个数组的起始和结束索引入栈

  • 这一步相当于递归排序中的初始调用。将数组的起始索引(left)和结束索引(right)作为一对入栈,表示接下来需要处理整个数组。

3. 循环处理栈中的子数组边界

  • 不断从栈中弹出子数组的边界索引(一对),然后对这个子数组进行快速排序的单趟排序。

4. 单趟排序

  • 在单趟排序中,首先选择一个元素作为基准(pivot)。基准的选择可以是子数组的第一个元素,也可以通过其他策略(如三数取中法)来选择。
  • 进行分区操作,将子数组划分为比基准小的左侧部分和比基准大的右侧部分,并确定基准元素的最终位置。

5. 处理分区后的子数组

  • 分区操作完成后,基准元素左侧的子数组(如果存在)和右侧的子数组(如果存在)可能还需要继续排序。
  • 如果左侧子数组有多个元素,则将其起始和结束索引作为一对入栈。
  • 如果右侧子数组有多个元素,也将其起始和结束索引作为一对入栈。

6. 重复步骤3和步骤5

  • 继续迭代该过程,直到栈为空。此时,所有的子数组都已经被正确排序,整个数组也就完成了排序。

注意事项

  • 在单趟排序中,分区操作是关键步骤,它决定了基准元素的最终位置,并将数组划分为两个子数组。
  • 栈的使用是非递归快速排序的核心,它模拟了递归调用栈的功能,允许我们手动管理子数组的排序顺序。
  • 非递归快速排序的性能与递归快速排序相近,但在某些情况下(如递归深度过大导致栈溢出)可能更加稳定可靠。

总结:通过以上步骤,我们可以实现快速排序的非递归版本,从而在处理大规模数据时避免递归调用栈的限制。

由于c语言没有STL,我们使用c++来实现:

int GetMiddle(int* a, int left, int right)
{
	int mid = left + right + 1;
	if (a[left] < a[mid])
	{
		if (a[right] > a[mid])
		{
			return mid;
		}
		else if (a[right] > a[left])
		{
			return right;
		}
		else
		{
			return left;
		}
	}
	else //a[left]>a[mid]
	{
		if (a[right] > a[left])
		{
			return left;
		}
		else if (a[right] > a[mid])
		{
			return right;
		}
		else
		{
			return mid;
		}
	}
}
int PartSort1(int* a, int left, int right)
{
	// 三数取中
	int midi = GetMiddle(a, left, right);
	Swap(&a[left], &a[midi]);

	int keyi = left;
	int begin = left, end = right;
	while (begin < end)
	{
		// 右边找小
		while (begin < end && a[end] >= a[keyi])
		{
			--end;
		}

		// 左边找大
		while (begin < end && a[begin] <= a[keyi])
		{
			++begin;
		}

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

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

//非递归
void QuickSortNonR(int* a, int left, int right)
{
	stack<int> st;
	st.push(right);
	st.push(left);

	while (!st.empty())
	{
		int begin = st.top();
		st.pop();
		int end = st.top();
		st.pop();

		int keyi = PartSort1(a, begin, end);

		if (keyi+1 < end)
		{
			st.push(end);
			st.push(keyi+1);
		}

		if (begin < keyi - 1)
		{
			st.push(keyi - 1);
			st.push(begin);
		}
	}
}

我们来测试一下:

可以看到我们的快排非递归也是成功地实现了。

快速排序的特性总结

1. 快速排序整体的综合性能和使用场景都是比较好的,所以才敢叫快速排序
2. 时间复杂度:O(N*logN)

3. 空间复杂度:O(logN)
4. 稳定性:不稳定 

总结:快速排序是一种非常高效的排序算法,尤其适用于大部分元素已经有序的情况。通过合理选择基准和优化递归过程,可以进一步提高其性能

2.7 归并排序

基本思想:

   归并排序(Merge Sort)是一种分治策略的排序算法,其基本思想是将一个序列分为两个较小的子序列,直到子序列的大小为1,然后将已排序的子序列合并成一个大的有序序列。以下是归并排序的完整思路:

一、算法步骤

  1. 分割

    • 将待排序的数组分成两半,如果数组长度为奇数,则其中一部分会比另一部分多一个元素。
    • 对这两部分数组继续递归地进行分割,直到子数组的长度为1,此时可以认为每个子数组都是有序的(因为只有一个元素)。
  2. 治理(解决)

    • 这一步在归并排序中实际上是通过分割步骤隐含完成的。当子数组的长度为1时,它们自然就是有序的,无需进一步处理。
  3. 合并

    • 将相邻的有序子数组合并成一个有序数组,直到合并为1个完整的数组。
    • 合并过程中,通过比较两个子数组的元素,按大小顺序依次放入新的数组中,从而实现排序。

二、具体实现

  1. 申请空间

    • 创建一个临时数组,用于存放合并后的子数组。
  2. 设定指针

    • 在两个待合并的子数组上分别设置指针,初始时都指向子数组的起始位置。
  3. 比较合并

    • 比较两个指针所指向的元素,将较小的元素放入临时数组,并移动该指针。
    • 重复此过程,直到某个子数组的所有元素都被复制到临时数组中。
    • 将另一个子数组中剩余的元素(如果有的话)直接复制到临时数组的末尾。
  4. 复制回原数组

    • 将临时数组中的元素复制回原数组,以替换原来的子数组。

归并排序递归实现:

void _MergeSort(int* a, int* tmp,int begin,int end)
{
	if (begin >= end)
	{
		return;
	}
	int mid = (begin + end) / 2;
	_MergeSort(a, tmp, begin, mid);
	_MergeSort(a, tmp, 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");
	}
	_MergeSort(a, tmp, 0, n - 1);

	free(tmp);
	tmp = NULL;

}

此程序涉及在堆上申请资源,所以我们分两个接口实现。

归并排序非递归实现: 

//归并排序非递归
void MergeSortNonR(int* a, int n)
{
	int* tmp = (int*)malloc(sizeof(int) * (n+1));
	if (tmp == NULL)
	{
		perror("malloc fail");
		return;
	}

	int gap = 1;

	while (gap <= n)
	{
		for (int j = 0; j <= n;j+=gap*2)
		{
			int begin1 = j, end1 = j + gap - 1;
			int begin2 = j + gap, end2 = j + gap * 2 - 1;
			if (begin2 > n )
			{
				break;
			}

			if (end2 > n)
			{
				end2 = n;
			}

			int i = j;
			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 + j, tmp + j, sizeof(int) * (end2 - j+1));
		}
		gap *= 2;
			
	}
	free(tmp);
	tmp == NULL;

}

给一组无序数,我们来测试一下:

int a[] = { 3,5,7,1,2,0,9,10,4,8,11 };

 

可以看到没有什么问题。

 

三、性能分析

  • 时间复杂度:归并排序的时间复杂度为O(n log n),其中n是数组的长度。在每一层递归中,合并操作的时间复杂度为O(n),而递归的层数为log n,因此总的时间复杂度为O(n log n)。
  • 空间复杂度:归并排序的空间复杂度也是O(n),因为需要额外的空间来创建临时数组。
  • 稳定性:归并排序是稳定的排序算法,即相等的元素在排序后的顺序与它们在原数组中的顺序相同。

总结:归并排序因其稳定的排序特性和较好的平均性能,在实际应用中非常广泛。特别是在数据量较大时,归并排序能够展现出其高效的排序能力。 

2.8 计数排序

计数排序概念:

     计数排序(Counting Sort)是一种非基于比较的排序算法,由Harold H. Seward在1954年提出。它适用于待排序元素为整数且范围较小的情况。计数排序的基本思想是统计每个元素的出现次数,然后利用这些信息将原始序列重新组合成有序序列。

一、计数排序的原理与步骤

计数排序的工作原理主要包括以下几个步骤:

  1. 统计元素出现次数

    • 遍历待排序的数组,统计每个元素出现的次数。这通常通过创建一个辅助数组(计数数组)来实现,该数组的长度等于待排序数组中元素的最大值加1(如果元素是非负整数)。
  2. 前缀和操作

    • 对计数数组进行前缀和操作,使得每个元素的值变为小于等于该元素的值的元素总数。这样,计数数组的每个位置就对应了原数组中元素在排序后数组中的位置。
  3. 重建有序数组

    • 遍历待排序数组,根据计数数组的信息,将元素放回其在有序数组中的正确位置。同时,更新计数数组中对应元素的值,以确保相同元素的相对顺序不变。

二、计数排序的示例

假设我们有一个待排序的数组arr = [4, 2, 2, 8, 3, 3, 1],我们可以按照计数排序的步骤对其进行排序:

  1. 统计元素出现次数

    • 遍历数组arr,得到计数数组count = [0, 1, 2, 2, 2, 0, 1, 1](假设数组中的元素都是非负整数,且最大值不超过7)。
  2. 前缀和操作

    • 对计数数组进行前缀和操作,得到count = [0, 1, 3, 5, 7, 7, 7, 8]。
  3. 重建有序数组

    • 遍历数组arr,根据计数数组的信息将元素放回有序数组中的正确位置。排序后的数组为sorted_arr = [1, 2, 2, 3, 3, 4, 8]。
 计数排序代码实现:
void CountSort(int* a, int n)
{
	int max = a[0], min = a[0];
	for (int i = 0; i < n; i++)
	{
		if (a[i] > max)
		{
			max = a[i];
		}

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

	int range = max - min + 1;
	int* count = (int*)calloc(range, sizeof(int));
	if (count == NULL)
	{
		perror("calloc fail");
	}

	for (int i = 0; i <n; i++)
	{
		count[a[i] - min]++;
	}

	int j = 0;

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

}

给一组无序数测试一下:

int a[] = { 2,4,76,87654,98,12,76,454,23,98 };

 

    虽然我们的排序成功了,但是这个程序内部开辟了非常多的空间,所以当一组数的最大值和最小值相差太多时,不建议使用计数排序

三、注意事项

  • 在使用计数排序时,需要确保待排序数组中的元素都是非负整数或可映射到非负整数范围内。如果待排序的元素不满足这个要求,就需要对其进行映射转换。
  • 当待排序元素的范围非常大时,计数排序可能会消耗大量的内存空间,因此在实际应用中需要注意这一点。

四、计数排序的特点

  1. 时间复杂度

    • 计数排序的时间复杂度为O(n+k),其中n是待排序数组的长度,k是待排序数组中元素的范围。当k不是很大时,计数排序的时间复杂度接近线性,快于任何基于比较的排序算法(如快速排序、归并排序等,它们的时间复杂度在理论上的下限是O(n log n))。
  2. 空间复杂度

    • 计数排序的空间复杂度为O(k),其中k是待排序数组中元素的范围。因此,计数排序是一种牺牲空间换取时间的排序算法。
  3. 稳定性

    • 计数排序是稳定的排序算法,即相等的元素在排序后的序列中保持它们原有的先后顺序。
  4. 适用场景

    • 计数排序特别适用于整数排序,特别是当整数的范围不是很大时。对于非整数类型的数据或整数范围非常大的情况,计数排序可能不适用或效率较低。

 本章完。

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

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

相关文章

Linux驱动开发(速记版)--热插拔

第九十六章 热插拔简介 热插拔是指在设备运行时安全地插入或拔出硬件&#xff0c;无需关闭或重启系统。 它提供了方便性和灵活性&#xff0c;允许快速更换或添加硬件而无需中断任务。 以下是一些应用场景及支持热插拔所需的条件&#xff1a; 应用场景&#xff1a; USB设备&…

python中,try-except捕获异常的意义(通过ai智库学习)

python中&#xff0c;不但可以用try-except捕获异常&#xff0c; 还可以自定义异常提示字符串&#xff0c;更可以自定义捕获异常后的处置。 (笔记模板由python脚本于2024年10月03日 06:47:06创建&#xff0c;本篇笔记适合喜欢研究python的coder翻阅) 【学习的细节是欢悦的历程】…

交叠型双重差分法

交叠型双重差分法&#xff08;Staggered Difference-in-Differences, Staggered DiD&#xff09;是一种扩展的双重差分&#xff08;Difference-in-Differences, DiD&#xff09;方法&#xff0c;用于处理多个时间点的政策干预或处理组&#xff08;treatment group&#xff09;并…

每日读则推(四)

Whats this...? | An invitation letter n.邀请函 n.邀请(invite v.邀请) Can a tool grasp the meaning in a song? v. 握紧,理解 n.紧握,理解(力) Can it feel the melody, where emotions belong? …

C++ union的运用

// // Created by 徐昌真 on 2024/10/5. // #include <iostream> #include <cstring> using namespace std;//定义一个结构体(类) struct Info{char _name[20];int _role; //老师是0 同学是1union { //用union存放score和course 节省内存int score;char course[2…

全球十大独角兽(完整榜单),你猜中国占几席?

全球十大独角兽 10月3日&#xff0c;OpenAI 宣布已完成 66 亿美元融资&#xff0c;估值达 1570 亿美元&#xff0c;成为全球第三的独角兽。 给新来的读者重温一下"独角兽"的定义&#xff1a;估值超过10亿美元的未上市企业。 你可能会好奇&#xff0c;OpenAI 是第三&a…

Linux·进程概念(下)

1. 进程优先级 优先级就是获得某种资源的先后顺序&#xff0c;因为CPU资源是有限的&#xff0c;因此各个进程之间要去争取CPU的资源。 那么针对Linux操作系统下的PCB中&#xff0c;也就是task_struct结构体中&#xff0c;使用了int类型的变量记录了每个进程的优先级属性&#x…

WIFI网速不够是不是光猫的“路由模式”和“桥接模式”配置错了?

光猫&#xff08;光纤调制解调器&#xff09;是一种用于将光纤信号转换为数字信号的设备&#xff0c;通常用于家庭或企业网络中。光猫可以在不同的工作模式下运行&#xff0c;其中最常见的两种模式是“路由模式”和“桥接模式”。以下是这两种模式的详细解释及其优缺点。 一、路…

python实现单例模式的常用三种方法-基于__new__/使用装饰器以及Python中的值类型、引用类型以及类的静态变量、读取进程和线程ID

一、python实现单例模式的常用三种方法-基于__new__,使用装饰器 涉及到类的使用就会有类的实例化&#xff0c;就会有类单例实现的需求&#xff0c;因为重复实例化会浪费资源。python中的单例模式与别的语言相比&#xff0c;单例实现的方法更丰富。虽然python实现单例的模式的方…

MobaXterm使用

Linux连接工具MobaXterm详细使用教程-CSDN博客

Elasticsearch学习笔记(五)Elastic stack安全配置二

一、手动配置http层SSL 通过前面的配置&#xff0c;我们为集群传输层手动配置了TLS&#xff0c;集群内部节点之间的通信使用手动配置的证书进行加密&#xff0c;但是集群与外部客户端的http层目前还是使用的自动配置&#xff0c;集群中HTTP的通信目前仍然使用自动生成的证书ht…

【韩顺平Java笔记】第7章:面向对象编程(基础部分)【227-261】

文章目录 227. 重载介绍228. 重载快速入门229. 重载使用细节230. 重载课堂练习1231. 232. 重载课堂练习2,3233. 可变参数使用233.1 基本概念233.2 基本语法233.3 快速入门案例 234. 可变参数细节235. 可变参数练习236. 作用域基本使用237. 作用域使用细节1238. 作用域使用细节2…

Docker安装部署和常用命令

Docker 是一种开源的平台&#xff0c;旨在帮助开发者和运维人员更轻松地创建、部署和运行应用程序。通过将应用程序及其依赖项打包到一个名为容器的标准化单位中&#xff0c;Docker 提供了一种轻量级的虚拟化解决方案。与传统虚拟机相比&#xff0c;Docker 容器可以在同一主机上…

GoogleNet原理与实战

在2014年的ImageNet图像识别挑战赛中&#xff0c;一个名叫GoogLeNet 的网络架构大放异彩。以前流行的网络使用小到11&#xff0c;大到77的卷积核。本文的一个观点是&#xff0c;有时使用不同大小的卷积核组合是有利的。 回到他那个图里面你会发现,这里的一个通过我们最大的池化…

12条职场经验总结

01 事干得好不好尚且不说&#xff0c;但是话一定要说得漂亮。 比如&#xff0c;当领导给你安排工作的时候&#xff0c;你一定要非常积极地响应&#xff0c;拍着胸脯跟领导说“放心吧”。至于后续到底怎么干&#xff0c;再结合实际情况有的放矢地把握。 02 当别人夸奖你的时…

记录使用crypto-js、jsencrypt实现js加密的方法

实用为主&#xff0c;直接上干货。 使用工具&#xff1a;pycharm专业版2020.3.2。 记录通过crypto-js模块、jsencrypt模块两种方式实现加密。 本文在pycharm中新建一个项目&#xff0c;一步一步记录实现步骤。 一、新建pycharm项目并新建两个js文件&#xff0c;分别命名为c…

Python 工具库每日推荐 【Requests】

文章目录 引言Python网络库的重要性今日推荐:Requests工具库主要功能:使用场景:安装与配置快速上手示例代码代码解释实际应用案例案例1:获取天气信息案例分析案例2:文件上传案例分析高级特性会话和Cookie处理自定义请求头超时设置代理设置扩展阅读与资源优缺点分析优点:缺…

Markdown 语法详解大全(超级版)(三)——甘特图语法详解

Markdown 语法详解大全(超级版)&#xff08;三&#xff09;——甘特图语法详解 Markdown 语法详解大全(超级版)&#xff08;一&#xff09; Markdown 语法详解大全(超级版)&#xff08;二&#xff09; Markdown 语法详解大全(超级版)&#xff08;三&#xff09; Markdown 语法…

[Linux#61][UDP] port | netstat | udp缓冲区 | stm32

目录 0. 预备知识 1. 端口号的划分范围 2. 认识知名端口号 3. netstat 命令 4. pidof 命令 二.UDP 0.协议的学习思路 1. UDP 协议报文格式 报头与端口映射&#xff1a; 2. UDP 的特点 面向数据报&#xff1a; 3. UDP 的缓冲区 4. UDP 使用注意事项 5. 基于 UDP 的…

栈的介绍与实现

一. 概念与结构 栈&#xff1a;⼀种特殊的线性表&#xff0c;其只允许在固定的⼀端进⾏插⼊和删除元素操作。进⾏数据插⼊和删除操作的⼀端称为栈顶&#xff0c;另⼀端称为栈底。栈中的数据元素遵守后进先出LIFO&#xff08;Last In First Out的原则。 压栈&#xff1a;栈的插…