数据结构-八大排序详解(动图+实现详解+总结)

news2024/11/27 22:30:41

1 前言

本章主要讲解:

八大排序的基本知识及其实现
注:这里的八大排序指直接插入,希尔,选择,堆排,冒泡,快排,归并,基数

八大排序汇总图:

在这里插入图片描述

2 排序概念及应用

2.1 排序概念

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

2.2 排序应用

示例:搜索电影、搜索商品、搜索引擎、文件系统等

在这里插入图片描述

3 排序算法接口展示

// 排序实现的接口

// 插入排序
void InsertSort(int* a, int n);
// 希尔排序
void ShellSort(int* a, int n);
// 选择排序
void SelectSort(int* a, int n);
// 堆排序
void AdjustDwon(int* a, int n, int root);
void HeapSort(int* a, int n);
// 冒泡排序
void BubbleSort(int* a, int n)
// 快速排序递归实现
// 快速排序hoare版本
int PartSort1(int* a, int left, int right);
// 快速排序挖坑法
int PartSort2(int* a, int left, int right);
// 快速排序前后指针法
int PartSort3(int* a, int left, int right);
void QuickSort(int* a, int left, int right);
// 快速排序 非递归实现
void QuickSortNonR(int* a, int left, int right)
// 归并排序递归实现
void MergeSort(int* a, int n)
// 归并排序非递归实现
void MergeSortNonR(int* a, int n)
// 计数排序
void CountSort(int* a, int n)

4 插入排序

4.1 直接插入排序

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

4.1.1 基本思想

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

4.1.2 动图展示

在这里插入图片描述

4.1.3 实现代码

//直接插入排序
void InsertSort(int* a, int n)
{
	assert(a);//传入数组不为空指针
	int i;
	for (i = 0; i < n - 1; i++)
		//注意:最后一个要插入数据的下标为n-1,此次插入有序数列的end下标为n-2
	{
		int end = i;//标记当前有序数列的最后一个位置下标
		int x = a[end + 1];//要插入的数据为有序数列的后一个位置

		while (end >= 0)//进行当前趟次的插入排列
		{
			//升序
			if (a[end] >x)//有序数列的数据比插入数据大,则往后挪动
			{
				a[end + 1] = a[end];
				end--;//向前找,进行排列数据
			}
			else//遇到不大于要插入数据,则不再往前找
			{
				break;
			}
		}
		a[end + 1] = x;//将要插入数据插入到不大于该数据的后一个位置
	}
}

4.1.4 直接插入排序的特性总结

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

4.2 希尔排序

希尔排序法又称缩小增量法,是在直接插入排序基础上的一个优化版

4.2.1 基本思想

对于直接插入排序在面对一些特殊情况时,效率非常低(例如:将降序排成升序),而对于已经接近排好的序列,效率非常高

希尔排序在直接排序之前,进行预排列,将某些极端数据更快的排列到数列前面,构成一个接近排列好的序列,最后再进行一次直接插入排序
预排列的原理也是插入排列,只不过这里的将数组分成了gap组,分别对每一个小组进行插入排序

如下动图:对于升序,当gap从5 – 2 – 1的过程中,排在后面的数值小的数能更快排到前面,当gap为1的时候实际上就是进行了一次插入排序

4.2.2 动图展示

在这里插入图片描述

4.2.3 实现代码

// 希尔排序
void ShellSort(int* a, int n)
{
	//多组预排(一锅炖)+插排
	int gap = n;
	while (gap > 1)
	{
		gap /= 2;//保证最后一次分组gap==1,即最后一次为直接插入排序
		//gap = gap / 3 + 1;//也可以写成这样,除3预排的效率相比于除2的好点
		for (int i = 0; i < n - gap; i++)
		{
			int end = i;
			int x = a[end + gap];
			while (end >= 0)
			{
				if (a[end] > x)
				{
					a[end + gap] = a[end];
					end-=gap;
				}
				else
					break;
			}
			a[end + gap] = x;
		}
	}
}

