数据结构 — 【排序算法】

news2024/11/18 9:40:40

目录

1.排序的概念及其运用

 1.1排序的概念

 1.2排序运用

1.3 常见的排序算法

2.常见排序算法的实现

 2.1 插入排序

   直接插入排序

  希尔排序

 2.2 选择排序

   直接选择排序

堆排序

 2.3 交换排序

  冒泡排序

   快速排序

2.4 归并排序

2.5 非比较排序

   计数排序

基数排序 

3.排序算法复杂度及稳定性分析


 

1.排序的概念及其运用

 1.1排序的概念

  • 排序:所谓排序,就是使一串记录,按照其中的某个或某些关键字的大小,递增或递减的排列起来的操作。
  • 稳定性:假定在待排序的记录序列中,存在多个具有相同的关键字的记录,若经过排序,这些记录的相对次序保持不变,即在原序列中,r[i]=r[j],且r[i]r[j]之前,而在排序后的序列中,r[i]仍在r[j]之前,则称这种排序算法是稳定的;否则称为不稳定的。
  • 内部排序:数据元素全部放在内存中的排序。
  • 外部排序:数据元素太多不能同时放在内存中,根据排序过程的要求不能在内外存之间移动数据的排序。

 1.2排序运用

        排序经常运用于某些购物软件或消费软件中,常体现在某种商品以价格或销售量进行升序和降序的排列。排序的应用十分广泛。

1.3 常见的排序算法


2.常见排序算法的实现

 2.1 插入排序

   直接插入排序

直接插入排序是一种简单的插入排序法。

思想:把待排序的记录按其关键码值的大小逐个插入到一个已经排好序的有序序列中,直到所有的记录插入完为止,得到一个新的有序序列 。每次从尾部插入一个数,经过比较和挪动,让数据到对应的位置去。就像我们玩扑克牌一样。

 以升序为例,给定一个数组,从第一个数开始向后遍历,end下标之前一定是有序的,新插入tmp,如果tmp小于end下标的数,那么将end下标的数向后覆盖(也就是向后挪一位),再--end,如果tmp还小于end下标的数,就再将--之后的end下标的数向后挪,重复循环直至tmp大于end下标的数,结束循环,因为我们要将tmp放到end下标的后面,所以赋值的时候end要+1。

//插入排序
void InsertSort(int* arr, int n)
{
	for (int i = 0; i < n - 1; ++i)
	{
		int end = i;
		int tmp = arr[end + 1];
		while (end >= 0)
		{
			if (arr[end] > tmp)
			{
				arr[end + 1] = arr[end];
				--end;
			}
			else
			{
				break;
			}
		}
		arr[end+1] = tmp;
	}
}

直接插入排序的特性总结:
1. 元素集合越接近有序,直接插入排序算法的时间效率越高
2. 时间复杂度:O(N^2)
3. 空间复杂度:O(1),它是一种稳定的排序算法
4. 稳定性:稳定

  希尔排序

希尔排序法又称缩小增量法。希尔排序法的基本思想是:先选定一个整数m,把待排序文件中所有记录分成n个组,所有距离为m的记录分在同一组内,并对每一组内的记录进行排序。然后,取重复上述分组和排序的工作。当到达=1时,所有记录在统一组内排好序。

 希尔排序是插入排序的优化版本,在插入排序的基础上加以修改得到的,但是效率高了很多。

希尔排序是对数组先进行预排序,选定一个整数gap,跟插入排序类似,只不过这里比较的跨度稍微有些大,进行1次或多次预排序后,数组就变得十分接近有序了,当gap = 1时,就相当于又进行了一次插入排序,但这次插入排序挪动数据的次数很少,甚至为0,结束后数组就变得有序了。我们知道插入排序的特性是,数组越接近有序,插入排序效率越高,希尔排序就是利用了这个特性。

