排序算法(C语言版)

news2024/11/16 1:41:00

前言

排序作为生产环境中常见的需求之一,对整个产品有举足轻重的影响,可以说使用一个合适的排序算法是业务逻辑中比较重要的一部分。今天我们就来介绍常见的排序算法以及实现

排序

所谓排序无非就是按照特定的规则对一组数据就行顺序化。

常见的排序有升序和降序。

对于数字类型的数据常见的规则是按照其大小排序。如果学过Java的朋友应该知道,Java引入了对象的概念,对象的排序规则一般需要程序员自己定义,实现Comparable或者Comparator接口,在在接口内部实现排序的逻辑

除排序本身的定义外,我们还需要了解一点关于排序的性质

  • 稳定性:当“大小”一致的两个数据应该如何规定两者的顺序呢?比如一个班级中有两位考100分的同学,我们应该如何规定两者的顺序呢?显然,一个常见的想法是谁先交卷谁是第一名。这种保证“交卷顺序”的排序算法,我们可以描述为稳定的排序算法。稳定性即保证排序后“大小”一致的数据顺序与排序前一致
  • 内/外排序:这是一组相对的概念。内部排序要求排序的数据全部在内存中完成排序。外部排序要求排序的数据在硬盘内存中移动来完成排序,常用于数据量巨大或者内存不够的时候使用。

常见的排序算法

常见的排序算法有比较类的排序:冒泡排序,插入排序,希尔排序,选择排序,堆排序,快速排序,归并排序

非比较类的排序:基数排序,计数排序,桶排序

下面我们就一个一个来了解上述算法的思想和代码实现,以下笔者均以排升序举例。

笔者在排序算法的实现中可能会用到交换算法,在这里先实现,后面不在介绍

//交换元素
void swap(int* p1, int* p2) {
	int tmp = *p1;
	*p1 = *p2;
	*p2 = tmp;
}

这里是一个简答的交换,我们只是把它封装一层,实现代码的复用

冒泡排序

冒牌排序,思路是相邻两个数据就行比较,遍历一遍为一趟排序。一趟排序后可以保证本趟最大值位于合适的位置。由此,每趟排序后下一趟的比较次数可以优化。也可以定义一个标记,如果本趟没有交换,整个序列均有序

实现

//冒泡排序
void BubbleSort(int* a, int n);

void BubbleSort(int* a, int n)
{
	for (int y = 0; y < n - 1; y++)
	{
		int flag = 1;
		int len = n - y;
		for (int i = 0; i < len - 1; i++)
		{
			if (a[i] > a[i + 1])
			{
				swap(a + i + 1, a + i);
				flag = 0;
			}
		}
		if (flag) return;
	}
}

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

插入排序

插入排序,思路是假设前n个数据已经有序,第n+1个数据插入到有序的序列中。这个算法的适应性很强

实现

//插入排序
void InsertSort(int* a, int n);

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,2,3……往上自增。希尔排序的优化在于增加跨度的方让序列更快的接近有序。 引入一个gap变量,即数据分为gap组,以组为单位进行插入排序。不断改变gap的值,有两个常用的gap算式 gap=gap/2 和 gap=gap/3+1。这两个算式均可以保证最后一次gap为1,即保证最后一次希尔排序转换为插入排序(此时序列接近有序,前面介绍插入排序时介绍插入排序的适应性很强,表现在排序一个接近有序的序列时效率很高)。当然也可以自定义gap算式,但是需要保证gap最后一次的取值是1

实现

//希尔排序
void ShellSort(int* a, int n);

void ShellSort(int* a, int n)
{
	int gap = n;
	while (gap > 1)
	{
		gap = gap / 3 + 1;
		int i = 0;
		while (i < gap)
		{
			int y = i;
			while (1)
			{
				if (y >= n || y + gap >= n)
					break;
				if (a[y + gap] < a[y]) {
					int x = y;
					int tmp = a[y + gap];
					while (x >= 0 && tmp < a[x])
					{
						a[x + gap] = a[x];
						x -= gap;
					}
					a[x + gap] = tmp;
				}
				y += gap;
			}
			i++;
		}
	}
}

