超超超级详细的画图以及代码分析各种排序的实现!

news2024/11/22 16:37:32

各种排序的实现

  • 排序的概念
  • 直接插入排序
    • 基本思想
    • 实现
    • 直接插入排序的特性总结
  • 希尔排序
    • 基本思想
    • 实现
    • 希尔排序的特性总结
  • 简单选择排序
    • 基本思想
    • 实现
    • 直接选择排序的特性总结
  • 堆排序
    • 实现
    • 堆排序的特性总结
  • 冒泡排序
    • 基本思想
    • 实现
    • 冒泡排序的特性总结
  • 快速排序
    • 基本思想
    • hoare版本
    • 挖坑法
    • 前后指针法
    • 快速排序的改进
    • 快速排序非递归
    • 快速排序的特性总结
  • 归并排序
    • 基本思想
    • 实现
    • 归并排序非递归
    • 归并排序的特性总结
  • 基数排序
    • 基本思想
    • 实现
  • 计数排序
    • 基本思想
    • 实现

排序的概念

排序:所谓排序,就是使一串记录,按照其中的某个或某些关键字的大小,递增或递减的排列起来的操作。

稳定性:假定在待排序的记录序列中,存在多个具有相同的关键字的记录,若经过排序,这些记录的相对次序保持不变,即在原序列中,r[i]=r[j],且r[i]在r[j]之前,而在排序后的序列中,r[i]仍在r[j]之前,则称这种排序算法是稳定的;否则称为不稳定的。

内部排序:数据元素全部放在内存中的排序。

外部排序:数据元素太多不能同时放在内存中,根据排序过程的要求不能在内外存之间移动数据的排序。

直接插入排序

基本思想

把待排序的记录按其关键码值的大小逐个插入到一个已经排好序的有序序列中,直到所有的记录插入完为止,得到一个新的有序序列 。

在这里插入图片描述

实现

把下标[0 end]看成有序,现在插入end+1,插入之后还要保持[0 end+1]有序

在这里插入图片描述
记住,写排序一定要先写单趟排序,在控制写全部的!

//直接插入排序O(N^2)
void InsertSort(int* a, int n)
{
	for (int i = 0; i < n - 1; ++i)
	{
		//[0 end]有序,插入end+1,[0 end+1]保存有序
		//一趟
		int end = i;
		int tmp = a[end + 1];
		while (end >= 0)
		{
			if (a[end] > tmp)
			{
				a[end + 1] = a[end];
				--end;
			}
			else
			{
				break;
			}
		}
		a[end + 1] = tmp;
	}

}

直接插入排序的特性总结

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

希尔排序

基本思想

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

希尔排序其实是对直接插入排序的改进,直接插入排序当数据是有序的时候,时间复杂度时O(N),是一个很好的算法。而希尔排序选定一个整数gap,当gap>1的时候预排序,使距离为gap的每组数据进行排序,gap==1的时候,数据基本有序了,然后再执行一次直接插入排序。

实现

把[0 end]看成有序,插入end+gap,使得[0 edd+gap]有序,

写法1
共有gap组,每组N/gap个,每次对每组进行排序
在这里插入图片描述

void ShellSort(int* a, int n)
{
	//写法1
	int gap=n;
	//gap>1 预排序
	//gap==1 直接插入排序
	while (gap > 1)
	{
		gap /= 2;
		//分成gap组
		for (int j = 0; j < gap; j++)
		{
		    //每组排序
			for (int i = 0; i < n - gap; i += gap)
			{
				int end = i;
				int tmp = a[end + gap];
				while (end >= 0)
				{
					if (a[end] > tmp)
					{
						a[end + gap] = a[end];
						end -= gap;
					}
					else
					{
						break;
					}
				}
				a[end + gap] = tmp;
			}
		}
	}

}

写法2
一趟就把每组的都排完
在这里插入图片描述

