常见七大排序算法总结--超详细适合初学者

一、排序算法的概念以及应用

(一)概念
所谓排序,就是使一串记录,按照其中的某个或某些关键字的大小,递增或递减的排列起来的操作。排序算法,就是如何使得记录按照要求排列的方法。
(二)分类
内部排序:数据元素全部放在内存中的排序。
外部排序:数据元素太多不能同时放在内存中,根据排序过程的要求不能在内外存之间移动数据的排序。
(三)应用
1.淘宝京东商品的销量排名
2.各大高校的综合实力排名
3.以及各大城市GDP排名
4.外卖评分以及百度竞价排名
(四)常见的排序算法
常见七大排序算法总结--超详细适合初学者_第1张图片

二、排序算法的实现(升序排列)

(一)插入排序

1.直接插入排序
1.1基本思想
直接插入排序是一种简单的插入排序法,它是把待排序的记录按其关键码值的大小逐个插入到一个已经排好序的有序序列中,直到所有的记录插入完为止,得到一个新的有序序列 ,类似于玩扑克牌摸到新牌插入到已有牌中。
常见七大排序算法总结--超详细适合初学者_第2张图片
个人理解
1.1.1单趟排序
假设我们手中有多张已经按升序排列好的扑克牌(这里定义为数组arr,长度为n=6,end+1位即为n-1位,end即为n-2位)现在我们摸到了一张4的牌,接下来我们肯定是将4这张牌放入我们手中原有的牌当中并且按照顺序排列好,我们将4先于最边上的9(数组arr end位的数)进行比较,4小于9此时我们应该继续与7进行比较,依次end–,这样就可以读取上一位7,接着与5进行比较,与3进行比较,这时我们发现4小于3(每比较1次,都要end–),所以我们应该把4放到3的后面这个位置,现实中很好放,可是在数组中我们需要将5的位置挪出来放入4,所以我们从9开始往后挪,但是9往后挪会覆盖掉4,所以我们定义一个temp来存放4。
常见七大排序算法总结--超详细适合初学者_第3张图片

