【八大排序】一篇文章搞定所有排序

文章目录

  • 1.排序的概念
  • 2.常见排序算法的实现
  • 2.1 插入排序
    • 2.1.1直接插入排序
    • 2.1.2希尔排序
  • 2.2选择排序
    • 2.2.1直接选择排序:
    • 2.2.2堆排序
  • 2.3交换排序
    • 2.3.1冒泡排序
    • 2.3.2快速排序
      • Hoare法
      • 前后指针法
      • 挖坑法
      • 非递归版本
  • 2.4归并排序
      • 递归版本
      • 非递归版本
  • 2.5计数排序
  • 3.排序的比较

在这里插入图片描述

1.排序的概念

1.1排序的概念

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

2.常见排序算法的实现

2.1 插入排序

直接插入排序是一种简单的插入排序法,其基本思想是:把待排序的记录按其关键码值的大小逐个插入到一个已经排好序的有序序列中,直到所有的记录插入完为止,得到一个新的有序序列 。

2.1.1直接插入排序

在这里插入图片描述
当插入第i(i>=1)个元素时,前面的array[0],array[1],…,array[i-1]已经排好序,此时用array[i]的排序码与array[i-1],array[i-2],…的排序码顺序进行比较,找到插入位置即将array[i]插入,原来位置上的元素顺序后移。

void InsertSort(int* a, int n)
{
	for (int i = 0; i < n - 1; i++)
	{
		int end = i;
		int tmp = a[end + 1];//待排序数据
		while (end >= 0)
		{
			if (tmp < a[end])//将待排序数据前的数据依次与其比较
			{
				a[end + 1] = a[end];//数据后移
				end--;
			}
			else
			{
				break;
			}
		}
		a[end + 1] = tmp;//将待排序数据放在合适位置
	}
}

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

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

2.1.2希尔排序

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

  • 希尔排序是对直接插入排序的优化。
    当gap > 1时都是预排序,目的是让数组更接近于有序。当gap == 1时,数组已经接近有序的了,在使用直接插入排序,这样就会很快。这样整体而言,可以达到优化的效果。

  • 在初始时,gap通常选择 n/2 或 n/3+1(n为数据的个数)

在这里插入图片描述
在这里插入图片描述

void ShellSort(int* a, int n)
{
	int gap = n;
	while (gap > 1)
	{
		//gap = gap / 3 + 1;
		gap = gap / 2;
		//gap趟
		for (int j = 0; j < gap; j++)
		{
			//一趟的
			for (int i = j; i < n - gap; i+=gap)//每次跳跃gap
			{
				int end = i;
				int tmp = a[end + gap];
				while (end >= 0)
				{
					if (tmp < a[end])
					{
						a[end + gap] = a[end];
						end -= gap;
					}
					else
					{
						break;
					}
				}
				a[end + gap] = tmp;
			}
		}
	}
}

上面的代码可以进行优化,不需要单独排每一趟,直接可将gap趟一次排。

void ShellSort(int* a, int n)
{
	int gap = n;
	while (gap > 1)
	{
		//gap = gap / 3 + 1;
		gap = gap / 2;
		//gap趟一起排
		for (int i = 0; i < n - gap; i++)
		{
			int end = i;
			int tmp = a[end + gap];
			while (end >= 0)
			{
				if (tmp < a[end])
				{
					a[end + gap] = a[end];
					end -= gap;
				}
				else
				{
					break;
				}
			}
			a[end + gap] = tmp;
		}

	}
}

希尔排序的特性总结:

  1. 时间复杂度:O(N^1.3)
  2. 空间复杂度:O(1)
  3. 稳定性:不稳定

2.2选择排序

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

2.2.1直接选择排序:

在元素集合array[i]–array[n-1]中选择关键码最大(小)的数据元素若它不是这组元素中的最后一个(第一个)元素,则将它与这组元素中的最后一个(第一个)元素交换在剩余的array[i]–array[n-2](array[i+1]–array[n-1])集合中,重复上述步骤,直到集合剩余1个元素。
在这里插入图片描述

