C++常见十种排序方式

news2024/11/28 3:30:50

目录

前言

1、选择排序

介绍

参考代码

2、冒泡排序

        介绍

        参考代码

3、插入排序

        介绍

        参考代码 

4、希尔排序 

        介绍

        参考代码 

5、快速排序

        介绍

        参考代码 

6、并归排序

        介绍

        参考代码 

7、堆排序

        介绍

        参考代码

8、基数排序 

        介绍

          参考代码

9、计数排序

        介绍

        参考代码

10、桶排序 

        介绍

        参考代码 

总结


前言

        本期我们将学习C++常见的十种排序方式,它们的优缺点作者都会写在这里。

        排序(Sorting)是计算机程序设计中的一种重要操作,其功能是对一个数据元素集合或序列重新排列成一个按数据元素某个项值有序的序列。

        常见的快速排序、归并排序、堆排序以及冒泡排序等都属于比较类排序算法。比较类排序是通过比较来决定元素间的相对次序,由于其时间复杂度不能突破,因此也称为非线性时间比较类排序。在冒泡排序之类的排序中,问题规模为 O(n^2)。在归并排序、快速排序之类的排序中,问题规模通过分治法消减为 log n次,所以时间复杂度平均 O(nlogn)

        比较类排序的优势是,适用于各种规模的数据,也不在乎数据的分布,都能进行排序。可以说,比较排序适用于一切需要排序的情况。

        而计数排序、基数排序、桶排序则属于非比较类排序算法。非比较排序不通过比较来决定元素间的相对次序,而是通过确定每个元素之前,应该有多少个元素来排序。由于它可以突破基于比较排序的时间下界,以线性时间运行,因此称为线性时间非比较类排序。非比较排序只要确定每个元素之前的已有的元素个数即可,所有一次遍历即可解决。算法时间复杂度O(n).

        非比较排序时间复杂度低,但由于非比较排序需要占用空间来确定唯一位置。所以对数据规模和数据分布有一定的要求。


1、选择排序

介绍

        选择排序(Selection sort)是一种简单直观的排序算法。它的工作原理是:第一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,然后再从剩余的未排序元素中寻找到最小(大)元素,然后放到已排序的序列的末尾。以此类推,直到全部待排序的数据元素的个数为零。选择排序是不稳定的排序方法。

        每次从待排序列中选出一个最小值,然后放在序列的起始位置,直到全部待排数据排完即可。实际上,我们可以一趟选出两个值,一个最大值一个最小值,然后将其放在序列开头和末尾,这样可以使选择排序的效率快一倍。

时间复杂度:最坏情况:O(n^2)
                      最好情况:O(n^2)
空间复杂度:O(1)

参考代码

//选择排序
void swap(int* a, int* b)
{
	int tem = *a;
	*a = *b;
	*b = tem;
}
void SelectSort(int* arr, int n)
{
	//保存参与单趟排序的第一个数和最后一个数的下标
	int begin = 0, end = n - 1;
	while (begin < end)
	{
		//保存最大值的下标
		int maxi = begin;
		//保存最小值的下标
		int mini = begin;
		//找出最大值和最小值的下标
		for (int i = begin; i <= end; ++i)
		{
			if (arr[i] < arr[mini])
			{
				mini = i;
			}
			if (arr[i] > arr[maxi])
			{
				maxi = i;
			}
		}
		//最小值放在序列开头
		swap(&arr[mini], &arr[begin]);
		//防止最大的数在begin位置被换走
		if (begin == maxi)
		{
			maxi = mini;
		}
		//最大值放在序列结尾
		swap(&arr[maxi], &arr[end]);
		++begin;
		--end;
	}
}

2、冒泡排序

       

        介绍

        冒泡排序(Bubble Sort),是一种计算机科学领域的较简单的排序算法。

它重复地走访过要排序的元素列,依次比较两个相邻的元素,如果顺序(如从大到小、首字母从Z到A)错误就把他们交换过来。走访元素的工作是重复地进行,直到没有相邻元素需要交换,也就是说该元素列已经排序完成。

这个算法的名字由来是因为越小的元素会经由交换慢慢“浮”到数列的顶端(升序或降序排列),就如同碳酸饮料中二氧化碳的气泡最终会上浮到顶端一样,故名“冒泡排序”。

        左边大于右边交换一趟排下来最大的在右边。