4.2.4 希尔排序的特性总结

  1. 希尔排序是对直接插入排序的优化
  2. 当gap > 1时都是预排序,目的是让数组更接近于有序。当gap == 1时,数组已经接近有序的了,这样就会很快。这样整体而言,可以达到优化的效果。我们实现后可以进行性能测试的对比
  3. 希尔排序的时间复杂度不好计算,因为gap的取值方法很多,一般来说为O(n^1.3)
  4. 稳定性:不稳定

5 选择排序

5.1 直接选择排序

5.1.1 基本思想

每一次遍历待排序的数据元素从中选出最小(或最大)的一个元素,存放在序列的起始(或者末尾)位置,直到全部待排序的数据元素排完

5.1.2 动图展示

在这里插入图片描述

5.1.3 实现代码

// 选择排序
void SelectSort(int* a, int n)
{
	int begin = 0, end = n - 1;//记录下标
	while (begin < end)
	{
		int mini = begin;
		for (int i = begin; i <= end; i++)
		{
			//遍历找到最小数据并记录下标
			if (a[i] < a[mini])
				mini = i;
		}
		Swap(&a[begin], &a[mini]);//交换
		begin++;//缩小范围
	}
}

这里我们还可以对直接选择排序做一个优化:每次遍历待排序数据找出最大和最小的数据,分别排列到序列起始和末尾。

优化代码如下:

选择排序(优化版)
void SelectSort(int* a, int n)
{
	int begin = 0, end = n - 1;
	while (begin < end)
	{
		int maxi = begin, mini = begin;
		for (int i = begin; i <= end; i++)//遍历找到最大最小的下标
		{
			if (a[i] > a[maxi])
				maxi = i;
			if (a[i] < a[mini])
				mini = i;
		}
		Swap(&a[begin], &a[mini]);//交换
		//当最初始位置begin与对大数据下标重合的情况
		if (begin == maxi)//修正下标位置
			maxi = mini;
		Swap(&a[end], &a[maxi]);
		begin++;//缩小范围
		end--;
	}
}

5.1.4 直接选择排序的特性总结

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

5.2 堆排序

堆排序是指利用堆(数据结构)进行选择数据的一种排序算法

5.2.1 基本思想

原则:
先将原数组建成堆,需要注意的是排升序要建大堆,排降序建小堆
注:以大堆为例

建堆:
一个根节点与子节点数据如果不符合大堆结构,那么则对根节点数据进行向下调整,而向下调整的前提是左右子树也符合大堆结构,所以从堆尾数据的根节点位置开始向下调整建大堆

排序:
大堆堆顶数据一定是待排数据中最大的,将堆顶数据与堆尾数据交换,交换后将除堆尾数据看成新堆,对现堆顶数据进行向下调整成大堆,以此循环直至排列完毕

向下调整:
找到子节点中的较大数据节点比较,如果父节点数据比大子节点小则交换,直到不符合则停止向下交换,此时再次构成了一个大堆结构

5.2.2 动图展示

在这里插入图片描述

5.2.3 实现代码

void Adjustdown(int* a, int n,int parent)
{
	int child = parent * 2 + 1;
	while (child < n)
	{
		//找到数据大的子结点
		if (child + 1 < n && a[child + 1] > a[child])
		{
			child++;
		}
		//父节点数据小于子节点就交换
		if (a[parent] < a[child])
		{
			Swap(&a[parent], &a[child]);
			//更新下标
			parent = child;
			child = parent * 2 + 1;
		}
		else//否则向下调整完毕
			break;
	}
}

// 堆排序(升序)建大堆
void HeapSort(int* a, int n)
{
	int i;
	//建大堆
	for (i = (n - 1 - 1) / 2; i >= 0; i--)
	{
		Adjustdown(a, n, i);
	}
	//交换调整
	for (i = n - 1; i >= 0; i--)
	{
		Swap(&a[0], &a[i]);//与当前堆尾数据交换
		Adjustdown(a, i, 0);//对交换后堆顶数据进行向下调整
	}
}

5.2.4 直接选择排序的特性总结

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

6 交换排序

6.1 冒泡排序

6.1.1 基本思想

