常见排序算法解析

news2025/1/22 12:17:34

在这里插入图片描述

芝兰生于深林,不以无人而不芳;君子修道立德,不为穷困而改节

文章目录

  • 插入排序
    • 直接插入排序
    • 希尔排序
  • 选择排序
    • 直接选择排序
    • 堆排序
  • 交换排序
    • 冒泡排序
    • 快速排序
      • 优化
      • 挖坑法
      • 前后指针法
      • 非递归版
  • 归并排序
      • 递归
      • 非递归
  • 总结


插入排序

插入排序(Insertion Sort)是一种简单直观的排序算法,它的工作原理类似于我们打扑克牌时对手中的牌进行排序的方式。插入排序的基本思想是将一个待排序的元素插入到已经排好序的部分的适当位置,使得插入后的序列仍然有序。

在这里插入图片描述

插入排序的基本步骤如下:

  1. 从第一个元素开始,认为第一个元素已经是排好序的部分
  2. 取出下一个元素,在已经排序的元素序列中从后向前扫描
  3. 如果该元素(已排序)大于新元素,将该元素移到下一位置
  4. 重复步骤3,直到找到已排序的元素小于或等于新元素的位置
  5. 将新元素插入到该位置后
  6. 重复步骤2~5,直到所有元素都排序完成

插入排序是一种稳定的排序算法,其时间复杂度为 O(n2),其中 n 是待排序元素的数量。最好情况下,如果数组已经有序,时间复杂度为 O(n);最坏情况下,如果数组逆序,时间复杂度为 O(n^2)。其空间复杂度为 O(1),因为它仅使用了常量级的额外空间。

插入排序在小规模数据或基本有序数据的排序中性能较好,但在大规模数据排序中通常不如快速排序、归并排序等高级排序算法。然而,在某些特定情况下,插入排序可能会比其他高级算法更加有效,因此它仍然是一种重要的排序方法之一。

直接插入排序

直接插入排序是一种简单直观的排序算法,它的基本思想是将一个记录插入到已经排好序的有序表中,从而得到一个新的、记录数增加1的有序表。直接插入排序的实现过程通常如下:

  1. 初始状态:假设待排序的元素存储在一个数组中,初始时将第一个元素看作是已排序的序列,其余元素看作是未排序的序列。
  2. 遍历未排序序列:从第二个元素开始,依次将未排序的元素插入到已排序的序列中。
  3. 插入操作:对于未排序序列中的每个元素,依次与已排序序列中的元素比较,找到合适的位置并插入,使得已排序序列保持有序。
  4. 重复步骤2和3,直到所有元素都插入到已排序序列中,排序完成。
bool insertSort(std::vector<int>& arr, int gap = 1)
{
	int n = arr.size();
	for (int i = 1; i < n; ++i) 
	{
		int key = arr[i];
		int j = i - 1;
		// 将 arr[0..i-1] 中比 key 大的元素都向后移动一个位置
		while (j >= 0 && arr[j] > key) 
		{
			arr[j + 1] = arr[j];
			--j;
		}
		// 将 key 插入到合适的位置
		arr[j + 1] = key;
	}
	return true;
}

直接插入排序的时间复杂度取决于输入数据的初始状态。

  • 最好情况: 如果输入数组已经是有序的,那么每次比较只需要比较一次,时间复杂度为 (O(n))。
  • 最坏情况: 如果输入数组是逆序排列的,那么每次插入一个新元素时都需要比较和移动前面已排序部分的所有元素,时间复杂度为 (O(n2))。
  • 平均情况: 平均时间复杂度也是O(n2),因为在平均情况下,每次插入新元素的位置不确定,需要移动的元素个数与已排序部分的长度相关。

直接插入排序的空间复杂度主要取决于需要排序的数组本身,以及一些额外的辅助空间。

  • 原地排序: 直接插入排序是一种原地排序算法,它不需要额外的辅助空间,空间复杂度为 (O(1))。
  • 除数组本身外的空间: 在进行直接插入排序时,只需要使用常量级别的额外空间来存储临时变量和索引,因此空间复杂度较低。

综上所述,直接插入排序的时间复杂度主要取决于输入数据的初始状态,而空间复杂度相对较低,是一种简单但不够高效的排序算法。

希尔排序