时间复杂度:最坏情况:O(n^2)
      最好情况:O(n)
空间复杂度:
O(1)

        

        参考代码

//冒泡排序
void BubbleSort(int* arr, int n)
{
	int end = n;
	while (end)
	{
		int flag = 0;
		for (int i = 1; i < end; ++i)
		{
			if (arr[i - 1] > arr[i])
			{
				int tem = arr[i];
				arr[i] = arr[i - 1];
				arr[i - 1] = tem;
				flag = 1;
			}
		}
		if (flag == 0)
		{
			break;
		}
		--end;
	}
}

3、插入排序

        介绍

        插入排序,一般也被称为直接插入排序。对于少量元素的排序,它是一个有效的算法。插入排序是一种最简单的排序方法,它的基本思想是将一个记录插入到已经排好序的有序表中,从而一个新的、记录数增1的有序表。在其实现过程使用双层循环,外层循环对除了第一个元素之外的所有元素,内层循环对当前元素前面有序表进行待插入位置查找,并进行移动 。

        在待排序的元素中,假设前n-1个元素已有序,现将第n个元素插入到前面已经排好的序列中,使得前n个元素有序。按照此法对所有元素进行插入,直到整个序列有序。
  但我们并不能确定待排元素中究竟哪一部分是有序的,所以我们一开始只能认为第一个元素是有序的,依次将其后面的元素插入到这个有序序列中来,直到整个序列有序为止。

时间复杂度:最坏情况下为O(N*N),此时待排序列为逆序,或者说接近逆序
      最好情况下为O(N),此时待排序列为升序,或者说接近升序。
空间复杂度:O(1)
 

        参考代码 

void InsertSort(int* arr, int n)
{
	for (int i = 0; i < n - 1; ++i)
	{
		//记录有序序列最后一个元素的下标
		int end = i;
		//待插入的元素
		int tem = arr[end + 1];
		//单趟排
		while (end >= 0)
		{
			//比插入的数大就向后移
			if (tem < arr[end])
			{
				arr[end + 1] = arr[end];
				end--;
			}
			//比插入的数小,跳出循环
			else
			{
				break;
			}
		}
		//tem放到比插入的数小的数的后面
		arr[end  + 1] = tem;
		//代码执行到此位置有两种情况:
		//1.待插入元素找到应插入位置(break跳出循环到此)
		//2.待插入元素比当前有序序列中的所有元素都小(while循环结束后到此)
	}
}