每次遍历待排序数组,对相邻数据进行比较,不符合排序要求则交换

6.1.2 动图展示

在这里插入图片描述

6.1.3 实现代码

// 冒泡排序
void BubbleSort(int* a, int n)
{
	int i, j;
	for (i = 0; i < n - 1; i++)//遍历趟数
	{
		for (j = 0; j < n - 1 - i; j++)//比较次数
		{
			if (a[j] > a[j + 1])//升序
				Swap(&a[j], &a[j + 1]);//交换
		}
	}
}

6.1.4 冒泡排序的特性总结

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

6.2 快速排序

6.2.1 基本思想、实现代码及动图展示

任取待排序元素序列中的某元素作为基准值,按照该排序码将待排序集合分割成两子序列

左子序列中所有元素均小于基准值,右子序列中所有元素均大于基准值 然后最左右子序列重复该过程,直到所有元素都排列在相应位置上为止

按基准值划分左右的方式有:

1)hoare
  • 基本操作过程如图所示:

在这里插入图片描述

  • 实现代码:
// 按基准划分hoare版本
int PartSort1(int* a, int left, int right)
{
	int mid = GetMidIndex(a, left, right);//三数取中(优化取基准值,后面会解释)
	Swap(&a[mid], &a[left]);//使得中间值永远在最左,便于决定谁先走
	int key = left;
	while (left < right)
	{
		//Key设在左边,先从右边寻找小于a[key]的
		while (left < right && a[right] >= a[key])
		{
			right--;
		}
		//再从左边寻找大于a[key]的
		while (left < right && a[left] <= a[key])
		{
			left++;
		}
		//找到后交换
		Swap(&a[left], &a[right]);
	}
	//最后相遇时将key与相遇点交换
	Swap(&a[key], &a[left]);

	return left;//返回相遇点下标
}

  • key的位置与左右下标谁先走的关系:

注:对于排升序来说一般来说在三数取中后得到中等值key,我们让该值与待排序数组的最左边起始位置交换,使得key永远在最左边,并且之后会让右下标先走找小于key的值,找到后再让左下标走找大于key的值,都找到则交换,相遇后再将key与相遇位置的值交换

  • 右下标先走的话,对于两下标相遇的的情况只有两种:

1.右下标走着走着遇到左下标,此时左下标的值一定是小于key的值(交换后左下标是原来右下标的小于key的值)
2. 左下标走着走着遇到右下标,此时右下标的值一定是小于key的是(右下标找小于key的值)
所以这样保证了最后下标相遇与key交换后,key左边区间一定小于key,右边区间一定大于key

2)挖坑法
  • 基本操作过程如图示:
    在这里插入图片描述
  • 实现代码:
// 快速排序挖坑法
int PartSort2(int* a, int left, int right)
{
	int mid = GetMidIndex(a, left, right);
	Swap(&a[mid], &a[left]);//使得中间值永远在最左,便于决定谁先走
	int key = a[left];//保存key值(基准值)
	int pivot = left;//保存坑下标
	while (left < right)
	{
		//右边先找
		while (left<right && a[right]>=key)
		{
			right--;
		}
		//填坑
		a[pivot] = a[right];
		pivot = right;
		//再从左边找
		while (left < right && a[left] <= key)
		{
			left++;
		}
		//填坑
		a[pivot] = a[left];
		pivot = left;
	}
	//相遇
	a[pivot] = key;
	return pivot;
}
3)前后指针法
  • 基本操作过程如图示:

在这里插入图片描述

  • 实现代码:
// 快速排序前后指针法(推荐)
int PartSort3(int* a, int left, int right)
{
	int mid = GetMidIndex(a, left, right);
	Swap(&a[mid], &a[left]);
	//初始化前后指针
	int cur = left, prev = left-1;
	while (cur < right)
	{
		if(a[cur]<a[right] )//找到比基准值小的
		Swap(&a[++prev], &a[cur]);

		cur++;
	}
	Swap(&a[++prev], &a[right]);//遍历结束将基准值放在定位点
	return prev;
}

注:推荐掌握,简单易于操控

