八大排序算法

本文详细介绍了四种常见的排序算法:选择排序、冒泡排序、插入排序和归并排序,包括它们的基本逻辑、时间复杂度和空间复杂度。同时,针对每种排序算法进行了优化,减少了不必要的比较和交换操作,提高了效率。此外,还提到了希尔排序和快速排序,这两种更高效的排序方法,特别强调了快速排序在不同情况下的时间复杂度。最后,简要提及了堆排序的概念。
摘要由CSDN通过智能技术生成

一.选择排序

选择排序的逻辑非常简单,就是循环遍历所有还未排序的数据,然后每轮循环取出最大或最小的数据置于已排序序列的末位,非常稳定,因为它不会申请额外空间,所以空间复杂度为O(1)。算法逻辑上只用两层循环,所以时间复杂度为O(n^2).

代码实现

template<class T>
void SelectSort3(T* pData, size_t count, bool IsUpSort)
{
	T swap;
	if (IsUpSort)
	{   
		//升序
        //从第一个元素开始,将它与后面的元素比较,遇到比它小的元素时便交换里面的数据,依次循环,直到末位;
		//一遍内循环后,第一个元素的值便是最小值,然后从第二个元素开始再次循环,
        //如此反复,直到外循环结束,数据便排序好了;
		for (size_t i=0;i<count;++i)
		{
			for (size_t j=i+1;j<count;++j)
			{
				if (pData[i]>pData[j])
				{
					swap = pData[i];
					pData[i] = pData[j];
					pData[j] = swap;
				}
			}
		}
	}
	else
	{
		for (size_t i = 0; i < count; ++i)
		{
			for (size_t j = i + 1; j < count; ++j)
			{
				if (pData[i] < pData[j])
				{
					swap = pData[i];
					pData[i] = pData[j];
					pData[j] = swap;
				}
			}
		}
	}
}

选择排序耗时在每次找到合适的元素都要交换依次数据,可以上面代码轻微优化一下。

	for (size_t i = 0; i < count- 1; ++i)     //控制循环次数;
		{
			MinIndex = i;
			for (size_t j = i + 1; j < count;++j)   //用来将当前数据与后面的数据比较,找出合适的值;
			{
				if (pData[MinIndex]>pData[j])        //记录下标;
				{
					MinIndex = j;
				}
			}
			swap = pData[MinIndex];          //交换数据;
			pData[MinIndex] = pData[i];
			pData[i] = swap;
		}

只记录下标,这样每次内循环只交换一次。

二.冒泡排序

冒泡排序也很简单,它也是通过两层循环就可以简单实现的,与选择排序不同的是,它是相邻数据之间比较,条件合适便交换,就与它的名字一样,数组前面是水面,后面的元素就像气泡一样从后面比较冒上来。它的时间复杂度也是O(n^2),空间复杂度是O(1).

代码实现:

template<class T>
void PopSort(T *pData, size_t count, bool IsUpSort) //冒泡排序,通过元素与下一个元素比较,每次比较两个元素来逐步排序;
{
	//两层循环,一层用来控制循环次数,由元素个数决定,第二层循环将元素相互比较,
	//由第一个开始分别与下一个开始比较;满足条件就交换数据;
	//通过比较找到需要的值,进行值交换;
	T swap;          
	if (IsUpSort)   //升序
	{
		for (size_t i = count - 1; i > 0; --i)    //控制循环次数
		{
			for (size_t j = 0; j < i ;++j)  //控制交换比较次数;
			{
				if (pData[j]>pData[j+1])
				{
					swap = pData[j];
					pData[j] = pData[j + 1];
					pData[j + 1] = swap;
				}
			}
		}
	}
	else  //降序
	{
		for (size_t i = count - 1; i > 0; --i)    //控制循环次数
		{
			for (size_t j = 0; j < i; ++j) //控制交换比较次数;
			{
				if (pData[j]<pData[j + 1])
				{
					swap = pData[j + 1];
					pData[j+1] = pData[j];
					pData[j] = swap;
				}
			}
		}
	}
}

简单优化一下,与选择排序相同

if (IsUpSort)
	{
		T swap;
		int MinIndex;
		for (size_t i = count - 1; i > 0;--i)   //控制循环次数;
		{
			MinIndex = 0;
			for (size_t j = 0; j < i; ++j)           //控制比较次数;
			{
				if (pData[MinIndex] < pData[j + 1])      //比较交换索引;
				{
					MinIndex = j + 1;
				}
			}
			swap = pData[MinIndex];                      //根据索引交换元素;
			pData[MinIndex] = pData[i];
			pData[i] = swap;
		}
	}

三.插入排序

插入排序的逻辑思想就是逐步插入元素,我们将第一个元素默认为第一个有序元素,将第二个元素与它比较,比它大就插入到第一个元素的前面,比他小就插入到它的后面,后面的元素逐个与它前面的元素比较,直到找到合适的位置插入。