void ShellSort(int* a, int n)
{

	//写法2 
	int gap = n;
	while (gap > 1)
	{
		gap /= 3+1;

		for (int i = 0; i < n - gap; i++)
		{
			int end = i;
			int tmp = a[end + gap];
			while (end >= 0)
			{
				if (a[end] > tmp)
				{
					a[end + gap] = a[end];
					end -= gap;
				}
				else
				{
					break;
				}
			}
			a[end + gap] = tmp;
		}
	}

}

gap/2或gap/3+1;都是可以的,都能保证最后一次gap==1,进行直接插入排序。

希尔排序的特性总结

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

《数据结构(C语言版)》— 严蔚敏
在这里插入图片描述

《数据结构-用面相对象方法与C++描述》— 殷人昆
在这里插入图片描述

因为我们的gap是按照Knuth提出的方式取值的,而且Knuth进行了大量的试验统计,我们暂时就按照:O(n^1.25) 到 O(1.6*n^1.25) 来算。
4. 稳定性:不稳定

简单选择排序

基本思想

每次从待排数据中选择最小(或最大)的数据,放在序列的起始位置,直到所有待排序的数据排完
在这里插入图片描述

实现

写法1,(升序)从待排数据里一次选择一个最小的数据。

void SelectSort(int* a, int n)
{
	//写法1 
	for (int i = 0; i < n-1; i++)
	{
		int mini = i;
		for (int j = mini+1; j < n; j++)
		{
			if (a[j] < a[mini])
			{
				mini = j;
			}
		}
		Swap(&a[i], &a[mini]);
	}
}

写法2,对上面代码的改进,但是时间复杂度没变,从待排数据中选择两个元素,一个最小一个最大。放在数组开头和结点,然后选择次大次小再放。
但是这样写会有一个问题,我下面代码是先找大后找小,然后交换大,再交换小,遇到问题是如果最小的是数组最后一个位置,可以会把mini位置的值覆盖,所有这种情况需要特殊处理。
在这里插入图片描述

//直接选择排序
void SelectSort(int* a, int n)
{
	//写法2
	int begin = 0, end = n - 1;
	while (begin < end)
	{
		int mini = begin, maxi = begin;
		for (int i = begin + 1; i <= end; i++)
		{
			//找大
			if (a[i] > a[maxi])
			{
				maxi = i;
			}
			//找小
			if (a[i] < a[mini])
			{
				mini = i;
			}
		}
		Swap(&a[end], &a[maxi]);

		//如果mini是最后一个位置,就更新mini位置
		if (a[mini] == a[end])
			mini = maxi;

		Swap(&a[begin], &a[mini]);
		++begin;
		--end;
	}
}

直接选择排序的特性总结

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

堆排序

实现

堆排序所有代码再这里堆的实现,画图和代码分析建堆,堆排序,时间复杂度以及TOP-K问题写的非常清晰,有兴趣的可以看一看。

堆排序的特性总结

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

冒泡排序

基本思想

基本思想:所谓交换,就是根据序列中两个记录键值的比较结果来对换这两个记录在序列中的位置,交换排序的特点是:将键值较大的记录向序列的尾部移动,键值较小的记录向序列的前部移动。
在这里插入图片描述

实现

(升序)两两交换,每次把最大的放在最后面。

首先有n个数,只进行n-1趟排序就行了,其次注意每次把最大的放在最后面,然后下一趟排序就可以把不用和这个数据进行比较。每次都少比较一个数。

void BubbleSort(int* a, int n)
{
	for (int j = 0; j < n - 1; j++)
	{
		int flag = 1;
		//一趟
		for (int i = 0; i < n - 1-j; i++)
		{
			if (a[i] > a[i + 1])
			{
				Swap(&a[i], &a[i + 1]);
				flag = 0;
			}
		}
		if (flag == 1)
			break;
	}

}

冒泡排序的特性总结

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

快速排序

基本思想

其基本思想为:任取待排序元素序列中的某元素作为基准值,按照该排序码将待排序集合分割成两子序列,左子序列中所有元素均小于基准值,右子序列中所有元素均大于基准值,然后最左右子序列重复该过程,直到所有元素都排列在相应位置上为止。

hoare版本