4、希尔排序 

        介绍

     

        希尔排序(Shell's Sort)是插入排序的一种又称“缩小增量排序”(Diminishing Increment Sort),是直接插入排序算法的一种更高效的改进版本。希尔排序是非稳定排序算法。该方法因 D.L.Shell 于 1959 年提出而得名。

        希尔排序是把记录按下标的一定增量分组,对每组使用直接插入排序算法排序;随着增量逐渐减少,每组包含的关键词越来越多,当增量减至 1 时,整个文件恰被分成一组,算法便终止。 

        希尔排序,先将待排序列进行预排序,使待排序列接近有序,然后再对该序列进行一次插入排序,此时插入排序的时间复杂度为O(N)。

时间复杂度平均:O(n^{1.3})
空间复杂度:O(1)

        

        参考代码 

        

//希尔排序
void ShellSort(int* arr, int n)
{
	int gap = n;
	while (gap>1)
	{
		//每次对gap折半操作
		gap = gap / 2;
		//单趟排序
		for (int i = 0; i < n - gap; ++i)
		{
			int end = i;
			int tem = arr[end + gap];
			while (end >= 0)
			{
				if (tem < arr[end])
				{
					arr[end + gap] = arr[end];
					end -= gap;
				}
				else
				{
					break;
				}
			}
			arr[end + gap] = tem;
		}
	}
}


5、快速排序

        介绍

        快速排序(Quicksort),计算机科学词汇,适用领域Pascal,C++等语言,是对冒泡排序算法的一种改进。

        1、选出一个key,一般是最左边或是最右边的。
        2、定义一个begin和一个end,begin从左向右走,end从右向左走。(需要注意的是:若选择最左边的数据作为key,则需要end先走;若选择最右边的数据作为key,则需要bengin先走)。
        3、在走的过程中,若end遇到小于key的数,则停下,begin开始走,直到begin遇到一个大于key的数时,将begin和right的内容交换,end再次开始走,如此进行下去,直到begin和end最终相遇,此时将相遇点的内容与key交换即可。(选取最左边的值作为key)
        4.此时key的左边都是小于key的数,key的右边都是大于key的数
        5.将key的左序列和右序列再次进行这种单趟排序,如此反复操作下去,直到左右序列只有一个数据,或是左右序列不存在时,便停止操作,此时此部分已有序。

时间复杂度:O(N*log_{2}^{N})

快速排序的过程类似于二叉树其高度为logN,每层约有N个数,如下图所示:

        

        参考代码 

//快速排序   hoare版本(左右指针法)
void QuickSort(int* arr, int begin, int end)
{
	//只有一个数或区间不存在
	if (begin >= end)
		return;
	int left = begin;
	int right = end;
	//选左边为key
	int keyi = begin;
	while (begin < end)
	{
		//右边选小   等号防止和key值相等    防止顺序begin和end越界
		while (arr[end] >= arr[keyi] && begin < end)
		{
			--end;
		}
		//左边选大
		while (arr[begin] <= arr[keyi] && begin < end)
		{
			++begin;
		}
		//小的换到右边,大的换到左边
		swap(&arr[begin], &arr[end]);
	}
	swap(&arr[keyi], &arr[end]);
	keyi = end;
	//[left,keyi-1]keyi[keyi+1,right]
	QuickSort(arr, left, keyi - 1);
	QuickSort(arr,keyi + 1,right);
}



//快速排序法  挖坑法
void QuickSort1(int* arr, int begin, int end)
{
	if (begin >= end)
		return;
	int left = begin,right = end;
	int key = arr[begin];
	while (begin < end)
	{
		//找小
		while (arr[end] >= key && begin < end)
		{
			--end;
		}
		//小的放到左边的坑里
		arr[begin] = arr[end];
		//找大
		while (arr[begin] <= key && begin < end)
		{
			++begin;
		}
		//大的放到右边的坑里
		arr[end] = arr[begin];
	}
	arr[begin] = key;
	int keyi = begin;
	//[left,keyi-1]keyi[keyi+1,right]
	QuickSort1(arr, left, keyi - 1);
	QuickSort1(arr, keyi + 1, right);
}



//单趟排
int PartSort(int* arr, int begin, int end)
{
	int key = arr[begin];
	while (begin < end)
	{
		while (key <= arr[end] && begin < end)
		{
			--end;
		}
		arr[begin] = arr[end];
		while (key >= arr[begin] && begin < end)
		{
			++begin;
		}
		arr[end] = arr[begin];
	}
	arr[begin] = key;
	int meeti = begin;
	return meeti;
}

void QuickSortNoR(int* arr, int begin, int end)
{
	stack<int> st;
	//先入右边
	st.push(end);
	//再入左边
	st.push(begin);
	while (!st.empty())
	{
		//左区间
		int left = st.top();
		st.pop();
		//右区间
		int right = st.top();
		st.pop();
		//中间数
		int mid = PartSort(arr, left, right);
		//当左区间>=mid-1则证明左区间已经排好序了
		if (left < mid - 1)
		{
			st.push(mid - 1);
			st.push(left);
		}
		//当mid+1>=右区间则证明右区间已经排好序
		if (right > mid + 1)
		{
			st.push(right);
			st.push(mid + 1);
		}
	}
}



//快速排序法  前后指针版本
void QuickSort2(int* arr, int begin, int end)
{
	if (begin >= end)
		return;
	int cur = begin, prev = begin - 1;
	int keyi = end;
	while (cur != keyi)
	{
		if (arr[cur] < arr[keyi] && ++prev != cur)
		{
			swap(&arr[cur], &arr[prev]);
		}
		++cur;
	}
	swap(&arr[++prev],&arr[keyi]);
	keyi = prev;
	//[begin,keyi -1]keyi[keyi+1,end]
	QuickSort2(arr, begin, keyi - 1);
	QuickSort2(arr, keyi + 1, end);

}


6、并归排序

        介绍

        归并排序(Merge Sort)是一种经典的排序算法,它采用了分治法的策略。

        将初始序列的n个元素看成n个有序的子序列,每个子序列中只有一个元素,将其两两归并,得到n/2个长度为2(或1、子序列不为偶数则有落单)的有序子序列,再两两归并…以此类推直到得到n长的有序序列。

时间复杂度O(n log n)
空间复杂度O(n)

 

        参考代码 

/*
 题目:归并排序
	划分成很小的组,然后两两归并
*/
#include<iostream>
using namespace std;

void Merge(int[], int, int[], int, int, int)  //归并函数的声明【把归并函数提到该函数前面,则不用声明】
//归并排序
//参数:
//		numbers[]:原数组
//		length:数组元素的个数(数组长度)
//		temp[]:辅助数组
//		begin:数组开头的下标
//		end:数组结尾的下标
void MergeSort(int numbers[], int length, int temp[], int begin, int end)
{
	//1. 同样判断传入的参数是否有效
	if (numbers == nullptr || length <= 0 || begin < 0 || end >= length)
		throw new exception("Invalid input.");
	
	//2. 作为递归的结束条件,开始下标和结束下标相等时,说明子序列中只有一个元素,看作有序的
	if (end - begin == 0)
		return;

	//3. 定义中间变量,将数组分半【如果有7个元素,下标0-6,则middle=3,数组分为长度为4和3的两段】
	int middle = ((end - begin) / 2 ) + begin;
	//4. 递归,先递归左半边,再递归右半边,将左右子序列不断分为长度为1的子序列才停止递归
	MergeSort(numbers, length, temp, begin, middle);
	MergeSort(numbers, length, temp, middle + 1, end);
	//5. 再慢慢归并
	Merge(numbers, length, temp, begin, end, middle);
}
//归并函数
//参数:
//		numbers[]:原数组
//		length:数组元素的个数(数组长度)
//		temp[]:辅助数组
//		begin:数组开头的下标
//		end:数组结尾的下标
//		middle:数组中间的下标
void Merge(int numbers[], int length, int temp[], int begin, int end, int middle)
{
	//1. 判断是否有不符合要求的参数传入,有则抛出错误
	if (numbers == nullptr || length <= 0 || begin < 0 || end >= length)
		throw new exception("Invalid input.");

	//2. 将原序列从中分开
	int leftIndex = begin;		//左边序列的开始(左边序列的结尾是middle)
	int rightIndex = middle + 1;//右边序列的开始(右边序列的结尾是end)
	int tempIndex = begin;		//辅助数组的下标
	//3. 当左右子序列尚未到头时,循环
	while (leftIndex <= middle && rightIndex <= end)
	{
		//4. 两两对比判断,谁大谁就放入辅助数组,同时指针后移
		if (numbers[leftIndex] < numbers[rightIndex])
			temp[tempIndex] = numbers[leftIndex++];
		else
			temp[tempIndex] = numbers[rightIndex++];
		//5. 辅助数组下标++
		++tempIndex;
	}

	//6. 当左边或右边子序列尚未到头时,直接放入辅助数组
	while (leftIndex <= middle)
		temp[tempIndex++] = numbers[leftIndex++];

	while (rightIndex <= end)
		temp[tempIndex++] = numbers[rightIndex++];

	//7. 再将辅助数组中已经有序的元素覆盖掉原数组中无序的元素,使原数组变成部分有序
	for (int i = begin; i <= end; ++i)
		numbers[i] = temp[i];
}
//简单测试
int main(int arvc, char* argv[])
{
	const int length = 9;
	int nums[length] = { 18, 7, 23, 3, 9, 32, 10 , 99, 0};
	int *temp = new int[length];

	MergeSort(nums, length, temp, 0, 8);

	for (int i = 0; i < length; i++)
		cout << nums[i] << "  ";

	delete[] temp;
	temp = nullptr;
	cout << endl;
	return 0;
}


7、堆排序

        介绍

        堆排序(英语:Heapsort)是指利用堆这种数据结构所设计的一种排序算法。堆是一个近似完全二叉树的结构,并同时满足堆积的性质:即子结点的键值或索引总是小于(或者大于)它的父节点。

        参考代码

#include<iostream>
 
using namespace std;
void sw(int &a,int &b) {
	int temp = a;
	a = b;
	b = temp;
}
// arr[]为完全二叉树层序遍历得到的数组
// n为完全二叉树的节点,即数组长度
// i为待维护的节点
void heapify(int arr[], int n, int i) {    //把这个二叉树先堆化
 
	//递归出口
	if (i >= n) return;
 
	int largest = i;
	int lson = i * 2 + 1;
	int rson = i * 2 + 2;
	if (lson < n && arr[largest] < arr[lson]) {  //和左孩子数值比较,找到最大节点,赋值下标
		largest = lson;
	}
	if (rson < n && arr[largest] < arr[rson]) {  //和右孩子数值比较,找到最大节点,赋值下标
		largest = rson;
	}
	if (largest != i) {   //如果现在的最大值下标和之前的不一样,那么交换二者的数值
		//sw(arr[largest], arr[i]);
		swap(arr[largest], arr[i]);
		heapify(arr, n, largest); //进行一个递归,因为在上一层的节点交换完之后,无法保证下边父节点大于孩子节点
	}
 
}
void heap_sort(int arr[], int n) {
	//建堆
	int lastNode = n - 1;    //从后往前建堆
	int parent = (lastNode - 1) / 2;
	for (int i = parent; i >= 0; i--) {
		heapify(arr, n, i);
	}
 
	//堆排序
	for (int i = n - 1; i >= 0; i--) {
		sw(arr[i], arr[0]);
		heapify(arr, i, 0);
	}
}
int main() {
	int arr[5] = { 5,4,3,2,1 };
	heap_sort(arr, sizeof(arr) / sizeof(arr[0]));
	for (int i = 0; i < sizeof(arr) / sizeof(arr[0]); i++) {
		cout << arr[i] << endl;
	}
 
	return 0;
}


8、基数排序 

        介绍

        基数排序(radix sort)属于“分配式排序”(distribution sort),又称“桶子法”(bucket sort)或bin sort,顾名思义,它是透过键值的部份资讯,将要排序的元素分配至某些“桶”中,藉以达到排序的作用,基数排序法是属于稳定性的排序,其时间复杂度为O (nlog(r)m),其中r为所采取的基数,而m为堆数,在某些时候,基数排序法的效率高于其它的稳定性排序法。

        1.计算出得到的序列中的位数最大的数字的位数,以便确定需要进行几次排序
        2.创建取出任意数字任意位数的函数,用于对数字的排序
        3.接下来便可以开始排序

时间复杂度:O(n*d)

空间复杂度:O(n)

 

          参考代码

        

#include <iostream>
#include <vector>
using namespace std;


int MaxBit(vector<int> input)    //求出数组中最大数的位数
{
	int max_num = input[0];      //默认最大数为第一个数字
	for (int i = 0; i < input.size(); i++)  //找出数组中的最大数
	{
		if (input[i] > max_num)
		{
			max_num = input[i];
		}
	}
	int p = 0;
	while (max_num > 0)
	{
		p++;
		max_num /= 10;   //每次除以10取整,即可去掉最低位
	}
	return p;
}

int GetNum(int num, int d)   //取出所给数字的第d位数字
{
	int p = 1;
	while (d - 1 > 0)
	{
		p *= 10;
		d--;
	}
	return num / p % 10;
}

vector<int> RadixSort(vector<int> input, int length)   //基数排序
{
	vector<int> bucket(length);   //创建临时存放排序过程中的数据
	vector<int> count(10);   //创建按位计数的技术容器,即记录排序中按个位、十位...各个数的位置的个数

	for (int d = 1; d <= MaxBit(input); d++) {
		// 计数器清0
		for (int i = 0; i < 10; i++) {
			count[i] = 0;
		}

		// 统计各个桶中的个数
		for (int i = 0; i < length; i++) {
			count[GetNum(input[i], d)]++;
		}

		for (int i = 1; i < 10; i++) {     //得到每个数应该放入bucket中的位置
			count[i] += count[i - 1];
		}

		for (int i = length - 1; i >= 0; i--) {  //采用倒序进行排序是为了不打乱已经排好的顺序
			int k = GetNum(input[i], d);
			bucket[count[k] - 1] = input[i];
			count[k]--;
		}


		for (int j = 0; j < length; j++)    // 临时数组复制到 input 中
		{
			input[j] = bucket[j];
		}
	}
	return input;
}

int main()
{
	int arr[] = { 50, 123, 543, 187, 49, 30, 0, 2, 11, 100 };
	vector<int> test(arr, arr + sizeof(arr) / sizeof(arr[0]));
	cout << "排序前:";
	for (int i = 0; i < test.size(); i++) {
		cout << test[i] << " ";
	}
	cout << endl;

	vector<int> result = test;
	result = RadixSort(result, result.size());
	cout << "排序后:";
	for (int i = 0; i < result.size(); i++) {
		cout << result[i] << " ";
	}
	cout << endl;
	system("pause");
}


9、计数排序

        介绍

        计数排序是一个非基于比较的排序算法,该算法于1954年由 Harold H. Seward 提出。它的优势在于在对一定范围内的整数排序时,它的复杂度为Ο(n+k)(其中k是整数的范围),快于任何比较排序算法。 [1] 当然这是一种牺牲空间换取时间的做法,而且当O(k)>O(n*log(n))的时候其效率反而不如基于比较的排序(基于比较的排序的时间复杂度在理论上的下限是O(n*log(n)), 如归并排序,堆排序)

  1. 找出待排序的数组中最大和最小的元素
  2. 统计数组中每个值为i的元素出现的次数,存入数组C的第i
  3. 对所有的计数累加(从C中的第一个元素开始,每一项和前一项相加)
  4. 反向填充目标数组:将每个元素i放在新数组的第C[i]项,每放一个元素就将C[i]减去1

时间复杂度:O(n+k)

 

        

        参考代码

        

#include <stdlib.h>

void countingSort(int *ini_arr, int *sorted_arr, int n, int maxValue)
{
    int *count_arr = (int *)malloc(sizeof(int) * maxValue);
    int i, j, k;
    for (k = 0; k <= maxValue; k++)
        count_arr[k] = 0;
    for (i = 0; i < n; i++)
        count_arr[ini_arr[i]]++;
    for (k = 1; k <= maxValue; k++)
        count_arr[k] += count_arr[k - 1];
    for (j = n; j > 0; j--)
        sorted_arr[--count_arr[ini_arr[j - 1]]] = ini_arr[j - 1];
    free(count_arr);
}


10、桶排序 

        介绍

        桶排序(Bucket Sort)是一种排序算法,其工作原理是将数组分配到有限数量的桶中,然后对每个桶中的元素进行排序,最后再将各个桶中的数据有序的合并起来。

    原理:将数值作为桶号,遍历整个数组,将相应的桶进行计数
        1、 遍历原数组,找到最大值max,然后申请max+1个空间(桶),初始化为0(下标为0-max),即    vector<int>bucket(max+1,0)
        2、 再次遍历原数组,找到每个数值对应的桶号,并对桶计数++,即bucket[vec[i]]++
        3、 遍历桶数组,看对应的桶内计数为几就取出几下下标值(桶号),放到原数组中。

时间复杂度: O(n)

空间复杂度:O(n+k)

        参考代码 

#include<iostream>
#include<vector>
using namespace std;
void bucketSort(vector<int>&vec, int n)
{
	int max = vec[0];
	for (int i = 0; i < n; i++)
	{
		if (vec[i] > max)
		{
			max = vec[i];
		}
	}
	//申请max+1个桶
	//int *bucket = new int[max + 1];
	//给每个桶赋初值为0;
	//memset(bucket, 0, (max + 1) * sizeof(int));
	vector<int>bucket(max + 1, 0);

	//遍历原数组,把相应的数放到相应的桶里
	for (int i = 0; i < n; i++)
	{
		bucket[vec[i]]++;
	}
	int index = 0;
	//从桶里把数取出来, i代表的数值对应桶下标, bucket[i]代表的是个数
	for (int i = 0; i < bucket.size(); i++)
	{
		while (bucket[i] > 0)
		{
			vec[index++] = i;
			bucket[i]--;
		}
	}
}
int main()
{
	vector<int>vec = { 2,3,5,8,9,7,4,6,1 };
	bucketSort(vec, vec.size());
	for (auto it : vec)
	{
		cout << it << " ";
	}
	return 0;
}


总结

        

        在编写合理的情况下,简单排序算法是稳定的;快速排序、堆排序是不稳定的。在CSP中,往往排序是没有附带其他项目的,也就不要求排序稳定。快速排序、堆排序仍然是最佳选择。可是有没有时间复杂度为O(nlogn)的稳定的排序算法呢?有的。归并排序基于分治思想:把要排序的数组平分两半,对两部分分别排序(递归地)后再合并起来。合并时,将一个数组按顺序插入另一个数组中,需要开辟一个暂存数组。利用空间优化,可只用开辟一个与原数组等大的数组。归并排序的优缺点都很明显。无论情形如何,它的比较次数、赋值次数都稳定在nlogn,没有最差情况,运行时间与快速排序、堆排序相当。而且,它是稳定的排序算法。但是,它的内存占用会达到快速排序、堆排序的两倍,合理选用排序算法。

        下面是一些主排序算法的优缺点比较:竞赛时使用容易造成内存超出限制。

        

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

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

相关文章

用户需求甄别和筛选的6大标准

产品经理日常经常接收到大量的需求&#xff0c;并不是所有的需求都需要开发&#xff0c;需要进行甄别和筛选&#xff0c;这样有利于确保项目的成功、优化资源利用以及提高产品质量。 那么针对这些用户需求进行甄别或筛选的评判标准是什么&#xff1f;需求筛选可以说是初步的需求…

设计模式-工厂模式设计与详解

一、设计模式介绍 设计模式是我们开发中常常需要面对的核心概念&#xff0c;它们是解决特定问题的模板或者说是经验的总结。这些模式被设计出来是为了让软件设计更加清晰、代码更加可维护且能应对未来的变化。良好的设计模式不仅能解决重复代码的问题&#xff0c;还能使团队中…

关于修改ant-design-vue的table组件背景色遇到闪动的问题

项目中需要修改表格的背景色为以下样式 修改完之后发现表格行还有个hover的背景色&#xff0c;于是再次重置样式如下 .ant-table-tbody > tr {&:hover {td {// background: red !important;background: transparent !important;}}}这样重置之后&#xff0c;hover的样式…

【中级软件设计师】上午题16-算法(应试考试简略版)

上午题16-算法 1 回溯法1.1 n皇后问题 2 分治法3 动态规划3.1 0-1背包问题3.2 最长公共子序列3.3 矩阵连乘 4 贪心算法5 分支限界法总结 1 回溯法 深度优先方法搜索 1.1 n皇后问题 2 分治法 一般来说&#xff0c;分治算法在每一层递归上都有3个步骤 &#xff08;1&#xff…

【C++】详解STL的适配器容器之一:优先级队列 priority_queue

目录 堆算法 概述 向下调整建堆 向上调整建堆 建堆算法 仿函数 概述 使用介绍 emtpy size top push pop 模拟实现 仿函数 框架 向下调整算法 向上调整算法 pop push empty top 要理解优先级队列&#xff0c;需要有如下知识 STL容器之一的vector&#xf…

嵌入式:基于STM32的RFID访问控制系统

在商业和住宅建筑中&#xff0c;访问控制系统是确保安全的关键组件。使用射频识别&#xff08;RFID&#xff09;技术&#xff0c;我们可以创建一个安全、方便的门禁系统。本教程将详细说明如何使用STM32微控制器实现RFID基础的门禁系统&#xff0c;该系统能够控制电子锁并记录访…

品鉴中的品鉴笔记:如何记录和分享自己的品鉴心得

品鉴云仓酒庄雷盛红酒的过程&#xff0c;不仅是品尝美酒&#xff0c;更是一次与葡萄酒深度对话的旅程。为了更好地记录和分享自己的品鉴心得&#xff0c;养成写品鉴笔记的习惯是十分必要的。 首先&#xff0c;选择一个适合的记录工具。可以是传统的笔记本&#xff0c;也可以是…

爆!1688「搜索推广」实操打法,8大要点快速上手!

想要1688平台上提高商品的搜索排名和曝光率&#xff0c;吸引更多的潜在客户并提升销量&#xff0c;店雷达将尽量具体地介绍相关操作方法和运营思路&#xff0c;建议大家收藏起来慢慢看。 一、了解1688平台搜索方式 1、品搜&#xff1a;这是买家最常用的搜索方式&#xff0c;通…

消息中间件是什么?有什么用?常见的消息中间件有哪些?

1.什么是消息中间件&#xff1f; 消息中间件基于队列模型在网络环境中为应用系统提供同步或异步、可靠的消息传输的支撑性软件系统。 2.现实中的痛点&#xff1a; 1.Http请求基于请求与响应的模型&#xff0c;在高并发的情况下&#xff0c;客户端发送大量的请求达到服务器端…

C# WinForm —— 19 PictureBox 介绍

1. 简介 PictureBox 主要用于显示图像&#xff0c;也可以给它注册单击事件&#xff0c;来把它变成一个按钮 2. 常用属性 属性解释(Name)控件ID&#xff0c;在代码里引用的时候会用到,一般以 pixB 开头BackColor控件的背景色BackgroundImage控件的背景图像BorderStylePictur…

开放式耳机什么品牌最好?2024五款新晋爆款产品推荐!

​如今的耳机市场天下三分&#xff0c;有线入耳式耳机、蓝牙无线入耳式耳机以及开放式耳机&#xff0c;传统的有线入耳式耳机戴着不稳就算了&#xff0c;线很容易揉成一团&#xff0c;看着就头大&#xff1b;无线入耳式的耳机&#xff0c;同样面临着戴着不稳的问题&#xff0c;…

如何在群晖NAS中开启FTP并实现使用公网地址远程访问传输文件

文章目录 1. 群晖安装Cpolar2. 创建FTP公网地址3. 开启群晖FTP服务4. 群晖FTP远程连接5. 固定FTP公网地址6. 固定FTP地址连接 本文主要介绍如何在群晖NAS中开启FTP服务并结合cpolar内网穿透工具&#xff0c;实现使用固定公网地址远程访问群晖FTP服务实现文件上传下载。 Cpolar内…

Nginx内网环境开启https

文章目录 前言一、open-ssl1. 验证2. 安装3.生成ssl证书 一、nginx1. 验证支持模块2. 安装必要模块2.1 重新编译nginx2.2 替换原文件 3. 配置https 总结 前言 nginx开启https前提&#xff1a; 服务器支持open-sslnginx 包含--with-http_ssl_module --with-stream --with-stre…

JavaScript原理篇——Promise原理及笔试题实战演练

Promise 是 JavaScript 中用于处理异步操作的对象&#xff0c;它代表了一个可能还没有完成的操作的最终完成或失败&#xff0c;以及其结果值。Promise 对象有三种状态&#xff1a; Pending&#xff08;进行中&#xff09;&#xff1a;初始状态&#xff0c;既不是成功&#xff0…

前端js面试题--从字符串中删除删除注释代码

问题&#xff1a;从字符串中删除删除注释代码 描述&#xff1a; solution(weex,rex # and react\nflutter\nnative ssss !hybrid app, [#, !]) 写一个solution函数清除后面参数数组里面的字符串 打印效果 代码1 思路&#xff1a; 将字符全凡是有去掉标志符号的全部添加\n…

flutter 使用Scrollbar 时出现 滚动条不置顶问题

Flutter 使用 CupertinoScrollbar 、Scrollbar 与 ListView.builder 结合使用时&#xff0c; 当把 ListView.builder 边距设置为 padding: const EdgeInsets.all(0) 的时候&#xff0c; Scrollbar 的滚动条不置顶。 如图&#xff1a;右侧边上的滚动条 解决方法&#xff1a; …

什么是.faust勒索病毒?应该如何防御?

faust勒索病毒详细介绍 faust勒索病毒是一种新型的勒索软件&#xff0c;最早出现在2018年。该病毒通过加密计算机系统中的文件并要求支付赎金来解锁文件&#xff0c;从而获取经济利益。与传统的勒索软件相比&#xff0c;faust勒索病毒采用了更加先进的加密算法和隐藏技术&#…

浅析视频汇聚EasyCVR视频融合云平台在机场安防智能检测建设中的应用

一、背景 机场作为国家交通枢纽和对外开放的窗口&#xff0c;其安全运行直接关系到乘客的生命安全、国家形象以及社会经济稳定。随着全球航空业的快速发展和人们出行需求的持续增长&#xff0c;机场作为重要的交通枢纽&#xff0c;其客流量和货运量均呈现出快速增长的态势。然而…

【鸿蒙+全国产瑞芯微】智慧楼宇解决方案 | 如何实现多场景下智慧化、精细化楼宇管理?

随着数字化、智能化与工作生活的联结日渐紧密&#xff0c;聚焦人性化服务&#xff0c;以数字和科技匹配多重需求&#xff0c;加速商业楼宇智能化转型的脚步&#xff0c;逐步形成智慧楼宇产品矩阵。 方案亮点 01/数字标牌——形象展示 企业文化宣传、公告通知等 播放内容统一远…

实时追踪维修进度,报修管理小程序让你省心又省力!

随着生活、工作节奏的日益加快&#xff0c;日常的售后报修、故障报修处理流程给我们带来种种困扰。我们都知道大多数企业、个人用户还在使用传统报修方式&#xff0c;如电话报修、纸质报修单等方式&#xff0c;不仅效率低下&#xff0c;而且难以追踪维修进度&#xff0c;给我们…