4)优化
  • 三数取中:
  1. 如果基准值取到的是待排序列中的中位数,对于快排来说效率是最优的

在这里插入图片描述

  1. 如果基准值取到的是待排序列中的最大或最小,对于快排来说效率是最差的
    在这里插入图片描述
    为了优化这种特殊情况,我们在取基准值时会采取三数取中,即堆待排序序列的开始处,末尾处和中间处位置的数据进行比较,得到排中的数据,尽量使得快速排序的效率达到理想状态O(N*logN)
  • 实现代码:
int GetMidIndex(int* a, int left, int right)//优化快排(避免特殊情况造成效率降低)
{
	int mid = right + (left - right) >> 1;//获取中间下标(注意避免和溢出)
	if (a[mid]>a[left])//返回中等数据的下标
	{
		return a[mid] < a[right] ? mid : right;
	}
	else//a[mid]<=a[left]
	{
		return a[left] < a[right] ? left : right;
	}
}
  • 整体实现代码:
//快排
void QuickSort(int* a, int left, int right)
{
	//当区间只有一个元素或没有元素时不需要排序了
	if (left >= right)
		return;
	//遍历一趟进行交换排序
	int mid=PartSort3(a, left, right);
	//递归排序左右区间
	QuickSort(a, left, mid - 1);
	QuickSort(a, mid+1, right);
}
  • 小区间优化:

当待排序数组的区间很小时,递归开辟的函数栈帧数量时很大的,很多时甚至可能造成栈溢出

在这里插入图片描述
为了解决这一问题,当区间小到一定程度时,我们选择使用希尔排序,小到一定程度时待排序数列已经快接近有序,而希尔排序对于接近有序数列的排序时非常高效的

  • 实现代码:
//快排+局部优化
void QuickSort1(int* a, int left, int right)
{
	if (left >= right)//当区间只有一个元素或没有元素时递归结束
		return;

	if (right - left + 1 <= 10)
	{
		InsertSort(a + left, right - left + 1);
	}
	else
	{
		int mid = PartSort3(a, left, right);//进行一趟交换排序
		QuickSort1(a, left, mid - 1);//递归交换排序
		QuickSort1(a, mid + 1, right);
	}
}

6.2.2 快速排序的特性总结

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

6.3 快排非递归

6.3.1 基本思想

对于递归函数在内存实际上是在栈中进行开辟函数栈帧,这里我们使用数据结构中的栈来模拟内存中的栈,从而实现快排的非递归

6.3.2 实现代码

// 快速排序 非递归实现
void QuickSortNonR(int* a, int left, int right)
{
	//首先构建一个栈(C语言来说需要自己实现)
	ST st;
	StackInit(&st);
	StackPush(&st, left);//将左右区间入栈
	StackPush(&st, right);
	while (!StackEmpty(&st))
	{
		int end = StackTop(&st);//读取区间数据
		StackPop(&st);
		int begin = StackTop(&st);
		StackPop(&st);

		int mid = PartSort3(a, begin, end);//排序(排好基准值)
		//划分基准值的左右区间
		int begin1 = mid + 1, end1 = end;
		//先入右边区域(栈的特点是先入后出)
		if (end1 - begin1 + 1 > 1)
		{
			StackPush(&st, begin1);
			StackPush(&st, end1);
		}
		//再将左边区域入栈
		int begin2 = begin, end2 = mid-1;
		if (end2 - begin2 + 1 > 1)
		{
			StackPush(&st, begin2);
			StackPush(&st, end2);
		}
	}
	//到空栈则排序结束
	StackDestroy(&st);//栈销毁
}

7 归并排序

归并排序是建立在归并操作上的一种有效的排序算法,采用分治法

7.1 归并排序

7.1.1 基本思想

将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序

7.1.2 核心步骤

在这里插入图片描述

7.1.3 动图展示

在这里插入图片描述

7.1.4 实现代码