void Swap(int* x, int* y)
{
	int tmp = *x;
	*x = *y;
	*y = tmp;
}

void SelectSort(int* a, int n)
{
	for (int i = 0; i < n - 1; i++)
	{
		int m = i;//记录当前数的下标
		for (int j = i + 1; j < n; j++)
		{
			if (a[j] < a[m])//将当前数与其后的数对比
			{
				m = j;//记录较小值的下标
			}
		}
		//较小值不是当前数,交换
		if (m != i)
		{
			Swap(&a[i], &a[m]);
		}
	}
}

优化:一趟遍历找出最大与最小值。

void SelectSort(int* a, int n)
{
	int left = 0;
	int right = n - 1;
	while(left < right)
	{
		int max = left;
		int min = left;
		for (int i = left; i <= right; i++)
		{
			//找最大值
			if (a[i] > a[max])
				max = i;
			//找最小值
			if (a[i] < a[min])
				min = i;
		}
		Swap(&a[min], &a[left]);
		//若最大值在最左侧,其会被换到最小值位置
		if (max == left)
		{
			max = min;
		}
		Swap(&a[max], &a[right]);
		left++;
		right--;
	}
}

在这里插入图片描述
选择排序的特性总结:

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

2.2.2堆排序

堆排序在前面已经写过,点击链接跳转至堆排序

排序的特性总结:

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

2.3交换排序

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

2.3.1冒泡排序

冒泡排序相信大家都非常熟悉了,直接上代码吧。
在这里插入图片描述

void BubbleSort(int* a, int n)
{
	//进行多少趟比较
	for (int i = 0; i < n - 1; i++)
	{
		//每趟比较的次数
		for (int j = 0; j < n - 1 - i; j++)
		{
			if (a[j] > a[j + 1])
			{
				Swap(&a[j], &a[j + 1]);
			}
		}
		Print(a, n);

	}
}

在这里插入图片描述
冒泡排序的特性总结:

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

2.3.2快速排序

Hoare法

快速排序是Hoare于1962年提出的一种二叉树结构的交换排序方法,其基本思想为:任取待排序元素序列中的某元素作为基准值,按照该排序码将待排序集合分割成两子序列左子序列中所有元素均小于基准值右子序列中所有元素均大于基准值,然后最左右子序列重复该过程,直到所有元素都排列在相应位置上为止。
在这里插入图片描述
如图所示:
right从右往左找小于key的;left从左往右找大于key的,然后二者交换。继续找,直到二者相遇;相遇以后,需要和key位置数交换,此时key位置的数就处在了排序完成后应该在的位置。
然后再对key位置左边与右边进行上述相同的操作即可完成排序。

为什么二者相遇的位置一定比key小呢?
此处就是该算法的精髓,因为是right先走,right停下来的位置一定比key小,或者right到了key位置。、

void QuickSort(int* a, int left,int right)
{
	//若区间只有一个数,则已经有序
	if (left >= right)
		return;

	int begin = left;
	int end = right;

	int key = left;//基准值
	
	while (left < right)
	{
		//从右往左,找比key位置小的,不小就左移
		while (left < right && a[right] >= a[key])
		{
			right--;
		}
		//从左往右,找比key位置大的,不大就右移
		while (left < right && a[left] <= a[key])
		{
			left++;
		}

		//left位置比key大,right位置比key小。交换
		Swap(&a[left], &a[right]);
	}
	//left与right相遇,那就与key交换
	Swap(&a[key], &a[right]);
	key = left;

	//递归左右区间
	//[begin,key-1] key [key+1,end]
	QuickSort(a, begin, key - 1);
	QuickSort(a, key + 1, end);
}

我们都知道快排很快,它的时间复杂度为O(N*logN),但真的是这样吗?

思考:若数据有序或已经接近有序,该算法还快吗?
在这里插入图片描述