希尔排序(Shell Sort)是一种基于插入排序的排序算法,它是由Donald Shell在1959年提出的,也称为“缩小增量排序”。在这里插入图片描述

希尔排序的基本思想是将原始数组分成若干个子序列,在每个子序列中进行插入排序,然后逐步缩小增量(间隔),最终完成对整个数组的排序。希尔排序的关键在于选择增量序列,并且在每轮排序中,将数组中相隔增量的元素分成一组,对各组进行插入排序。

  • 增量序列的选择对希尔排序的性能有较大影响,常用的增量序列包括希尔增量序列、Hibbard增量序列、Sedgewick增量序列等。
  • 希尔排序是不稳定排序算法,因为在排序过程中存在跨越多个子序列的元素交换。

希尔排序的时间复杂度取决于增量序列的选择,最坏情况下的时间复杂度为 O(n2),平均情况下的时间复杂度介于 (O(nlog2n)) 和 O(n2) 之间。希尔排序是一种原地排序算法,它的空间复杂度为 (O(1)),即常数级别的额外空间。

希尔排序相比直接插入排序效率高的原因主要是因为它利用了直接插入排序的优点,并通过分组插入的方式,使得数组在初始阶段就变得部分有序。

  1. 间隔插入: 希尔排序将整个数组按照一定的间隔分成若干个子序列,然后对每个子序列进行插入排序。通过这种方式,希尔排序可以在一定程度上解决直接插入排序中逆序对过多的问题,因为通过较大的间隔,可以更快地将较小的元素移到数组的前面。
  2. 逐步优化: 希尔排序通过不断减小间隔,逐步缩小子序列的规模,最终实现整个数组的基本有序。这种分阶段的逐步优化可以使得数组在排序过程中逐渐趋于有序状态,从而减少了每次插入操作的移动次数。
  3. 时间复杂度优化: 希尔排序的时间复杂度相比直接插入排序有所降低。尽管希尔排序的最坏时间复杂度仍然是 O(n2),但在一般情况下,由于数组在初始阶段就变得部分有序,因此实际的比较和移动操作会减少,从而提高了排序的效率。
算法步骤说明
确定增量序列选择一个增量序列,通常是 n/2, n/4, n/8,…, 1。增量序列的选择会影响到希尔排序的性能。
按增量进行分组根据选择的增量序列,将待排序的数组分成若干个子序列。
对各个子序列进行插入排序对每个子序列进行插入排序,这里的插入排序是根据增量值进行的,而不是对整个数组进行插入排序。
逐步减小增量重复步骤 2 和 3,直到增量为 1。最后一次排序使用增量为 1 的插入排序,这时候数组已经基本有序,插入排序的效率会比较高。
bool shellSort(std::vector<int>& arr)
{
	int gap = arr.size();
	while (gap)
	{
		for (int i = 1; i < arr.size(); i += gap)
		{
			int j = i - 1, key = arr[i];
			while (j >= 0 && arr[j] > key)
			{
				arr[j + 1] = arr[j];
				j--;
			}
			arr[j + 1] = key;
		}
		gap /= 2;
	}
	return true;
}

综上所述,希尔排序是一种简单而有效的排序算法,适用于中等大小的数据集合。尽管其时间复杂度并不是最优的,但在实际应用中,希尔排序的性能仍然比较可观,特别是对于某些特定的增量序列。

选择排序

直接选择排序

直接选择排序(Selection Sort)是一种简单直观的排序算法,它的思想是在未排序序列中找到最小(或最大)元素,将其放置在已排序序列的末尾,然后将未排序序列的第一个元素与已排序序列的最后一个元素交换位置,如此循环,直到整个数组排序完成。

在这里插入图片描述

  1. 从待排序序列中选择最小(或最大)的元素,记下其索引。
  2. 将最小(或最大)元素与待排序序列的第一个元素交换位置。
  3. 在剩余的待排序序列中继续执行步骤1和步骤2,直到所有元素都被排序。

直接选择排序的时间复杂度是 (O(n^2)),空间复杂度是 (O(1))。它是一种不稳定的排序算法,因为在选择过程中可能会改变相同元素的相对位置。

bool selectSort(std::vector<int>& arr)
{
	for (int i = 0; i < arr.size(); i++)
	{
		int minNum = i;
		for (int j = i + 1; j < arr.size(); j++)
		{
			if (arr[j] < arr[minNum]) minNum = j;
		}
		std::swap(arr[minNum], arr[i]);
	}
	return true;
}

