手撕各种排序

news2024/11/18 0:22:30

> 作者简介:დ旧言~,目前大一,现在学习Java,c,c++,Python等
> 座右铭:松树千年终是朽,槿花一日自为荣。

> 目标:掌握每种排序的方法,理解每种排序利弊和复杂度。

> 毒鸡汤:船停在码头是最安全的,但那不是造船的目的;人呆在家里是最舒服的,但那不是人生的意义。最美好的生活方式,莫过于和一群志同道合的人奔跑在理想的路上!
> 望小伙伴们点赞👍收藏✨加关注哟💕💕 

🌟前言 

        谈起排序这个事情,大家都是耳熟能详的事了,从我们认识的斗地主,每一复牌都是按照从小到大的顺序排序的,如图:


       排序的目的是为了让我们很好的管理,使无序的事情变成有序,当然排序的方法有很多,如由大到小,由大到小.....。而面对大量数据就需要排序。在数据结构中我们发明了多种排序,如我们最早认识的冒泡排序,本篇博客让大家对多种排序有一个很好的认知,闲话少谈。

主体  

咱们就掌握八种就行啦


🌙冒泡排序

这里博主在C语言刷题训练专栏中讲到过冒泡排序,咱们再回顾回顾


这里我们以接口的形式写代码,那咱们上代码咯


//冒泡排序测试
void BubbleSort(int* a, int n)
{
	for (int i = 0; i < n - 1; i++)
	{
		int exchange = 0;
		for (int j = 1; j < n - i; j++)
		{
			if (a[i - 1] > a[i])
			{
				//这里是一个交换函数
				Swap(&a[i - 1], &a[i]);
				exchange = 1;
			}
		}

		//这里进行一趟有序时,直接跳出循环
		if (exchange == 0)
		{
			break;
		}
	}
}

注意事项:
💦1.我们知道当给的数据有序时,就不再需要进行循环了,直接跳出循环(exchange作用)。
💦2. 第二个循环中  j < n - i  不能搞错。

时间复杂度:O(N²)

空间复杂度:O(1)

稳定性:稳定

复杂性:简单

🌙直接插入排序

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

咱们看看一个动态的插入动作。

 这里我们以接口的形式写代码,那咱们上代码咯


//插入排序
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];
			}
			else
			{
				break;
			}

			--end;
		}
		//最后把插入的元素赋值回去
		a[end + 1] = tmp;
	}
}

注意事项:
💦1.我们先进行第end个元素移动,再进行n个元素进行移动。
💦2. 最后需要a[end + 1] = tmp赋值

时间复杂度:O(N²)

空间复杂度:O(1)

稳定性:稳定

复杂性:简单

🌙希尔排序

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



在看看一个动态的图解:


  这里我们以接口的形式写代码,那咱们上代码咯

//希尔排序测试
void ShellSort(int* a, int n)
{
	int gap = n;
	while (gap > 1)
	{
		//间隔gap的元素进行排序
		gap = gap / 3 + 1;

		//本质上是插入排序
		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 = end - gap;
				}
				else
				{
					break;
				}
			}
			//最后把插入的元素赋值回去
			a[end + gap] = tmp;
		}
	}
}

注意事项:
💦1.首先先嵌套一个插入排序,再把分组的元素进行交换。
💦2. gap = gap / 3 + 1这个不要忘记。

时间复杂度:O(N¹·²³)

空间复杂度:O(1)

稳定性:不稳定

复杂性:复杂

🌙选择排序

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


 

这里我们看一个动态的图解:

 

上代码:

//选择排序测试
void SelectSort(int* a, int n)
{
	//初始化第一个元素 和 最后一个元素
	int begin = 0;
	int 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[begin], &a[mini]);

		//如果max被换走就需要重新赋值
		if (maxi == begin)
		{
			maxi = mini;
		}

		//最大值和最末的元素交换
		Swap(&a[end], &a[maxi]);

		++begin;
		--end;
	}
}

注意事项:
💦1.这里先找到最小值和最大值,然后交换到最前面和最后面,一次进行。
💦2. 如果最大值被交换后,需要从新赋值。

时间复杂度:O(N²)

空间复杂度:O(1)

稳定性:不稳定

复杂性:简单

🌙堆排序

       堆排序(Heapsort)是指利用堆积树(堆)这种数据结构所设计的一种排序算法,它是选择排序的一种。它是通过堆来进行选择数据。需要注意的是排升序要建大堆,排降序建小堆。咱们看看图解:



再看看动态的图解:



上代码:

//堆排序测试
//实现向下调整建大堆
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[child] < a[parent])
		{
			//元素交换
			Swap(&a[child], &a[parent]);
			// 继续往下调整
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			break;
		}
	}
}

//堆排序测试
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;
	}
}

注意事项:
💦1.首先我们需要建大堆(以在二叉树讲解咯),交换,再建大堆
💦2. 每交换一个元素都需要再建一次大堆。

时间复杂度:O(NlogN)

空间复杂度:O(1)

稳定性:不稳定

复杂性:复杂

🌙快排

        在快排这个板块中我将讲述四个方法,希小伙伴都能掌握。

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

💫Hoare方法

       在动态图中,key称为基准值,默认把基准值定义在数组的首元素上,其次为了加快遍历的速度,需要用到两个变量分别去对应数组的首尾部分,L处在数列的首部,它需要从左往右寻找比Keyi位置的值大的,遇到后就停下来,没遇到就一直走;R处在数列的尾部,它需要从右往左去寻找比keyi位置的值小的,也是遇到后就停下来,没遇到就一直走。

        当L与R都遇到停下来后开始互换位置,然后继续遍历,直到L==R时,双方都不会再走了,因为它们走到了同一个位置,这个位置被称为它俩的相遇点,之后便需要将keyi位置的值与相遇点的值互换位置,这样keyi位置的值就放到了中间,成为了数组的中心——基准值,它意味着将数组分为两个子序列,左序列全是小于Keyi的值,右序列全是大于Keyi的值,这样便可以利用递归,一层一层再对左右两个子序列进行相同的步骤——将一个大的无序序列转化为多个小的序列,从而进行排序,最终排列为完全有序的序列。
 



上代码:

//三数取中 (找出中间值)
int GetMidi(int* a, int left, int right)
{
	//中间值
	int mid = (left + right) / 2;
	//三个数比较找出中间值
	if (a[left] < a[mid])
	{
		if (a[mid] < a[right])
		{
			return mid;
		}
		else if (a[left] > a[right])
		{
			return left;
		}
		else
		{
			return right;
		}
	}
	else
	{
		if (a[mid] > a[right])
		{
			return mid;
		}
		else if (a[left] < a[right])
		{
			return left;
		}
		else
		{
			return right;
		}
	}
}

// 右边找小,左边找大,交换,把左边的值跟a[keyi]交换
int PartSort1(int* a, int left, int right)
{
	//找中间值(相对)
	int midi = GetMidi(a, left, right);
	//把最左边(相对)跟中间值(相对)换
	Swap(&a[left], &a[midi]);

	//定位最左边(相对)
	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]);
	}

	//此时左边走到中间了,开始交换
	Swap(&a[keyi], &a[left]);
	//返回
	return left;
};

注意事项:
💦1.首先先找到中间值。
💦2.再和最左边元素互换

💦3.左边找比(中间值)大的数,右边找(中间值)小的数,然后左右值交换。

💦4.此时左边走到中间了,开始交换

💦5.上述进行循环,知道排序完成

💫挖坑法

大家就看一下下面图解:



//三数取中 (找出中间值)
int GetMidi(int* a, int left, int right)
{
	//中间值
	int mid = (left + right) / 2;
	//三个数比较找出中间值
	if (a[left] < a[mid])
	{
		if (a[mid] < a[right])
		{
			return mid;
		}
		else if (a[left] > a[right])
		{
			return left;
		}
		else
		{
			return right;
		}
	}
	else
	{
		if (a[mid] > a[right])
		{
			return mid;
		}
		else if (a[left] < a[right])
		{
			return left;
		}
		else
		{
			return right;
		}
	}
}

//挖坑法
int PartSort2(int* a, int left, int right)
{
	//找中间值(相对)
	int midi = GetMidi(a, left, right);
	//把最左边(相对)跟中间值(相对)换
	Swap(&a[left], &a[midi]);

	//定位最左边(相对)
	int key = a[left];

	//保存key值以后,左边形成第一个坑
	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;
}

💫前后指针

       通过创建两个指针,prev指针指向数组序列首元素位置,cur指向prev的下一个位置,cur通过遍历去寻找比key基准值小的,若找到了,还得看prev的下一个位置是否为cur所处的位置,若prev的下一个位置确实为cur所处位置,则将cur指向的值与prev指向的值进行互换,若prev的下一个位置不是cur所处位置,则cur继续往后遍历,直到cur遍历结束,最后要将key基准值与prev指向的值进行互换,最终确认基准值处于数组序列的中间位置。