//希尔排序
void ShellSort(int* arr, int n)
{
	int gap = n;
	while (gap > 1)
	{
        //gap /= 2;    //两个都可以
		gap = gap / 3 + 1;
		for (int i = 0; i < n - gap; ++i)
		{
			int end = i;
			int tmp = arr[end + gap];
			while (end >= 0)
			{
				if (arr[end] > tmp)
				{
					arr[end + gap] = arr[end];
					end -= gap;
				}
				else
				{
					break;
				}
			}
			arr[end + gap] = tmp;
		}
	}
}

希尔排序的特性总结:
1. 希尔排序是对直接插入排序的优化。
2. 当gap > 1时都是预排序,目的是让数组更接近于有序。当gap == 1时,数组已经接近有序的了,这样就会很快。这样整体而言,可以达到优化的效果。我们实现后可以进行性能测试的对比。
3. 希尔排序的时间复杂度不好计算,因为gap的取值方法很多,导致很难去计算,因此在好些树中给出的希尔排序的时间复杂度都不固定,因为当前的gap是按照Knuth提出的方式取值的,我们暂时就按照:O(N^1.25)到 O(1.6N^1.25)来算。

4. 稳定性:不稳定

 2.2 选择排序

基本思想:每一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,直到全部待排序的数据元素排完。

   直接选择排序

以升序为例,直接选择排序是遍历数组,找最小的数放到开始的位置,然后就不动那个位置了,再从最小的数后面继续开始循环找小,直至数组有序。

这里由于每次找一个效率太低,所以优化一下。每次找一个最小的和一个最大的,将他们两个分别放到自己该去的位置上,数组长度再向中间收缩,然后再循环动作,直至左边下标大于或等于右边下标就结束。结束后数组就变有序了。

//直接选择排序
void SelectSort(int* arr, int n)
{
	int begin = 0, end = n-1;
	while (begin < end)
	{
		int mini = begin,maxi = end;
		for (int i = begin; i <= end; ++i)
		{
			if (arr[mini] > arr[i])
				mini = i;
			if (arr[maxi] < arr[i])
				maxi = i;
		}

		Swap(&arr[mini], &arr[begin]);
		//可能会出现最大的数在begin位置上,一交换最大数跑到mini位置了,如果不修正会出问题
		if (maxi == begin)
			maxi = mini;
		Swap(&arr[maxi], &arr[end]);

		++begin;
		--end;
	}
}

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

堆排序

堆排序在之前的文章里有写:
(1条消息) 数据结构 — 二叉树_晚风不及你的笑427的博客-CSDN博客

1. 堆排序使用堆来选数,效率就高了很多。
2. 时间复杂度:O(N*logN)
3. 空间复杂度:O(1)
4. 稳定性:不稳定

 2.3 交换排序

基本思想:所谓交换,就是根据序列中两个记录键值的比较结果来对换这两个记录在序列中的位置

交换排序的特点是:将键值较大的记录向序列的尾部移动,键值较小的记录向序列的前部移动。
 

  冒泡排序

以升序为例,冒泡排序就是遍历数组,然后两两对比,前一个数比后一个数大就进行交换,第一趟下来,最大的数就会到最后去了,下一趟遍历时会减去上一趟最大的数的位置,也就是说每一趟都会让数组长度减去1,循环往复,最后数组就会变得有序啦。

//冒泡排序
void BubbleSort(int* arr, int n)    //因为传的是左闭右开,所以n-1
{
	int flag = 0;
	for (int i = 0; i < n-1; ++i)
	{
		for (int j = 0; j < n -1- i; ++j)
		{
			if (arr[j] > arr[j + 1])
			{
				int tmp = arr[j];
				arr[j] = arr[j + 1];
				arr[j + 1] = tmp;
				flag = 1;
			}
		}
		if (flag == 0)
			break;
	}
}

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

   快速排序

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

快排的单趟排序分为三种:hoare法,挖坑法,双指针法(前后指针法)