堆排序

堆排序(Heap Sort)是一种基于完全二叉树(堆)的排序算法,它利用了堆的性质进行排序。堆是一种特殊的树形数据结构,具有以下特点:

  1. 在堆中,每个节点的值都大于等于(最大堆)或小于等于(最小堆)其子节点的值。
  2. 堆是一个完全二叉树,即除了最底层之外,其他层都被完全填满,且最底层从左到右填入。

堆排序的基本思想是:

  1. 将待排序的序列构建成一个堆(通常是最大堆)。
  2. 交换堆顶元素(最大值)和堆的最后一个元素,然后重新调整堆。
  3. 重复步骤2,直到整个序列有序。

在这里插入图片描述

堆排序的步骤:

  1. 构建堆:从最后一个非叶子节点开始,依次向前构建最大堆。
  2. 交换堆顶元素和堆的最后一个元素,并调整堆。
  3. 重复步骤2,直到堆中只剩一个元素。

堆排序是一种原地排序算法,它的时间复杂度是 (O(n log n)),空间复杂度是 (O(1))。堆排序是一种不稳定的排序算法,因为在调整堆的过程中可能改变相同元素的相对顺序。

void adjustDown(std::vector<int>& arr, int cur, int len)
{
	int leftChild = cur * 2 + 1;
	while (leftChild < len) 
	{
		int rightChild = leftChild + 1;
		int maxChild = (rightChild < len && arr[rightChild] > arr[leftChild]) ? rightChild : leftChild;
		if (arr[maxChild] > arr[cur])
		{
			std::swap(arr[maxChild], arr[cur]);
			cur = maxChild;
			leftChild = cur * 2 + 1;
		}
		else break;
	}
}

void createMaxHeap(std::vector<int>& arr)
{
	int len = arr.size();
	for (int i = len / 2; i >= 0; i--)
	{
		adjustDown(arr, i, len);
	}
}

bool heapSort(std::vector<int>& arr)
{
	createMaxHeap(arr);
	for (int i = arr.size() - 1; i > 0; i--)
	{
		std::swap(arr[0], arr[i]);
		adjustDown(arr, 0, i);
	}
	return true;
}

交换排序

冒泡排序

冒泡排序是一种简单直观的排序算法,它的基本思想是通过交换相邻元素多次对数组进行遍历,每次遍历都将最大(或最小)的元素交换到数组的末尾(或开始)。这个过程类似于气泡在水中上升的过程,故称为冒泡排序。

在这里插入图片描述

  1. 从数组的第一个元素开始,依次比较相邻的两个元素,如果它们的顺序不正确(比如前面的元素大于后面的元素),则交换它们的位置。
  2. 继续比较相邻的元素,直到整个数组被遍历一次。
  3. 重复上述步骤,每次遍历都将最大(或最小)的元素交换到数组的末尾(或开始),并缩小待排序数组的范围。

冒泡排序的时间复杂度取决于数组的初始状态,最好情况下(数组已经有序)时间复杂度为 (O(n)),最坏情况下(数组逆序)时间复杂度为 (O(n^2)),平均时间复杂度也为 (O(n^2))。冒泡排序是一种原地排序算法,只需要常数级别的额外空间来存储临时变量,因此空间复杂度为 (O(1))。

冒泡排序是一种稳定的排序算法。在相邻元素相等时,不会改变它们的相对顺序,因此是稳定的。冒泡排序的实现较为简单,但由于其时间复杂度较高,在实际应用中并不常见,通常被更高效的排序算法所取代,比如快速排序、归并排序等。

bool bubbleSort(std::vector<int>& arr)
{
	for (int i = 0; i < arr.size(); i++)
	{
		for (int j = 1; j < arr.size(); j++)
		{
			if (arr[j - 1] > arr[j]) std::swap(arr[j - 1], arr[j]);
		}
	}
	return true;
}

快速排序

快速排序(Quick Sort)是一种高效的排序算法,属于交换排序的一种。它的基本思想是通过分治法(Divide and Conquer)来实现排序。