//三数取中 (找出中间值)
int GetMidi(int* a, int left, int right)
{
	//中间值
	int mid = (left + right) / 2;
	//三个数比较找出中间值
	if (a[left] < a[mid])
	{
		if (a[mid] < a[right])
		{
			return mid;
		}
		else if (a[left] > a[right])
		{
			return left;
		}
		else
		{
			return right;
		}
	}
	else
	{
		if (a[mid] > a[right])
		{
			return mid;
		}
		else if (a[left] < a[right])
		{
			return left;
		}
		else
		{
			return right;
		}
	}
}

//前后指针
int PartSort3(int* a, int left, int right)
{
	//找中间值(相对)
	int midi = GetMidi(a, left, right);
	//把最左边(相对)跟中间值(相对)换
	Swap(&a[left], &a[midi]);

	//定义指针开头
	int prev = left;
	//定义指针开头后一个
	int cur = prev + 1;

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

		++cur;
	}

	//交换
	Swap(&a[prev], &a[keyi]);
	return prev;
}

💫快排非递归

1.初始化我们的栈。

2.入栈把我们的开始的left==0  right==n-1;

3.进入我们的循环体循环体进入的条件是判断栈中是否还有数值如果有数值的化什么其中的数值对应的范围还是没有序的就需要出栈(这个时候就需要进行出栈(注意栈的数值的左右范围的对应))进行我们的挖坑排序(对于挖坑我们应该把key返回出来通过key的数值进行我们再一次的入栈操作同时我们的范围)。

3.1[left   key-1] 和[key+1   right]   这样的范围满足条件才能继续push  之后pop进行我们的排序;

4如果说我们的循环体结束了的话我们的数组就一定有序!



前面已经讲述了栈,这里就不再实现栈了

//快排非递归(采用栈的形式)(先进后出)
void QuickSortNonR(int* a, int begin, int end)
{
	//定义
	ST st;
	//初始化
	STInit(&st);
	//添加元素
	STPush(&st, end);
	STPush(&st, begin);

	while (!STEmpty(&st))
	{
		//剥离元素  让left取先入但是后出的元素(区间的左边)
		int left = STTop(&st);
		STPop(&st);

		//让right取栈顶元素(是我们后入的区间的右边)
		int right = STTop(&st);
		STPop(&st);

		// 右边找小,左边找大,交换,把左边的值跟a[keyi]交换
		int keyi = PartSort1(a, left, right);
		
		//分割成段 
		// [lefy,keyi-1] keyi [keyi+1, right]
		if (keyi + 1 < right)
		{
			//添加元素
			STPush(&st, right);
			STPush(&st, keyi + 1);
		}

		if (left < keyi - 1)
		{
			//添加元素
			STPush(&st, keyi - 1);
			STPush(&st, left);
		}
	}

	//销毁
	STDestroy(&st);
}

🌙归并排序

这里讲述两种方法,一种是递归型另一种则是非递归

💫归并排序递归型

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



void _MergeSort(int* a, int* tmp, int begin, int end)
{
	//尾小于头时
	if (end <= begin)
		return;

	//开始分割
	int mid = (end + begin) / 2;
	// [begin, mid][mid+1, end]
	_MergeSort(a, tmp, begin, mid);
	_MergeSort(a, tmp, mid + 1, end);

	//归并到tmp数据组,再拷贝回去
	//a->[begin, mid][mid+1, end]->tmp
	int begin1 = begin, end1 = mid;
	int begin2 = mid + 1, end2 = end;
	int index = begin;
	
	//开始拷贝
	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++];
	}

	//拷贝回原数组
	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, tmp, 0, n - 1);

	//释放内存
	free(tmp);
}

💫归并排序非递归型

       第一次当 gap 为 1 的时候,我们将距离为 1 的两个数组(其实就是两个元素为 1 的数)进行归并,得到了拥有两个元素的一个有序数组,然后通过循环将后面的元素全部用同样的方式归并。然后就得到了如上图所示蓝色表示的元素排布。同时我们在将这一步骤之后的数组拷贝回到原数组。在进行接下来的操作(这里的意思和上递归的是一样的)。接着每次将 gap 的值增加 2 倍,直到 gap 的值不小于 n 结束归并。这个过程我们将其称为小区间优化。