hoare版本

 上图为hoare版本的单趟排序。

        以升序为例,选择数组左侧第一个数为基准值key(下标方便交换),让right先走,反向遍历找比基准值小的数,找到就停下来,再让left找比基准值大的数,同样找到就停下来,交换left下标和right下标的值,只要left还小于right就继续循环找数,进行交换,最后left和right相遇结束循环。交换基准值和两个下标相遇位置,此时基准值就到它该去的位置上了,再将基准值返回。这就是单趟。这里可以选左侧第一个为基准值,也可以选右侧为基准值,选右侧的话,代码要稍微变一变。

        这里需要注意的是:right在找数的时候,还没找到对应的数就与left相遇了,此时就不能再继续走了(很有可能会一直找不到数,然后就越界访问了),left也一样。这里无需担心相遇位置的数会比key值要大,因为是right先找数,即使相遇了也不会出问题。

        然后快排函数里是一个递归的思想,先走一遍单趟,让基准值就位,然后再依次走基准值的左边和右边即可(像二叉树一样),如果begin>= end,证明区间里可能没有数了或者只有一个数,那么无需排序直接返回。

这个快排正常情况时间复杂度是O(n logn),最坏的情况会变成O(n^2);比如数组是逆序。

//key值快排单趟				左闭右闭
int partSort(int* arr, int left, int right)
{
	int keyi = left;
	while (left < right)
	{
		//R找小
		while (left < right && arr[keyi] <= arr[right])
		{
			--right;
		}
		//L找大
		while (left < right && arr[keyi] >= arr[left])
		{
			++left;
		}
		if(left < right)
			Swap(&arr[left], &arr[right]);
	}
	Swap(&arr[left], &arr[keyi]);
	return left;
}
//快排递归						//左闭右闭
void QuickSort(int* arr, int begin, int end)
{
	if (begin >= end)
		return;

	int keyi = partSort(arr, begin, end);

	QuickSort(arr, begin, keyi-1);

	QuickSort(arr,keyi + 1,end);
}

因为当前的快排有缺陷,比如数组是逆序的时候,效率变低,且容易栈溢出。那么就需要进行优化,怎么优化呢?这里主要的问题在于key值的选取,那么就有人提出了用三数取中法,选取key值。怎么做呢?往下看。

通过函数去对比,看左边第一个数和右边第一个数以及中间下标位置的数,看他们三个谁是中间数。

//三数取中
int GetMidIndex(int* arr, int left, int right)
{
	//int mid = (left + right) / 2;		//如果right和left都很大,相加会溢出
	int mid = left + (right - left) / 2;

	if (arr[left] < arr[mid])
	{
		if (arr[mid] < arr[right])		//left < mid < right
			return mid;
		else if (arr[left] < arr[right])//left < right < mid
			return right;
		else                            //right < left < mid
			return left;
	}
	else
	{
		if (arr[right] < arr[mid])		//right < mid < left
			return mid;
		else if (arr[right] < arr[left])//mid < right < left
			return right;
		else                            //mid < left < right
			return left;
	}
}

 因为我们用了三数取中法,所以key值就算是最坏的情况也只有可能是次大或次小的数,那么就不会出现效率特别低下的问题,因为三数取中法,情况会由最坏往最好发展。

//key值快排单趟+三数取中				左闭右闭
int partSort(int* arr, int left, int right)
{
	int mid = GetMidIndex(arr, left, right);
	Swap(&arr[left], &arr[mid]);

	int keyi = left;
	while (left < right)
	{
		//R找小
		while (left < right && arr[keyi] <= arr[right])
		{
			--right;
		}
		//L找大
		while (left < right && arr[keyi] >= arr[left])
		{
			++left;
		}
		if (left < right)
			Swap(&arr[left], &arr[right]);
	}
	Swap(&arr[left], &arr[keyi]);
	return left;
}

还可以对递归思想进行优化,前面说过,快排会走左右区间,像二叉树一样,如果数组很大,递归的深度就会特别深,而最后一层调用栈的次数直接占用整个次数的一半,越往上占用次数越少,所以最开始可以用快排思想先将数组分好区间,随着递归的深度,数组区间也会越来越接近有序,,我们知道接近有序的数组用插入排序效率会很高,再继续用快排不如用直接用插入排序。