在这里插入图片描述

  1. 选择基准值: 从数组中选择一个元素作为基准值(pivot)。
  2. 分区操作: 将数组中小于基准值的元素放置在基准值的左侧,大于基准值的元素放置在右侧,基准值则放置在正确的位置上。
  3. 递归排序: 对基准值左右两侧的子数组进行递归排序,直到整个数组有序。
实现思路说明
选择基准值通常选择数组中的第一个元素、最后一个元素或者随机一个元素作为基准值。
分区操作使用两个指针,一个指向数组的起始位置,一个指向数组的末尾位置。通过交替移动这两个指针,并交换元素,将数组分为两个部分。
递归排序对分区后的左右子数组分别进行递归排序。

快速排序的平均时间复杂度为 (O(nlogn)),最坏情况下的时间复杂度为 (O(n^2)),最好情况下的时间复杂度也为 (O(nlog n))。快速排序的性能很大程度上取决于选取的基准值。快速排序是一种原地排序算法,它的空间复杂度主要取决于递归调用栈的深度,平均空间复杂度为 (O(log n)),最坏情况下空间复杂度为 (O(n))。

快速排序是一种不稳定的排序算法,因为在分区操作过程中,相同元素的相对位置可能会发生变化。

	int sort(std::vector<int>& arr, int begin, int end)
	{
		int key = begin;
		while (begin < end)
		{
			while (begin < end && arr[end] >= arr[key]) end--;
			while (begin < end && arr[begin] <= arr[key]) begin++;
			std::swap(arr[begin], arr[end]);
		}
		std::swap(arr[key], arr[begin]);
		return begin;
	}

	void _quikSort(std::vector<int>& arr, int begin, int end)
	{
		if (begin >= end) return;
		int key = sort(arr, begin, end);
		_quikSort(arr, begin, key - 1);
		_quikSort(arr, key + 1, end);
	}

	bool quikSort(std::vector<int>& arr)
	{
		_quikSort(arr, 0, arr.size() - 1);
		return true;
	}

优化

在快速排序中使用三数取中和小区间直接插入排序的优化,可以提高快速排序的性能,尤其是在处理小规模数据时。三数取中法是一种选择基准值的方法,它可以避免选取到最坏情况下的基准值,从而提高快速排序的性能。具体步骤如下:

  1. 从待排序数组的起始、中间和末尾位置选择三个元素。
  2. 对这三个元素进行排序,并取其中间位置的元素作为基准值。

在快速排序中,当待排序的子数组规模较小时,直接插入排序的性能可能比快速排序更好。因此,可以在快速排序中添加一个判断条件,当子数组的规模小于某个阈值时,使用直接插入排序对子数组进行排序。

以下是结合三数取中和小区间直接插入排序优化的快速排序算法的代码:

	int getKey(std::vector<int>& arr, int begin, int end)
	{
		int mid = (begin + end) / 2;
		if (arr[end] > arr[begin])
		{
			if (arr[mid] > arr[end]) return end;
			else if (arr[mid] < arr[begin]) return begin;
			else return mid;
		}
		else
		{
			if (arr[mid] < arr[end]) return end;
			else if (arr[mid] > arr[begin]) return begin;
			else return mid;
		}
	}

	int sort(std::vector<int>& arr, int begin, int end)
	{
		int cur = getKey(arr, begin, end);
		std::swap(arr[cur], arr[begin]);
		int key = begin;
		while (begin < end)
		{
			while (begin < end && arr[end] >= arr[key]) end--;
			while (begin < end && arr[begin] <= arr[key]) begin++;
			std::swap(arr[begin], arr[end]);
		}
		std::swap(arr[key], arr[begin]);
		return begin;
	}

	void _quikSort(std::vector<int>& arr, int begin, int end)
	{
		if (begin >= end) return;
		if (end - begin < 20)
		{
			insertSort(arr);
		}
		else
		{
			int key = sort(arr, begin, end);
			_quikSort(arr, begin, key - 1);
			_quikSort(arr, key + 1, end);
		}
	}

	bool quikSort(std::vector<int>& arr)
	{
		_quikSort(arr, 0, arr.size() - 1);
		return true;
	}

在实际代码中,要根据具体情况选择合适的阈值来确定是否使用直接插入排序,以及合适的阈值大小。此外,三数取中法的选择也可以根据实际情况进行优化和改进。

挖坑法