如果是这种情况,那么这个基准值每次都会选到最小的值,此时它的时间复杂度就是N^2。那有什么解决办法吗?
使用随机取key法或三数取中法。

  • 随机数选key就是每次选的key都不一定,但有时可以避免最坏情况
	int begin = left;
	int end = right;

	//使随机数的范围再[left,rihgt]中
	int randk = rand() % (right - left);
	randk += left;
	//为了我们后面代码的逻辑不改变,将randk位置与left位置交换
	Swap(&a[left], &a[randk]);

	int key = left;//基准值
  • 三数取中法
    这里的取中并不是取中间的数,而是三个数中,位于中间大小的数。
int GetMid(int* a, int left, int right)
{
	int mid = (left + right) / 2;
	if (a[mid] < a[right])
	{
		if (a[mid] > a[left])
		{
			//left < mid < right
			return mid;
		}
		else if (a[left] < a[right])
		{
			//mid < left < right
			return left;
		}
		else
		{	//left < right < mid
			return right;
		}
	}
	else//a[mid] > a[right]
	{
		if (a[mid] < a[left])
		{
			//right < mid < left
			return mid;
		}
		else if (a[left] > a[right])
		{
			//right < left < mid
			return left;
		}
		else
		{
			//left < right < mid
			return right;
		}
	}
}

void QuickSort(int* a, int left,int right)
{
	//若区间只有一个数,则已经有序
	if (left >= right)
		return;

	int begin = left;
	int end = right;

	使随机数的范围再[left,rihgt]中
	//int randk = rand() % (right - left);
	//randk += left;
	为了我们后面代码的逻辑不改变,将randk位置与left位置交换
	//Swap(&a[left], &a[randk]);
	int midk = GetMid(a, left, right);

	Swap(&a[left], &a[midk]);
	int key = left;//基准值
	
	while (left < right)
	{
		//从右往左,找比key位置小的,不小就左移
		while (left < right && a[right] >= a[key])
		{
			right--;
		}
		//从左往右,找比key位置大的,不大就右移
		while (left < right && a[left] <= a[key])
		{
			left++;
		}

		//left位置比key大,right位置比key小。交换
		Swap(&a[left], &a[right]);
	}
	//left与right相遇,那就与key交换
	Swap(&a[key], &a[right]);
	key = left;

	//递归左右区间
	//[begin,key-1] key [key+1,end]
	QuickSort(a, begin, key - 1);
	QuickSort(a, key + 1, end);
}
  • 小区间优化
    当快排进行到数据元素比较少的时候,使用快速排序走递归的消耗是比较大的,最后一层的递归占整个递归的80%-%90。是不值当的,如下图这种情况:

在这里插入图片描述
对于这种情况,我们给出小区间优化的方式。即当递归进入待排序数字只剩下10个左右时,直接使用插入排序。

	//小区间优化
	if (right - left + 1 < 10)
	{
		//进行插入排序
		InsertSort(a + left, right - left - 1);
	}
	else
	{
		//继续进行递归
	}

前后指针法

先看一下动图演示
在这里插入图片描述
此方法与第一种方法不同之处在于,它不是一左一右了。使用前后指针,cur指针为前指针,它找比key小的,找到以后,先让prev++,然后二者在交换,最后自己在++;当cur超出范围时,prev位置小于key,将prev与key交换,本轮排序结束。
在这里插入图片描述

void QuickSort2(int* a, int left, int right)
{
	if (left >= right)
		return;

	int prev = left;
	int cur = prev + 1;
	int key = left;

	//当前值比key小,先++prev,然后交换
	while (cur <= right)
	{
		if (a[cur] < a[key])
		{
			++prev;
			Swap(&a[prev], &a[cur]);
		}
		cur++;
	}
	//使key位于正确位置
	Swap(&a[key], &a[prev]);
	key = prev;
	//递归左右区间
	QuickSort2(a, left, key - 1);
	QuickSort2(a, key + 1, right);
}

为了避免自己与自己进行无意义的交换,代码也可以写成这样:

void QuickSort2(int* a, int left, int right)
{
	if (left >= right)
		return;

	int prev = left;
	int cur = prev + 1;
	int key = left;

	//当前值比key小,先++prev,然后交换
	/*while (cur <= right)
	{
		if (a[cur] < a[key])
		{
			++prev;
			Swap(&a[prev], &a[cur]);
		}
		cur++;
	}*/
	while (cur <= right)
	{
		if (a[cur] < a[key] && ++prev != cur)
		{
			Swap(&a[prev], &a[cur]);
		}
		cur++;
	}

	//使key位于正确位置
	Swap(&a[key], &a[prev]);
	key = prev;
	//递归左右区间
	QuickSort2(a, left, key - 1);
	QuickSort2(a, key + 1, right);
}

挖坑法

挖坑法与Hoare法类似
在这里插入图片描述
将步骤拆解下来就是下面这样
在这里插入图片描述

void QuickSort3(int* a, int left, int right)
{
	if (left >= right)
		return;
	int begin = left;
	int end = right;
	int key = a[left];
	int pos = left;//选定坑位
	while (left < right)
	{
		while (left <  right && a[right]>= key)
		{
			right--;
		}

		//从右边找,找到了比key小的,数据变化,坑位变化
		a[pos] = a[right];
		pos = right;//换坑

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

		//从左边找,找到了比key大的,数据变化,坑位变化
		a[pos] = a[left];
		pos = left;
	}
	//此时left与right相遇
	a[pos] = key;

	//[begin,pos-1] pos [pos+1,end]
	QuickSort3(a, begin, pos - 1);
	QuickSort3(a, pos + 1, end);
}

非递归版本

如果待排序的数据量非常大时,会递归很多次,可能会导致栈溢出,所以非递归版本也是我们必须掌握的。
其实非递归版本也是利用栈后进先出的特性,模拟出的递归的效果。
在这里插入图片描述

void QuickSortNonR(int* a, int left, int right)
{
	Stack st;
	StackInit(&st);

	//左右区间先入栈
	StackPush(&st, right);
	StackPush(&st, left); 

	//栈不为空,就取栈顶元素排序
	while (!StackEmpty(&st))
	{
		int begin = StackTopElement(&st);
		StackPop(&st);
		int end = StackTopElement(&st);
		StackPop(&st);

		//前后指针法,进行单趟排
		int prev = begin;
		int cur = prev + 1;
		int key = begin;
		while (cur <= end)
		{
			if (a[cur] < a[key] && ++prev != cur)
			{
				Swap(&a[prev], &a[cur]);
			}
			cur++;
		}
		//使key位于正确位置
		Swap(&a[key], &a[prev]);
		key = prev;

		//[begin,key-1] key [key+1,end]
		//继续入栈,先入右,再入左
		if (key + 1 < end)
		{
			StackPush(&st, end);
			StackPush(&st, key + 1);
		}
		if (key - 1 > begin)
		{
			StackPush(&st, key - 1);
			StackPush(&st, begin);
		}
	}
	StackDestroy(&st);
}

快速排序的特性总结:

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

在这里插入图片描述

  1. 空间复杂度:O(logN)
  2. 稳定性:不稳定

2.4归并排序

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

归并排序算法有两个基本的操作,一个是分,也就是把原数组划分成两个子数组的过程。另一个是治,它将两个有序数组合并成一个更大的有序数组。

  1. 将待排序的线性表不断地切分成若干个子表,直到每个子表只包含一个元素,这时,可以认为只包含一个元素的子表是有序表。
  2. 将子表两两合并,每合并一次,就会产生一个新的且更长的有序表,重复这一步骤,直到最后只剩下一个子表,这个子表就是排好序的线性表

分的过程就是将数据拆到只有一个数据时,此时该数据自身有序。
什么时候就拆好了呢?
当数据的左右边界相等时,就只剩下一个数据,此时可以合并了,如下图:
在这里插入图片描述
那它的合并又是怎么合的呢?难道在原数组上合并吗?
肯定不是这样的,如果在原数组上合并的话,那数据就乱套了。
因此,我们需要借助一个临时数组来保存数据,待数据合并后,在将临时数组中的数据拷回到原数组中。