在这里插入图片描述
升序)选取左边第一个元素做key,右边先走找小,左边后走找大,找到之后交换,当L和R相撞,再把key和相撞位置的值交换一下。返回相撞的位置,这是一趟排序。然后根据这个相撞返回的地址,把整块区间分成[begin keyi-1] keyi [keyi+1 end],再对左右子区间再次进行排序,当左右子区间有序了,这组数据就全部有序了,因此这是一个递归的过程。
在这里插入图片描述
那么有的同学就可能想问了,最后相撞的位置一定是比key小的吗?
有两种情况:
1.L撞R
2.R撞L
上面图就是L撞R,下面我们再看R撞L的情况。
在这里插入图片描述
选择左边第一个位置做key,右边先走,左边后走,是一定能保证L和R相撞位置的值小于key;
同样的道理如果右边第一个做key,左边先走————

//hoare版本
int PartSort(int* a, int left, int right)
{
	int keyi = left;
	while (left < right)
	{
		//右边先走,找小
		while ( a[right] > a[keyi])
		{
			--right;
		}
		//左边后走,找大
		while ( a[left] <a[keyi])
		{
			++left;
		}

		Swap(&a[left], &a[right]);
	}

	int meeti = left;
	Swap(&a[keyi], &a[meeti]);

	return meeti;
}

上面代码就对了吗? 我们好好分析一下,修改一下我们的代码。

注意找大找小一定要加上left<right,不然两个就会错过
在这里插入图片描述
还得加上=,不然如果出现下面这种情况,可能会死循环
在这里插入图片描述
正确代码如下:

//hoare版本
int PartSort(int* a, int left, int right)
{
	int mid = GetMidIndex(a, left, right);
	Swap(&a[left], &a[mid]);
	int keyi = left;
	while (left < right)
	{
		//右边先走,找小
		while (left < right && a[right] >= a[keyi])
		{
			--right;
		}
		//左边后走,找大
		while (left < right && a[left] <= a[keyi])
		{
			++left;
		}

		Swap(&a[left], &a[right]);
	}

	int meeti = left;
	Swap(&a[keyi], &a[meeti]);

	return meeti;
}

然后根据返回的下标分为左右两个子区间,再次递归

//快速排序
void QuickSort(int* a, int begin, int end)
{

	int keyi = PartSort(a, begin, end);
	//[0,keyi-1] keyi [keyi+1 ,end]
	QuickSort(a, begin, keyi - 1);
	QuickSort(a, keyi + 1, end);

}

我们知道递归需要返回条件,不然就会栈溢出。我们画图分析一下递归返回条件。
在这里插入图片描述

//快速排序
void QuickSort(int* a, int begin, int end)
{
	if (begin >= end)
		return;
	
	int keyi = PartSort(a, begin, end);
	//[0,keyi-1] keyi [keyi+1 ,end]
	QuickSort(a, begin, keyi - 1);
	QuickSort(a, keyi + 1, end);


}

这样快递排序hoare版本代码,就全部搞定了,里面有很多坑,一定要注意。

挖坑法

理解了上面的方法,我们再看这种方法。
(升序)把左边第一个元素记录做key,并且当作第一个坑位,右边先走找小,找到比key小的元素把这个元素填到坑位,然后这里就形成了一个新坑位,左边再走找大,找到比key大的元素,再填到坑里。然后形成一个新坑位。重复上述过程,直到L和R相撞,再把key填到相撞的位置,并且把该位置返回。
在这里插入图片描述

//挖坑法
int PartSort1(int* a, int left, int right)
{
	int key = a[left];
	int hole = left;
	while (left < right)
	{
		while (left < right && a[right] >= key)
		{
			--right;
		}
		a[hole] = a[right];
		hole = right;

		while (left < right && a[left] <= key)
		{
			++left;
		}
		a[hole] = a[left];
		hole = left;
	}

	a[hole] = key;
	return hole;
}

前后指针法