挖坑法也是快速排序的一种优化方法,用于在分区操作中避免不必要的元素交换。其基本思想是通过找到一个“坑”,将基准值放入坑中,然后利用坑的位置来实现元素的移动和交换,从而实现分区操作。

在这里插入图片描述

  1. 选择基准值:从待排序数组中选择一个元素作为基准值。
  2. 分区操作:
    • 将基准值保存到一个临时变量中,此时数组中留下了一个“坑”。
    • 从数组的另一端开始(通常是右侧),找到一个小于基准值的元素,并将其填入坑中。
    • 然后从数组的左侧开始,找到一个大于基准值的元素,并将其放入刚才填入的坑中。
    • 重复上述过程,直到左右指针相遇。
    • 最后将基准值放入最后一个坑中,此时基准值左侧的元素都小于基准值,右侧的元素都大于基准值。
  3. 递归排序:对基准值左右两侧的子数组进行递归排序。

挖坑法的优点是减少了元素的交换次数,因为每次交换都需要消耗额外的时间,通过找到“坑”来避免了多余的交换操作,提高了快速排序的效率。

int potholingSort(std::vector<int>& arr, int left, int right)
{
	int cur = left, val = arr[left];
	while (left < right)
	{
		while (left < right && arr[right] >= val) right--;
		arr[cur] = arr[right];
		cur = right;
		while (left < right && arr[left] <= val) left++;
		arr[cur] = arr[left];
		cur = left;
	}
	arr[left] = val;
	return left;
}

前后指针法

int twopointSort(std::vector<int>& arr, int left, int right)
{
	int prev = left, cur = prev + 1, key = left;
	while (cur <= right)
	{
		if (arr[cur] < arr[key]) std::swap(arr[++prev], arr[cur]);
		cur++;
	}
	std::swap(arr[key], arr[prev]);
	return prev;
}

在这里插入图片描述

  1. 函数定义

    • twopointSort 函数接受一个整数向量 arr,以及数组的左右边界 leftright
    • 函数返回的是基准值最终的位置。
  2. 前后指针

    • 在该算法中,使用了两个指针 prevcur,分别表示前后指针。
    • prev 初始化为 left,表示指向数组的最左边界。
    • cur 初始化为 prev + 1,表示指向 prev 右边的第一个元素。
  3. 分区操作

    • 使用循环遍历数组,从 cur 开始向右移动,直到达到数组的右边界 right
    • 在遍历过程中,如果当前元素 arr[cur] 小于基准值 arr[key](这里的 key 是选择的基准值的索引),则将其与 prev 右边的元素交换,并将 prev 指针向右移动一位。
    • 最后,将基准值 arr[key]arr[prev] 交换,将基准值放在正确的位置上。
  4. 返回值

    • 返回的是基准值最终的位置 prev

这种前后指针的快速排序思想在于通过两个指针的移动,将小于基准值的元素放在基准值的左边,大于基准值的元素放在基准值的右边。最终,将基准值放在正确的位置上,使得左侧的元素都小于等于基准值,右侧的元素都大于等于基准值。

非递归版

非递归版本的快速排序可以使用栈来模拟递归调用的过程,以避免使用递归函数带来的额外开销。

void sort(std::vector<int>& arr, int left, int right)
{
	if (left >= right) return;
	if (right - left < 20) insertSort(arr);
	else
	{
		std::stack<std::vector<int>> st;
		st.push({ left, right });
		while (st.size())
		{
			auto tmp = st.top();
			st.pop();
			int begin = tmp[0], end = tmp[1];
			int key = twopointSort(arr, begin, end);
			if(begin < key - 1) st.push({ begin, key - 1 });
			if(key + 1 < end) st.push({ key + 1, end });
		}
	}
}

在这个非递归版本的快速排序中,使用了一个栈 stk 来模拟递归调用的过程。每次循环中,从栈中弹出当前的左右边界,然后进行分区操作,将左右子数组的边界压入栈中,以便下一轮循环继续处理。这样就实现了快速排序的非递归版本。

归并排序

归并排序是一种经典的排序算法,它采用分治法的思想,将待排序的数组分成若干个子数组,分别对子数组进行排序,然后将排好序的子数组合并成一个整体有序的数组。