//快排递归	+小区间优化					//左闭右闭
void QuickSort(int* arr, int begin, int end)
{
	if (begin >= end)
		return;

	if (end - begin <= 8)
	{
		InsertSort(arr + begin, end - begin+1);
	}
	else
	{
		int keyi = partSort(arr, begin, end);

		QuickSort(arr, begin, keyi-1);
		QuickSort(arr, keyi + 1, end);
	}
}

  挖坑法

 挖坑法的大思路和hoare版的差不多,hoare版是找两个进行交换,而挖坑法是找一个填一个坑,步骤分开进行了。

挖坑法也更容易理解一些,选择一个坑位,让right从右边开始找比key值小的数,找到就放到坑位上,那么right当前位置就形成了新的坑位,然后让left找比key大的数,找到后就放到坑位上,再次更新坑位,left和right向中间靠拢,直至相遇,再让key值放到最后一个坑位上,单趟就完成了。这里注意不是交换,而是直接覆盖。

//挖坑法单趟
int partSort2(int* arr, int left, int right)
{
	int mid = GetMidIndex(arr, left, right);
	Swap(&arr[left], &arr[mid]);

	int key = arr[left];
	int hole = left;
	while (left < right)
	{
		//R找小
		while (left < right && arr[right] >= key)
		{
			--right;
		}
		arr[hole] = arr[right];
		hole = right;
		//L找大
		while (left < right && arr[left] <= key)
		{
			++left;
		}
		arr[hole] = arr[left];
		hole = left;
	}
	arr[hole] = key;
	return hole;
}

前后指针法

 前后指针法这里和前两个稍微有点不同,他主要是通过对比,将较大的值一点一点向后翻转。

还是先取一个keyi下标(因为需要交换,所以用下标更好),定义两个指针prev和cur分别指向数组第一个和第二个,cur向后遍历数组,如果cur下标的值比基准值大,cur++向后走,如果cur下标的值比基准值小,先让prev++,再进行交换,这里可能会出现原地交换的情况,所以加一个条件判断(++prev!= cur,才进行交换),cur遍历完数组后,再将keyi下标和prev下标的值进行交换,最后在返回prev即可。

//前后指针法单趟            单趟都是左闭右闭
int partSort3(int* arr, int left, int right)
{
	int mid = GetMidIndex(arr, left, right);
	Swap(&arr[left], &arr[mid]);

	int keyi = left;
	int prev = left;
	int cur = left + 1;
	while (cur <= right)
	{
		if (arr[cur] < arr[keyi] && ++prev != cur)
		{
			Swap(&arr[cur], &arr[prev]);
		}
		cur++;
	}
	Swap(&arr[keyi], &arr[prev]);
	return prev;
}

我们知道数组很大的时候,用递归很容易栈溢出,那么怎么解决呢?当然就需要非递归版本的快排咯。

非递归快排

非递归想要模拟递归过程就需要用到一个数据结构-栈(stack),栈里存的就是我们需要进行调整的区间,每次把区间拿出来进行一次单趟排序(加个条件判断,需要的话再进行区间排序),像上图画的一样,把数组分成多个子区间,再进行排序,子区间有序数组也就有序了。

注:文件是.cpp才可以用stl库的stack

#include <stack>
//快排非递归,借助stack实现,
void QuickSortNonR(int* arr, int begin, int end)
{
	std::stack<int> st;
	//先入数组区间
	st.push(begin);
	st.push(end-1);
	//栈不为空就继续
	while (!st.empty())
	{	//因为是正着存的,所以要反着取
		int right = st.top();
		st.pop();

		int left = st.top();
		st.pop();
		if (left >= right)//如果区间只有一个数,或者区间不存在就跳过下面的代码
			continue;
		int keyi = partSort3(arr, left, right);	//单趟排序
		//插入右区间
		st.push(keyi + 1);
		st.push(right);
		//插入左区间
		st.push(left);
		st.push(keyi - 1);
	}
    //这两个都可以
	//while (!st.empty())
	//{	//因为是正着存的,所以要反着取
	//	int right = st.top();
	//	st.pop();

	//	int left = st.top();
	//	st.pop();
	//	
	//	int keyi = partSort3(arr, left, right);	//单趟排序
	//	//插入右区间	区间有两个或两个以上的数才需要插入进行排序
	//	if (keyi + 1 < right)
	//	{
	//		st.push(keyi + 1);
	//		st.push(right);
	//	}
	//	//插入左区间
	//	if (left < keyi - 1)
	//	{
	//		st.push(left);
	//		st.push(keyi - 1);
	//	}
	//}
}

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

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