(升序),选取左边第一个元素做key,并且申请两个指针Prev和Cur(在数组里面是下标),Prev初始是指向key位置,Cur初始时指向Prev的下一个位置。当Cur遇到比key小的时候,++Prev,然后Prev和Cur位置的值交换,当Cur遇到比key大的时候,++Cur,直到Cur>right,跳出循环,最后把Prev指向的值和key交换。返回Prev;
在这里插入图片描述

//前后指针法
int PartSort2(int* a, int left, int right)
{
	int keyi = left;
	int prev = left, cur = left + 1;
	while (cur <= right)
	{
		if (a[cur] < a[keyi] && ++prev != cur)
		{
			Swap(&a[cur], &a[prev]);
		}
		++cur;
	}
	Swap(&a[keyi], &a[prev]);

	return prev;
}

快速排序的改进

在这里插入图片描述
优选Key逻辑
1.随机选择一个位置做key
2.针对有序,选取中间值做key
3.三数取中,第一个位置,中间位置,和最后一个位置选取中间值

但是我们选择第一个位置不就是随机选择的key吗,针对有序处理太麻烦了,所以我们采用第三种方法。

//三数取中
int GetMidIndex(int* a, int left, int right)
{
	int mid = left+(right-left)/2;
	if (a[left] < a[mid])
	{
		if (a[mid] < a[right])
			return mid;
		else if (a[right] < a[left])
			return left;
		else
			return right;
	}
	else //a[left] >= a[mid]
	{
		if (a[mid] > a[right])
			return mid;
		else if (a[right] > a[left])
			return left;
		else
			return right;
	}
}

递归到小的区间时,可以考虑使用插入排序
在这里插入图片描述

	if (end - begin <= 8)
	{
		InsertSort(a + begin, end - begin + 1);
	}

注意直接插入排序a是排序的起始位置,n是要排数据个数。
因为我们每段区间起始是不一样的,要排数据个数也是不一样的 。所以就得上面那种写法。

完整的快速排序代码如下(我们使用的是前后指针版本的代码,换做另两种也是同样的写法)

//三数取中
int GetMidIndex(int* a, int left, int right)
{
	int mid = left+(right-left)/2;
	if (a[left] < a[mid])
	{
		if (a[mid] < a[right])
			return mid;
		else if (a[right] < a[left])
			return left;
		else
			return right;
	}
	else //a[left] >= a[mid]
	{
		if (a[mid] > a[right])
			return mid;
		else if (a[right] > a[left])
			return left;
		else
			return right;
	}
}

//前后指针法
int PartSort2(int* a, int left, int right)
{
	int mid = GetMidIndex(a, left, right);
	Swap(&a[left], &a[mid]);
	int keyi = left;
	int prev = left, cur = left + 1;
	while (cur <= right)
	{
		if (a[cur] < a[keyi] && ++prev != cur)
		{
			Swap(&a[cur], &a[prev]);
		}
		++cur;
	}
	Swap(&a[keyi], &a[prev]);

	return prev;
}

//快速排序
void QuickSort(int* a, int begin, int end)
{
	if (begin >= end)
		return;

	if (end - begin <= 8)
	{
		InsertSort(a + begin, end - begin + 1);
	}
	else
	{	
		int keyi = PartSort2(a, begin, end);
		//[0,keyi-1] keyi [keyi+1 ,end]
		QuickSort(a, begin, keyi - 1);
		QuickSort(a, keyi + 1, end);
	}

}

快速排序非递归

我们知道内存中的栈区空间太小了,递归太深,容易栈溢出。所以我们自己写一个数据结构种的栈来模拟实现内存中的栈区空间,这个栈是在内存中的堆区申请空间的。

注意看我们递归的时候,递归是什么东西。
在这里插入图片描述
其实我们递归的是每段区间,先递归左区间在递归右区间,那么我们来使用栈把递归改成非递归。
在这里插入图片描述