在这里插入图片描述

  1. 分解:将待排序的数组递归地分解为越来越小的子数组,直到每个子数组只包含一个元素。
  2. 合并:将相邻的子数组合并成一个有序的数组,然后依次将合并后的有序数组再次合并,直到合并成一个完整的有序数组。

归并排序的关键在于合并操作,合并两个有序数组的过程是比较简单的。

  1. 分解:将数组分成两个子数组,然后分别对左右两个子数组进行归并排序。
  2. 合并:将排好序的左右两个子数组合并成一个有序数组。

递归

void _Merger(std::vector<int>& arr, int begin, int end, std::vector<int>& tmp)
{
	if (begin >= end) return;

	int mid = (begin + end) / 2;
	_Merger(arr, begin, mid, tmp);
	_Merger(arr, mid + 1, end, tmp);

	int begin1 = begin, end1 = mid;
	int begin2 = mid + 1, end2 = end;
	int cur = begin;
	while (begin1 <= end1 && begin2 <= end2)
	{
		if (arr[begin1] < arr[begin2]) tmp[cur++] = arr[begin1++];
		else tmp[cur++] = arr[begin2++];
	}
	while (begin1 <= end1) tmp[cur++] = arr[begin1++];
	while (begin2 <= end2) tmp[cur++] = arr[begin2++];

	memcpy(&arr[begin], &tmp[begin], sizeof(int) * (end - begin + 1));
}

在归并排序中,递归函数 mergeSort 对数组进行分解并递归地调用自身,直到分解出的子数组只有一个元素时停止递归。然后,通过 merge 函数将相邻的两个有序子数组合并为一个更大的有序数组。最终,整个数组就变得有序了。

非递归

void _MergerNonR(std::vector<int>& arr, std::vector<int>& tmp)
{
	int n = 1;
	while (n < arr.size())
	{
		for (int i = 0; i < arr.size(); i += 2 * n)
		{
			int begin1 = i, end1 = i + n - 1;
			int begin2 = i + n, end2 = i + 2 * n - 1;
			if (begin1 >= arr.size() || begin2 >= arr.size()) break;
			if (end2 >= arr.size()) end2 = arr.size() - 1;

			int cur = i;
			while (begin1 <= end1 && begin2 <= end2)
			{
				if (arr[begin1] < arr[begin2]) tmp[cur++] = arr[begin1++];
				else tmp[cur++] = arr[begin2++];
			}
			while (begin1 <= end1) tmp[cur++] = arr[begin1++];
			while (begin2 <= end2) tmp[cur++] = arr[begin2++];
			memcpy(&arr[i], &tmp[i], sizeof(int) * (end2 - i + 1));
		}
		n *= 2;
	}
}

在非递归版本的归并排序中,外层循环控制子数组的大小 n,初始值为1,每次循环翻倍,直到子数组的大小超过数组的长度。内层循环每次从数组的开头开始,将相邻的子数组两两合并,并将合并后的结果放回原数组。这样就完成了非递归版本的归并排序。

总结

下面是对这些排序算法的时间复杂度、空间复杂度和稳定性的总结:

排序算法时间复杂度(平均)时间复杂度(最坏)时间复杂度(最好)空间复杂度稳定性
直接插入排序O(n^2)O(n^2)O(n)O(1)稳定
希尔排序O(n log^2 n)O(n^2)O(n log n)O(1)不稳定
直接选择排序O(n^2)O(n^2)O(n^2)O(1)不稳定
堆排序O(n log n)O(n log n)O(n log n)O(1)不稳定
冒泡排序O(n^2)O(n^2)O(n)O(1)稳定
快速排序O(n log n)O(n^2)O(n log n)O(log n)不稳定
归并排序O(n log n)O(n log n)O(n log n)O(n)稳定
  • 时间复杂度
    • 平均时间复杂度表示在所有可能情况下的运行时间的期望值。
    • 最坏时间复杂度表示在最差情况下的运行时间。
    • 最好时间复杂度表示在最好情况下的运行时间。
  • 空间复杂度:表示算法在执行过程中所需的额外空间。
  • 稳定性:如果排序算法能够保持相同键值的原始顺序,则称其为稳定的,否则称为不稳定的。

根据上述总结,可以选择适合特定场景和需求的排序算法。例如,对于大规模数据集合,快速排序和堆排序通常是比较好的选择,而对于小规模数据集合,直接插入排序可能更有效。同时,如果需要保持相同键值的相对位置不变,应选择稳定的排序算法。

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

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

