常见排序算法解析

在这里插入图片描述

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

文章目录

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


插入排序

插入排序(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)稳定
  • 时间复杂度
    • 平均时间复杂度表示在所有可能情况下的运行时间的期望值。
    • 最坏时间复杂度表示在最差情况下的运行时间。
    • 最好时间复杂度表示在最好情况下的运行时间。
  • 空间复杂度:表示算法在执行过程中所需的额外空间。
  • 稳定性:如果排序算法能够保持相同键值的原始顺序,则称其为稳定的,否则称为不稳定的。

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

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

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

相关文章

STM32控制气泵和电磁阀实现

一、功能简介 使用STM32控制气泵和电磁阀的开和关&#xff0c;气泵和电磁阀的供电电压为12V。 二、实现过程 1、气泵和电磁阀的开和关均为开关量&#xff0c;实现控制方法有多种&#xff0c;比如继电器&#xff0c;但是继电器动作有噪声且体积较大&#xff0c;更好的方法为使…

Xilinx 7系列FPGA配置(ug470)

Xilinx 7系列FPGA配置&#xff08;ug470&#xff09; 配置模式串行配置模式接口从-连接方式主-连接方式串行菊花链&#xff08;非同时配置&#xff09;串行配置&#xff08;同时配置&#xff09;时序 主SPI配置模式SPIx1/x2 连接图SPIx1模式时序SPIx4 连接图SPI操作指令操作fla…

php反序列化字符逃逸

php反序列化和序列化 PHP序列化&#xff1a;serialize() 序列化是将变量或对象转换成字符串的过程&#xff0c;用于存储或传递 PHP 的值的过程中&#xff0c;同时不丢失其类型和结构。“序列化”是一种把对象的状态转化成字节流的机制 类似于这样的结构&#xff1a; O:4:&quo…

【Linux】Linux网络故障排查与解决指南

&#x1f34e;个人博客&#xff1a;个人主页 &#x1f3c6;个人专栏&#xff1a;Linux ⛳️ 功不唐捐&#xff0c;玉汝于成 目录 前言 正文 检查网络连接状态&#xff1a; 检查路由表&#xff1a; 检查DNS配置&#xff1a; 检查网络连接状态&#xff1a; 检查防火墙设…

类与对象(三)--构造函数体中的赋值和初始化列表的区别

&#x1f3a7;1构造函数体赋值 &#x1f50e;在创建对象时&#xff0c;编译器通过调用构造函数&#xff0c;给对象中各个成员变量一个合适的初始值。 ⭐️就像上述代码中的构造函数&#xff0c;其函数体的语句只能被称为赋予初值而不能称为初始化。因为初始化是在定义的同时赋…

解决WordPress更新插件或者更新版本报WordPress 需要访问您网页服务器的权限的问题

文章目录 前言一、原因二、解决步骤总结 前言 当对WordPress的插件或者版本进行更新时报错&#xff1a;要执行请求的操作&#xff0c;WordPress 需要访问您网页服务器的权限。 请输入您的 FTP 登录凭据以继续。 如果您忘记了您的登录凭据&#xff08;如用户名、密码&#xff09…

前端面试拼图-原理源码

摘要&#xff1a;最近&#xff0c;看了下慕课2周刷完n道面试题&#xff0c;记录下... 1. JS内存泄漏如何检测&#xff1f;场景有哪些? 1.1 垃圾回收 GC 垃圾回收是一种自动管理内存的机制&#xff0c;它负责在运行时跟踪内存的分配和使用情况&#xff0c;并在不再需要的对象…

Python 开发图形界面程序

用 Python 语言开发图形界面的程序&#xff0c;有2种选择&#xff1a; Tkinter 基于Tk的Python库&#xff0c;这是Python官方采用的标准库&#xff0c;优点是作为Python标准库、稳定、发布程序较小&#xff0c;缺点是控件相对较少。 PySide2/PySide6 基于Qt 的Python库&#x…

鸿蒙Harmony应用开发—ArkTS声明式开发(通用属性:浮层)

设置组件的遮罩文本。 说明&#xff1a; 从API Version 7开始支持。后续版本如有新增内容&#xff0c;则采用上角标单独标记该内容的起始版本。 overlay overlay(value: string | CustomBuilder, options?: { align?: Alignment; offset?: { x?: number; y?: number } })…