代码实现


template<class T>
void InsertSort(T *pData, size_t count, bool IsUpSort) //插入排序  将元素插入到已经排序好的序列里;
{
	//先取第二个元素,与第一个元素比较,根据升降序来判断是否进行转换;
	//然后逐渐取后面的元素,从后面开始判断是否交换,如果要逐个交换找到位置。如果不要直接不动就是放入尾处;
	//这是提高效率的地方;
	T swap;
	if (IsUpSort)
	{
		for (size_t i = 1; i < count;++i)   //循环控制从第二个元素考试取数;
		{
			for (size_t j = i; j>0; --j)   //从尾开始有序序列的尾开始遍历;
			{
				if (pData[j] < pData[j - 1])      //能交换就循环找到位置
				{
					swap = pData[j];
					pData[j] = pData[j - 1];
					pData[j - 1] = swap;
				}
				else   //找不到进入下一个元素的循环以提高效率;
				{
					break;
				}
			}
		}
	}
	else    //降序
	{
		for (size_t i = 1; i < count; ++i)
		{
			for (size_t j = i; j>0; --j)
			{
				if (pData[j] > pData[j - 1])
				{
					swap = pData[j];
					pData[j] = pData[j - 1];
					pData[j - 1] = swap;
				}
				else
				{
					break;
				}
			}
		}
	}
}

插入排序的时间复杂度与空间复杂度与选择冒泡一样都是O(n^2)和O(1).break跳出循环的过程减少了比较多的交换与比较,所以在一般情况下它要比选择和冒泡快

简单优化一下

for (size_t i = 1; i < count; ++i)      //控制循环次数;
		{
			swap = pData[i];          //定位自己;
			size_t j = i ;
			for (; j > 0; --j)           //控制比较次数;
			{
				if (swap < pData[j-1])          //控制条件移动元素;
				{
					pData[j] = pData[j-1];
				}
				else
				{
					break;
				}
				        //插入最终位置;
				
			}
			pData[j] = swap;
		}

归并排序

归并排序的核心思想是分冶,我们先对数据进行划分,先将整个数据分成两半,再将这两份数据分成四份,逐步循环划分,直到数据被分到最小,然后开始归并,两两比较排序合并,逐渐合并成排序好的数组。归并排序的时间复杂度为O(nlogn),如果是非递归版只需要申请一次额外空间,它适合大量数据排序。并且归并排序在对链表的排序中有绝对的优势。

代码展示:

#ifndef SAFE_DELARR
#define SAFE_DELARR(p) {if(p){delete []p;p=nullptr;}}
#endif
template<class T>
void MereSort(T *pData, size_t count, bool IsUpSort = true) //归并排序,
{
	//判断元素大小,对元素进行插分,把整个数据链插分成一个个元素;
	//将插分出来的元素当作最底层的构建,两两比较构成新的有序组件,
	//将做好的组件相互再次比较排序,形成新的组件,直到整个序列排序完;
	//排序的过程中减少了许多不必要的比较次数,大幅提高了效率

	if (count>1)
	{                                            
		//进行前后分组,将数组逐步分化成一个个的单位
		int Fcount = count / 2;
		int Bcount = count - Fcount;

		//为分裂的数组分配新的空间;
		T *pfData = new T[Fcount];
		T *pbData = new T[Bcount];

		//转移数据
		memcpy(pfData, pData, sizeof(T)*Fcount);
		memcpy(pbData, pData + Fcount, sizeof(T)*Bcount);

		//用递归分裂数据元素
		MereSort(pfData, Fcount, IsUpSort);
		MereSort(pfData, Fcount, IsUpSort);

		///
        //开始归并

		int dataindex = 0;  //定位原来数组空间的位置
		int frontindex = 0;//分裂出来的前数组数据域
		int backindex = 0;//分裂出来的后半组数据域

		if (IsUpSort)
		{
			//将分裂出来的数组进行逐个比较排序,
			while (frontindex < Fcount&&backindex < Bcount)
			{
				if (pfData[frontindex] < pbData[backindex])
				{
					pData[dataindex++] = pfData[frontindex++];
				}
				else
				{
					pData[dataindex++] = pbData[backindex++];
				}
			}
		}
		else
		{
			while (frontindex < Fcount&&backindex < Bcount)
			{
				if (pfData[frontindex] > pbData[backindex])
				{
					pData[dataindex++] = pfData[frontindex++];
				}
				else
				{
					pData[dataindex++] = pbData[backindex++];
				}
			}
		}
        //将还未排序,即节省出来的操作空间给补上去;
		while (frontindex<Fcount)
		{
			pData[dataindex++] = pfData[frontindex++];
		}
		while (backindex < Bcount)
		{
			pData[dataindex++] = pbData[backindex++];
		}

		//释放临时数据空间;
		SAFE_DELARR(pbData);
		SAFE_DELARR(pfData);
	}
}