1. 可以理解为gap!=1时是预排序,gap=1时是插入排序。希尔排序是对插入排序的优化
2. 时间复杂度:O(N^1.3)
3. 空间复杂度:O(1)
4. 稳定性:不稳定

选择排序

选择排序是一个比较简单的排序算法,主要的逻辑就是每一趟找出最小的值,和本趟开头位置的元素呼唤。这里可以有一点优化,就是在一趟遍历的时候,同时找最大值和最小值。

实现

这里我们提供两个版本的实现,优化和非优化的版本(说是优化,其实性能并没有提升多少)

//选择排序
void SelectSort(int* a, int n);//优化版,同时选择大小
void SelectSort1(int* a, int n);//只选择小

void SelectSort1(int* a, int n)//取小交换
{
	for (int y = 0; y < n - 1; y++) {
		int i = y;
		int tmp = y + 1;
		for (; tmp < n; tmp++)
		{
			if (a[tmp] < a[i])
				i = tmp;
		}
		swap(a + i, a + y);
	}
}
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;
			else if (a[i] > a[max])
				max = i;
		}
		swap(a + begin, a + min);
		if (begin == max)
			max = min;
		swap(a + end, a + max);
		begin++;
		end--;
	}
}

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

堆排序

堆排序是依赖于堆这个数据结构实现的一种排序算法。什么是堆这里不再展开。我们想要用堆排序算法实现升序,需要用所有的数据建立一个大根堆,然后不断的pop堆顶的元素与堆末尾(逻辑上是堆,实际上是数组)元素交换,调整堆……知道堆中只剩一个元素,此时排序完成。

实现

//堆排序
void HeapSort(int* a, int n);

void xiangXiaTiaoZheng(int* arr, int pr, int k) {//向下调整
	int ch = pr * 2 + 1;
	while (ch < k) {
		if (ch + 1 < k && arr[ch + 1] > arr[ch]) ch++;
		if (arr[ch] > arr[pr]) {
			swap(arr + ch, arr + pr);
			pr = ch;
			ch = pr * 2 + 1;
		}
		else return;
	}
}
void jianDui(int* arr, int k)//建堆
{
	for (int i = (k - 2) / 2; i >= 0; i--) {
		xiangXiaTiaoZheng(arr, i, k);
	}
}
void HeapSort(int* a, int n)
{
	jianDui(a, n);
	int end = n - 1;
	while (end > 0) {
		swap(a, a + end);
		xiangXiaTiaoZheng(a, 0, end);
		end--;
	}
}

1. 堆排序使用堆来选数,效率就高了很多(topK问题)。向下调整建堆的算法要比向上调整建堆的算法效率高
2. 时间复杂度:O(N*logN)
3. 空间复杂度:O(1)
4. 稳定性:不稳定
 

快速排序

快速排序的思想是以每趟开始的元素为基准,将大于基准的元素放在基准数的右边,将小于基准的元素放在基准的左边,此时可以保证基准位于合适的位置。然后再分别处理基准的左半边和右半边,知道所有的元素都位于合适的位置上。

我们可以将实现分类为递归实现和非递归实现

递归实现

递归实现的大体逻辑是相同的,只不过在每一趟的实现有可以分为不同的版本。这里我们介绍三种不同的实现方式——hoare版本、挖坑版本、双指针版本

hoare版本

这里单趟的逻辑是以左侧开始位置的元素为基准元素,右边开始向左遍历在第一个小于基准元素的位置停下,左边开始向右遍历在第一个大于基准元素的位置停下,交换左右的元素,以上逻辑循环直到左右相遇,退出循环再交换相遇位置和起始位置的元素。

void QuickSort(int* a, int begin, int end);//hoare版本