//归并排序
void _MergeSort(int* a, int left, int right, int* tmp)
{
	if (left >= right)//只有一个元素或没有元素为有序,则返回
		return;
	int mid = (right + left) / 2;
	_MergeSort(a, left, mid, tmp);
	_MergeSort(a, mid+1, right, tmp);
	//左区间和右区间有序后开始归并
	int begin1 = left, end1 = mid;
	int begin2 = mid+1, end2 = right;
	int p = left;//记录下标
	while (begin1<=end1&&begin2<=end2)//归并排序
	{
		if (a[begin1] < a[begin2])//升序
		{
			tmp[p++] = a[begin1++];
		}
		else
		{
			tmp[p++] = a[begin2++];
		}
	}
	while (begin1 <= end1)//剩下部分
	{
		tmp[p++] = a[begin1++];
	}
	while (begin2 <= end2)
	{
		tmp[p++] = a[begin2++];
	}
	//拷贝回数组a
	for (int i = left; i <= right; i++)
	{
		a[i] = tmp[i];
	}

void MergeSort(int* a, int n)
{
	//创建暂存数据数组(保存归并好的数据)
	int* tmp = (int*)malloc(sizeof(int) * n);
	if (tmp == NULL)
	{
		perror("nalloc fail\n");
		exit(-1);
	}
	//归并排序
	_MergeSort(a, 0, n - 1, tmp);
	//释放
	free(tmp);
	tmp = NULL;
}

7.1.5 归并排序的特性总结

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

7.2 非递归归并

7.2.1 基本思路

对于归并的非递归来说可以用栈也可以用循环,这里主要讲解循环(比较简单,直接从合并步骤开始)

7.2.2 实现代码

void MergeSortNonR(int* a, int n)
{
	int* tmp = (int*)malloc(sizeof(int)*n);
	if (tmp == NULL)
	{
		perror("malloc fail");
		exit(-1);
	}
	int gap = 1;//数组归并距离
	//(初始gap为1即每个数组只有一个元素,此时每个数组都为有序数组)
	while (gap < n)//归并趟次
	{
		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|| begin2 >= n)
			{
				break;
			}
			//这种情况需要归并
			if (end2 >= n)
			{
				end2 = n - 1;
			}
			//归并
			int p = i;
			while (begin1 <= end1 && begin2 <= end2)
			{
				if (a[begin1] < a[begin2])
				{
					tmp[p++] = a[begin1++];
				}
				else
				{
					tmp[p++] = a[begin2++];
				}
			}
			while (begin1 <= end1)
			{
				tmp[p++] = a[begin1++];
			}
			while (begin2 <= end2)
			{
				tmp[p++] = a[begin2++];
			}
			//拷贝排序后数据到原数组
			for (int j = i; j <= end2; j++)
			{
				a[j] = tmp[j];
			}
		}
		gap *= 2;
	}
	free(tmp);//释放
	tmp = NULL;
}

8 基数排序和计数排序

基数排序和计数排序属于线性时间非比较类排序,即:不通过比较来决定元素间的相对次序,它可以突破基于比较排序的时间下界,以线性时间运行,因此也称为线性时间非比较类排序

8.1 基数排序

8.1.1 基本思想

基数排序第i趟将待排数组里的每个数的i位数放到tempj(j=1-10)队列中,然后再从这十个队列中取出数据,重新放到原数组里,直到i大于待排数的最大位数。

1.数组里的数最大位数是n位,就需要排n趟,例如数组里最大的数是3位数,则需要排3趟。

2.若数组里共有m个数,则需要十个长度为m的数组tempj(j=0-9)用来暂存i位上数为j的数,例如,第1趟,各位数为0的会被分配到temp0数组里,各位数为1的会被分配到temp1数组里…

3.分配结束后,再依次从tempj数组中取出数据,遵循先进先进原则,例如对数组{1,11,2,44,4},进行第1趟分配后,temp1={1,11},temp2={2},temp4={44,4},依次取出元素后{1,11,2,44,4},第一趟结束

4.循环到n趟后结束,排序完成

8.1.2 动图展示

在这里插入图片描述

8.1.3 实现代码