6. Gin集成redis

文章目录 一&#xff1a;连接Redis二&#xff1a;基本使用三&#xff1a;字符串四&#xff1a;列表五&#xff1a;哈希六&#xff1a;Set七&#xff1a;管道八、事务九&#xff1a;示例 代码地址&#xff1a;https://gitee.com/lymgoforIT/golang-trick/tree/master/14-go-redi…

跨境电商选品API商品采集API接入指南

选品是每个电商卖家的必经之路&#xff0c;产品的好坏将直接决定店铺的盈利、发展方向。选择合适的产品可以让卖家事半功倍&#xff0c;快速爆单。 用API实现代购系统和1688淘宝等平台的商品信息对接&#xff0c;可以免去很多选品工作。 item_get 获得淘宝商品详情item_get_p…

Maven入门(作用,安装配置,Idea基础maven,Maven依赖,Maven构建项目)【详解】

目录 一. Maven的作用 1.依赖管理 2.统一项目结构 3.项目构建 二.Maven安装配置 1. Maven的仓库类型 2 加载jar的顺序 3. Maven安装配置 4.安装Maven 5.配置仓库 三.idea集成maven 1.给当前project集成maven 2.给新建project集成maven 3.创建maven项目 4.pom…

闫震海:腾讯音乐空间音频技术的发展和应用 | 演讲嘉宾公布

一、3D 音频 3D 音频分论坛将于3月27日同期举办&#xff01; 3D音频技术不仅能够提供更加真实、沉浸的虚拟世界体验&#xff0c;跨越时空的限制&#xff0c;探索未知的世界。同时&#xff0c;提供更加丰富、立体的情感表达和交流方式&#xff0c;让人类能够更加深入地理解彼此&…

Spring——Bean的作用域

bean的作用域 Bean Scope Scope说明singleton&#xff08;默认情况下&#xff09;为每个Spring IoC容器将单个Bean定义的Scope扩大到单个对象实例。prototype将单个Bean定义的Scope扩大到任何数量的对象实例。session将单个Bean定义的Scope扩大到一个HTTP Session 的生命周期…

Linux之cd、pwd、mkdir 命令

cd命令&#xff0c;切换目录 1&#xff09;当Linux终端&#xff08;命令行&#xff09;打开的时候&#xff0c;会默认以用户的HOME目录作为当前的工作目录。 2&#xff09;我们可以通过cd命令&#xff0c;更改当前所在的工作目录。 3&#xff09;cd命令来自英文&#xff1a;C…

LeetCode-第67题-二进制求和

1.题目描述 给你两个二进制字符串 a 和 b &#xff0c;以二进制字符串的形式返回它们的和。 2.样例描述 3.思路描述 将两个二进制字符串转换成整型&#xff0c;然后相加后的整型转为二进制字符串 4.代码展示 class Solution(object):def addBinary(self, a, b):# 将字符串…

11. C语言标准函数库

C语言制定了一组使用方式通用的函数&#xff0c;称为C语言标准函数库&#xff0c;用于实现编程常用功能&#xff0c;标准函数库由编译器系统提供&#xff0c;并按功能分类存储在不同源代码文件中&#xff0c;调用标准库内函数时需要首先使用 #include 连接对应的源代码文件。 【…

操作教程|使用MeterSphere对恒生UFX系统进行压力测试

恒生UFX&#xff08;United Finance Exchange&#xff0c;统一金融交换&#xff09;系统&#xff08;以下简称为“UFX系统”&#xff09;&#xff0c;是一款帮助证券公司统一管理外部接入客户的系统&#xff0c;该系统整体上覆盖了期货、证券、基金、银行、信托、海外业务等各类…

【CSP试题回顾】201604-1-折点计数

CSP-201604-1-折点计数 解题代码 #include <iostream> #include <vector> #include <algorithm> using namespace std;int n, pointSum;int main() {cin >> n;vector<int>myData(n);for (int i 0; i < n; i){cin >> myData[i];}// 统…

LinkedList集合源码分析

LinkedList集合源码分析 文章目录 LinkedList集合源码分析一、字段分析二、构造函数分析三、方法分析四、总结 看到实现了Deque 就要联想到这个数据结构肯定是属于双端队列了。Queue 表示队列&#xff0c;Deque表示双端队列。 一、字段分析 LinkedList 字段很少&#xff0c;就…