2.4 归并排序

归并排序(MERGE-SORT)是建立在归并操作上的一种有效的排序算法,该算法是采用分治法(Divide andConquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。

 递归版本

还是将数组分为多个子区间,像二叉树一样,只不过这里走的是后序遍历。这里需要借助一个同样大小的tmp数组,帮助我们完成归并的动作(归并:取小的数尾插到tmp数组中)。借助tmp数组让左右子区间通过对比变得有序,因为循环是左右区间有任意一个结束了该循环就结束了,那么就会导致另一个区间还有数没归并到tmp数组中,所以还需要处理一下。最后归并完成,将tmp数组中归并好的的区间拷贝回原数组区间。

//归并排序  -> 递归
void _MergeSort(int* a ,int begin,int end,int* tmp)
{
	if (begin >= end)
		return;
	int mid = (begin + end) / 2;

	_MergeSort(a, begin, mid, tmp);
	_MergeSort(a, mid+1, end, tmp);

	//归并
	int begin1 = begin, end1 = mid;
	int begin2 = mid + 1, end2 = end;
	int idx = begin;
	while (begin1 <= end1 && begin2 <= end2)
	{
		if (a[begin1] <= a[begin2])
			tmp[idx++] = a[begin1++];
		else
			tmp[idx++] = a[begin2++];
	}
    //上面的循环会导致,只有一个区间全部归并到tmp数组里了,
    //要把另一个区间剩余的也都归并过来,否则拷贝回原数组时原数组数据会被随机数覆盖
	while (begin1 <= end1)    
		tmp[idx++] = a[begin1++];
	while (begin2 <= end2)    //
		tmp[idx++] = 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");
		return;
	}

	_MergeSort(a, 0, n - 1, tmp);
	free(tmp);
	tmp = NULL;
}

非递归版本 

 不管是递归还是非递归,归并的动作是一样的。非递归版本要通过gap来控制子区间的大小,再通过归并从而让子区间有序,从小的子区间有序到大的子区间有序,再到整体有序。

如上图,由于gap是以二倍增长的,所以会出现数组长度是奇数,会有访问越界的问题。但是数组长度是偶数不能说明一定不越界,只能说gap为1的时候不越界,gap增大的时候可能会导致越界。所以我们针对三种情况要特殊处理,比如第一组部分越界,先跳出该循环,先不处理,第二组全部越界时也不处理,跳出循环,等到第二组部分越界时,再将end2修正一下,就可以啦。

//归并排序 ->非递归
void MergeSortNonR(int* a, int n)
{
	int* tmp = (int*)malloc(sizeof(int) * n);
	if (tmp == NULL)
	{
		perror("malloc fail");
		return;
	}
	int gap = 1;
	while (gap < n)
	{		//gap个数据进行归并
		for (int i = 0; i < n; i += gap * 2)
		{
			int begin1 = i, end1 = i + gap - 1;
			int begin2 = i + gap, end2 = i + 2 * gap - 1;
			//调整
			if (end1 >= n)//第一组部分越界
				break;
			if (begin2 >= n)//第二组全部越界
				break;
			if (end2 >= n )//第二组部分越界
				end2 = n - 1;
			printf("[%d,%d][%d,%d] ", begin1, end1, begin2, end2);

			int idx = i;
			while (begin1 <= end1 && begin2 <= end2)
			{
				if (a[begin1] <= a[begin2])
					tmp[idx++] = a[begin1++];
				else
					tmp[idx++] = a[begin2++];
			}
			while (begin1 <= end1)
				tmp[idx++] = a[begin1++];
			while (begin2 <= end2)
				tmp[idx++] = a[begin2++];

			//归并哪部分区间,就将哪部分拷贝回原数组 
			//(不建议整个数组归并完了,统一拷贝回去,因为边界不好控制)
			memcpy(a+i, tmp+i, (end2-i+1) * sizeof(int));
		}
		printf("\n");
		gap *= 2;
	}
	free(tmp);
	tmp = NULL;
}

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