在这里插入图片描述
动图演示

在这里插入图片描述

递归版本

#include<string.h>

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 left1 = begin, right1 = mid;
	int left2 = mid + 1, right2 = end;
	int tmpi = begin;//往tmp数组存放位置的下标

	//两区间均未遍历完
	while (left1 <= right1 && left2 <= right2)
	{
		//按序存放进tmp数组
		if (a[left1] < a[left2])
		{
			tmp[tmpi++] = a[left1++];
		}
		else
		{
			tmp[tmpi++] = a[left2++];
		}
	}
	//有一个遍历完了
	while (left1 <= right1)
	{
		tmp[tmpi++] = a[left1++];
	}
	while (left2 <= right2)
	{
		tmp[tmpi++] = a[left2++];
	}

	//tmp数组中的数据已经有序,拷贝回原数组
	//注意数据拷贝元素的位置
	memcpy(a + begin, tmp + begin, sizeof(int) * (tmpi - begin));
}


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;
}

非递归版本

该方法的思想和递归相似。

在这里插入图片描述

void MergeSortNonR(int* a, int n)
{
	//开辟临时数组
	int* tmp = (int*)malloc(sizeof(int) * n);
	if (tmp == NULL)
	{
		perror("malloc fail");
		return;
	}
	int gap = 1; //最开始为 1个一组,每组均有序

	while (gap < n)
	{
		//gap个分为一组,每次分两组,然后取小进行尾插
		for (int i = 0; i < n; i += 2*gap)
		{
			int left1 = i, right1 = left1 + gap - 1;
			int left2 = left1 + gap, right2 = left2 + gap - 1;

			int tmpi = i;

			while (left1 <= right1 && left2 <= right2)
			{
				//按序存放进tmp数组
				if (a[left1] < a[left2])
				{
					tmp[tmpi++] = a[left1++];
				}
				else
				{
					tmp[tmpi++] = a[left2++];
				}
			}
			//有一个遍历完了
			while (left1 <= right1)
			{
				tmp[tmpi++] = a[left1++];
			}
			while (left2 <= right2)
			{
				tmp[tmpi++] = a[left2++];
			}
			//将临时数组拷回原数组
			memcpy(a + i, tmp + i, sizeof(int) * tmpi - i);
		}
		gap *= 2;
	}

	free(tmp);
	tmp = NULL;
}

运行上述代码,我们会发现会出现越界问题
在这里插入图片描述
我们分析一下:

  • 首先,我们的left1=i,i<n,所以left1不可能越界
  • right1可能越界,如果right1越界那说明数组已经有序了,不需要进行排序
  • left2可能越界,如果right2越界那说明数组也已经有序了,不需要进行排序
  • right2也可能越界,当right2越界时,[left2,未越界] right2,那么我们需要对未越界的部分进行排序。
	if (right1 >= n || left1 >= n)
	{
		break;
	}
	if (right2 >= n)
	{
		right2 = n - 1;
	}

到此,我们的归并排序就算完美了。

void MergeSortNonR(int* a, int n)
{
	//开辟临时数组
	int* tmp = (int*)malloc(sizeof(int) * n);
	if (tmp == NULL)
	{
		perror("malloc fail");
		return;
	}
	int gap = 1; //最开始为1个一组,每组均有序

	while (gap < n)
	{
		//gap个分为一组,每次分两组
		for (int i = 0; i < n; i += 2*gap)
		{
			int left1 = i, right1 = left1 + gap - 1;
			int left2 = left1 + gap, right2 = left2 + gap - 1;
			//printf("[%d,%d][%d,%d] ", left1, right1, left2, right2);

			//无需再排
			if (right1 >= n || left1 >= n)
			{
				break;
			}
			//部分要排
			if (right2 >= n)
			{
				right2 = n - 1;
			}

			int tmpi = i;

			while (left1 <= right1 && left2 <= right2)
			{
				//按序存放进tmp数组
				if (a[left1] < a[left2])
				{
					tmp[tmpi++] = a[left1++];
				}
				else
				{
					tmp[tmpi++] = a[left2++];
				}
			}
			//有一个遍历完了
			while (left1 <= right1)
			{
				tmp[tmpi++] = a[left1++];
			}
			while (left2 <= right2)
			{
				tmp[tmpi++] = a[left2++];
			}
			//将临时数组拷回原数组
			memcpy(a + i, tmp + i, sizeof(int) * (tmpi - i));
		}
		gap *= 2;
		//printf("\n");
	}

	free(tmp);
	tmp = NULL;
}