void QuickSort(int* a, int begin, int end)//hoare版本
{
	if (begin >= end)
		return;
	/*
    if ((end - begin + 1) < 10)
	{
		InsertSort(a + begin, end - begin + 1);
		return;
	}
	int z = QuZhong(a, begin, end);
	swap(a + begin, a + z);
    */
	int tmp = a[begin];
	int left = begin;
	int right = end;
	while (left < right)
	{
		while (left < right && a[right] >= tmp)
		{
			right--;
		}
		while (left < right && a[left] <= tmp)
		{
			left++;
		}
		swap(a + left, a + right);
	}
	swap(a + begin, a + left);
	QuickSort(a, begin, left - 1);
	QuickSort(a, left + 1, end);
}

此版本是最初实现快速排序是使用的版本。

为了方便理解,后面再开发出双指针法和挖坑法

挖坑版本

挖坑法单趟的思路是以左侧开始位置的元素为基准元素,同时将左侧开始位置的元素记录到tmp中,设置此位置为坑。右边开始向左遍历在第一个小于基准元素的位置停下,将此位置的元素放到坑中,再将此位置设置为坑。将左边开始向右遍历在第一个大于基准元素的位置停下,将此位置的元素放到坑中,再将此位置设置为坑。以上逻辑循环直到左右相遇,退出循环,此时相遇的位置是坑位,将tmp添入坑位中

void QuickSort1(int* a, int begin, int end);//挖坑法版本

void QuickSort1(int* a, int begin, int end)
{
	if (begin >= end)
		return;
    /*
	if ((end - begin + 1) < 10)
	{
		InsertSort(a + begin, end - begin + 1);
		return;
	}
	int z = QuZhong(a, begin, end);
	swap(a + begin, a + z);
    */
	int tmp = a[begin];
	int tmpi = begin;
	int left = begin;
	int right = end;
	while (left < right)
	{
		while (left < right && a[right] >= tmp)
		{
			right--;
		}
		if (left < right)
		{
			swap(a + tmpi, a + right);
			tmpi = right;
			left++;
		}
		while (left < right && a[left] <= tmp)
		{
			left++;
		}
		if (left < right)
		{
			swap(a + tmpi, a + left);
			tmpi = left;
			right--;
		}
	}
	QuickSort1(a, begin, left - 1);
	QuickSort1(a, left + 1, end);
}

双指针版本

以本趟开始位置的元素为基准元素。定义两个指针变量,第一个指针prev指向本趟开始的元素,第二个指针cut指向本趟的第二个元素。如果cut指向的元素小于等于基准,则prev指针+1,prev与cut元素交换,cut指针+1:否则cut指针+1。直到cut指向空指针,退出循环,将基准元素与prev指向的元素互换。

void QuickSort2(int* a, int begin, int end);//双指针版本

void QuickSort2(int* a, int begin, int end)
{
	if (begin >= end)
		return;
    /*
	if ((end - begin + 1) < 10)
	{
		InsertSort(a + begin, end - begin + 1);
		return;
	}
	int z = QuZhong(a, begin, end);
	swap(a + begin, a + z);
    */
	int prev = begin;
	int cut = begin + 1;
	int tmp = a[begin];
	while (cut <= end)
	{
		while (cut <= end && a[cut] <= tmp)
		{
			prev++;
			cut++;
		}
		while (cut <= end && a[cut] > tmp)
		{
			cut++;
		}
		if (cut <= end)
		{
			prev++;
			swap(a + prev, a + cut);
		}
	}
	swap(a + begin, a + prev);
	QuickSort2(a, begin, prev - 1);
	QuickSort2(a, prev + 1, end);
}

非递归实现

递归版本在内存的开销上有可能会造成栈溢出,此时改成非递归是常见的需求。该非递归的核心思路就是用栈模拟递归的过程,利用栈存储关键的信息。

非递归需要依赖于栈这个数据结构,这里也不在展开

栈:

#pragma once
#include <stdio.h>
#include <stdlib.h>

// 支持动态增长的栈
typedef int STDataType;
typedef struct Stack
{
	STDataType* _a;
	int _top;		// 栈顶
	int _capacity;  // 容量 
}Stack;