相关文章

论文阅读笔记 | MetaIQA: Deep Meta-learning for No-Reference Image Quality Assessment

文章目录 文章题目发表年限期刊/会议名称论文简要动机主要思想或方法架构实验结果 文章链接&#xff1a;https://doi.org/10.48550/arXiv.2004.05508 文章题目 MetaIQA: Deep Meta-learning for No-Reference Image Quality Assessment 发表年限 2020 期刊/会议名称 Publi…

Unity性能优化篇(八) 导入的模型网格优化设置

模型导入Unity后&#xff0c;可以选中这个模型&#xff0c;在Inspector窗口设置它的属性。下面说的都是可自定义选择优化的地方 Model选择卡: 1.在Model选项卡&#xff0c;启用Mesh Compression可以压缩模型&#xff0c;压缩程度越高&#xff0c;模型精度越低&#xff0c;但是…

Docker前后端项目部署

目录 一、搭建项目部署的局域网 二、redis安装 三、MySQL安装 四、若依后端项目搭建 4.1 使用Dockerfile自定义镜像 五、若依前端项目搭建 一、搭建项目部署的局域网 搭建net-ry局域网&#xff0c;用于部署若依项目 docker network create net-ry --subnet172.68.0.0/1…

网络安全审计是什么意思?与等保测评有什么区别?

网络安全审计和等保测评在信息安全领域中都是非常重要的环节。但不少人对于这两者是傻傻分不清楚&#xff0c;今天我们就来简单聊聊网络安全审计是什么意思&#xff1f;与等保测评有什么区别&#xff1f; 网络安全审计是什么意思&#xff1f; 网络安全审计是通过对网络系统和网…

python 蓝桥杯填空题

文章目录 字母数判断列名&#xff08;进制问题&#xff09;特殊日期大乘积星期几 字母数 由于是填空题&#xff0c;那么寻找的话&#xff0c;就直接让每一个位置都是A,通过计算看看是不是结果大于2022即可 判断列名&#xff08;进制问题&#xff09; 这道题目&#xff0c;我们可…

Linux篇:基础IO

一 预备知识 1. 文件内容属性&#xff0c;内容与属性都是数据&#xff0c;都要在磁盘中保存。 2. 文件分为打开的文件和没打开的文件。 3. 进程在访问一个文件的时候&#xff0c;都是要先打开这个文件。打开文件之前&#xff0c;文件在磁盘&#xff0c;打开文件之后&#xff0…

RocketMQ架构详解

文章目录 概述RocketMQ架构Broker 高可用集群刷盘策略 概述 RocketMQ一个纯java、分布式、队列模型的开源消息中间件&#xff0c;前身是MetaQ&#xff0c;是阿里研发的一个队列模型的消息中间件&#xff0c;后开源给apache基金会成为了apache的顶级开源项目&#xff0c;具有高…

Mybatis-Spring | Mybatis与Spring的“整合“

目录 : 一、配置环境1. 整合环境需导入的JAR :Spring框架所需JARMybatis框架所需JARMyBatis与Spring整合的中间JAR数据库驱动JAR包数据源所需JAR包 &#xff08;下面的例子中 : 用的不是这个数据源&#xff09; 2. 编写“配置文件” 和 “.properties文件” ( 只是概述&#xf…

mysql从旧表 取出部分列并保存到新表几种方式介绍

在MySQL中&#xff0c;从旧表取出部分列并保存到新表有多种方式&#xff0c;主要包括以下几种&#xff1a; 1. 使用INSERT INTO ... SELECT语句&#xff1a; 这是最常用的方法。通过SELECT语句从旧表中选择需要的数据&#xff0c;然后使用INSERT INTO语句将数据…

【PHP】PHP通过串口与硬件通讯,向硬件设备发送数据并接收硬件返回的数据

一、前言 之前写过两篇PHP实现与硬件串口交互的文章&#xff0c;一篇是【PHP】PHP实现与硬件串口交互&#xff0c;接收硬件发送的实时数据&#xff08;上&#xff09;_php串口通信-CSDN博客&#xff0c;另一篇是【PHP】PHP实现与硬件串口交互&#xff0c;向硬件设备发送指令数…