归并排序的特性总结:

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

2.5计数排序

计数排序属于非比较排序,那什么较非比较排序呢?–排序的关键逻辑不是比较出来的。
那具体是如何实现的呢?

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

动图演示
在这里插入图片描述
从图中我们可以看出,它是将数据的个数收集到一个临时数组中,最后根据临时数组的内容有序放回到原数组中。
但我们这个临时数组开多大呢?和原数组一样大吗?那未免有点太浪费了。
我们只需要找出数据中的最大与最小值,数组大小开最大-最小+1就够了。

例如:假设数组元素大小为100-200之间的数据,那就只需要开100+1个空间。 由于数组下标是从0开始的,而我最小的数据是100,那101就越界了,所以我们需要将待排序数据减去所有数组中的最小值再放进临时数组中

void CountSort(int* a, int n)
{
	int max = a[0];
	int min = a[0];
	for (int i = 0; i < n; i++)
	{
		if (a[i] > max)
		{
			max = a[i];
		}
		if (a[i] < min)
		{
			min = a[i];
		}
	}

	//所开数组的大小
	int range = max - min + 1;
	int* tmp = (int*)calloc(range, sizeof(int));
	if (tmp == NULL)
	{
		perror("calloc fail");
		return;
	}

	//统计各数据的个数
	for (int i = 0; i < n; i++)
	{
		tmp[a[i]-min]++;
	}
	
	int j = 0;
	for (int i = 0; i < range; i++)
	{
		//当前数组元素不为0,还有为排序元素
		while (tmp[i]--)
		{
			a[j++] = i + min;
		}
	}
}

计数排序的特性总结:

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

3.排序的比较

排序方法平均情况最好情况最坏情况辅助空间稳定性
冒泡排序O(n2)O(n)(有序)O(n2)O(1)稳定
简单选择排序O(n2)O(2)O(n2)O(1)不稳定
直接插入排序O(n2)O(n)(有序)O(n2)O(1)稳定
希尔排序O(nlogn)~O(n2)O(n1.3)O(n2)O(1)不稳定
堆排序O(nlogn)O(nlogn)O(nlogn)O(1)不稳定
归并排序O(nlogn)O(nlogn)O(nlogn)O(n)稳定
快速排序O(nlogn)O(nlogn)O(nlogn)O(logn)~O(n)(递归)不稳定
计数排序O(Max(n,范围))O(Min(n,范围))O(Max(n,范围))O(范围)不稳定

稳定性:

若经过排序,这些记录的相对次序保持不变,即在原序列中,r[i]=r[j],且r[i]在r[j]之前,而在排序后的序列中,r[i]仍在r[j]之前,则称这种排序算法是稳定的;否则称为不稳定的。

稳定的都好理解,不稳定的如下:
在这里插入图片描述
在这里插入图片描述

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

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

相关文章

软件测试基础理论、测试用例及设计方法、易混淆概念总结【软件测试】

一.软件测试基础理论 1.软件定义 软件是计算机系统中与硬件相互依存的一部分&#xff0c;包括程序、数据以及与其相关文档 的完整集合。 程序是按事先设计的功能和性能要求执行的指令序列&#xff1b; 数据是使程序能正常操作信息的数据结构&#xff1b; 文档是与程序开发、维…

算法之美:B+树原理、应用及Mysql索引底层原理剖析