//快速排序非递归版本
void QuickSortNonR(int* a, int begin, int end)
{
	Stack st;
	StackInit(&st);
	//整个区间先入栈
	StackPush(&st, begin);
	StackPush(&st, end);

	while (!StackEmpty(&st))
	{
		int right = StackTop(&st);
		StackPop(&st);

		int left = StackTop(&st);
		StackPop(&st);

		//不需要在排
		if (left >= right)
			continue;

		int keyi = PartSort2(a, left, right);
		//[left , keyi-1] keyi [keyi+1 ,right]

		//右区间先进栈
		StackPush(&st, keyi+1);
		StackPush(&st, right);

		//左区间进栈
		StackPush(&st, left);
		StackPush(&st, keyi - 1);

	}
	
	StackDestroy(&st);
}

快速排序的特性总结

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

归并排序

基本思想

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

在这里插入图片描述

实现

我们画图分析一下归并的核心操作
在这里插入图片描述
由于我们需要额外一个数组,所以写一个递归子函数,不然就重复申请数组了。

void _MergerSort(int* a, int left, int right,int* tmp)
{
	//分解返回条件
	if (left >= right)
		return;

	//分解
	int mid = (left + right) / 2;
	//[left  mid] [mid+1 right]
	_MergerSort(a,left, mid ,tmp);
	_MergerSort(a, mid + 1, right, tmp);

	//合并
	int begin1 = left, end1 = mid;
	int begin2 = mid + 1, end2 = right;
	int i = left;
	while (begin1 <= end1 && begin2 <= end2)
	{
		if (a[begin1] <= a[begin2])
		{
			tmp[i++] = a[begin1++];
		}
		else
		{
			tmp[i++] = a[begin2++];
		}
	}
	//右区间拷贝完,把左区间剩下拷贝
	while (begin1 <= end1)
	{
		tmp[i++] = a[begin1++];
	}
	//左区间拷贝完,把右区间剩下拷贝
	while (begin2 <= end2)
	{
		tmp[i++] = a[begin2++];
	}

	//把数据拷贝回原数组
	memcpy(a + left, tmp + left, (right - left + 1) * sizeof(int));
}


//归并排序
void MergerSort(int* a, int n)
{
	int* tmp = (int*)malloc(n * sizeof(int));
	if (tmp == NULL)
	{
		perror("malloc fail");
		return;
	}

	_MergerSort(a, 0, n - 1,tmp);
}

归并排序非递归

在这里插入图片描述

//归并排序非递归
void MergerSortNonR(int* a, int n)
{
	int* tmp = (int*)malloc(n * sizeof(int));
	if (tmp == NULL)
	{
		perror("malloc fail");
		return;
	}

	int gap = 1;
	while (gap < n)
	{
		for (int i = 0; i < n; i += 2 * gap)
		{
			//归并,取小的尾插
			int begin1 = i, end1 = i + gap - 1;
			int begin2 = i + gap, end2 = i + 2 * gap - 1;
			int j = i;
			while (begin1 <= end1 && begin2 <= end2)
			{
				if (a[begin1] <= a[begin2])
				{
					tmp[j++] = a[begin1++];
				}
				else
				{
					tmp[j++] = a[begin2++];
				}
			}
			while (begin1 <= end1)
			{
				tmp[j++] = a[begin1++];
			}
			while (begin2 <= end2)
			{
				tmp[j++] = a[begin2++];
			}
			//拷贝回原数组
			memcpy(a + i, tmp + i, (end2 - i + 1) * sizeof(int));
		}
		gap *= 2;
	}
	
	free(tmp);
	tmp = NULL;
}

我们的归并排序已经写好一大半了,但是还有一些问题,
数据个数不一定是整数倍,计算直接是按照整数倍计算的,存在越界需要修正一下
在这里插入图片描述
针对三种越界情况对代码修改一下