// 初始化栈 
void StackInit(Stack* ps);
// 入栈 
void StackPush(Stack* ps, STDataType data);
// 出栈 
void StackPop(Stack* ps);
// 获取栈顶元素 
STDataType StackTop(Stack* ps);
// 获取栈中有效元素个数 
int StackSize(Stack* ps);
// 检测栈是否为空,如果为空返回非零结果,如果不为空返回0 
int StackEmpty(Stack* ps);
// 销毁栈 
void StackDestroy(Stack* ps);

#define _CRT_SECURE_NO_WARNINGS 1
#include "Stack.h"

void StackInit(Stack* ps)
{
	ps->_a = NULL;
	ps->_top = -1;
	ps->_capacity = 0;
}

void StackPush(Stack* ps, STDataType data)
{
	if (ps->_capacity == 0) {
		ps->_a = (STDataType*)malloc(sizeof(STDataType) * 4);//默认初始化长度为4
		ps->_capacity = 4;
		ps->_top = 0;
	}
	else if (ps->_top == ps->_capacity)
	{
		ps->_capacity *= 2;
		ps->_a = (STDataType*)realloc(ps->_a, sizeof(STDataType) * ps->_capacity);//默认扩容至原先的两倍

	}
	(ps->_a)[ps->_top++] = data;
}

void StackPop(Stack* ps)
{
	if (ps->_top <= 0) return;
	ps->_top--;
}

STDataType StackTop(Stack* ps) {
	if (ps->_top <= 0) return 0;
	return (ps->_a)[--ps->_top];
}

int StackSize(Stack* ps)
{
	return ps->_top;
}

int StackEmpty(Stack* ps)
{
	if (ps->_top <= 0) return 1;
	return 0;
}

void StackDestroy(Stack* ps)
{
	free(ps->_a);
	ps->_a = NULL;
	ps = NULL;
}
void QuickSortNonR(int* a, int begin, int end);//非递归版本

void QuickSortNonR(int* a, int begin, int end) 	//非递归版本
{
	Stack sk;
	StackInit(&sk);
	StackPush(&sk, end);
	StackPush(&sk, begin);
	while (!StackEmpty(&sk))
	{
		int left = StackTop(&sk);
		int right = StackTop(&sk);
		int rembegin = left;
		int remend = right;
		int tmp = a[left];

		while (left < right)
		{
			while (left < right && a[right] >= tmp)
			{
				right--;
			}
			while (left < right && a[left] <= tmp)
			{
				left++;
			}
			swap(a + left, a + right);
		}
		swap(a + rembegin, a + left);
		if (left + 1 < remend) {
			StackPush(&sk, remend);
			StackPush(&sk, left + 1);
		}
		if (rembegin < left - 1) {
			StackPush(&sk, left - 1);
			StackPush(&sk, rembegin);
		}
	}
	StackDestroy(&sk);
}

优化

在此基础上可以对快速排序展开两个简单的优化

  • 小区间优化(当排序元素小于10可以转换为插入排序,减少递归的深度,减少开销)

将代码中注释的部分放开即可以实现小区间优化

  • 三数取中(避免出现向一边递归,算法退化为N^2的情况)
int QuZhong(int* arr, int first, int last)//优化,三数取中
{
	int z = (first + last) / 2;
	int a = arr[first];
	int b = arr[z];
	int c = arr[last];
	if (b > c)
	{
		if (c > a)
			return last;
		else
		{
			if (a < b)
				return first;
			else
				return z;
		}
	}
	else
	{
		if (b > a)
			return z;
		else
		{
			if (a < c)
				return first;
			else
				return last;
		}
	}
}

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

归并排序

归并排序是一种典型的分治思想,将大问题拆分成不能再分割的小问题。思路在将待排序不断对半分割分割,分割到不能再分割时将相邻两个组合并为一个有序的大组,再将两个相邻的两个大组合并为一个有序的大大组……

这里依然是提供两个版本的实现,递归版和非递归版