2.5 非比较排序

前面用数据进行比较大小的排序都统称为比较排序。

非比较排序就是不对数据进行比较大小,比如统计元素出现的次数

   计数排序

计数排序又称为鸽巢原理,是对哈希直接定址法的变形应用。
思想:1统计相同元素出现次数,2. 根据统计的结果将序列回收到原来的序列中

 这里其实就是运用的类似哈希方式的映射,

需要先找a数组中最大的数max和最小的数min,我们需要借助额外数组去映射数值的次数,新数组的范围,就是max-min+1,先遍历a数组,a数组中的数据减去min就是映射的下标,把每个数出现的次数都统计到tmp数组中,最后遍历tmp数组,下标数据出现的次数不为0就将原数值(i+min)写回原数组(a),有几次就写几个。

//计数排序
void CountSort(int* a, int n)
{
	//先找max,min,
	int max = a[0], min = a[0];
	for (int i = 1; i < n; ++i)
	{
		if (max < a[i])
			max = a[i];
		if (min > a[i])
			min = a[i];
	}
	//开辟空间
	int range = max - min + 1; //需要的区间
	int* tmp = (int*)malloc(sizeof(int) * range);
	if (tmp == NULL)
	{
		perror("malloc,fail");
		return;
	}
	memset(tmp, 0, sizeof(int) * range);    //把tmp数组中全部初始化为0
	//遍历a,a[i]-min ->就是tmp数组中对应位置的下标
	for (int i = 0; i < n; ++i)
	{
		tmp[a[i] - min]++;
	}
	//把tmp数组的下标+min就是原来的数据,将该数据写回原数组,
	int idx = 0;
	for (int i = 0; i < range; ++i)
	{
		while (tmp[i]--)
		{
			a[idx++] = i+min;
		}
	}

	free(tmp);
	tmp = NULL;
}

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

基数排序 

基数排序是先从数的最低位开始,因为每一位数的区间是0~9,所以开辟一个队列数组(普通数组也可以),用于存放数据(多个数据的某一位相同会被放到同一个队列),分发完数据后,再将数据回收到原数组,因为是队列,相同的数位置的先后是不会变的,所以可以很好的保证稳定性。再次按照次低位开始分发数据,然后回收,循环重复,最大的数是几位就走几轮,走完轮次数组就有序了,而且相同的数也不会变更位置。

#include <iostream>
#include <vector>
#include <queue>

using namespace std;

#define K 3
#define RADIX 10

queue<int> qe[RADIX];

//基数排序
//获取数值中的第k位
int Getnum(int num,int k)
{
	int key = 0;
	while (k >= 0)
	{
		key = num % 10;
		num /= 10;
		k--;
	}
	return key;
}
void Distribute(vector<int>& arr,int left,int right,int k)//分发数据
{
	for (int i = left; i < right; ++i)
	{
		int idx = Getnum(arr[i], k);
		qe[idx].push(arr[i]);
	}

}

void Collect(vector<int>& arr)//回收数据
{
	int i = 0;
	for (int j = 0; j < RADIX; ++j)
	{
		while (!qe[j].empty())
		{
			arr[i++] = qe[j].front();
			qe[j].pop();
		}
	}
	
}
//基数排序
void RadixSort(vector<int>& arr, int left, int right)
{
	for (int i = 0; i < K; ++i)
	{
		//分发数据
		Distribute(arr,left,right,i);
		//回收数据
		Collect(arr);
	}
}

 

3.排序算法复杂度及稳定性分析

完整代码: sort/sort/sort.c · 晚风不及你的笑/作业库 - 码云 - 开源中国 (gitee.com)

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

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

相关文章

【Unity入门】12.MonoBehaviour事件函数