//归并排序(非递归)
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)
	{
		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;
			// [begin1,end1] [begin2,end2] 归并

			// 如果第二组不存在,这一组不用归并了
			if (begin2 >= n)
			{
				break;
			}

			// 如果第二组的右边界越界,修正一下
			if (end2 >= n)
			{
				end2 = n - 1;
			}

			//printf("[%d,%d][%d,%d] ", begin1, end1, begin2, end2);

			
			int index = i;
			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++];
			}

			// 拷贝回原数组
			memcpy(a + i, tmp + i, (end2 - i + 1) * sizeof(int));
		}

		printf("\n");

		gap *= 2;
	}

	free(tmp);
}

🌙计数排序

计数排序的朴素方法步骤:

        1、从无序数组中取出最大值max,新建一个长度为max+1的数组。

        2、遍历无序数组,取其中元素作为新建数组的索引,存在一个则新数组该索引所在的值自增。

        3、遍历新数组,当存在不为0的元素,取该元素的索引放入最终数组,并且该元素自减,直到为0,返回最终数组。
 



上代码:

//计数排序测试
void CountSort(int* a, int n)
{
	//找最大值和最小值
	int min = a[0], max = a[0];
	for (size_t i = 0; 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*)malloc(sizeof(int) * range);
	printf("range:%d\n", range);
	//判断
	if (count == NULL)
	{
		perror("malloc fail");
		return;
	}
	//计入数字的个数
	memset(count, 0, sizeof(int) * range);

	// 统计数据出现次数
	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;
		}
	}
}

 ⭐总结

咱看看每种排序的复杂度和稳定性。


 🌠Sort.h代码


#include<stdio.h>

//打印数据
void PrintArray(int* a, int n);

//冒泡排序测试
void BubbleSort(int* a, int n);
//插入排序测试
void InsertSort(int* a, int n);
//希尔排序测试
void ShellSort(int* a, int n);
//选择排序测试
void SelectSort(int* a, int n);
//堆排序测试
void HeapSort(int* a, int n);

//快排测试
void QuickSort1(int* a, int begin, int end);
void QuickSort2(int* a, int begin, int end);
//快排非递归测试
void QuickSortNonR(int* a, int begin, int end);

//归并排序测试
void MergeSort(int* a, int n);
//归并排序(非递归)测试
void MergeSortNonR(int* a, int n);
//计数排序测试
void CountSort(int* a, int n);

🌠Test.c代码

#define _CRT_SECURE_NO_WARNINGS 1

#include<time.h>
#include<stdlib.h>
#include"Sort.h"
#include"Stack.h"

//希尔排序测试
void TestShellSort()
{
	int a[] = { 9,1,2,5,7,4,8,6,3,5,1,2,3,5,1,8,3 };
	ShellSort(a, sizeof(a) / sizeof(int));
	PrintArray(a, sizeof(a) / sizeof(int));
}

//冒泡排序测试
void TestBubbleSort()
{
	int a[] = { 9,1,2,5,7,4,8,6,3,5,1,2,3,5,1,8,3 };
	BubbleSort(a, sizeof(a) / sizeof(int));
	PrintArray(a, sizeof(a) / sizeof(int));
}

//堆排序测试
void TestHeapSort()
{
	int a[] = { 9,1,2,5,7,4,8,6,3,5,1,2,3,5,1,8,3 };
	HeapSort(a, sizeof(a) / sizeof(int));
	PrintArray(a, sizeof(a) / sizeof(int));
}

//选择排序测试
void TestSelectSort()
{
	int a[] = { 9,1,2,5,7,4,8,6,3,5,1,2,3,5,1,8,3 };
	SelectSort(a, sizeof(a) / sizeof(int));
	PrintArray(a, sizeof(a) / sizeof(int));
}