private static void radixSort(int[] arr) {
        //求出待排数的最大数
        int maxLength=0;
        for (int i = 0; i < arr.length; i++) {
            if(maxLength<arr[i])
                maxLength = arr[i];
        }
        //根据最大数求最大长度
        maxLength = (maxLength+"").length();

        //用于暂存数据的数组
        int[][] temp = new int[10][arr.length];
        //用于记录temp数组中每个桶内存的数据的数量
        int[] counts = new int[10];
        //用于记录每个数的i位数
        int num = 0;
        //用于取的元素需要放的位置
        int index = 0;
        //根据最大长度决定排序的次数
        for (int i = 0,n=1; i < maxLength; i++,n*=10) {
            for (int j = 0; j < arr.length; j++) {
                num = arr[j]/n%10;
                temp[num][counts[num]] = arr[j];
                counts[num]++;
            }
            
            //从temp中取元素重新放到arr数组中
            for (int j = 0; j < counts.length; j++) {
                for (int j2 = 0; j2 < counts[j]; j2++) {
                    arr[index] = temp[j][j2];
                    index++;
                }
                counts[j]=0;
            }
            index=0;
        }
        System.out.println(Arrays.toString(arr));
    }

8.1.4 排序的特性总结

  1. 时间复杂度:每一次关键字的桶分配都需要O(n)的时间复杂度,而且分配之后得到新的关键字序列又需要O(n)的时间复杂度。假如待排数据可以分为d个关键字,则基数排序的时间复杂度将是O(d2n) ,当然d要远远小于n,因此基本上还是线性级别的。系数2可以省略,且无论数组是否有序,都需要从个位排到最大位数,所以时间复杂度始终为O(dn) 。其中,n是数组长度,d是最大位数。
  2. 空间复杂度: 基数排序的空间复杂度为O(n+k),其中k为桶的数量,需要分配n个数。

8.2 计数排序

计数排序是一种非比较排序,又称为鸽巢原理,是对哈希直接定址法的变形应用

8.2.1 基本思想

在排序数组中找到最大最小的数据,算出对应范围并创建对应长度个数组用来计数,遍历排序数组,根据每个出现的数据值与计数数组下标构建的相对映射关系进行统计数据出现次数,最后将统计的出的数据按次序赋值给原数组

8.2.2 动图展示

在这里插入图片描述

8.2.3 代码实现

void CountSort(int* a, int n)
{
	//遍历找出数组最大最小值(算出范围)
	int max = a[0], min = a[0];
	for (int i = 1; i < n; i++)
	{
		if (a[i] > max)
			max = a[i];
		if (a[i] < min)
			min = a[i];
	}
	int range = max - min + 1;
	//开辟对应长度个计数数组
	int* count = (int*)malloc(sizeof(int) * range);
	if (count == NULL)
	{
		perror("malloc fail");
		exit(-1);
	}
	//初始化数组计数为0
	memset(count, 0, sizeof(int)*range);
	//遍历计数据出现次数
	for (int i = 0; i < n; i++)
	{
		count[a[i] - min]++;
		//a[i] - min:数据与下标构成的相对映射关系
	}
	//排入原数组
	int p = 0;
	for (int i = 0; i < range; i++)
	{
		while (count[i]--)
		{
			a[p++] = i + min;
		}
	}
	free(count);//释放
	count = NULL;
}

8.2.4 计数排序的特性总结

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

9 性能分析

  • 稳定:如果a原本在b前面,而a=b,排序之后a仍然在b的前面。
  • 不稳定:如果a原本在b的前面,而a=b,排序之后 a 可能会出现在 b 的后面。
  • 时间复杂度:对排序数据的总的操作次数。反映当n变化时,操作次数呈现什么规律。
  • 空间复杂度:是指算法在计算机内执行时所需存储空间的度量,它也是数据规模n的函数。
    在这里插入图片描述
    总体来说插入排序,选择排序,冒泡排序是低一级水平的排序算法,希尔排序,堆排序,归并排序和快速排序是高一级别的排序,而计数排序效率非常高,但有一定局限

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

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

相关文章

MindOpt 云上建模求解平台:多求解器协同优化