【Unity入门】MonoBehaviour事件函数 大家好&#xff0c;我是Lampard~~ 欢迎来到Unity入门系列博客&#xff0c;所学知识来自B站阿发老师~感谢 &#xff08;一&#xff09;常用的事件函数 &#xff08;1&#xff09;start和update方法 之前我们写的脚本&#xff0c;会默认帮助…

4.3 分部积分法

学习目标&#xff1a; 学习分部积分法&#xff0c;我可能会按照以下步骤进行&#xff1a; 理解分部积分法的基本思想。分部积分法是一种通过对积分式中的不同部分进行乘积分解&#xff0c;然后对乘积中的某一项进行积分&#xff0c;对另一项进行微分&#xff0c;从而将原积分式…

NumPy 秘籍中文第二版:五、音频和图像处理

原文&#xff1a;NumPy Cookbook - Second Edition 协议&#xff1a;CC BY-NC-SA 4.0 译者&#xff1a;飞龙 在本章中&#xff0c;我们将介绍 NumPy 和 SciPy 的基本图像和音频&#xff08;WAV 文件&#xff09;处理。 在以下秘籍中&#xff0c;我们将使用 NumPy 对声音和图像进…

叮咚买菜基于 Apache Doris 统一 OLAP 引擎的应用实践

导读&#xff1a; 随着叮咚买菜业务的发展&#xff0c;不同的业务场景对数据分析提出了不同的需求&#xff0c;他们希望引入一款实时 OLAP 数据库&#xff0c;构建一个灵活的多维实时查询和分析的平台&#xff0c;统一数据的接入和查询方案&#xff0c;解决各业务线对数据高效实…

一键构建分布式云原生平台

目录专栏导读一、分布式云原生平台1、应用无所不能2、运行无处不在3、服务千行白业二、分布式云原生平台关键要素1、统一应用管理2、统一流量自治3、统一数据管理4、统一运维三、多云多集群已经广泛应用四、分布式云的优势&#xff1a;1、避免厂商锁定2、满足合规化要求3、增强…

收藏!7个国内「小众」的程序员社区

技术社区是大量开发者的集聚地&#xff0c;在技术社区可以了解到行业的最新进展&#xff0c;学习最前沿的技术&#xff0c;认识有相同爱好的朋友&#xff0c;在一起学习和交流。 国内知名的技术社区有CSDN、博客园、开源中国、51CTO&#xff0c;还有近两年火热的掘金&#xff…

基于决策树及集成算法的回归与分类案例

基于决策树及集成算法的回归与分类案例 描述 本任务基于决策树及集成算法分别实现鲍鱼年龄预测案例和肿瘤分类案例。鲍鱼年龄预测案例是建立一个回归模型&#xff0c;根据鲍鱼的特征数据&#xff08;长度、直径、高度、总重量、剥壳重量、内脏重量、壳重&#xff09;等预测其…

Python:超级大全网上面试题搜集整理(四)

转载参考&#xff1a; python 面试题(高级)_python高级面试题_梦幻python的博客-CSDN博客 cpython pypy_介绍Cython&#xff0c;Pypy Cpython Numba各有什么缺点【面试题详解】_函明的博客-CSDN博客 Cython、PyPy专题开篇 - 知乎 Python抽象类和接口类_python 接口类_代码输…

蓝桥杯客观题知识点

一、异步和同步的在于 有无统一的时钟信号 异步无 同步有 RS485 半双工、异步、串行、差分输入------多级通信&#xff08;USB\键盘等外设&#xff09; RS232 全双工、异步、串行、单端输入------一对一通信 二、组合逻辑电路和时序逻辑电路的区别 组合&#xff1a;任意时…

使用反射重新执行不同的方法

0. 用到的技术 反射获取正在执行的方法名称Class[]数组的获取 1. 为什么要这样做? 情况如下: 当我调用sendCommands方法发送请求时可能会收到errorCode为403也就是代码中的MDS_ERROR,就是当token(mds)失效了这种情况,我们就需要重新刷新token,并且重新执行该方法 假设还有1…

SYN FLOOD攻击和HTTP慢速攻击实验笔记