Vue3+Vue Router使用<transition>过渡动画实现左右分栏后台布局

摘要 利用Vue3及其配套的Vue Router实现后台管理系统中的页面过渡动画。文章首先简要介绍了Vue3的特性和Vue Router的基本用法&#xff0c;利用Vue3提供的组件以及Vue Router的路由钩子函数来实现页面过渡效果。 代码结构 在 components 里有4个组件&#xff0c;其中 Layout…

HTTPS是什么,那些行业适合部署呢?

随着在线活动的增加&#xff0c;对您共享的关键数据的威胁已经产生了严重的后果&#xff0c;包括欺诈性金融交易、在线身份盗窃等。此外&#xff0c;随着技术使用的增加&#xff0c;网络攻击也变得更加复杂和具有挑战性。 毫无疑问&#xff0c;互联网用户的数据安全意识成倍增长…

(学习日记)2024.03.06:UCOSIII第八节:空闲任务+阻塞延时+main函数修改

写在前面&#xff1a; 由于时间的不足与学习的碎片化&#xff0c;写博客变得有些奢侈。 但是对于记录学习&#xff08;忘了以后能快速复习&#xff09;的渴望一天天变得强烈。 既然如此 不如以天为单位&#xff0c;以时间为顺序&#xff0c;仅仅将博客当做一个知识学习的目录&a…

uniapp小程序获取位置权限(不允许拒绝)

需求 小程序上如果需要一些定位功能&#xff0c;那么我们需要提前获取定位权限。我们页面的所有功能后续都需要在用户同意的前提下进行&#xff0c;所以一旦用户点了拒绝&#xff0c;我们应该给予提示&#xff0c;并让用于修改为允许。 实现 1.打开手机GPS 经过测试发现即使…

第102讲:MySQL多实例与Mycat分布式读写分离的架构实践

文章目录 1.Mycat读写分离分布式架构规划2.在两台服务器中搭建八个MySQL实例2.1.安装MySQL软件2.2.创建每个MySQL实例的数据目录并初始化2.3.准备每个实例的配置文件2.4.准备每个实例的启动脚本2.6启动每台机器的MySQL多实例2.7.为每个MySQL实例设置密码2.8.查看每个MySQL实例的…

2024三掌柜赠书活动第十三期:API安全技术与实战

目录 前言 API安全威胁与漏洞 API安全技术与实践 API安全实战案例 关于《API安全技术与实战》 编辑推荐 内容简介 作者简介 图书目录 书中前言/序言 《API安全技术与实战》全书速览 结束语 前言 随着互联网的快速发展和应用程序的广泛使用&#xff0c;API&#xff…

excel统计分析——正交设计

参考资料&#xff1a;生物统计学 单因素试验通常采用完全随机设计活动随机区组设计&#xff1b;两因素试验通常采用析因设计&#xff1b;多因素试验不考虑因素间的互作时&#xff0c;可以采用拉丁方设计或正交拉丁方设计&#xff1b;需要考虑因素间的互作时&#xff0c;析因设计…

手回科技:人生的“小雨伞”,能否撑起自己的增长路?

有道是一年之计在于春。新年伊始&#xff0c;多家券商发布研报表达了对2024年保险市场表现的观点。 比如&#xff0c;开源证券表示&#xff0c;政策组合拳带来beta催化&#xff0c;保险业务端和弹性占优&#xff1b;中国银行证券指出&#xff0c;2024年&#xff0c;保险行业景…

HarmonyOS应用开发-使用Local Emulator运行应用/服务(使用本地模拟器)

1、单击File > Settings > SDK&#xff0c;下拉框选择HarmonyOS&#xff0c;勾选并下载Platforms下的System-image和Tools下的Emulator资源。 点击ok&#xff0c;等待安装完成 2、单击菜单栏的Tools > Device Manager&#xff0c;在Local Emulator页签&#xff0c;…

百度给程序员发放京东购物卡,注册即送30元购物卡

活动真实有效&#xff1a; 添加图片注释&#xff0c;不超过 140 字&#xff08;可选&#xff09;https://comate.baidu.com/?inviteCodeexf818mt 活动参与流程说明&#xff1a;点击下面的邀请链接进行登陆&#xff0c;注意一定要邀请链接&#xff0c;因为通过链接注册可以获…