//测试代码
void TestOP()
{
	srand(time(0));
	const int N = 100000;
	int* a1 = (int*)malloc(sizeof(int) * N);
	int* a2 = (int*)malloc(sizeof(int) * N);
	int* a3 = (int*)malloc(sizeof(int) * N);
	int* a4 = (int*)malloc(sizeof(int) * N);
	int* a5 = (int*)malloc(sizeof(int) * N);
	int* a6 = (int*)malloc(sizeof(int) * N);
	int* a7 = (int*)malloc(sizeof(int) * N);

	for (int i = N - 1; i >= 0; --i)
	{
		a1[i] = rand();
		a2[i] = a1[i];
		a3[i] = a1[i];
		a4[i] = a1[i];
		a5[i] = a1[i];
		a6[i] = a1[i];
		a7[i] = a1[i];
	}

	int begin1 = clock();
	//插入排序
	InsertSort(a1, N);
	int end1 = clock();
	int begin2 = clock();

	//希尔排序测试
	ShellSort(a2, N);
	int end2 = clock();
	int begin7 = clock();

	//冒泡排序测试
	BubbleSort(a7, N);
	int end7 = clock();
	int begin3 = clock();

	//选择排序测试
	SelectSort(a3, N);
	int end3 = clock();
	int begin4 = clock();

	//堆排序测试
	HeapSort(a4, N);
	int end4 = clock();
	int begin5 = clock();

	//快排测试
	//QuickSort(a5, 0, N - 1);
	int end5 = clock();

	int begin6 = clock();
	//MergeSort(a6, N);
	
	//计数排序测试
	//CountSort(a6, N);
	int end6 = clock();

	printf("InsertSort:%d\n", end1 - begin1);
	printf("ShellSort:%d\n", end2 - begin2);
	printf("BubbleSort:%d\n", end7 - begin7);

	printf("SelectSort:%d\n", end3 - begin3);
	printf("HeapSort:%d\n", end4 - begin4);
	printf("QuickSort:%d\n", end5 - begin5);
	printf("MergeSort:%d\n", end6 - begin6);
	printf("CountSort:%d\n", end6 - begin6);

	free(a1);
	free(a2);
	free(a3);
	free(a4);
	free(a5);
	free(a6);
	free(a7);
}


int main()
{
	TestOP();
	//TestBubbleSort();
	//TestHeapSort();
	//TestSelectSort();
	//TestQuickSort();
	//TestMergeSort();

	//TestCountSort();

	//printf("%d\n", Func(100000));


	return 0;
}

 🌠Sort.c代码

#define _CRT_SECURE_NO_WARNINGS 1

#include"Sort.h"
#include"Stack.h"

//交换函数
void Swap(int* x, int* y)
{
	int tmp = *x;
	*x = *y;
	*y = tmp;
}

//打印数据
void PrintArray(int* a, int n)
{
	//循环
	for (int i = 0; i < n; i++)
	{
		printf("%d ", a[i]);
	}
	printf("\n");
}

//冒泡排序测试
void BubbleSort(int* a, int n)
{
	for (int i = 0; i < n - 1; i++)
	{
		int exchange = 0;
		for (int j = 1; j < n - i; j++)
		{
			if (a[i - 1] > a[i])
			{
				//这里是一个交换函数
				Swap(&a[i - 1], &a[i]);
				exchange = 1;
			}
		}

		//这里进行一趟有序时,直接跳出循环
		if (exchange == 0)
		{
			break;
		}
	}
}

//插入排序
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];
			}
			else
			{
				break;
			}

			--end;
		}
		//最后把插入的元素赋值回去
		a[end + 1] = tmp;
	}
}

//希尔排序测试
void ShellSort(int* a, int n)
{
	int gap = n;
	while (gap > 1)
	{
		//间隔gap的元素进行排序
		gap = gap / 3 + 1;

		//本质上是插入排序
		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 = end - gap;
				}
				else
				{
					break;
				}
			}
			//最后把插入的元素赋值回去
			a[end + gap] = tmp;
		}
	}
}

//void ShellSort(int* a, int n)
//{
	/*int gap = 3;

	for (int j = 0; j < gap; j++)
	{
		for (int i = j; i < n - gap; i += 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;
		}
	}	*/

//选择排序测试
void SelectSort(int* a, int n)
{
	//初始化第一个元素 和 最后一个元素
	int begin = 0;
	int 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[begin], &a[mini]);

		//如果max被换走就需要重新赋值
		if (maxi == begin)
		{
			maxi = mini;
		}

		//最大值和最末的元素交换
		Swap(&a[end], &a[maxi]);

		++begin;
		--end;
	}
}

//堆排序测试
//实现向下调整建大堆
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[child] < a[parent])
		{
			//元素交换
			Swap(&a[child], &a[parent]);
			// 继续往下调整
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			break;
		}
	}
}

//堆排序测试
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;
	}
}

//三数取中 (找出中间值)
int GetMidi(int* a, int left, int right)
{
	//中间值
	int mid = (left + right) / 2;
	//三个数比较找出中间值
	if (a[left] < a[mid])
	{
		if (a[mid] < a[right])
		{
			return mid;
		}
		else if (a[left] > a[right])
		{
			return left;
		}
		else
		{
			return right;
		}
	}
	else
	{
		if (a[mid] > a[right])
		{
			return mid;
		}
		else if (a[left] < a[right])
		{
			return left;
		}
		else
		{
			return right;
		}
	}
}