这样9往4位置挪,7往9, 5往7,放入4,这样我们的单趟插入排序就算实现成功了,所以我们成功的条件肯定是这张牌小于前一张(也就是temp 常见七大排序算法总结--超详细适合初学者_第4张图片

当然成功的条件还有另一个,当temp是0的时候,我们1前面已经没有数了,所以就得终止,即当end减到小于0的时候,我们将temp插入到1的前面,这样就算成功了。
常见七大排序算法总结--超详细适合初学者_第5张图片

小结:摸牌(将要比较的数存入temp中)、比较(从后往前依次比较大小)、插入(将该数放入合适的位置)。每次比大小都需要end-1,排好的终止条件是temp 1.1.2多趟排序
刚才我们只摸了一张牌进行比较,这次就是我们需要连续摸多张牌,每摸一次都要比一下,假设我们手中没有牌,摸第一张牌5,先放手中(即存入数组arr中),在摸第二张3的时候和第一张5进行比较,将3插入到5之前,同理摸第四张牌时,按照之前单趟排序比较将1插入到3之前,然后一直摸,一直排,这里我们思考程序停止问题,也就是当没有牌可以让我们摸的时候,我们就相当于程序停止,也就是数组arr里边的所有数已经依次比较好大小了,这样我们就相当于完成了多趟排序。
常见七大排序算法总结--超详细适合初学者_第6张图片

总结:相当于进行了多趟单趟排序,直到arr没有数可以插入了。

1.2代码部分实现
1.2.1单趟排序
这里我们先将该数用temp存起来,然后用一个while让它一直进行比较,当end<0则跳出该循环,然后用if来比较两个数,如果该数小于比的数,就将要比的数往后挪,挪完后end-1。

int temp = arr[end + 1];	 
		while (end >=0)                               //第一个条件end必须大于等于0
		{
			if (temp < arr[end])                     //第二个条件该数小于比的数
			{
				arr[end + 1] = arr[end];             //将比的数往后挪位
				end--;                               
			}   
			else
			{
				break;
			}
		}
		arr[end + 1] = temp;

这里有人会问为什么不直接将arr[end+1]=temp放到else中,而是放到while之外,因为当0与3比较时小于3,则3往后挪,end–,此时end已经挪到小于0了,那么就无法在进入while循环中,那么就无法再将0放入3挪好的空位了,当它进入else中就直接跳出while,干脆arr[end+1]=temp放入while循环之后。
常见七大排序算法总结--超详细适合初学者_第7张图片
1.2.2多趟排序
有了单趟排序的代码理解那么我们多趟排序就更好理解了,只需要加入一个for循环就ok了。

	// 插入排序
void InsertSort(int* arr, int n)
{
	assert(arr);
	for (int i = 0; i < n-1; i++)
	{
		int end = i;
		int temp = arr[end + 1];	 
		while (end >=0)                               //第一个条件end必须大于等于0
		{
			if (temp < arr[end])                     //第二个条件该数小于比的数
			{
				arr[end + 1] = arr[end];             //将比的数往后挪位
				end--;                               
			}   
			else
			{
				break;
			}
		}
		arr[end + 1] = temp;
	}
}

在这里需要理解一个概念就是end取值范围的问题,因为单趟排序end取值是固定的,所以我们需要让end“动起来”,所以从数组下标为0时开始for循环往后动,这里要控制的界限就是temp不能越界,所以temp的最后界限为n-1,所以end就为n-2.理解了多趟插入排序就可以理解了end起始从0开始,一趟一趟进行排序,终止是到n-2,也就是数组下标为5的位置,所以for循环我们i取值为[0,n-2]。
常见七大排序算法总结--超详细适合初学者_第8张图片

1.3完整代码及运行结果
这个里边的assert函数是用来断言防止空数组地址的,不懂的可以百度一下下。

#include
#include
// 插入排序
void InsertSort(int* arr, int n)
{
	assert(arr);
	for (int i = 0; i < n-1; i++)
	{
		int end = i;
		int temp = arr[end + 1];	 
		while (end >=0)                               //第一个条件end必须大于等于0
		{
			if (temp < arr[end])                     //第二个条件该数小于比的数
			{
				arr[end + 1] = arr[end];             //将比的数往后挪位
				end--;                               
			}   
			else
			{
				break;
			}
		}
		arr[end + 1] = temp;
	}
}
//打印数组函数
void printArr(int *arr,int n)
{
	for (int i = 0; i < n; i++)
	{
		printf("%d ", arr[i]);
	}
	printf("\n ");
}
int main()
 {
	int a[6] = {5,7,9,1,3,0 };
	int n =sizeof(a)/sizeof(a[0]);
	InsertSort(a, n);
    printArr(a, n);
	system("pause");
	return 0;
}

常见七大排序算法总结--超详细适合初学者_第9张图片
1.4总结
至此,直接插入排序已经全部实现了,我们来进行它的特性总结:
1.4.1 元素集合越接近有序,直接插入排序算法的时间效率越高
1.4.2 时间复杂度:O(N^2)
1.4.3 空间复杂度:O(1),它是一种稳定的排序算法
1.4.4 稳定性:稳定

2.希尔排序( 缩小增量排序 )
2.1基本思想
希尔排序法又称缩小增量法。先选定一个整数,把待排序文件中所有记录分成gap个组,所有距离为gap的记录分在同一组内,并对每一组内的记录进行排序。然后重复上述分组和排序的工作。当gap到达=1时,所有记录在统一组内排好序。
个人理解
首先必须要理解上一个直接插入排序,希尔排序相当于在直接插入排序基础上优化而来,当我们需要对一组数进行排序的时候,我们从该组数中每距离一个gap抽取一个数,这样我们就可以得到gap组数,我们让它三组同时进行直接插入排序,接着我们不断缩小gap的值(根据前人经验不断总结得出每次缩小gap/3+1可以让它的排序效率最高)此时就可以称之为预排序的过程,为之后排序做铺垫,该组数越接近有序,直接排序的效率会越高,所以直到gap缩小到1时,就相当于我们的直接插入排序了,这样排序的话效率会大大提高。直接上图来理解整个思路吧!
假设我们有一组数,其个数n=7,第一步我们需要确定出gap,此时gap=7/3+1=3,即我们需要从该组数每间隔3抽取一个数,一共可以抽取3组进行预排序。
常见七大排序算法总结--超详细适合初学者_第10张图片
当gap=3时我们可以进行第一次预排序,此时这组数较比之前稍微接近有序。
常见七大排序算法总结--超详细适合初学者_第11张图片
关键部分来啦!!!!一定要仔细看,这个理解了,希尔排序就明白啦,在之前我们已经会求gap的值了,接着我们需要确定end的范围,这里我以第一组为例,end不能小于0,根据数组位数我们可以知道end=n-4即end=n-gap-1,而temp需要不断与end位的值进行比较,当temp值比end位的值小时,end位值每次需要+3才能移动到该组数的下一位,在直接插入排序中每比一次end需要减1,那是因为直接插入排序中每个数之间间隔为1,而该排序中间隔为gap=3,所以每次需要减3才能到达前一位,想当于也是用的插入排序的思想,只不过数与数之间距离为3。
常见七大排序算法总结--超详细适合初学者_第12张图片
接下来缩减gap值,令gap=gap/3+1=3/3+1=2,进行分组预排序,然后继续缩减直至gap=1时,该排序就同间接排序步骤啦,至此该思想就讲解完了。
常见七大排序算法总结--超详细适合初学者_第13张图片

总结一句话就是先分组(预排序,使数组接近有序)此时gap>1,不断缩减gap,最后直接插入排序此时gap=1。

2.2代码部分实现
2.2.1求gap值并分组
在这里插入图片描述
2.2.2以间隔为gap进行排序
常见七大排序算法总结--超详细适合初学者_第14张图片

2.3完整代码及运行结果

#include
#include
// 希尔排序
void ShellSort(int* arr, int n)
{
	assert(arr);  
	int gap = n;                                         //这里现将数组长度值赋给gap,之后在求一次gap
	while (gap > 1)                                      //当gap=1时进行最后一次直接插入排序而后跳出循环,至此排序完成
	{
		gap = gap / 3 + 1;                               //求gap,并分组
		for (int i = 0; i < n - gap; i++)                //由上思想可知n最大为n-gap-1,所以n
		{
			int end = i;                                 
			int temp = arr[end + gap];                   //由上思想可知temp=end+gap
			while (end >= 0)                             //第一个条件end必须大于等于0
			{
				if (temp < arr[end])                     //第二个条件该数小于比的数
				{
					arr[end +gap] = arr[end];            //将比的数往后挪gap个位置
					end-=gap;                            //end往后挪gap
				}
				else
				{
					break;
				}
			}
			arr[end + gap] = temp;                       
		}
	}
	//打印数组函数
void printArr(int *arr,int n)
{
	for (int i = 0; i < n; i++)
	{
		printf("%d ", arr[i]);
	}
	printf("\n ");
}
int main()
 {
	int a[7] = {5,7,9,1,3,0,23 };
	int n =sizeof(a)/sizeof(a[0]);
	ShellSort(a, n);
	printArr(a, n);
	system("pause");
	return 0;
}

在这里插入图片描述
2.4小结

  1. 希尔排序是对直接插入排序的优化。
  2. 关于gap,当gap > 1时都是预排序, gap越大,前面大的数据可以越快到后面,后面小的数,可以越快到前面。gap越大,越不接近有序gap越小越接近有序。如果gap==1其实就相当于直接插入排序,数组就有序了 。
  3. 希尔排序的时间复杂度不好计算,需要进行推导,推导出来平均时间复杂度: O(N1.3—N2)
  4. 稳定性:不稳定

(二)选择排序

1. 直接选择排序
1.1基本思想
每一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,直到全部待排序的数据元素排完 。
个人理解
在一个数组中找出最大的一个数和最小的一个数,把最大的与end交换,最小的与begin交换,而后begin++,end–,直到begin==end结束,此时相当已经排好序了。
常见七大排序算法总结--超详细适合初学者_第15张图片

但是要注意有一个特殊情况存在,当begin位置刚好是最大值时,max的位置就需要修正。即将该位置max=min
常见七大排序算法总结--超详细适合初学者_第16张图片

1.2代码实现

//交换
void swap(int* a, int* b)
{
	int temp=0;
	temp = *a;
	*a = *b;
	*b = temp;
}
//选择排序
void SelectSort(int* a, int n)
{
	assert(a);
	int begin = 0, end = n - 1;
	int min ,max;
	//在[begin,end]间找到最小数和最大数的下标
	while (begin < end)
	{
		min = max = begin;
		//因为要比较,所以从数组第二位开始找
		for (int i = begin+1; i <= end; i++)
		{
			if (a[i] < a[min])
			{
				min = i;
			}
			if (a[i] > a[max])
			{
				max = i;
			}
		}
		//将找到的最小值和begin位置交换
		swap(&a[begin], &a[min]);
		//特殊情况时,max位置需要修正
		if (begin== max)
			max = min;
		//交换最大值和end位置
		swap(&a[end], &a[max]);
		begin++;
		end--;
	}
}

1.3小结
直接选择排序思考非常好理解,但是效率不是很好。实际中很少使用,时间复杂度:O(N^2),空间复杂度:O(1),稳定性:不稳定
2. 堆排序
2.1基本思想
堆排序(Heapsort)是指利用堆积树(堆)这种数据结构所设计的一种排序算法,它是选择排序的一种。它是通过堆来进行选择数据。需要注意的是排升序要建大堆,排降序建小堆。
个人理解可分为三部分实现堆排序
第一部分:进行向下调整算法实现,向下调整算法有一个前提:左右子树必须是一个堆,才能调整。如下图:
从根15开始,先找到15的子结点,然后判断85和65哪个大,找出大的孩子与15进行比较如果大于15,则交换两个数值,把85所在的下标给15,而后依次向下调整。该算法的前提是左右子树必须是一个堆,就像刚刚我们调整的时候并没有管65结点的堆,是因为事先65所在子树就是一个堆,所以该算法相当于一个子问题的解决方法。
常见七大排序算法总结--超详细适合初学者_第17张图片

void AdjustDown(int* a, int n, int root)
{
	int parent = root;
	int child = parent * 2 + 1;
	while (child < n)
	{
		if (child+1 < n && a[child+1] > a[child])
		{
			++child;
		}

		if (a[child] > a[parent])
		{
			Swap(&a[child], &a[parent]);
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			break;
		}
	}
}

第二部分:建大堆
这里相当于是从倒数第二个结点开始算出他的父亲结点,从父亲结点再开始使用向下调整算法依次进行建堆,一次循环结点向前移一位,而后在调整,直到调到根节点证明已经建堆完毕。

void HeapSort(int* a, int n)
{
	// 排升序,建大堆
	//先从倒数第二个结点计算父亲结点下标,而后使用算法建堆
	//当i小于9时,证明结点已经走到根节点了,也说明建堆已经完毕
	for (int i = (n-1-1)/2; i >= 0; --i)
	{
		AdjustDown(a, n, i);
	}

第三部分:排序
因为大根堆堆顶数值是最大的,所以将堆顶的最大值与最后一个节点数值进行交换,最大的数值就会被换到结尾,将end–,而后继续向下调整算法,会产生一个第二大的数,则将第二大数与倒数第二个数交换,这样依次交换、调整、交换,当end<0时,证明所有的数已经按照升序调整完毕。

	int end = n - 1;
	while (end > 0)
	{
		Swap(&a[0], &a[end]);
		AdjustDown(a, end, 0);
		--end;
	}

2.2代码实现

// 堆排序
void AdjustDown(int* a, int n, int root)
{
	int parent = root;
	int child = parent * 2 + 1;
	while (child < n)
	{
		if (child+1 < n && a[child+1] > a[child])
		{
			++child;
		}

		if (a[child] > a[parent])
		{
			Swap(&a[child], &a[parent]);
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			break;
		}
	}
}

// 时间复杂度O(N*logN)
void HeapSort(int* a, int n)
{
	// 排升序,建大堆
	for (int i = (n-1-1)/2; i >= 0; --i)
	{
		AdjustDown(a, n, i);
	}

	int end = n - 1;
	while (end > 0)
	{
		Swap(&a[0], &a[end]);
		AdjustDown(a, end, 0);
		--end;
	}

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

(三)交换排序

1. 冒泡排序
1.1基本思想
所谓交换,就是根据序列中两个记录键值的比较结果来对换这两个记录在序列中的位置,将键值较大的记录向序列的尾部移动,键值较小的记录向序列的前部移动。
常见七大排序算法总结--超详细适合初学者_第18张图片
1.2代码实现

// O(N^2)
void BubbleSort(int* a, int n)
{
	int end = n;
	while (end > 0)
	{
	//定义一个标志位,用来判断一趟下来有没有进行交换
	//从第二个开始和前一个进行比大小
	//如果前一个大于这个,则进行交换,
		int exchange = 0;
		for (int i = 1; i < end; ++i)
		{
			if (a[i - 1] > a[i])
			{
				Swap(&a[i - 1], &a[i]);
				exchange = 1;
			}
		}

		// 如果一趟冒泡的过程中没有发生交换,则前部分已经有序,不需要再冒泡
		//这样有助于节省效率
		if (exchange == 0)
		{
			break;
		}
		--end;
	}
}

1.3小结
冒泡排序是一种非常容易理解的排序、时间复杂度:O(N^2)、空间复杂度:O(1)、稳定性:稳定
2. 快速排序
2.1基本思想
快速排序是Hoare于1962年提出的一种二叉树结构的交换排序方法,其基本思想为:任取待排序元素序列中的某元素作为基准值,按照该排序码将待排序集合分割成两子序列,左子序列中所有元素均小于基准值,右子序列中所有元素均大于基准值,然后最左右子序列重复该过程,直到所有元素都排列在相应位置上为止。将区间按照基准值划分为左右两半部分的常见方式有:
2.1. 1左右指针法

// [begin, end]
// 左右指针法
int GetMidIndex(int* a, int begin, int end)
{
	int mid = (begin + end) / 2;
	if (a[begin] < a[mid])
	{
		if (a[mid] < a[end])
			return mid;
		else if (a[begin] > a[end])
			return begin;
		else
			return end;
	}
	else // a[begin] > a[mid]
	{
		if (a[mid] > a[end])
			return mid;
		else if (a[begin] < a[end])
			return begin;
		else
			return end;
	}
}
int PartSort1(int* a, int begin, int end)
{
	int midIndex = GetMidIndex(a, begin, end);
	Swap(&a[midIndex], &a[end]);

	int keyindex = end;
	while (begin < end)
	{
		// begin找大
		while (begin < end && a[begin] <= a[keyindex])
		{
			++begin;
		}

        // end找小
		while (begin < end && a[end] >= a[keyindex])
		{
			--end;
		}

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

	Swap(&a[begin], &a[keyindex]);

	return begin;
}

2.1.2. 挖坑法

// 挖坑法
int PartSort2(int* a, int begin, int end)
{
	int midIndex = GetMidIndex(a, begin, end);
	Swap(&a[midIndex], &a[end]);

	// 坑(坑的意思就是这位置的值被拿走了,可以覆盖填新的值)
	int key = a[end];
	while (begin < end)
	{
		while (begin < end && a[begin] <= key)
			++begin;

		// 左边找到比key大的填到右边的坑,begin位置就形成的新的坑
		a[end] = a[begin];

		while (begin < end && a[end] >= key)
			--end;

		// 右边找到比key小的填到左边的坑,end位置就形成的新的坑
		a[begin] = a[end];
	}

	a[begin] = key;

	return begin;
}

2.1.3. 前后指针法

// 3、前后指针法
int PartSort3(int* a, int begin, int end)
{
	int midIndex = GetMidIndex(a, begin, end);
	Swap(&a[midIndex], &a[end]);

	int prev = begin - 1;
	int cur = begin;
	int keyindex = end;

	while (cur < end)
	{
		if (a[cur] < a[keyindex] && ++prev != cur)
			Swap(&a[prev], &a[cur]);

		++cur;
	}
	
	Swap(&a[++prev], &a[keyindex]);

	return prev;
}

2.2代码实现

// 时间复杂度:O(N*logN)
// 空间复杂度:O(logN)
void QuickSort(int* a, int left, int right)
{
	assert(a);
	if (left >= right)
		return;

	if ((right -left + 1) > 10)
	{
	//使用前后指针法排序
		int div = PartSort3(a, left, right);

		QuickSort(a, left, div - 1);
		QuickSort(a, div + 1, right);
	}
	else
	{
		// 小于等于10个以内的区间,不再递归排序,则使用插入排序
		InsertSort(a + left, right - left + 1);
	}
}

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

(四)归并排序
1.基本思想
归并排序(MERGE-SORT)是建立在归并操作上的一种有效的排序算法,该算法是采用分治法(Divide and
Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有
序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。 归并排序核心步骤:
常见七大排序算法总结--超详细适合初学者_第19张图片
2.代码实现

void MergeArr(int* a, int begin1, int end1, int begin2, int end2, int* tmp)
{
	int left = begin1, right = end2;
	int index = begin1;
	while (begin1 <= end1 && begin2 <= end2)
	{
		if (a[begin1] < a[begin2])
			tmp[index++] = a[begin1++];
		else
			tmp[index++] = a[begin2++];
	}

	while (begin1 <= end1)
		tmp[index++] = a[begin1++];

	while (begin2 <= end2)
		tmp[index++] = a[begin2++];

	// 把归并好的再tmp的数据在拷贝回到原数组
	for (int i = left; i <= right; ++i)
		a[i] = tmp[i];
}

// 时间复杂度O(N*logN)
// 空间复杂度O(N)
void _MergeSort(int* a, int left, int right, int* tmp)
{
	if (left >= right)
		return;

	int mid = (left + right) / 2;
	_MergeSort(a, left, mid, tmp);
	_MergeSort(a, mid + 1, right, tmp);

	// 归并[left,mid][mid+1,right]有序
	MergeArr(a, left, mid, mid + 1, right, tmp);
}

// 归并排序递归实现
void MergeSort(int* a, int n)
{
	assert(a);
	int* tmp = malloc(sizeof(int)*n);

	_MergeSort(a, 0, n - 1, tmp);

	free(tmp);
}

3.小结
归并的缺点在于需要O(N)的空间复杂度,归并排序的思考更多的是解决在磁盘中的外排序问题。 时间复杂度:O(N*logN)、空间复杂度:O(N)、 稳定性:稳定
(五)非比较排序
1.基本思想
1.1官方回答
计数排序又称为鸽巢原理,是对哈希直接定址法的变形应用。 统计相同元素出现次数,根据统计的结果将序列回收到原来的序列中。
1.2个人理解
通过遍历原数组,数组中每个数的值是多少就对统计次数的数组位+1,从而统计出数组每个数出现的次数,根据统计的结果将该数放回到原来的数组中确定统计次数数组的范围,而后遍历原数组从而得到最大值和最小值,从而确定范围。
如下图,要对该数组进行计数排序,首先遍历该数组可知该数组范围为[0,5]
其中0出现了2次,1出现了0次,2出现了2次,3出现了3次,4出现了0次,5出现了1次。
常见七大排序算法总结--超详细适合初学者_第20张图片
通过次数排序所以可知
常见七大排序算法总结--超详细适合初学者_第21张图片
最后我们将该结果放回原数组中即可实现排序
常见七大排序算法总结--超详细适合初学者_第22张图片
思想优化
当数组中存在2000、2500、2500、2564、2561、2850、3000数时我们如果在使用找到的最大值5000和最小值2000当做范围进行排序的话,会白白浪费很多空间,所以这里我们使用相对位置(数组中所有值减去数组中的最小值)作为范围,即会产生一组新的相对数据为0、500、500、564、561、850、1000.此时我们计数数组的区间范围为[0,1000].
常见七大排序算法总结--超详细适合初学者_第23张图片
2.代码分部实现
2.1遍历原数组,确定计数数组的相对数值区间范围。
常见七大排序算法总结--超详细适合初学者_第24张图片
2.2开辟一个新的计数数组空间
在这里插入图片描述
memset的作用是将range空间大小的数组所有值置为0,方便为之后的计数做好铺垫。
2.3统计原数组值个数并计入计数数组
在这里插入图片描述
arr[j]-min意思是:arr数组中的数值减去最小值得到的相对数值通过计数数组的某一位次数存储起来,假设arr[1]-min即2500-2000为500,那么计数数组中的第500位加1次,从而统计出2500出现了一次。
2.4将计数数组中的值存入原数组中,即为新的排序结果
常见七大排序算法总结--超详细适合初学者_第25张图片
countArr[K]–意思是计数数组中某一位出现多少次就循环该数组多少次,比如countArr[500]=2相当于2500这个数出现了两次,那么我们需要循环两次赋值给原数组,最后将计数数组中的数值赋给原数组,即可达到排序的目的。
3.完整代码及运行结果

#include
#include
#include
#include
// 计数排序
void CountSort(int* arr, int n)
{
	 assert(arr);

	//遍历数组,确定计数数组的相对数值范围
	int min = arr[0];                    //将min和max值设为原数组第一个值
	int max = arr[0];

	for (int i = 1; i < n; i++)         //从i=1开始是因为两两进行比较,i=0时的那个值已经给min和max了,所以从i=1开始
	{
		if (arr[i] < min)                
			min = arr[i];               //找出最小的值
		if (arr[i] > max)
			max = arr[i];               //找出最大的值
	}
	int range = max - min + 1;          //+1 是因为假设区间为[0,7]则该区间一共有7-0+1个位置

	//开辟一个计数数组空间
	int* countArr = (int * ) malloc (sizeof(int)*range);
	memset(countArr, 0, sizeof(int)*range);         //将该计数数组所有位置值置为0

	//统计原数组值个数并计入计数数组
	for (int j = 0; j < n; j++)
	{
		countArr[arr[j] - min]++;           //arr[j]-min意思是:arr数组中的数值减去最小值得到的相对数值通过计数数组的某一位次数存储起来
		                                    //假设arr[1]-min即2500-2000为500,那么计数数组中的第500位加1次,从而统计出2500出现了一次。
	}

	//将计数数组中的值存入原数组中,即为新的排序结果
	int index = 0;                            
	for (int k = 0; k < range; k++)
	{

		while (countArr[k]--)                //countArr[K]--意思是计数数组中某一位出现多少次就循环该数组多少次
		{                                    //比如countArr[500]=2相当于2500这个数出现了两次,那么我们需要循环两次赋值给原数组		
			arr[index++] = k + min;           //将计数数组中的数值赋给原数组
		}

	}

	free(countArr);                            //释放该计数数组
}

int main()
 {
	int arr[7] = { 2000,2500,2500,2564,2561,2850,3000 };
	//int arr[7] = { 42,2,0,2,1,50,30 };
	int n = 7;
	CountSort(arr,n);
	for (int i = 0; i < n; i++)
	{
		printf("%d ",arr[i]);
	}
	printf("\n ");
	system("pause");
	return 0;
}

在这里插入图片描述

4.总结
4.1计数排序在数据范围集中时,效率很高,但是该算法只适用于整型,如果浮点数或者字符串排序,还得用比较排序。
4.2 时间复杂度:O(MAX(N,范围)) N和范围哪个大选取哪个
4.3 空间复杂度:O(范围)
4.4 稳定性:稳定

三、排序算法的总结

1.排序算法复杂度及稳定性分析
常见七大排序算法总结--超详细适合初学者_第26张图片
常见七大排序算法总结--超详细适合初学者_第27张图片

你可能感兴趣的:(数据结构学习笔记,算法)