这里非递归的实现,是直接将单个元素视为递归下来不能再分割的最小单位,实现往上回归的部分

//归并排序
void MergeSort(int* a, int begin, int end);//递归版本
void MergeSortNonR(int* a, int begin, int end);//非递归版本

void MergeSort(int* a, int begin, int end)//归并排序
{
	int n = end - begin + 1;
	if (n < 2) return;
	int min = begin + n / 2;
	MergeSort(a, begin, min - 1);
	MergeSort(a, min, end);
	int* arr = (int*)malloc(sizeof(int) * n);
	int left = begin;
	int right = min;
	int i = 0;
	while (left < min && right <= end)
	{
		if (a[left] < a[right])
		{
			arr[i++] = a[left++];
		}
		else
		{
			arr[i++] = a[right++];
		}
	}
	if (left == min) {
		while (right <= end) {
			arr[i++] = a[right++];
		}
	}
	else
	{
		while (left < min) {
			arr[i++] = a[left++];
	 	}
	}
	i = 0;
	for (int y = begin; y <= end; y++)
	{
		a[y] = arr[i++];
	}
	free(arr);
}

void MergeSortNonR(int* a, int begin, int end)//非递归版本
{
	int gap = 1;
	int n = end - begin + 1;
	int* arr = (int*)malloc(sizeof(int) * n);
	while (gap < n)
	{
		for (int y = 0; y < n; y += gap * 2)
		{
			int left = y;
			int right = y + gap;
			if (right >= n) {
				for (int z = left; z < n; z++)
				{
					arr[z] = a[z];
				}
				break;
			}
			int i = y;
			int end1 = y + gap;
			int end2 = right + gap;
			if (end2 >= n)
				end2 = n;
			while (left < end1 && right < end2)
			{
				if (a[left] < a[right])
				{
					arr[i++] = a[left++];
				}
				else
				{
					arr[i++] = a[right++];
				}
			}
			if (left == end1) {
				while (right < end2) {
					arr[i++] = a[right++];
				}
			}
			else
			{
				while (left < end1) {
					arr[i++] = a[left++];
				}
			}
		}
		for (int z = 0; z < n; z++)
		{
			a[z] = arr[z];
		}
		gap *= 2;
	}
	free(arr);
}

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

计数排序

计数排序是一种非比较的排序,适用于对整数进行排序。思想是得到序列中的最大值和最小值,计算得到中间最多有多少个元素,遍历一遍序列,将元素映射到计数表中,再从计数表中依次读取出元素。

//计数排序
void CountSort(int* a, int n);

void CountSort(int* a, int n)
{
	int min = a[0];
	int max = a[0];
	for (int i = 1; i < n; i++)
	{
		if (a[i] < min)
			min = a[i];
		else if (a[i] > max)
			max = a[i];
	}
	int countN = max - min + 1;
	int* arr = (int*)calloc(countN, sizeof(int));
	for (int i = 0; i < n; i++)
	{
		arr[a[i] - min]++;
	}
	int y = 0;
	for (int i = 0; i < countN&&y<n; i++)
	{
		int sz = arr[i];
		while (sz--)
		{
			a[y++] = i + min;
		}
	}
}

1. 计数排序在数据范围集中时,效率很高,但是适用范围及场景有限。
2. 时间复杂度:O(MAX(N,范围))
3. 空间复杂度:O(范围)
4. 稳定性:稳定

桶排序和基数排序

桶排序和基数排序不常用,这里只简单介绍思想不介绍具体实现

桶排序工作的原理是将数组分到有限数量的桶里。每个桶再分别排序,最后依次把各个桶中的记录列出来记得到有序序列

基数排序是一种非比较型整数排序算法,其原理是将整数按位数切割成不同的数字,然后按每个位数分别比较

排序算法的比较

 

 

结语

排序这部分重点掌握不同算法的思想,把握一趟是如何实现的,其他每一趟都是一样的思想

以上便是今天的全部内容。如果有帮助到你,请给我一个免费的赞。

因为这对我很重要。

编程世界的小比特,希望与大家一起无限进步。