B树的一种变种形式&#xff0c;B树上的叶子结点存储关键字以及相应记录的地址&#xff0c;同等存储空间下比B-Tree存储更多Key。非叶子节点不对关键字记录的指针进行保存&#xff0c;只进行数据索引 , 树的层级会更少 , 所有叶子节点都在同一层, 叶子节点的关键字从小到大有序排…

Spring Boot 统一数据返回格式 分析 和 处理

目录 实现统一数据格式 测试 原因分析 解决方案 &#x1f3a5; 个人主页&#xff1a;Dikz12&#x1f4d5;格言&#xff1a;吾愚多不敏&#xff0c;而愿加学欢迎大家&#x1f44d;点赞✍评论⭐收藏 实现统一数据格式 统⼀的数据返回格式使⽤ ControllerAdvice 和 Response…

Vue生命周期,从听说到深入理解(全面分析)

每个 Vue 组件实例在创建时都需要经历一系列的初始化步骤&#xff0c;比如设置好数据侦听&#xff0c;编译模板&#xff0c;挂载实例到 DOM&#xff0c;以及在数据改变时更新 DOM。在此过程中&#xff0c;它也会运行被称为生命周期钩子的函数&#xff0c;让开发者有机会在特定阶…

【数据结构】新篇章 -- 顺序表

&#x1f308; 个人主页&#xff1a;白子寰 &#x1f525; 分类专栏&#xff1a;python从入门到精通&#xff0c;魔法指针&#xff0c;进阶C&#xff0c;C语言&#xff0c;C语言题集&#xff0c;C语言实现游戏&#x1f448; 希望得到您的订阅和支持~ &#x1f4a1; 坚持创作博文…

【算法】第一篇 外观数列