前言 数学规划是一种数学优化方法&#xff0c;主要是寻找变量的取值在特定的约束情况下&#xff0c;使我们的决策目标得到一个最大或者最小值的决策。 使用数学规划的方法我们需要确定问题的目标、约束、变量的取值范围&#xff0c;然后进行数学建模&#xff0c;将数学公式转化…

前端使用高德api的AMap.Autocomplete无效,使用AMap.Autocomplete报错

今天需要一个坐标拾取器&#xff0c;需要一个输入框输入模糊地址能筛选的功能 查看官方文档&#xff0c;有一个api可以直接满足我们的需求 AMap.Autocomplete 上代码 AMapLoader.load({"key": "你的key", // 申请好的Web端开发者Key&#xff0c;首次调…

[AI编程]AI辅助编程助手-亚马逊AI 编程助手 Amazon CodeWhisperer

亚马逊AI 编程助手 Amazon CodeWhisperer 是一种基于人工智能技术的编程辅助工具&#xff0c;旨在帮助开发人员更高效地编写代码。它可以提供实时的代码建议、自动补全和错误检查&#xff0c;帮助优化代码质量和提高编程效率。 Amazon CodeWhisperer 使用了自然语言处理和机器…

Lunix的奇妙冒险————权限篇

文章目录 一.什么是权限二.用户权限和类别。1.用户2.角色3.更换文件角色 三.文件的类别和对应权限1.文件的类别。2.文件属性权限1.权限说明。2.默认生成文件权限来源3.更改权限 3.文件的执行与删除 四.不同用户共同在一个目录下的权限。1.普通用户家目录2.在同一目录下文件的权…

述职报告一般怎么写?

在日常生活中&#xff0c;我们经常需要撰写各种报告。对于报告的撰写&#xff0c;我们需要清晰地解释涉及的专业术语。现在&#xff0c;我为大家整理了一些精选的晋升述职报告范文&#xff0c;供大家参考和借鉴。希望这些范文能对大家有所帮助。 晋升述职报告范文精选1 一、个…

力扣题目学习笔记(OC + Swift)19. 删除链表的倒数第 N 个结点

19. 删除链表的倒数第 N 个结点 给你一个链表&#xff0c;删除链表的倒数第 n 个结点&#xff0c;并且返回链表的头结点。 此题目为链表题&#xff0c;拿出我们的杀手锏&#xff0c;链表解题经典三把斧&#xff1a; 哑巴节点栈快慢指针 关于内存问题&#xff1a;由于Swift及…

Fiddler 抓包工具的安装与使用

今天分享Fiddler 抓包工具的安装与使用&#xff0c;基于HTTP应用层协议的抓包工具。 一、下载安装 1、下载地址&#xff1a; The Ultimate Web Debugging Tool - Download Fiddler Everywhere | Telerik 选择相应类型&#xff1a; 2、下载完成 3、安装 傻瓜式安装&#xf…

【开源学习】ThingsBoard -- 基本配置与使用

【开源学习】ThingsBoard -- 基本配置与使用 租户及客户管理租户及租户账号管理租户管理租户创建租户修改租户删除 租户账号管理租户账号创建租户账号修改租户账号删除 客户及客户账号管理客户管理客户创建客户修改客户删除 客户用户管理客户用户创建客户用户修改客户用户删除 …

蔓灵花组织wmRAT攻击武器对比分析

概述 蔓灵花&#xff0c;又名"Bitter"、"APT-C-08"、"T-APT-17"以及"苦象"&#xff0c;常对南亚周边及孟加拉湾海域的相关国家发起网络攻击&#xff0c;主要针对巴基斯坦和中国两国。其攻击目标主要包括政府部门、核工业、能源、国防…

【数据分享】2023年我国省市县三级的生活服务设施数量(23类设施/Excel/Shp格式)

人才市场、售票处、旅行社等生活服务设施的配置情况是一个城市公共基础设施完善程度的重要体现&#xff0c;一个城市生活服务设施种类越丰富&#xff0c;数量越多&#xff0c;通常能表示这个城市的公共服务水平越高&#xff01; 本次我们为大家带来的是我国各省份、各地级市、…