非递归版

template<class T>
void MereSort(T *pData, size_t count, bool IsUpSort = true)    //归并函数的非递归版;
{
	//相比较与递归版;
	//非递归版的方法是用两个范围来划定区域;
	//一个是从二开始逐步扩大,直到包括整个容器的范围;
	//一个是对上一个范围的分裂,对其排序合成大区;
	//大区分成小区,小区排序好和成大区,大区翻倍再分小区,直到排好序合成整个大区;


	//两个范围;
	int bigblocksize = 2;   
	int samllblocksize;
    
	//用来合并原数据;
	T *_pTempData = new T[count];

	while (bigblocksize < (int)count * 2)       //通过大区大小判断是否全部排序;
	{
		samllblocksize = bigblocksize / 2;      //划分小块;

		for (int bbh = 0; bbh < (int)count; bbh += bigblocksize)
		{

			//原数据的索引,前后区域的索引;
			int dataindex = bbh;    
			int findex = bbh;      
			int bindex = bbh + samllblocksize;

			//如果两个数据的数据域都没有超出,就排序;
			//如果超出了就说明有一个区域已经排好了;
			while (((findex - bbh) < samllblocksize) &&
				(findex < (int)count) &&
				(bindex - (bbh + samllblocksize) < samllblocksize) &&
				(bindex < (int)count))
			{
				if (pData[findex] < pData[bindex])
				{
					_pTempData[dataindex++] = pData[findex++];
				}
				else
				{
					_pTempData[dataindex++] = pData[bindex++];
				}
			}

			//将剩余的前小块直接合并
			while (((findex - bbh) < samllblocksize) &&
				(findex < (int)count))
			{
				_pTempData[dataindex++] = pData[findex++];
			}

			//将剩余的后小块直接合并
			while ((bindex - (bbh + samllblocksize) < samllblocksize) &&
				(bindex < (int)count))
			{
				_pTempData[dataindex++] = pData[bindex++];
			}
		}

		//扩大大范围为之前的两倍
		bigblocksize *= 2;
		//将合并好的缓冲区数据拷回原数据区
		memcpy(pData, _pTempData, sizeof(T)* count);
	}

	SAFE_DELARR(_pTempData);   //删除空间;
}

桶排序

也叫基排序,比起归并排序,桶排序在分冶上做的更加的彻底,它适合小范围的大量数据;

逻辑思想是:将一个数据划分成多个范围,将他们依次倒入这些范围的桶内,这些桶是有序的,倒入时是按顺序倒入,在桶里面不用排序,再倒入下一个桶,从最后一个桶里出来后就全都排序好了。例如9,59,28,27四个数,先将个位倒入9和8,7三个桶中,再按顺序倒回来,就是27,28,9,59。再将10位倒入桶中,即0,2,5三个桶中。再按顺序倒出来,即9,27,28,59。其实就是一种简单的逐步按位排序。(此处为升序)

桶排序的时间复杂度为O(n),是一种用空间换时间的算法,需要注意的是桶排序的目标数据应该尽量均匀。最坏的情况下桶排序的数据全在一个桶里面,时间复杂度就会退化到O(nlogn).

 代码展示:

template<class T>
void RadixSort(T *pData, size_t size, size_t databit, bool IsUp = true)
{
	int baserate = 1;
	T *Tong[10];
	int TongIndex[10];
	int _tongindex;

	int dataindex = 0;
	int _daoindex[10];

	for (int i = 0; i < 10; ++i)
	{
		Tong[i] = new T[size];
	}

	for (size_t sorttime = 0; sorttime < databit; ++sorttime)
	{
		for (int i = 0; i < 10; ++i)
		{
			TongIndex[i] = 0;
		}
		for (size_t i = 0; i < size; ++i)
		{
			_tongindex = (pData[i] / baserate) % 10;
			Tong[_tongindex][TongIndex[_tongindex]++] = pData[i];
		}

		dataindex = 0;
		memset(_daoindex, 0, sizeof(_daoindex));

		if (IsUp)
		{
			for (int t = 0; t < 10; ++t)
			{
				while (_daoindex[t] < TongIndex[t])           //桶里有东西
				{
					pData[dataindex++] = Tong[t][_daoindex[t]++];
				}
			}
		}
		else
		{
			for (int t = 9; t >= 0; --t)
			{
				while (_daoindex[t] < TongIndex[t])
				{
					pData[dataindex++] = Tong[t][_daoindex[t]++];
				}
			}
		}

		baserate *= 10;
	}

	for (int i = 0; i < 10; ++i)
	{
		SAFE_DELARR(Tong[i]);
	}
}

 希尔排序