int gap = 1;
	while (gap < n)
	{
		//每组gap个数据归并
		for (int i = 0; i < n; i += 2 * gap)
		{
			

			//归并 取小的尾插
			int begin1 = i, end1 = i + gap - 1;
			int begin2 = i + gap, end2 = i + 2 * gap - 1;
			int j = i;

			//第一组end1越界
			if (end1 >= n)
			{
				break;
			}
			//第二组越界全部越界
			if (begin2 >= n)
			{
				break;
			}
			//第二组越界部分越界,修改end2区间
			if (end2 >= n)
			{
				end2 = n - 1;
			}

			while (begin1 <= end1 && begin2 <= end2)
			{
				if (a[begin1] <= a[begin2])
				{
					tmp[j++] = a[begin1++];
				}
				else
				{
					tmp[j++] = a[begin2++];
				}
			}
			while (begin1 <= end1)
			{
				tmp[j++] = a[begin1++];
			}
			while (begin2 <= end2)
			{
				tmp[j++] = a[begin2++];
			}
			//拷贝回原数组
			memcpy(a + i, tmp + i, (end2 - i + 1) * sizeof(int));
		}
		printf("\n");

		gap *= 2;
	}
	
	free(tmp);
	tmp = NULL;
}

还得注意不能一趟完成之后在拷贝回原数组,需要到那一块就把那一块拷贝回去,因为tmp里面是随机数。
在这里插入图片描述

归并排序的特性总结

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

基数排序

基本思想

在这里插入图片描述

实现

基数排序回收数据一个先进先出的思想,因此我们就可以使用队列。一共要使用10个队列。那申请一个队列数组,由于还没有学到C++因此下面是用C语言实现的。关于对队列有问题的可以看这个队列的实现

#define  QENumber 10
typedef struct QArray
{
	QE qe[QENumber];

}QArray;

QArray Qarray;

//获得每趟要排序的数字
int GetKey(int value, int k)
{
	int key = 0;
	while (k >= 0)
	{
		key = value % 10;
		value /= 10;
		--k;
	}
	return key;
}

//分发数据
void Distribute(int* a, int n, int k)
{
	for (int i = 0; i < n; ++i)
	{
		int key = GetKey(a[i], k);
		//进对应的队列
		QueuePush(&(Qarray.qe[key]), a[i]);
	}
}

//回收数据
void Collect(int* a)
{
	int j = 0;
	for (int i = 0; i < QENumber; ++i)
	{
		while (!QueueEmpty(&(Qarray.qe[i])))
		{
			a[j++] = QueueFront(&(Qarray.qe[i]));
			QueuePop(&(Qarray.qe[i]));
		}

	}
}

//基数排序
void RadixSort(int* a, int n)
{
	
	//初始化QENumber个队列
	for (int i = 0; i < QENumber; i++)
	{
		QueueInit(&(Qarray.qe[i]));
	}


	//找最大数,看需要排几趟
	int max = a[0];
	for (int i = 1; i < n; i++)
	{
		if (a[i] > max)
		{
			max = a[i];
		}
	}
	int k = 0;
	while (max)
	{
		++k;
		max = max / 10;
	}

	//排k趟
	for (int i = 0; i < k; ++i)
	{
		//分发数据
		Distribute(a,n,i);
		//回收数据
		Collect(a);

	}

	//销毁队列
	for (int i = 0; i < QENumber; ++i)
	{
		QueueDestroy(&(Qarray.qe[i]));
	}

}

计数排序

基本思想

思想:计数排序又称为鸽巢原理,是对哈希直接定址法的变形应用。 操作步骤:

  1. 统计相同元素出现次数
  2. 根据统计的结果将序列回收到原来的序列中

在这里插入图片描述

但是如果是下面这种情况
在这里插入图片描述
因此我们进行映射的时候,为了避免浪费,采取相对映射
在这里插入图片描述

实现

//计数排序
//时间复杂度O(N+range)
//空间复杂度O(rang)
void CountSort(int* a, int n)
{
	//相对映射
	int min = a[0], max = a[0];
	for (int i = 1; i < n; i++)
	{
		if (a[i] < min)
		{
			min = a[i];
		}
		if(a[i] > max)
		{
			max = a[i];
		}
	}

	int range = max - min + 1;

	int* count = (int*)calloc(range, sizeof(int));
	if (count == NULL)
	{
		perror("malloc fail\n");
		return;
	}

	//统计每个数出现的次数
	for (int i = 0; i < n; i++)
	{
		count[a[i]-min]++;
	}
	//排序
	int j = 0;
	for (int i = 0; i < range; i++)
	{
		while (count[i]--)
		{
			a[j++] = i+min;
		}
	}
}

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

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