导航 1. 简介2. 生成规则3. 代码演示1. 简介 外观数列是指数列中的每一项都是描述前一项的外观或者外貌。它通常由初始项开始,通过描述前一项的外观来生成下一项。外观数列最初由John H. Conway在1969年发现,并在他的著作《外貌数列和自动机理论》(The Construction of Look…

TongWeb7.0-8.0Java代码使用JMX获取应用通道端口

以下通过java代码实现获取TongWeb7.0/8.0应用通道端口使用到的JMX均为TongWeb自带的JMX功能。 一、TongWeb7.0 1、使用本地JMX获取应用通道端口 public String getTw7PortByLocalJmx() { try { MBeanServer beanServer ManagementFactory.getPlatformMBeanServer(); Set&l…

基于Python微博舆情数据爬虫可视化分析系统(NLP情感分析+爬虫+机器学习)

这里写目录标题 基于Python微博舆情数据爬虫可视化分析系统(NLP情感分析爬虫机器学习)一、项目概述二、微博热词统计析三、微博文章分析四、微博评论分析五、微博舆情分析六、项目展示七、结语 基于Python微博舆情数据爬虫可视化分析系统(NLP情感分析爬虫机器学习) 一、项目概…

2023年第十四届蓝桥杯大赛软件类省赛CC++大学C 组真题(代码完整题解)

C题-三国游戏⭐ 标签&#xff1a;贪心 简述&#xff1a;三个国家初始人数都为0&#xff0c;n个事件&#xff0c;第i个事件若发生每个国家分别加Ai&#xff0c;Bi&#xff0c;Ci人&#xff0c;求最多发生几个事件使得两个国家人数之和小于第三国 链接&#xff1a;三国游戏 思…

深入解析消息认证码(MAC)算法:HmacMD5与HmacSHA1

码到三十五 &#xff1a; 个人主页 心中有诗画&#xff0c;指尖舞代码&#xff0c;目光览世界&#xff0c;步履越千山&#xff0c;人间尽值得 ! 目录 引言一、消息认证码&#xff08;MAC&#xff09;简介二、HmacMD5算法HmacMD5算法的工作原理 三、HmacSHA1算法HmacSHA1算法的…

直流马达驱动芯片D6289ADA介绍

应用领域 适用于智能断路器&#xff08;家用或工业智能空开&#xff09;、新能源汽车充电枪锁、电动玩具、电磁门锁、自动阀门等的直流电机驱动。 功能介绍 D6289ADA是一款直流马达驱动芯片&#xff0c;它有两个逻辑输入端子用来控制电机前进、后退及制动。该电路具有良好的抗干…

2024软件设计师备考讲义——(8)

操作系统 〇、操作系统概述 OS作用、OS特征、OS分类 作用&#xff1a;提高计算机效率&#xff0c;人机交互友好特征&#xff1a;并发性、共享性、虚拟性、不确定性分类&#xff1a;批处理、分时、实时、网络、分布式、微机嵌入式操作系统&#xff1a;微型化、可定制、实时性、可…

岭师大数据技术原理与应用-序章-软工版

HeZaoCha-CSDN博客 序章—软工版 一、环境介绍1. VMware Workstation Pro2. CentOS3. Java4. Hadoop5. HBase6. MySQL7. Hive 二、系统安装1. 虚拟网络编辑器2. 操作系统安装 三、结尾 先说说哥们写这系列博客的原因&#xff0c;本来学完咱也没想着再管部署这部分问题的说&…

卷积神经网络(CNN)——基础知识整理

文章目录 1、卷积神经网络 2、图片格式 3、图片卷积运算 4、Kernel 与 Feature Map 5、padding/边缘填充 6、Stride/步长 7、pooling/池化 8、shape 9、epoch、batch、Batch Size、step 10、神经网络 11、激活函数 1、卷积神经网络 既然叫卷积神经网络&#xff0c;这里面首先是…

设计模式——结构型——外观模式Facade

处理器类 public class Cpu {public void start() {System.out.println("处理器启动了...");} } 内存类 public class Memory {public void start() {System.out.println("内存启动了...");} } 硬盘类 public class Disk {public void start() {Syste…

【娱乐】战双帕弥什游戏笔记攻略

文章目录 Part.I IntroductionChap.I Information Part.II 新手攻略Chap.I 角色和武器挑选Chap.II 新手意识推荐 Part.II 阵容搭配Chap.I 一拖二Chap.II 毕业队 Reference Part.I Introduction 2019年12月5日全平台公测。 偶然间入坑战双&#xff0c;玩了几天&#xff0c;觉得…

V R虚拟现实元宇宙的前景|虚拟现实体验店加 盟合作|V R设备在线购买

VR&#xff08;虚拟现实&#xff09;技术作为一种新兴的技术&#xff0c;正在逐渐改变人们的生活和工作方式。随着技术的不断进步&#xff0c;人们对于元宇宙的概念也越来越感兴趣。元宇宙是一个虚拟世界&#xff0c;通过VR技术可以实现人们在其中进行各种活动和交互。 元宇宙的…

戴尔灵越3000来说2.5G的双核显存能干啥?

吃鸡已经成为大家耳熟能详的网络游戏。 很多人认为&#xff0c;想要享受吃鸡的乐趣&#xff0c;就必须组装一台高端电脑。 虽然配置越高越好&#xff0c;但现实是很多配置都是以性能为标准的。 有余了&#xff0c;没必要刻意追求高配置、高特效。 说实话&#xff0c;吃鸡不一定…

【Qt】:多种方式编辑hello world

多种方式编辑hello world 一.QLabel二.对象树三.使用单行编辑框四.使用按钮 (小技巧&#xff1a;1.可以使用F4来进行头文件和对应cpp文件的切换&#xff1b;2.写完一个函数的声名之后,按下altenter,就可以自动的在对应的cpp 文件中添加函数的定义了.) 一.QLabel 注意这里是QSt…

数据可视化基础与应用-04-seaborn库从入门到精通01-02

总结 本系列是数据可视化基础与应用的第04篇seaborn&#xff0c;是seaborn从入门到精通系列第1-2篇。本系列的目的是可以完整的完成seaborn从入门到精通。主要介绍基于seaborn实现数据可视化。 参考 参考:数据可视化-seaborn seaborn从入门到精通01-seaborn介绍与load_datas…
最新文章