希尔排序可以说是插入排序的一种进阶算法,相比较插入排序,希尔排序的插入是通过一种距离插入来实现的。在插入排序中,如果数据比较有序,那么效率会很高,我们通过“步长”调整数组,使得数组尽量有序,那么在步长为1即标准插入时就会快上许多。

template<class T>
void ShellSort(T *pData, size_t count, bool IsUpSort = true)   //希尔排序,更像是归并的变种
{
	//用步长来分裂数据元素,分为多个数组,每个数组进行一次插入排序后为一次排序;
    //步长每次缩小一半,再次排序;直到步长等于0;
    //比起归并,它减少了交换值的过程,节约了效率;
	int step = count / 2;
	T temp;

	if (IsUpSort)
	{
		while (step > 0)           //通过步长来控制循环
		{
			for (int current = step; current < (int)count;++current)    //用步长来定位数据位置,控制循环;
 			{
				temp = pData[current];                   // 将数据逐步后移,当前位置就是要比较插入的位置
				int frontindex = current - step;

				while (frontindex >= 0)
				{
					if (temp < pData[frontindex])                          //如果他的前一个位置符合条件可以插入
					{
						pData[frontindex + step] = pData[frontindex];       //将前面的数据后移;
					}
					else
					{
						break;
					}
					frontindex -= step;     //往前再移动一步,直到找到位置
				}
				pData[frontindex + step] = temp;   //找到位置后将数据插入该位置;完成一次步长的排序 
			}

			step /= 2;  //缩减排序;
		}
	}
	else
	{
		while (step > 0)           //通过步长来控制循环
		{
			for (int current = step; current < (int)count; ++current)    //用步长来定位数据位置,控制循环;
			{
				temp = pData[current];                   // 将数据逐步后移,当前位置就是要比较插入的位置
				int frontindex = current - step;

				while (frontindex >= 0)
				{
					if (temp > pData[frontindex])                          //如果他的前一个位置符合条件可以插入
					{
						pData[frontindex + step] = pData[frontindex];       //将前面的数据后移;
					}
					else
					{
						break;
					}
					frontindex -= step;     //往前再移动一步,直到找到位置
				}
				pData[frontindex + step] = temp;   //找到位置后将数据插入该位置;完成一次步长的排序 
			}

			step /= 2;  //缩减排序;
		}
	}
	
}

希尔排序在最好的情况下时间复杂度是O(ologn),最坏的情况下是n(1.5);即数据比较大时。

快速排序 

快速排序的实现原理不难,他的逻辑思想是选定一个基准数数,以这个数为中心,将整个数据域分成两个部分,即都比他大或者都比它小的部分(等于看自己怎么写判断),这个就是一次快牌,接着我们再对分出来的两块数据进行同样的操作,选择基准数将其再分,直到每一块只有一个数据。

template<class T>
void _QuickSort(T *pData, int low, int high, bool IsUpSort)        //快速排序,对归并排序的优化,
{
	//将传进来的数据用两个下标直线头尾;
	//选取一边的第一个数为key值用来划分数组,
	//将key插入到相应的位置,此时左边的恒比小于等于key,右边的大于等于key的值,
	//将插入的key值左边化成一个数组快排,右边也快排,直到每个数据排号;


	int first = low;    //指向数据的左右边
	int last = high;
	if (first>=last)        //只有一个数据退出函数
	{
		return;
	}

	T key = pData[first];   //key值为第一个数

	while (first < last)
	{
		while (first<last)    //从后比较key值,找到比key值小的元素,找到后将其付给fisrt位置,结束这次的last查找,或者first与last重叠退出
		{
			if (pData[last] < key)
			{
				pData[first] = pData[last];
				break;
			}
			else
			{
				--last;
			}
		}

		while (first < last)    //再从前开始比较,找到大于等于key值的数,将其付给last,或者两个坐标重叠,退出此次查找;
		{
			if (pData[first] > key)
			{
				pData[last] = pData[first];
				break;
			}
			else
			{
				++first;
			}
		}
	}
	pData[first] = key; //将key值找到的位置

	_QuickSort(pData, low, first - 1, IsUpSort);  //归并类似,继续快排;
	_QuickSort(pData, first + 1, high, IsUpSort);
}

template<class T>
void QuickSort(T *pData, size_t count, bool IsUpSort = true)   //快速排序的函数接口;
{
	_QuickSort(pData, 0, count - 1, IsUpSort);   //调用写好的函数,传入合理的数据;
}

快速排序最好的时间复杂度是O(nlogn),最差的情况是O(n^2),即逆序有序的情况下,针对这一点我们可以通过随机选择基数来优化这一点,也可有每一步多选基数进行多路快排。

堆排序,这个可以看下面这个博主的介绍:

堆排序算法(图解详细流程)_阿顾同学的博客-CSDN博客_堆排序过程图解

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值