相关文章

JDBC测试

JDBC是什么? JDBC是一套接口,各大厂商来实现这套接口&#xff0c;进行数据库连接操作 比如Mysql驱动,Oracle驱动,sqlServer驱动,高斯驱动 以Mysql为例: JDBC编程六步 第一步&#xff1a;注册驱动 第二步&#xff1a;获取连接 第三步&#xff1a;获取数据库操作对象 第…

为什么不用Go开发操作系统?

操作系统 (OS) 是计算机系统的心脏和灵魂&#xff0c;它管理着计算机的硬件和软件资源&#xff0c;并为用户提供与计算机交互的方式。传统上&#xff0c;C 和 Assembly 等语言因其低开销和 “接近机器码” 的特性而被用于开发操作系统。 但诸如 Go 等高级语言的兴起引入了一些…

黑客为什么不攻击赌博网站?

攻击了&#xff0c;只是你不知道而已&#xff01; 同样&#xff0c;对方也不会通知你&#xff0c;告诉你他黑了赌博网站。 攻击赌博网站的不一定是正义的黑客&#xff0c;也可能是因赌博输钱而误入歧途的法外狂徒。之前看过一个警方破获的真实案件&#xff1a;28岁小伙因赌博…

Xubuntu22.04之替换blueman-manager连接蓝牙设备(一百七十五)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 人生格言&#xff1a; 人生…

【C++】static在类中修饰成员变量成员函数

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 一、定义&#xff1a;二、特性&#xff1a;1. 静态成员为所有类对象所共享&#xff0c;不属于某个具体的对象&#xff0c;存放在静态区2. 静态成员变量必须在类外定…

C++学习笔记3:sort和priority_queue的比较器重载

1 sort 三种方法 1. 直接重载函数 #include <vector> #include <memory> #include <vector> #include <queue> #include <iostream> #include <algorithm>using namespace std;class Node{ public:int value;Node(){value 0;};explici…

【解决】sklearn-LabelEncoder遇到没在编码规则里的新值

文章目录 一、问题描述二、解决方法Reference 一、问题描述 问题&#xff1a;sklearn-LabelEncoder 遇到没在编码规则里的新值 二、解决方法 方法一&#xff1a;直接保存old_data和encoder_data和之间的映射关系&#xff0c;字典或者下面的csv格式里都可以。 for col in be…

UDS诊断实战系列-再谈19 04读取冻结帧子服务

本文框架 1. 前言2. 19 04 子服务2.1 请求某DTC快照信息2.1.1 请求报文格式及说明2.1.2 响应报文格式及说明 3. 开发注意事项3.1 快照高低字节顺序3.2 快照DID 1. 前言 19服务在整个UDS服务中非常重要&#xff0c;而19 04读取DTC冻结帧数据子服务又在0x19服务中非常重要&#…

<Linux开发>驱动开发 -之-资源的并发与竞争处理

&#xff1c;Linux开发&#xff1e;驱动开发 -之-资源的并发与竞争处理 交叉编译环境搭建&#xff1a; &#xff1c;Linux开发&#xff1e; linux开发工具-之-交叉编译环境搭建 uboot移植可参考以下&#xff1a; &#xff1c;Linux开发&#xff1e; -之-系统移植 uboot移植过…

K8s之Pod生命周期、启动停止钩子详解

文章目录 一、Pod生命周期流程二、初始化容器-initContainers三、主容器操作-containers1、启动钩子-lifecycle.postStart2、停止钩子-lifecycle.preStop 一、Pod生命周期流程 Pod生命周期整个过程 如下图&#xff1a; 1、在启动任何容器之前&#xff0c;前创建 pause 容器&am…

网络通信IO模型上