Hive05_DML 操作

1 DML 数据操作 1.1 数据导入 1.1.1 向表中装载数据&#xff08;Load&#xff09; 1&#xff09;语法 hive> load data [local] inpath 数据的 path [overwrite] into table student [partition (partcol1val1,…)];&#xff08;1&#xff09;load data:表示加载数据 &…

Matplotlib、Pandas可视化工具

一、Matplotlib 1.简介&#xff1a; 数据可视化可以看到变量的分布和变量之间的关系&#xff0c;还可以检查建模过程中的假设。Python 提供了若干种用于绘图的扩展包&#xff0c;包括:Matplotlib、 Pandas、 ggplot 和 Seaborn等。Matplotlib 是最基础的扩展包&#xff0c;它…

CUDA驱动深度学习发展 - 技术全解与实战

全面介绍CUDA与pytorch cuda实战 关注TechLead&#xff0c;分享AI全维度知识。作者拥有10年互联网服务架构、AI产品研发经验、团队管理经验&#xff0c;同济本复旦硕&#xff0c;复旦机器人智能实验室成员&#xff0c;阿里云认证的资深架构师&#xff0c;项目管理专业人士&…

利用全面预算管理,构建企业数智化管理体系

财务团队对于一个企业的发展来说一直承担着巨大的压力&#xff0c;特别是当今的经济世态&#xff0c;财务管理被赋予比以往任何时候更高的期望。为了提高运营效率&#xff0c;降低管理成本&#xff0c;企业朝着数智化的方向逐渐靠拢。其中&#xff0c;全面预算管理的潜在价值是…

Walker S敲锣,人形机器人第一股优必选在港交所主板挂牌上市

12月29日&#xff0c;优必选&#xff08;股份代码&#xff1a;9880.HK&#xff09;正式在香港交易所主板挂牌上市&#xff0c;股价最高达91港元&#xff0c;对应市值约380亿港元。 图片1&#xff1a;从左至右依次为&#xff1a;香港交易所联席营运总监陈翊庭&#xff0c;香港联…

1.Linux快速入门

Linux快速入门 Linux操作系统简介Linux操作系统优点Linux操作系统发行版1. Red Hat Linux2. CentOS3. Ubuntu4. SUSE Linux5. Fedora Linux 32位与64位操作系统的区别Linux内核命名规则 Linux操作系统简介 Linux操作系统是基于UNIX以网络为核心的设计思想&#xff0c;是一个性…

禁止浏览器记住密码和自动填充 element-ui+vue

vue 根据element-ui 自定义密码输入框&#xff0c;防止浏览器 记住密码和自动填充 <template><divclass"el-password el-input":class"[size ? el-input-- size : , { is-disabled: disabled }]"><inputclass"el-input__inner"…

ssm基于JAVA的学生在线考试系统+vue论文

摘 要 如今社会上各行各业&#xff0c;都喜欢用自己行业的专属软件工作&#xff0c;互联网发展到这个时候&#xff0c;人们已经发现离不开了互联网。新技术的产生&#xff0c;往往能解决一些老技术的弊端问题。因为传统考试信息管理难度大&#xff0c;容错率低&#xff0c;管理…

目标检测-One Stage-SSD

文章目录 前言一、SSD的网络结构和流程二、SSD的创新点总结 前言 根据前文目标检测-Two Stage-YOLOv1可以看出YOLOv1的主要缺点是&#xff1a; 每个格子针对目标框的回归是不加限制的&#xff0c;导致目标的定位并不是很精准和Faster RCNN等先进Two Stage算法相比&#xff0c…

1.DQL查询数据(超重点)以及distinct(去重)

DQL(Data Query Language:数据查询语言) 1.所有查询操作都用 SELECT 2.无论是简单的查询还是复杂的查询它都能做 3.数据库中最核心的语言&#xff0c;最重要的语句 4.使用频率最高的语句 语法&#xff1a; SELECT 字段1&#xff0c;字段2&#xff0c;……FROM 表 有时候…