感谢阅读!

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

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

相关文章

柠檬班车载测试视频课程

这门课程将教授学员如何进行车载测试视频拍摄。学习者将学习如何选择合适的拍摄设备、构思拍摄场景、拍摄技巧和后期制作等内容。课程结合实例演练和个性化指导&#xff0c;帮助学员掌握车载测试视频拍摄的关键技能&#xff0c;提升视频制作能力。无论您是初学者还是有经验者&a…

从移动切换到电信IP:详细介绍两种方法

在当前的互联网环境中&#xff0c;用户可能会因为各种原因需要切换网络服务提供商&#xff0c;比如从移动切换到电信。这种切换不仅涉及到网络服务的变更&#xff0c;还可能意味着IP地址的改变。那么&#xff0c;移动的怎么切换成电信的IP&#xff1f;下面一起来了解一下吧。 方…

React:tabs或标签页自定义右击菜单内容,支持内嵌iframe关闭菜单方案

React&#xff1a;tabs或标签页自定义右击菜单内容&#xff0c;支持内嵌iframe关闭菜单方案 不管是react、vue还是原生js&#xff0c;原理是一样的。 注意如果内嵌iframe情况下&#xff0c;iframe无法使用事件监听&#xff0c;但是可以使用iframe的任何点击行为都会往父级wind…

Python | Leetcode Python题解之第169题多数元素

题目&#xff1a; 题解&#xff1a; class Solution:def majorityElement(self, nums: List[int]) -> int:count 0candidate Nonefor num in nums:if count 0:candidate numcount (1 if num candidate else -1)return candidate

查看es p12证书文件过期方法

查看证书过期时间: openssl pkcs12 -in elastic-certificates.p12 -nokeys -out elastic-certificates.crt (需要输入证书生成时配置密码) openssl x509 -enddate -noout -in elastic-certificates.crt

Elasticsearch 使用误区之一——将 Elasticsearch 视为关系数据库!

Elasticsearch 是一个强大的工具&#xff0c;尤其在全文检索、实时分析、机器学习、地理数据应用、日志和事件数据分析、安全信息和事件管理等场景有大量的应用。 然而&#xff0c;Elastic Stack 技术栈的选型及应用效能取决于正确的使用方式。选型错误或者误用 Elasticsearch …

如何在Windows系统部署Terraria私服并配置公网地址实现远程联机

文章目录 前言1. 下载Terraria私服2. 本地运行Terraria 私服3. 本地Terraria私服连接4. Windwos安装Cpolar 工具5. 配置Terraria远程联机地址6. Terraria私服远程联机7. 固定远程联机地址8. 固定的联机地址测试 前言 本文将为你详细介绍在本地如何运行泰拉瑞亚本地私服和结合C…

Recovery

Steal&#xff1a;允许未提交的事务写到磁盘上 Force&#xff1a;在事务提交之前该事务所有更新必须被写到磁盘上 No-StealForce 性能差&#xff0c;需要等待修改被写到磁盘上才能顺利commit 不需要undo&#xff0c;因为aborted事务不会被写到磁盘上 不需要redo&#xff0…

Ubuntu Apache2 搭建Gerrit 环境

一、前言 时隔多年&#xff0c;好久没有更新CSDN 博客了&#xff0c;主要原因有如下两点&#xff1a; 1、平时工作繁忙&#xff0c;无暇更新。 2、工作内容涉及信息安全&#xff0c;一些工作经验积累不便更新到互联网上。 最近一直在折腾搭建Gerrit 环境&#xff0c;最开始…

红酒邂逅时尚,品味生活的双重魅力,引领潮流新风尚

在繁华的都市中&#xff0c;红酒与时尚如同一对孪生姐妹&#xff0c;共同诠释着品味生活的双重魅力。红酒&#xff0c;那深邃的色泽中蕴藏着千年的历史与文化&#xff1b;时尚&#xff0c;那流转的光影中凝聚着时代的潮流与个性。当两者相遇&#xff0c;便碰撞出了特别的火花&a…