计算机组成 计算机由软件和硬件组成&#xff0c;软件包括CPU、内存等&#xff0c;硬件包括主板&#xff0c;磁盘&#xff0c;IO设备&#xff08;网卡、鼠标、键盘等&#xff09;、电源按钮。 内核程序加载过程 当接通电源的时候1、BIOS就会把它的一段代码放入了内存当中&#…

ORB SLAM3 构建Frame

1.构造Frame 为了构建一帧Frame&#xff0c;主要的步骤如下&#xff1a; 提取ORB特征点(ExtractORB)对提取的特征点进行矫正(cv::undistortPoints)计算去畸变后的图像边界(ComputeImageBounds)将特征点分配到网格中(AssignFeaturesToGrid) A.提取ORB特征点 首先需要对当前帧…

某程序员哀叹:月薪四五万,却每天极度焦虑痛苦,已有生理性不适,又不敢裸辞,该怎么办?

高薪能买来快乐吗&#xff1f; 来看看这位程序员的哀叹&#xff1a; 实在是扛不住了&#xff0c;每天都在极度焦虑和痛苦中度过&#xff0c;早上起来要挣扎着做心理建设去上班&#xff0c;已经产生生理性的头晕恶心食欲不振。有工作本身的原因&#xff0c;更多是自己心态的问…

如何在CSDN获得铁粉

文章目录 前言关于铁粉方法总结 前言 提示&#xff1a;这里可以添加本文要记录的大概内容&#xff1a; 铁粉512位即可参加此活动 完成一篇如何获得铁粉&#xff0c;或者相关的文章且质量分达到80分以上即可 关于铁粉 简单地说&#xff0c;就是在过去 N 个月内&#xff0c;一…

vscode配置flutter开发环境,不需要安装第三方安卓模拟器

1.获取Flutter SDK 点击下方的安装包&#xff0c;获取 stable 发行通道的 Flutter SDK 最新版本&#xff1a;Flutter SDK 将压缩包解压&#xff0c;然后把其中的 flutter 目录整个放在你想放置 Flutter SDK 的路径中**&#xff08;注意不要出现中文目录&#xff09;** 配置Wi…

Spring Boot配置文件(5/27)

1.Spring Boot 配置文件的分类和作用 整个项目所有重要的数据都是在配置文件中配置的 1.数据库连接信息&#xff08;包含用户名和密码的设置&#xff09; 2.项目的启动窗口&#xff1b; 3.第三方系统调用密匙等信息 4.用于发现和定位问题的普通日志和异常日志等等 大体上可以分…

基于FPGA的Bayer转RGB算法实现

1 概述 Bayer转RGB在图像处理中被称为去马赛克&#xff08;Demosaic&#xff09;&#xff0c;是机器视觉ISP流程中的一个基础且重要的算法&#xff0c;主要完成彩色图像传感器原始的Bayer格式图像到RGB格式图像的转换。 关于Bayer图像的相关概念和知识&#xff0c;本文不作介绍…

jquery data和data-属性不一致问题

延申val和value属性同样不一致 <script src"https://code.jquery.com/jquery-3.7.0.min.js"></script> <input type"text" value"F119-PW110" data-tag"F119" id"txtEngine" name"Engine" placeh…

第十六届全国大学生信息安全竞赛创新实践能力赛(CISCN)

目录 Misc 1、被加密的生产流量 Crypto 2、Sign_in_passwd Web 3、unzip 4、dumpit Re 5、babyRE Pwn 6、funcanary Misc 1、被加密的生产流量 下载附件解压后是一段流量&#xff0c;使用wireshark打开 最开始做的时候找错了方向&#xff0c;追踪到了另一个东西 …

阿里云服务器配置怎么选择合适?CPU内存带宽配置

阿里云服务器配置如何选择&#xff1f;个人用户选择通用算力型u1云服务器或轻量应用服务器&#xff0c;2核2G、2核4G配置即可&#xff0c;企业公司用户可以选择独享型ECS计算型c7、通用型g7等&#xff0c;4核8G、8核16G、4核32G等配置&#xff0c;阿里云百科来详细说下不同用户…