// 右边找小,左边找大,交换,把左边的值跟a[keyi]交换
int PartSort1(int* a, int left, int right)
{
	//找中间值(相对)
	int midi = GetMidi(a, left, right);
	//把最左边(相对)跟中间值(相对)换
	Swap(&a[left], &a[midi]);

	//定位最左边(相对)
	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]);
	}

	//此时左边走到中间了,开始交换
	Swap(&a[keyi], &a[left]);
	//返回
	return left;
};

//挖坑法
int PartSort2(int* a, int left, int right)
{
	//找中间值(相对)
	int midi = GetMidi(a, left, right);
	//把最左边(相对)跟中间值(相对)换
	Swap(&a[left], &a[midi]);

	//定位最左边(相对)
	int key = a[left];

	//保存key值以后,左边形成第一个坑
	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;
}

//前后指针
int PartSort3(int* a, int left, int right)
{
	//找中间值(相对)
	int midi = GetMidi(a, left, right);
	//把最左边(相对)跟中间值(相对)换
	Swap(&a[left], &a[midi]);

	//定义指针开头
	int prev = left;
	//定义指针开头后一个
	int cur = prev + 1;

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

		++cur;
	}

	//交换
	Swap(&a[prev], &a[keyi]);
	return prev;
}

//快排测试一
void QuickSort1(int* a, int begin, int end)
{
	if (begin >= end)
		return;

	//小区间优化,小区间不再递归分割排序,降低递归次数
	//当元素小于10时,采用插入排序
	if ((end - begin + 1) > 10)
	{
		//找值
		int keyi = PartSort3(a, begin, end);

		// [begin, keyi-1] keyi [keyi+1, end]
		QuickSort1(a, begin, keyi - 1);
		QuickSort1(a, keyi + 1, end);
	}
	else
	{
		//插入排序
		InsertSort(a + begin, end - begin + 1);
	}
}

//快排测试二
void QuickSort2(int* a, int begin, int end)
{

	if (begin >= end)
		return;

	//调用
	int keyi = PartSort3(a, begin, end);

	//[begin, keyi-1] keyi [keyi+1, end]

	QuickSort2(a, begin, keyi - 1);
	QuickSort2(a, keyi + 1, end);
}

//快排非递归(采用栈的形式)(先进后出)
void QuickSortNonR(int* a, int begin, int end)
{
	//定义
	ST st;
	//初始化
	STInit(&st);
	//添加元素
	STPush(&st, end);
	STPush(&st, begin);

	while (!STEmpty(&st))
	{
		//剥离元素  让left取先入但是后出的元素(区间的左边)
		int left = STTop(&st);
		STPop(&st);

		//让right取栈顶元素(是我们后入的区间的右边)
		int right = STTop(&st);
		STPop(&st);

		// 右边找小,左边找大,交换,把左边的值跟a[keyi]交换
		int keyi = PartSort1(a, left, right);
		
		//分割成段 
		// [lefy,keyi-1] keyi [keyi+1, right]
		if (keyi + 1 < right)
		{
			//添加元素
			STPush(&st, right);
			STPush(&st, keyi + 1);
		}

		if (left < keyi - 1)
		{
			//添加元素
			STPush(&st, keyi - 1);
			STPush(&st, left);
		}
	}

	//销毁
	STDestroy(&st);
}

void _MergeSort(int* a, int* tmp, int begin, int end)
{
	//尾小于头时
	if (end <= begin)
		return;

	//开始分割
	int mid = (end + begin) / 2;
	// [begin, mid][mid+1, end]
	_MergeSort(a, tmp, begin, mid);
	_MergeSort(a, tmp, mid + 1, end);

	//归并到tmp数据组,再拷贝回去
	//a->[begin, mid][mid+1, end]->tmp
	int begin1 = begin, end1 = mid;
	int begin2 = mid + 1, end2 = end;
	int index = begin;
	
	//开始拷贝
	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++];
	}

	//拷贝回原数组
	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, tmp, 0, n - 1);

	//释放内存
	free(tmp);
}