BEVDistill

摘要 将激光雷达检测器纳入多视图 3D 物体检测&#xff0c;在 BEV 空间中统一图像和激光雷达特征&#xff0c;让图像BEV特征自适应学习点云BEV特征。 背景 LiDAR 点可捕获精确的 3D 空间信息&#xff0c;为基于相机的目标检测提供自然指导。鉴于此&#xff0c;最近的相关工作…

Handling `nil` Values in `NSDictionary` in Objective-C

Handling nil Values in NSDictionary in Objective-C When working with Objective-C, particularly when dealing with data returned from a server, it’s crucial (至关重要的) to handle nil values appropriately (适当地) to prevent unexpected crashes. Here, we ex…

ModbusRTU协议报文解析

ModbusRTU协议报文解析 报文格式&#xff1a; 设备地址/从站地址&#xff1a; 1个字节 指定目标设备地址&#xff08;从站地址&#xff09; 功能码&#xff1a;1个字节 功能码在modbus协议用于表示信息帧的功能&#xff0c;例如读取线圈状态、读取寄存器等。 数据&#xff…

SSRF漏洞原理与案例分析

一、什么是SSRF漏洞 SSRF (Server-Side Request Forgery&#xff1a;服务器端请求伪造)是一种由攻击者构造请求&#xff0c;由服务端发起请求的安全漏洞。一般情况下&#xff0c;SSRF攻击的目标是外网无法访问的内部系统(正因为请求是由服务端发起的&#xff0c;所以服务端能请…

论文速递 | Management Science 4月文章合集(下)

编者按 在本系列文章中&#xff0c;我们梳理了运筹学顶刊Management Science在2024年4月份发布有关OR/OM以及相关应用的13篇文章的基本信息&#xff0c;旨在帮助读者快速洞察领域新动态。本文为第二部分&#xff08;2/2&#xff09;。 推荐文章1 ● 题目&#xff1a;Social Le…

HarmonyOS应用开发——Hello World

下载 HUAWEI DevEco Studio: https://developer.harmonyos.com/cn/develop/deveco-studio/#download 同意&#xff0c;进入配置页面&#xff1a; 配置下载源以及本地存放路径&#xff0c;包括nodejs和ohpm: 配置鸿蒙SDK路径&#xff1a; 接受协议&#xff1a; 确认无误后&#…

面试-细聊synchronized

1.线程安全问题的主要诱因&#xff1a; 存在多条共享数据(临界资源) 存在多条线程共同操作这些共享数据 解决问题的根本方法&#xff1a; 同一时刻有且仅有一个线程在操作共享数据&#xff0c;其他线程必须等到该线程处理完数据后在对共享数据进行操作。 2.synchroized锁 分…

边缘计算为企业解决数据问题,提升业务效率和竞争力-天拓四方

企业在当前数字化时代面临着一系列具体的问题和挑战&#xff0c;这些问题往往与数据处理、实时响应、安全性以及运营成本等方面密切相关。边缘计算作为一种新兴的计算模型&#xff0c;能够有效地帮助企业解决这些问题&#xff0c;提升业务效率和竞争力。 首先&#xff0c;企业…

清华、北大与微软推出Glyph-ByT5-v2,精准生成文字海报,支持10种语言,效果炸裂

前言 在 AI 领域&#xff0c;文生图技术已经取得了令人惊叹的进展&#xff0c;但如何将文字精准地融入图像&#xff0c;并支持多种语言&#xff0c;一直是研究人员面临的挑战。为了解决这一难题&#xff0c;清华大学、北京大学和微软亚洲研究院的研究人员合作推出了 Glyph-ByT…

网络安全等级保护测评

网络安全等级保护 《GB17859 计算机信息系统安全保护等级划分准则》 规定计算机信息系统安全保护等级共分五级 《中华人民共和国网络安全法》 “国家实行网络安全等级保护制度。 等级测评 测评机构依据国家网络安全等级保护制度规定&#xff0c;按照有关 管理规范和…