SYN_FLOOD攻击和HTTP慢速攻击是DDOS攻击的两种方式。 SYN Flood攻击 SYN Flood攻击的原理就是阻断TCP三次握手的第三次ACK包&#xff0c;即不对服务器发送的SYNACK数据包做出应答。由于服务器没有收到客户端发来的确认响应&#xff0c;就会一直保持连接直到超时&#xff0c;当…

产品营销软文怎么写吸引人?

随着互联网的发展&#xff0c;人们获取信息的渠道变得越来越多&#xff0c;其中软文营销成为了众多企业推广自己产品的主要方式之一。那么&#xff0c;软文营销怎么写才能吸引人呢&#xff1f;这里有一些建议&#xff0c;可以帮助你解决这个问题。 要想写出一篇成功的软文&…

自拍的照片不太清晰怎么办?拍摄的模糊照片如何修复高清?

如果您的人像照片不太清晰&#xff0c;可能是由于手持相机时快门速度过慢、摄像机抖动或者焦点不准确等原因造成的。 自己拍摄的照片总是感觉不太清晰&#xff0c;放大看的话更是模糊&#xff0c;该如何是好&#xff1f; 以下是一些避免自拍照片模糊的方法&#xff1a; 1、使…

XSKY星辰天合荣获环球网“年度科技优秀创新案例”

近日&#xff0c;环球网主办的第四届环球趋势大会在广州举行&#xff0c;由环球时报、环球网联合主办的“2022 环球趋势案例征集活动”评选结果同步揭晓&#xff0c;XSKY星辰天合荣获 2022 环球趋势案例“年度科技创新优秀案例”。“2022 环球趋势案例”是人民日报旗下&#xf…

6个免费高清图库素材库,设计师、自媒体都在用~

免费高清图片素材分享&#xff0c;建议收藏起来. 1、菜鸟图库 https://www.sucai999.com/pic.html?vNTYxMjky 超大图库网站&#xff0c;含有几百万张图片素材&#xff0c;自然、植物、人物、日常、交通等涵盖多种类型&#xff0c;全部都有详细的标签分类。图片素材质量都很高…

DOM 事件相关知识总结——事件绑定、事件流(事件冒泡、捕获)

1. 事件绑定方式 1. 直接给元素添加事件属性 <input onclick"alert(我被点击了&#xff01;)" type"button" value"点我试试" />优点&#xff1a;大家都会&#xff0c;几乎所有的浏览器都支持 缺点&#xff1a;夹杂在HTML代码中&…

79-Linux_Socket实现客户端与服务器端间通讯

Socket实现客户端与服务器端间通讯一.网络编程的接口1.socket2.bind3.listen4.accept5.connect6.close7.ssize_t recv和ssize_t send8.UDP 数据读写二.tcp流式服务和粘包问题三.客户端及服务器端实现的代码.1.客户端2.服务器端一.网络编程的接口 头文件: #include <sys/typ…

win11使用移动硬盘(固态非固态)卡顿问题解决

以前win10使用移动硬盘没用出现过卡顿的问题&#xff0c;后来更新win11后&#xff0c;硬盘在处理文件和文件新建以及编辑的时候&#xff0c;都会莫名其妙卡1-3秒左右。以为是盘坏了&#xff0c;各种检测和修复。发现没有问题 后来还找移动硬盘的商家沟通&#xff0c;也无果打算…

C语言基础应用(四)选择结构

引言&#xff1a; 在日常生活中&#xff0c;我们时时刻刻面临着选择&#xff0c;在C语言中&#xff0c;如果我们需要判断条件从而实现不同的要求&#xff0c;我们就需要使用选择结构。 注&#xff1a;以下代码均未导入头文件&#xff0c;如果读者使用了代码&#xff0c;请记得…

day9 条件变量的基本使用

目录 条件变量 条件变量 应用场景&#xff1a;生产者消费问题&#xff0c;是线程同步的一种手段&#xff1b; 必要性&#xff1a;为了实现等待某个资源&#xff0c;让线程休眠&#xff0c;提高运行效率&#xff1b; 等待资源&#xff1a; //1、一直等待资源 int pthread_c…