//归并排序(非递归)
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)
	{
		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;
			// [begin1,end1] [begin2,end2] 归并

			// 如果第二组不存在,这一组不用归并了
			if (begin2 >= n)
			{
				break;
			}

			// 如果第二组的右边界越界,修正一下
			if (end2 >= n)
			{
				end2 = n - 1;
			}

			//printf("[%d,%d][%d,%d] ", begin1, end1, begin2, end2);

			
			int index = i;
			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++];
			}

			// 拷贝回原数组
			memcpy(a + i, tmp + i, (end2 - i + 1) * sizeof(int));
		}

		printf("\n");

		gap *= 2;
	}

	free(tmp);
}

//计数排序测试
void CountSort(int* a, int n)
{
	//找最大值和最小值
	int min = a[0], max = a[0];
	for (size_t i = 0; 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*)malloc(sizeof(int) * range);
	printf("range:%d\n", range);
	//判断
	if (count == NULL)
	{
		perror("malloc fail");
		return;
	}
	//计入数字的个数
	memset(count, 0, sizeof(int) * range);

	// 统计数据出现次数
	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/1076729.html

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

相关文章

视频剪辑利器,批量随机抽帧轻松保存,让精彩片段永不丢失!

您是否经常遇到这样的情况&#xff1a;在一段长视频中&#xff0c;有一些精彩的瞬间&#xff0c;但却不知道如何将其提取并保存下来&#xff1f;现在&#xff0c;我们为您推出了一款强大的视频剪辑利器&#xff0c;能够帮助您批量从视频中指定的区间内随机抽帧&#xff0c;并轻…

NPDP考什么?难度大不大?

之前给大家分享了NPDP考试时间以及报名条件&#xff0c;最近有宝子问我&#xff0c;这个考试难度咋样&#xff1f;都考察什么内容啊&#xff1f;今天给大家详细回答一下~ 1&#xff09;NPDP考试及报名时间 2023年下半年NPDP考试将于12月2日进行 预报名时间&#xff1a;10月8日…

LLM 时代,如何优雅地训练大模型?

原作者王嘉宁 基于https://wjn1996.blog.csdn.net/article/details/130764843 整理 大家好&#xff0c;ChatGPT于2022年12月初发布&#xff0c;震惊轰动了全世界&#xff0c;发布后的这段时间里&#xff0c;一系列国内外的大模型训练开源项目接踵而至&#xff0c;例如Alpaca、B…

英码边缘计算盒子IVP03X——32T超强算力,搭载BM1684X算能TPU处理器

产品8大优势&#xff1a; 高效节能&#xff1a;相较异构产品&#xff0c;IVP03X数据调配效率更高&#xff0c;资源利用率更高&#xff0c;平均功耗更低&#xff1b;升级换代&#xff1a;相较算能BM1684平台&#xff0c;IVP03X算力、编码&#xff0c;模型转换性能均翻倍提升&am…

DALL-E 3调参教程;百度新出的AI写小说神器;通义听悟看播客也太爽了;系列博文带你理解生成式AI | ShowMeAI日报

&#x1f440;日报&周刊合集 | &#x1f3a1;生产力工具与行业应用大全 | &#x1f9e1; 点赞关注评论拜托啦&#xff01; &#x1f525; 2023年诺贝尔奖全部揭晓&#xff0c;一文看完6类奖项花落谁家 https://www.nobelprize.org/prizes 随着最后一项「经济学奖」的揭秘&a…

c++视觉检测-----Canny边缘算子

Canny边缘算子 cv::Canny()是OpenCV库中用于执行Canny边缘检测的函数。Canny边缘检测是一种广泛使用的图像处理技术&#xff0c;用于检测图像中的边缘。 以下是cv::Canny()函数的一般用法和参数&#xff1a; void cv::Canny(cv::InputArray image, // 输入图像&#x…

005 OA人事管理系统

人事管理系统 一、系统介绍 本系统为职工人事管理系统&#xff0c;系统分为七大模块&#xff1a;职工管理&#xff0c;部门管理&#xff0c;岗位管理&#xff0c;招聘管理&#xff0c;奖惩管理&#xff0c;薪资管理&#xff0c;培训管理 系统默认有两个个角色&#xff1a;管…

css实现一行N个元素动态布局(以4个为例)

昨日同事问了我一个前端问题&#xff0c;前端开发的尺寸都不按照UI图上面还原的吗&#xff1f; 我了解了其中原由&#xff0c;告知UI图并不会考虑到所有的场景&#xff0c;只能给个案例&#xff0c;画图是死的&#xff0c;代码写出来的得是活的。就像他遇到的案例&#xff0c;请…

【高级语言程序设计】python函数式编程(一)

基础知识 Python函数式编程的主要内容包括以下几个方面&#xff1a; (1)函数作为一等公民&#xff1a;在函数式编程中&#xff0c;函数被视为一等公民&#xff0c;可以像其他数据类型一样被传递、赋值以及作为返回值。 (2)不可变数据&#xff1a;函数式编程鼓励使用不可变数据…

函数栈帧的创建与销毁剖析

目录 一、前言 二、基础知识介绍 2.1 寄存器介绍 2.2、汇编指令介绍 三、函数栈帧的创建销毁过程 3.1 调用main函数的函数 3.2 main函数开辟栈帧 3.3 在main函数中创建变量 3.4 调用Add函数前的准备 3.5 为Add函数开辟栈帧 3.6 在Add函数中创建变量并运算 3.7 Add函…

给手机上液冷?谈谈华为Mate 60系列手机专属黑科技—— “微泵液冷”手机壳

最近&#xff0c;有一个手机配件吸引了我的注意——华为的微泵液冷壳。 简单来说&#xff0c;就是在手机壳里装了无线充电微泵&#xff0c;为手机实现外置水冷的能力。让手机壳在“外观装饰”和“防摔保护”的功能性上额外加了一个“降温提性能”的作用。 接下来&#xff0c;本…

ATFX汇市:9月非农再超预期,高利率并未导致美国宏观经济收缩

ATFX汇市&#xff1a;据美国劳工部数据&#xff0c;9月季调后非农就业人口33.6万人&#xff0c;远高于前值18.7万人&#xff0c;高于预期值17万人&#xff0c;创出今年一月份以来的新高。亮眼的就业数据意味着美国宏观经济仍处于急速扩张状态&#xff0c;高利率的破坏性远低于此…

【c#】adapter.fill(dt)报错specified cast is not valid

报错信息&#xff1a; 报错specified cast is not valid,指定转换类型无效。 原因 查出来的数据有小数&#xff0c;且小数位数较多&#xff0c;问题就出现在这里&#xff0c;ORacle可以查出精确度高的数据&#xff0c;但是C#没办法查出来&#xff0c;就导致了有数据类型转换&…

区块链的两个核心概念之一签名, 另一个是共识.

Alice的公私钥&#xff0c; 签名和验证签名仅仅确定了Alice对数字资产A所有权的宣言. 之后, Bob也可以用自己的私钥对资产A进行签名宣誓所有权。区块链中叫双花&#xff0c;即重复宣称所有权&#xff0c; 也称重复花费交易。这时候需要共识算法(集体成员pow或委员会代表pos监督…

【mmdetection代码解读 3.x版本】以Fcos+FasterRcnn为例

文章目录 前言RPN部分的代码1. loss函数&#xff08;two_stage.py&#xff09;1.1 loss_and_predict函数&#xff08;base_dense_head.py&#xff09;1.1.1 loss_by_feat函数&#xff08;fcos_head.py&#xff09;1.1.1.1 get_targets函数 1.1.2 predict_by_feat函数&#xff0…

fatal: Not a git repository (or any parent up to mount point /home)解决方法

Git遇到一个问题&#xff1a; fatal: Not a git repository (or any parent up to mount point /home) Stopping at filesystem boundary (GIT_DISCOVERY_ACROSS_FILESYSTEM not set). 解决办法&#xff1a;git init 错误信息指出不是一个git仓库&#xff0c;或者它的父级目录…

【网络安全】如何保护IP地址?

使用防火墙是保护IP地址的一个重要手段。防火墙可以监控和过滤网络流量&#xff0c;并阻止未经授权的访问。一家网络安全公司的研究显示&#xff0c;超过80%的企业已经部署了防火墙来保护他们的网络和IP地址。 除了防火墙&#xff0c;定期更新操作系统和应用程序也是保护IP地址…

JavaScript(CSS)动画引擎汇总

汇总记录前端实现动画相关的库 1、animejs animejs是一个轻量级的JavaScript动画库&#xff0c;具有简单但功能强大的API。 它适用于CSS属性&#xff0c;SVG&#xff0c;DOM属性和JavaScript对象。 官网anime.js • JavaScript animation engine anime.js - a Collection by…

怎么把amr格式转换成mp3?

怎么把amr格式转换成mp3&#xff1f;AMR格式是一种被广泛应用于语音通信的音频格式。在手机通话录音、语音留言和通信等场景中&#xff0c;AMR格式常被用来存储和传输语音数据。此外&#xff0c;AMR格式也常见于专用的录音设备和录音应用程序中&#xff0c;这些设备和应用通常用…