数据结构:排序

排序是计算机程序设计的中的一种重要,在很多领域中都有广泛的应用。如各种升学考试的录取工作,可以参考最近的四级考试,我们可以按成绩高低进行排等,还有日常生活中的各类竞赛活动等也都离不开排序。排序的主要目的是便于查找,从前面学到的查找可以看出,有序的顺序表可以采用查找效率较高的折半查找法,又比如创建树表(二叉排序树)的过程就是一个排序过程。主要的排序方法一共有八种,本期博客我们从最基本的插入排序开始。

(一)直接插入排序

插入排序的基本思想是:每一趟将一个待排序的记录,按其关键字的大小插入到已经排好序的一组记录的适当位置上,直到所有待排序的记录全部插入为止。举个例子,比如我们生活中的打扑克牌,我们在抓牌的时候要保证抓过的牌有序排列,所以需要我们每抓一张牌,就得把它插入的到合适的位置,直到抓完排为止,即得到一个有序的序列。可以选择不同的方法在已排好序的记录中寻找位置。根据查找方法的不同,可以有多种的插入排序方法,这里介绍直接插入排序和希尔排序。

首先是直接插入排序, 直接插入排序是一种最简单的排序方法,其基本操作是将一条记录插入到已排好序的有序表中,从而得到一个新的、记录数量增1的有序表。

如图所示,把这一串序列看成扑克牌,开始第一张牌是49,第二张是38,38在49之后所以往前放,这里的括号就可以看成当前的牌数,也就是i对应的值, 当i=8的时候则所有的牌的插入完毕

void InterSort(int* a, int n)
{//这里传参传的是一个数组a和数组的元素个数n
	int i, end, temp;//这里用一个整型变量end来记录已经排好序的序列元素个数,用temp表示插入的元素
	for (i = 0; i < n - 1; i++)
	{
		end=i;
		temp = a[end + 1];//记录插入元素的值
		while (end >= 0)
		{//判断插入元素的值与前面的有序列里面的值的关系大于就不动,小于就往前插
			if (a[end] >temp)
			{
				a[end+1] = a[end];
				end--;

			}
			else
				break;

		}
		a[end + 1] = temp;//插入操作
	}
}

运行结果如下:

时间复杂度

从时间上来看,插入排序的基本操作为:比较两个关键字的大小和移动记录。对于其中的某一趟插入排序,内层中的for循环次数取决于待插记录的关键字与前i个记录的关键字之间的关系(以上述例子为例)。其中,最好的是传入的数组就是个有序数组,从小到大排情况下,比较1次,不用移动;而最坏的情况就是传入的是一个逆序的数组,需要比较i次。

对于整个排序过程需要执行n-1趟,最好情况下,总的比较次数的最小值为n-1,记录不需要移动;最坏情况下,总的关键字比较次数KCN和记录移动次数RMN均达到最大值:

若待排序序列中出现各种排列的概率相同,则可取上述最好和最坏情况的平均情况。在平均情况下,直接插入排序关键字的比较次数和记录移动次数约为n^2/4。由此,直接插入排序的时间复杂度为O(n^2)

空间复杂度

直接插入排序只需要一个记录的辅助空间r[0],所以空间复杂度为O(1)

算法特点

  1. 稳定排序
  2. 算法简便,且容易实现。
  3. 适用于链式存储结构,只是在单链表上无需记录移动,只需修改相应的指针即可。
  4. 更适合于初识记录基本有序的情况,当初始记录无序,n较大时,此算法时间复杂度较高,则不宜采用。

(二)希尔排序

基本思想

希尔排序又称“缩小增量排序”,是插入排序的一种,因D.LShell于1959年提出而得名。

上面说到,当直接插入排序传入的序列是一个有序的序列时时间复杂度低,效率最高对吧,人们在计算机上程序肯定是运行的越快越好的对吧,那我们就从传入的序列有序来开始入手,如何在直接插入之前让整个序列有序,就拿整型数列来就例子吧,我们要用直接插入法给它排序,且让它的算法时间复杂度低,我们可以怎么做,怎么做才能让它们有序,这里就引入了希尔排序法,根据希尔排序的思想,我们可以将传入的整型数列进行分组,我们就当开始把序列分成三个小组,然后分别对这三个小组进行直接插入排序,排好之后再对整个数列进行分组,但这次分组为上一次的分组数减1,也就是分两组,然后对这两组分别用直接插入排序进行排序,排好之后,按照上述思路将分组的组数减1,变成一组,你想想,一组代表什么意思,一组就代表这整个数列,也就是说最后我们是对整个数列进行直接插入排序,但你们有没有意识到,我们之前因为分组排序的缘故,这个数列已经变得有序,而有序意味着什么,意味着我们直接插入排序的效率变高了,更快啦,而这些就是希尔排序,可以通俗的理解为希尔排序是直接插入排序的升级版。 

我们还是用一个整型序列举例如下图所示:

这里我们设递增量为d:

  1. 第一趟取递增量d1=5,所有间隔为5的记录分在同一组,全部记录分成5组,在各个组中分别进行直接插入排序,排序结果如上图所示。
  2. 第二趟取递增量d2=3,所有间隔为3的记录分在同一组,全部记录分成3组,在各个组中分别进行直接插入排序,排序结果如上图所示。
  3. 第三趟取递增量d3=1,对整个序列进行一趟直接插入排序即可,排序完成。 

 代码实现:

void ShellSort(int* a, int n)
{
	int  gap = n;
	//gap不为1之前进行预排序
	//gap值等于1时进行整体的直接插入排序
	while (gap > 1)
	{
		gap = gap / 3 + 1;//不断改变gap值进行多次预排序,+1是为了保证gap值最后一次为1
		for (int j = 0; j < gap; j++)//将整个数组分为gap组,每一组进行直接插入排序
		{
			for (int i = j; i < n - gap; i += gap)//一组进行直接插入排序
			{
				int temp, end = i;//end记录已排完元素的个数
				temp = a[end + gap];//temp保存插入元素
				while (end >= 0)//在组内找到合适的插入位置
				{
					if (temp < a[end])
					{
						a[end + gap] = a[end];
						end -= gap;
					}
					else
						break;
				}
				a[end + gap] = temp;//插入
			}
		}
	}
}

运行结果如下: 

这里我们传入一个序列即一个数组和数组的元素个数n,现在我们要对传入的数组进行分组,怎么分,这里我们就需要定义一个变量gap,这个gap作用很多,gap用来表示这个序列所分的组数,也表示分的小组元素在大组中的跨度,可能会比较抽象,数据结构嘛,就是要画图,画图才能更好的理解,那么请看下图: 

这里我们定义了一个六个元素的序列,然后对它进行希尔排序,首先我们得进行分组,分组怎么分就拿n/3进行分组,这里因为我们开头让gap=n,所以就拿gap/3,也就是每组有三个元素,有人就要问了,你不是说越有序,直接插入排序越快吗,最后我们不是要给整个序列排序对吧,所以肯定对序列越分越多到最后排序肯定是越来越快,这个问题呢根据前辈们的日积月累,无数次尝试总结,除3是效率最高的,至于为什么不是越多越好,我想了一个可以有助于大家理解,你想想,我们最终都是要将一个序列变成有序的序列,所以对于同一个无序序列来说,我们分很多组和分几组得出的结果都是一样的,那么为什么我们分得多加几次循环来排序呢对吧,好,理解完分组为什么除3的问题,那我们是不是还有后面的+1没说,这里为什么要+1,是因为当我们分到后面,如果分的组数小于三的时候,或者就打上面的六元素序列,当gap为1的时候我们除3,结果不就等于0了,但其实是一组,所以为了避免这种情况发生,我们就+1,用来当一根保险丝。

好,我们再接着一步一步的分析,这里我们由内向外将,上面讲完了分组,接下来就是对每个组进行直接插入排序了,这里我们定义一个i用来记录每小组第一个元素的位置,而j则表示分组后每个组对应的数组下标,原本的序列是六个整型元素的数组,分组后就变成元素是数组的数组,就是把你所分的小组当成数组元素。这里我们再画一个图来给大家理解一下,下图是在上面的图的基础上画的,所以一定要连着看,不然就会看天书一样,知道吧,好,那我们上图:这里的0,2,4为小组元素,而{0,2,4}为整个数组的元素,我们用j=0来表示该数组的首元素,由于这里的gap有表示所分小组的组数,所以这里的j不能超过gap,指的是for循环里面的。 好,这里用j来表示所分的小组后,下一步就是对各小组进行直接插入排序,因为我们用j来表示小组的下标,那我们在定义一个i,让i=j,让i表示该小组的第一个元素的下标,之后在循环里我们就把直接掺入排序的代码直接copy过来就行,但有些地方要调一下,比如循环的终止条件,就是这里的i不能大于n-gap,这里的n-gap表示的是分的第一个小组最后一个元素位置的下标,就是你i(每组的第一个元素下标)不能比第一组的最后一个元素下标大,为什么,假如大了,我们举个例子,本来是1和3是男女朋友,2和4是男女朋友,这里的i就表示1或者2,而n-gap表示3或4,如果这里的i>n-gap就相当于3,4也是男女朋友,那你想想,这会怎样,嗯咳咳,不用想了肯定有问题对吧,还有就是这里a[end + gap] = a[end],之前因为是一个一个按序插所以是end+1跨度为1,但这里是按一定跨度插,跨度就是gap,所以我们是end+gap。

最后只要gap大于1我们就一直分组,然后对每组进行直接插入排序,等于1则对整个序列进行直接插入排序,然后结束。

时间复杂度

希尔排序的时间复杂度不太好计算,根据前人在大量的实验基础上推出:当n在某个特定范围内,希尔排序所需的比较和移动次数约为n^1.3,当n趋近于无穷时则可减少到n(log2n)^2

空间复杂度

从空间上来看,希尔排序和前面两种排序方法一样,也只需要一个辅助空间r[0],空间复杂度为O(1)

算法特点

  1. 记录跳跃式地移动导致排序方法是不稳定的。
  2. 只能用于顺序结构,不能用链式结构。
  3. 增量序列可以有各种取法,但应该使增量序列中的值没有除1之外的公因子,并且最后一个增量值必须等于1,代表整个序列。
  4. 记录总的比较次数和移动次数都比直接插入排序的要少,n越大时,效果越明显。所以适合初识记录无序、n较大时的情况。

(三)直接选择排序

基本思想

直接选择排序是这里写的排序中较为简单的一类了,学过C语言的应该都基本了解,就拿我学的为例,我们学的是叫选择排序,跟这个直接选择一样的但有点不一样,还是画图解释比较好,不多说,上图:

直接插入排序我们可以看到,我们首先对整个序列进行遍历,然后找到整个序列的最小值和最大值,然后将最小值放到当前序列的首位,最大值放到整个序列的末尾,这里的放不如说是交换,,注意我这里的序列是按从小到大排序,换完后序列两边的元素就不管了,我们在两个元素的中间部分选,还是选当前序列的最大值和最小值,选完后将最小值放在当前序列的第一位,最大值放在当前序列的最后一位,以此往复,直到所有的都选完排序结束,我学的那个选择排序也是同样的道理只不过我只选了一个最小值放在序列的前面,最大值没选择罢了。好,讲完了思路我们将代码的实现,以下是相关代码的实现:

void Swap(int* a, int* b)
{//交换函数,注意这里传的是地址,不是值,只有传址才能改变函数外面的值。
	int temp = *a;
	*a=*b;
	*b = temp;
}

void SelectSort(int* a, int n)
{//直接选择排序
	int begin = 0, end = n - 1, max, min;//这里我们定义begin代表当前序列的第一位,end代表当前序列的最后一位,而这里的max表示当前序列的最大值,min表示当前序列的最小值
	while (begin < end)
	{//只有当begin一直表示当前序列的首位且不比当前序列的最后一位end大时进入循环
		min = begin;
		max = begin;//注意,该排序的思想里是对当前序列进行遍历把最大的值换到最后,最小的放最前,然后序列缩小,再在当前序列进行选择,但代码采用的是从左向右遍历,而不是我们理解的两边向中间遍历
		for (int i = begin + 1; i <= end; i++)
		{//找出当前序列的最大值和最小值.这里为什么要从begin+1开始,不从begin开始,如果我们从begin开始的话虽然也可以但会多比一次,就是a[i]与a[min]和a[max],而此时的max与min都等于begin,+1就是为了少比这一次多余的
			if (a[i] > a[max])
			{
				max = i;
			}
			if (a[i] < a[min])
			{
				min = i;
			}

		}
		Swap(&a[min], &a[begin]);//交换
		if (max == begin)
		{//当最大值为当前序列的首位时,因为上一步我们将最小值与当前序列的首位的值进行了互换,此时的min对应的值就是最大值,但如果我们直接将最大值进行互换则换掉的是最小值不是最大值,所以我们要进行相应的调整
			max = min;
		}
		Swap(&a[max], &a[end]);
		++begin;
		--end;//让当前序列不断缩小
	}
}

这个程序的精髓在于if (max == begin)这个语句,其他没什么,但这个不能不看,这个语句用来对于当最大值为当前序列的首位时,因为上一步我们将最小值与当前序列的首位的值进行了互换,此时的min对应的值就是最大值,但如果我们直接将最大值进行互换则换掉的是最小值不是最大值,所以我们要进行相应的调整。

运行结果如下:

拓展: 

这里引入一个随机数生成函数,可以自动产生随机数,我们将产生的随机数存到一个数组里面,再将这个数组传给排序函数就可以了。

int* numberopen(int N)
{//随机数生成函数
	int*p = (int*)malloc(sizeof(int) * N);
	int i;
	srand(time(0));
	for (i = 0; i < N; i++)
	{
		p[i] = rand();//产生随机数
	}
	return p;
}

这个函数我是从CSDN上的一篇博客上学的,然后我将它稍微改了一下变成这样,具体的原理你们也可以自己去看,我就不多赘述了,毕竟能用就行。 

时间复杂度

 该方法的时间复杂度是:O(n^2),不如直接插入排序的效率(直接选择排序时间复杂度不会随数据的变化而变化)

空间复杂度

此排序只有在两个记录交换时需要一个辅助空间,所以空间复杂度为O(1)

算法特点

  1. 就选择排序法本身来讲,它是一种稳定的排序方法。
  2. 可用于链式存储。
  3. 移动记录次数较少,当每一记录占用的空间较多时,此方法比直接插入排序快。

(四)冒泡排序

基本思想

冒泡排序法和直接选择排序都是我们学计算机中最简单的排序方法,主要的思想是换,冒泡冒泡,我们要形象的理解不断的换,就像不断的冒泡泡一样,每次换的时候将一个最大值后移,直到将最大值移到数组的末尾,然后保持末尾的最大值不变再从头进行排序,找到一个最大值放在上次排好的最大值的前一位,以此往复,还是那句话,数据结构嘛,最主要的是画图,那我来画一张原理图来帮助大家理解,如下图所示:

具体的排序过程如图所示,可以看出比较简单,好,既然现在理解了思路,我们就开始代码实现了。代码如下:

void BubbleSort(int* a, int n)
{//冒泡排序
	for (int i = 0; i < n; i++)
	{
		for (int j = 1; j < n - i; j++)
		{
			if (a[j - 1] > a[j])
				Swap(&a[j - 1], &a[j]);//将当前比较的数的最大值往后换,每换一次数组的尾部就减一
		}
	}

}

运行的结果如下图所示: 

这里的冒泡排序还可以进行优化。当某一趟排序没有发生交换我们就认为它已经排序成功,就没有必要进行下一步的排序了,exchange=0代表没有发生交换,最后给个判断,如果exchange=0则跳出程序。

void BubbleSortPlus(int* a, int n)
{//冒泡排序
	for (int i = 0; i < n; i++)
	{
		int exchange = 0;//当某一趟排序没有发生交换我们就认为它已经排序成功,就没有必要进行下一步的排序了,exchange=0代表没有发生交换
		for (int j = 1; j < n - i; j++)
		{
			if (a[j - 1] > a[j])
			{
				Swap(&a[j - 1], &a[j]);//将当前比较的数的最大值往后换,每换一次数组的尾部就减一
				exchange = 1;//只要换过则代表该序列还未排序成功,让exchange=1
			}
		}
		if (exchange == 0)
		{
			break;//没有发生交换就直接跳出排序程序
		}

	}

}

时间复杂度

最好情况(初识序列为正序):只需进行一趟排序,在排序过程中进行n-1次关键字间的比较,且不移动记录。

最坏情况(初始序列为逆序):需进行n-1趟排序,总的关键字比较次数KCN和记录移动次数RMN(每次交换都要移动3次记录)分别为下图所示:

所以在平均情况下,冒泡排序关键字的比较次数和记录移动次数分别约为n^2/43n^2/4,时间复杂度为O(n^2)

空间复杂度

冒泡排序只有在两个记录交换位置时需要一个辅助空间用于暂存记录,所以空间复杂度为O(1)。

算法特点

  1. 稳定排序。
  2. 可用于链式存储结构
  3. 移动记录此时较多,算法的平均时间性能比直接插入排序的差。当初始记录无序、n较大时,此算法不宜采用。

 (五)堆排序

堆的定义

堆排序是一种树形选择排序,在排序过程中,将待排序的记录r[1……n]看成一棵完全二叉树的顺序存储结构,利用完全二叉树中双亲结点和孩子结点之间的内在关系,在当前无序的序列中选择关键字最大(或最小)的记录。

首先给出堆的定义:n个元素的序列{k1,k2,k3……kn}称之为堆当且仅当满足以下条件时:

(1)ki大于等于k2i且ki大于等于k2i+1

(2)ki小于等于k2i且ki小于等于k2i+1(1小于等于i小于等于[n/2])

若将和此序列对应的一组数组(即以一维数组做此排序的存储结构)看成一个完全二叉树,则堆实质上满足如下性质的完全二叉树:树中所有非终端结点的值均不大于(或不小于)其左右还在结点的值。而堆顶元素显然在这里必为序列中n个元素的最值,最大值称为大根堆,最小值称为小根堆,而堆排序就是利用了大根或者小根堆堆顶记录的关键字最大或者最小这一特征,使得当前无序的序列中选择关键字最大(或最小)的记录变得简单。

左图为大堆,右图为小堆 

上面讲的罗里吧嗦的其实简单点说就是,堆表示2完全二叉树的顺序存储结构,本质是一个顺序表,但是这里用完全二叉树根结点与其左右孩子的关系进行了一个限制就成了堆,至于大堆,和小堆,通俗一点说就是大堆就是每个根结点的值都大于其左右孩子,最上面的根结点的值最大,小根就是每个根结点的值都比其左右孩子结点对应的值要小,为小堆。

之后就是根结点与孩子结点的一些关系汇总:

leftchild = parent*2+1 (都为奇数)

rightchild = parent*2+2 (都为偶数)

parent = (child-1)/2

在实现堆排之前我们先学会如何去定义一个堆,既然堆事顺序结构,并且是顺序表那么就可以参考顺序表的定义,可以借鉴一下我之前的博客,里面有顺序表相关的内容:

typedef int DataType;
typedef struct Seqlist
{//定义一个结构体用来控制顺序表
 
	DataType* Data;//可以使用malloc和realloc对其空间进行动态分配
	size_t size;//记录有效数据的个数
	size_t capacity;//记录空间可以存储元素空间的总大小
}Seqlist;

堆的初始化:

void InitSeqList(struct Seqlist* ps)
{//顺序表初始化
	assert(ps);
	ps->Data = NULL;
	ps->size = 0;//表示堆的实际长
	ps->capacity = 0;//表示堆的总长
}

堆的销毁:

void Destory(Seqlist* ps)
{//销毁堆
    assert(ps);
	free(ps->Data);//释放指针所对应的内存空间
    ps->capacity = ps->size = 0;
	ps->Data = NULL;//将ps->Data指针置空,防止其变成野指针
}

 堆排序的实现

void AdjustDown(int* data, int n,int parent)//向下调整
{
	int chlid = parent * 2 + 1;//找到其左孩子
	while (chlid < n)//防止数组越界
	{
		if (data[chlid + 1] > data[chlid] && chlid + 1 < n)//判断左右孩子的大小,chlid + 1 < n要防止存在左孩子而无右孩子时的数组越界(想要按小堆调整需将前一个>改为<号)
		{
			chlid += 1;
		}
		if (data[chlid] > data[parent])//(想要按小堆调整需将 > 改为 < 号)
		{
			Swap(&data[chlid], &data[parent]);//将大的孩子元素与其替换
			//继续向下比较替换
			parent = chlid;
			chlid = parent * 2 + 1;
		}
		else//如果孩子元素都没有其父元素大则直接跳出
		{
			break;
		}
	}
}
 
void HeapSort(int* a, int n)
{
	for (int i = (n - 1 - 1) / 2; i >= 0; i--)//n-1是最后一个元素的物理位置((n-1)-1)/2是其父节点的物理位置
	{
		AdjustDown(a, n, i);//向下调整
	}
	int end = n - 1;
	while (end > 0)
	{
		Swap(&a[end], &a[0]);//每一次调整之后将最大的元素挪到堆的最后面
		AdjustDown(a,end,0);
		end--;
	}
}

运行结果: 

时间复杂度

堆排序的时间复杂度是O(nlog2n) ,具体算的过程就不一一列举了。

算法特点

  1. 是不稳定排序。
  2. 只能用于顺序结构,不能用于链式结构。
  3. 初始建堆所需的比较次数较多,因此记录较少时不宜采用。堆排序在最坏的情况下时间复杂度为O(nlog2n),相当于快速排序的最坏情况下的O(n^2)更有优势,当记录较多时较为高效。

(六)快速排序

Hoare版本快速排序

基本思想

快排的主要思想是找基准值然后根据基准值判断,大于基准值的放序列的右边,小于基准值的放序列的左边,基准值一般为序列的首地址元素,然后我们再定义L和R两个指针,L指针从前往后遍历,而R指针则从后往前遍历,每当L指针指到比基准值大的元素时停下,等R指针遍历,R指针遍历到比基准值小的值的时候就停下,然后将两指针所对应的元素进行互换,,以此往复,直到两指针指向同一个位置,这时,我们将基准值与两指针共同指向的元素进行互换,还完之后,基准值应该会在序列的中间位置,然后以基准值为限将序列一分为二,变成两个子序列和基准值,然后再对子序列进行上述的排序,直到不能再分为止,这就是快排的基本思路,可以参考一下冒泡排序的思路,冒泡排序是拿定当前序列的一个最大值往当前序列的末尾放,而快排则是拿基准值把它往中间放。当然说这么多不如一张图理解的痛快,废话不多说,上图:

在快速排序中,按升序排序时为了保证L和R指针相遇位置比基准值要小,在第一个元素做基准值时R指针要先走,在最后个元素做基准值时L指针要先走。 

好,基本思路就是这样,那么就开始代码实战了: 

void QuickSort(int* a, int begin,int end)
{//Hoare版本快速排序
	if (begin >= end)
		return;//递归终止判断,因为随着不断的分组,数组分的越来越段短,当begin与end相等时数组只剩下一个元素,所以不用再继续递归分组了
	int left = begin, right = end;
	int keyi = left;//确定基准值的下标
	while (left < right)
	{
		while (left < right && a[right] >= a[keyi])
		{//当right一直在left的右边且,right对应的值大于基准值时,让right往前移,因为right是遇到比基准值小的数才停下来的
			--right;
		}
		while (left < right && a[left] <= a[keyi])
		{//同理当left一直在right的左边且,left对应的值小于基准值时,让left往后移,因为left是遇到比基准值大的数才停下来的
			++left;
		}
		Swap(&a[left], &a[right]);//当上述循环均经历之后,说明left和right已经指向了它们该指的位置,此时将它们对应的值进行交换
	}
	Swap(&a[keyi], &a[left]);//当left大于等于right时,说明left与right指向了同一个位置,这时让基准值与该位置的值进行互换
	keyi = left;//交换之后基准值对应的下标发生改变
	QuickSort(a, begin, keyi - 1);
	QuickSort(a, keyi + 1, end);//将序列以基准值为界进行分组,然后分别对这些组进行上述操作
}

运行结果如下: 

当然这个算法还能继续优化一下 ,具体优化分为三值取中和当分组后所得的子组的元素个数较少时可以用直接插入排序来进行排序,不用再特意分组用快排来排序了,三值取中则是对于一个很大的序列,对其用快排排序的时候不一定每次都是对半分组,有可能是一边元素多一边元素少的情况,对于这种情况用快排会比对半分使用快排要多开辟好几个函数内存,函数内存开的越多内存使用就会更大,时间花费更长,也就是时间和空间复杂度会变高,而这里的优化就是针对该算法的时间和空间复杂度进行优化降低,具体实现请看下面的代码:

int GetMidIndx(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])//如果首元素的值小于此时序列的中间值但大于末尾的元素值则此时begin对应的元素为中间值,返回begin
			return begin;
		else//其他则end为中间值,返回end
			return end;
	}
	else
	{
		if (a[end] < a[mid])//进行判断如果序列的首元素比此时的中间值大进入下面的程序
			return mid;//此时mid大于end但小于begin所以先mid就是中间值,返回mid即可
		else if (a[end] > a[begin])
			return begin;//如果begin大于mid但end又大于begin则此时的begin就是中间值,返回begin即可
		else
			return end;//否则返回end即end为中间值
	}
}

void QuickSort(int* a, int begin,int end)
{//Hoare版本快速排序
	if (begin >= end)
		return;//递归终止判断,因为随着不断的分组,数组分的越来越段短,当begin与end相等时数组只剩下一个元素,所以不用再继续递归分组了
	if ((end - begin+1) <= 8)//当传入的元素小于或等于8的时候我们就可以不用快排来排序了,可以直接用直接插入进行排序,因为此时的数组个数很少了并且经过之前的快排已经近乎有序了所以可以直接用插入排序来提高效率
	{//end - begin+1这里表示数组的元素个数,末尾元素的下标减去首元素的下标然后+1
		InterSort(&a[begin], end - begin + 1);//这里如果是左边的数组则首元素传入的下标是begin但如果传入的是右数组则传入的首元素的下标则是keyi + 1
	}
	else
	{
		int mid = GetMidIndx(a, begin, end);//三数取中,找出中间元素然后将其作为基准值的话就可以避免分组的时候一边长一边短的情况,保证每次基本都是对半分
		Swap(&a[begin], &a[mid]);//取中后交换元素位置
		int left = begin, right = end;
		int keyi = left;//确定基准值的下标
		while (left < right)
		{
			while (left < right && a[right] >= a[keyi])
			{//当right一直在left的右边且,right对应的值大于基准值时,让right往前移,因为right是遇到比基准值小的数才停下来的
				--right;
			}
			while (left < right && a[left] <= a[keyi])
			{//同理当left一直在right的左边且,left对应的值小于基准值时,让left往后移,因为left是遇到比基准值大的数才停下来的
				++left;
			}
			Swap(&a[left], &a[right]);//当上述循环均经历之后,说明left和right已经指向了它们该指的位置,此时将它们对应的值进行交换
		}
		Swap(&a[keyi], &a[left]);//当left大于等于right时,说明left与right指向了同一个位置,这时让基准值与该位置的值进行互换
		keyi = left;//交换之后基准值对应的下标发生改变
		QuickSort(a, begin, keyi - 1);
		QuickSort(a, keyi + 1, end);//将序列以基准值为界进行分组,然后分别对这些组进行上述操作
	}
}

优化之后算法的空间与时间效率都有一定的提升,虽然并不明显,但也算快了一点,可以试一下。

时间复杂度 

在理想情况下,假如每次取的基准值都在数组元素的中间值(这样每次进行拆分时都可以分掉数组的一半),快速排序算法的时间复杂度为:O(N*㏒⑵N)

在数组趋近于有序时,这样会导致每次进行拆分数组时一边元素极多一边元素极少,元素极多的数组会增加后续继续分组的次数,所以在最坏情况下快速排序算法的时间复杂度为:O(N^2)

填坑法快速排序

基本思想

本来这个方法应该叫挖坑法,但我觉得叫填坑法更形象所以我就用填坑法来表示了,首先不管是什么样的方法都只是为了一个目的,那就是找基准值,只要能找到基准值,我们就可以进行分组快排,所以这里的填坑法就是如此。其基本思想就是将基准值先提取出来,提出来之后基准值所在位置元素不就为空了,这里的空就可以理解为挖了一个坑,现在有个坑了,好让R往左移找到比当前的基准值小的数然后将该数换到基准值位置去填坑,但你换完之后原来的数位置不就空了吗,这时候就要让L往右移找到比基准值大的数去填刚刚留下来的坑然后......以此往复,直到L与R相遇时将之前提取的基准值填入当前位置即可。

以下是代码实现:

int PartSort2(int* a, int begin, int end)
{//快排填坑法
	int mid = GetMidIndx(a, begin, end);//三数取中,找出中间元素然后将其作为基准值的话就可以避免分组的时候一边长一边短的情况,保证每次基本都是对半分
	Swap(&a[begin], &a[mid]);//取中后交换元素位置
	int left = begin, right = end;
	int key = a[begin];//挖坑,将基准值提出来
	int hole = left;//记录坑所在位置
	while (left < right)
	{
		while (left < right && a[right] >= key)
		{//当right一直在left的右边且,right对应的值大于基准值时,让right往前移,因为right是遇到比基准值小的数才停下来的
			right--;
		}
		a[hole] = a[right];//right找到比基准值小的数然后去填之前挖的坑
		hole = right;//当前的数拿来去填坑了,现在right位置就是空的,变成新坑
		while (left < right && a[left] <= key)
		{//同理当left一直在right的左边且,left对应的值小于基准值时,让left往后移,因为left是遇到比基准值大的数才停下来的
			left++;
		}
		a[hole] = a[left];//left找到比基准值大的数然后去填之前挖的坑
		hole = left;//当前的数拿来去填坑了,现在left位置就是空的,变成新坑
	}
	a[hole] = key;//当left>=right时最后将基准元素插入到空缺位置
	return hole;//返回此时的基准值位置,用来做分组的界限
}


void QuickSort2(int* a, int begin, int end)
{//填坑版本快速排序
	if (begin >= end)
		return;//递归终止判断,因为随着不断的分组,数组分的越来越段短,当begin与end相等时数组只剩下一个元素,所以不用再继续递归分组了
	if ((end - begin + 1) <= 8)//当传入的元素小于或等于8的时候我们就可以不用快排来排序了,可以直接用直接插入进行排序,因为此时的数组个数很少了并且经过之前的快排已经近乎有序了所以可以直接用插入排序来提高效率
	{//end - begin+1这里表示数组的元素个数,末尾元素的下标减去首元素的下标然后+1
		InterSort(&a[begin], end - begin + 1);//这里如果是左边的数组则首元素传入的下标是begin但如果传入的是右数组则传入的首元素的下标则是keyi + 1
	}
	else
	{
		int keyi=PartSort2(a, begin, end);
		QuickSort(a, begin, keyi - 1);
		QuickSort(a, keyi + 1, end);//将序列以基准值为界进行分组,然后分别对这些组进行上述操作
	}
}

运行结果如下: 

指针法快速排序

基本思想

指针法快排主要思想其实也是为了找基准值的位置,至于怎么找,我们首先定义两个变量一个是prev开始指向序列的首元素的位置,还有一个变量是cur指向首元素后一位,然后让cur进行遍历,遇到比基准值小的数就停下来,然后让prev往后移一位,移完之后将prev指向的元素与cur所指向的元素进行交换,然后再让cur继续遍历,重复上述的操作,直到cur遍历完毕后将首地址的基准值与prev所对应的位置元素进行交换,最后返回prev即基准值的位置。

int PartSort3(int* a, int begin, int end)
{
	int mid = GetMidIndx(a, begin, end);//三数取中
	Swap(&a[begin], &a[mid]);//取中后交换元素位置
	int prev = begin, cur = begin + 1, key = a[begin];
	while (cur <= end)
	{
		while (cur <= end && a[cur] >= key)//一直找到比key小的元素
		{
			cur++;
		}
		Swap(&a[++prev], &a[cur++]);//找到了进行交换
	}
	Swap(&a[prev], &a[begin]);//最后进行基准元素与prev所在的元素进行交换
	return prev;
}

void QuickSort3(int* a, int begin, int end)
{//Hoare版本快速排序
	if (begin >= end)
		return;//递归终止判断,因为随着不断的分组,数组分的越来越段短,当begin与end相等时数组只剩下一个元素,所以不用再继续递归分组了
	if ((end - begin + 1) <= 8)//当传入的元素小于或等于8的时候我们就可以不用快排来排序了,可以直接用直接插入进行排序,因为此时的数组个数很少了并且经过之前的快排已经近乎有序了所以可以直接用插入排序来提高效率
	{//end - begin+1这里表示数组的元素个数,末尾元素的下标减去首元素的下标然后+1
		InterSort(&a[begin], end - begin + 1);//这里如果是左边的数组则首元素传入的下标是begin但如果传入的是右数组则传入的首元素的下标则是keyi + 1
	}
	else
	{
		int keyi = PartSort3(a, begin, end);
		QuickSort(a, begin, keyi - 1);
		QuickSort(a, keyi + 1, end);//将序列以基准值为界进行分组,然后分别对这些组进行上述操作
	}
}

运行结果如下: 

这个算法在运行的时候还可以进行相应的优化,因为有时候prev与cur会指向同一个位置,此时的交换就是没有意义的。 这里我们可以做一些优化:

int PartSort3Plus(int* a, int begin, int end)
{//指针版快排升级版
	int mid = GetMidIndx(a, begin, end);//三数取中
	Swap(&a[begin], &a[mid]);//取中后交换元素位置
	int prev = begin, cur = begin + 1, key = a[begin];

	while (cur <= end)
	{
		if (a[cur] < key && ++prev != cur)//找到比key小的元素并且prev和cur不在同一位置时进行交换
		{
			Swap(&a[prev], &a[cur]);//找到了进行交换
		}
		cur++;
	}
	Swap(&a[prev], &a[begin]);//最后进行基准元素与prev所在的元素进行交换
	return prev;
}

void QuickSort3Plus(int* a, int begin, int end)
{//指针版快排升级版
	if (begin >= end)
		return;//递归终止判断,因为随着不断的分组,数组分的越来越段短,当begin与end相等时数组只剩下一个元素,所以不用再继续递归分组了
	if ((end - begin + 1) <= 8)//当传入的元素小于或等于8的时候我们就可以不用快排来排序了,可以直接用直接插入进行排序,因为此时的数组个数很少了并且经过之前的快排已经近乎有序了所以可以直接用插入排序来提高效率
	{//end - begin+1这里表示数组的元素个数,末尾元素的下标减去首元素的下标然后+1
		InterSort(&a[begin], end - begin + 1);//这里如果是左边的数组则首元素传入的下标是begin但如果传入的是右数组则传入的首元素的下标则是keyi + 1
	}
	else
	{
		int keyi = PartSort3Plus(a, begin, end);
		QuickSort(a, begin, keyi - 1);
		QuickSort(a, keyi + 1, end);//将序列以基准值为界进行分组,然后分别对这些组进行上述操作
	}
}

运行结果如下:

算法特点 
  1. 记录非顺序的移动导致排序方法是不稳定的。
  2. 排序过程中需要定位表的下界和上界,所以适合用顺序结构,很难用链式结构。
  3. 当n较大时,在平均情况下快速排序是所有内部排序方法中速度最快的一种,所以其适合初始记录无序、n较大的情况。

完整代码

#include <stdio.h>
#include<stdlib.h>
#include<time.h>
void InterSort(int* a, int n)//直接插入排序
{//这里传参传的是一个数组a和数组的元素个数n
	int i, end, temp;//这里用一个整型变量end来记录已经排好序的序列元素个数,用temp表示插入的元素
	for (i = 0; i < n - 1; i++)
	{
		end=i;
		temp = a[end + 1];//记录插入元素的值
		while (end >= 0)
		{//判断插入元素的值与前面的有序列里面的值的关系大于就不动,小于就往前插
			if (a[end] >temp)
			{
				a[end+1] = a[end];
				end--;

			}
			else
				break;

		}
		a[end + 1] = temp;//插入操作
	}
}

void ShellSort(int* a, int n)
{//希尔排序
	int  gap = n;
	//gap不为1之前进行预排序
	//gap值等于1时进行整体的直接插入排序
	while (gap > 1)
	{
		gap = gap / 3 + 1;//不断改变gap值进行多次预排序,+1是为了保证gap值最后一次为1
		for (int j = 0; j < gap; j++)//将整个数组分为gap组,每一组进行直接插入排序
		{
			for (int i = j; i < n - gap; i += gap)//一组进行直接插入排序
			{
				int temp, end = i;//end记录已排完元素的个数
				temp = a[end + gap];//temp保存插入元素
				while (end >= 0)//在组内找到合适的插入位置
				{
					if (temp < a[end])
					{
						a[end + gap] = a[end];
						end -= gap;
					}
					else
						break;
				}
				a[end + gap] = temp;//插入
			}
		}
	}
}

void Swap(int* a, int* b)
{//交换函数,注意这里传的是地址,不是值,只有传址才能改变函数外面的值。
	int temp = *a;
	*a=*b;
	*b = temp;
}

void SelectSort(int* a, int n)
{//直接选择排序
	int begin = 0, end = n - 1, max, min;//这里我们定义begin代表当前序列的第一位,end代表当前序列的最后一位,而这里的max表示当前序列的最大值,min表示当前序列的最小值
	while (begin < end)
	{//只有当begin一直表示当前序列的首位且不比当前序列的最后一位end大时进入循环
		min = begin;
		max = begin;//注意,该排序的思想里是对当前序列进行遍历把最大的值换到最后,最小的放最前,然后序列缩小,再在当前序列进行选择,但代码采用的是从左向右遍历,而不是我们理解的两边向中间遍历
		for (int i = begin + 1; i <= end; i++)
		{//找出当前序列的最大值和最小值.这里为什么要从begin+1开始,不从begin开始,如果我们从begin开始的话虽然也可以但会多比一次,就是a[i]与a[min]和a[max],而此时的max与min都等于begin,+1就是为了少比这一次多余的
			if (a[i] > a[max])
			{
				max = i;
			}
			if (a[i] < a[min])
			{
				min = i;
			}

		}
		Swap(&a[min], &a[begin]);//交换
		if (max == begin)
		{//当最大值为当前序列的首位时,因为上一步我们将最小值与当前序列的首位的值进行了互换,此时的min对应的值就是最大值,但如果我们直接将最大值进行互换则换掉的是最小值不是最大值,所以我们要进行相应的调整
			max = min;
		}
		Swap(&a[max], &a[end]);
		++begin;
		--end;//让当前序列不断缩小
	}
}

int* numberopen(int N)
{//随机数生成函数
	int*p = (int*)malloc(sizeof(int) * N);
	int i;
	srand(time(0));
	for (i = 0; i < N; i++)
	{
		p[i] = rand()+1;//产生随机数
	}
	return p;
}

void BubbleSort(int* a, int n)
{//冒泡排序
	for (int i = 0; i < n; i++)
	{
		for (int j = 1; j < n - i; j++)
		{
			if (a[j - 1] > a[j])
				Swap(&a[j - 1], &a[j]);//将当前比较的数的最大值往后换,每换一次数组的尾部就减一
		}
	}

}

void BubbleSortPlus(int* a, int n)
{//冒泡排序
	for (int i = 0; i < n; i++)
	{
		int exchange = 0;//当某一趟排序没有发生交换我们就认为它已经排序成功,就没有必要进行下一步的排序了,exchange=0代表没有发生交换
		for (int j = 1; j < n - i; j++)
		{
			if (a[j - 1] > a[j])
			{
				Swap(&a[j - 1], &a[j]);//将当前比较的数的最大值往后换,每换一次数组的尾部就减一
				exchange = 1;//只要换过则代表该序列还未排序成功,让exchange=1
			}
		}
		if (exchange == 0)
		{
			break;//没有发生交换就直接跳出排序程序
		}

	}

}

void AdjustDown(int* data, int n, int parent)//向下调整
{
	int chlid = parent * 2 + 1;//找到其左孩子
	while (chlid < n)//防止数组越界
	{
		if (data[chlid + 1] > data[chlid] && chlid + 1 < n)//判断左右孩子的大小,chlid + 1 < n要防止存在左孩子而无右孩子时的数组越界(想要按小堆调整需将前一个>改为<号)
		{
			chlid += 1;
		}
		if (data[chlid] > data[parent])//(想要按小堆调整需将 > 改为 < 号)
		{
			Swap(&data[chlid], &data[parent]);//将大的孩子元素与其替换
			//继续向下比较替换
			parent = chlid;
			chlid = parent * 2 + 1;
		}
		else//如果孩子元素都没有其父元素大则直接跳出
		{
			break;
		}
	}
}

void HeapSort(int* a, int n)
{
	for (int i = (n - 1 - 1) / 2; i >= 0; i--)//n-1是最后一个元素的物理位置((n-1)-1)/2是其父节点的物理位置
	{
		AdjustDown(a, n, i);//向下调整
	}
	int end = n - 1;
	while (end > 0)
	{
		Swap(&a[end], &a[0]);//每一次调整之后将最大的元素挪到堆的最后面
		AdjustDown(a, end, 0);
		end--;
	}
}

int GetMidIndx(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])//如果首元素的值小于此时序列的中间值但大于末尾的元素值则此时begin对应的元素为中间值,返回begin
			return begin;
		else//其他则end为中间值,返回end
			return end;
	}
	else
	{
		if (a[end] < a[mid])//进行判断如果序列的首元素比此时的中间值大进入下面的程序
			return mid;//此时mid大于end但小于begin所以先mid就是中间值,返回mid即可
		else if (a[end] > a[begin])
			return begin;//如果begin大于mid但end又大于begin则此时的begin就是中间值,返回begin即可
		else
			return end;//否则返回end即end为中间值
	}
}

void QuickSort(int* a, int begin,int end)
{//Hoare版本快速排序
	if (begin >= end)
		return;//递归终止判断,因为随着不断的分组,数组分的越来越段短,当begin与end相等时数组只剩下一个元素,所以不用再继续递归分组了
	if ((end - begin+1) <= 8)//当传入的元素小于或等于8的时候我们就可以不用快排来排序了,可以直接用直接插入进行排序,因为此时的数组个数很少了并且经过之前的快排已经近乎有序了所以可以直接用插入排序来提高效率
	{//end - begin+1这里表示数组的元素个数,末尾元素的下标减去首元素的下标然后+1
		InterSort(&a[begin], end - begin + 1);//这里如果是左边的数组则首元素传入的下标是begin但如果传入的是右数组则传入的首元素的下标则是keyi + 1
	}
	else
	{
		int mid = GetMidIndx(a, begin, end);//三数取中,找出中间元素然后将其作为基准值的话就可以避免分组的时候一边长一边短的情况,保证每次基本都是对半分
		Swap(&a[begin], &a[mid]);//取中后交换元素位置
		int left = begin, right = end;
		int keyi = left;//确定基准值的下标
		while (left < right)
		{
			while (left < right && a[right] >= a[keyi])
			{//当right一直在left的右边且,right对应的值大于基准值时,让right往前移,因为right是遇到比基准值小的数才停下来的
				--right;
			}
			while (left < right && a[left] <= a[keyi])
			{//同理当left一直在right的左边且,left对应的值小于基准值时,让left往后移,因为left是遇到比基准值大的数才停下来的
				++left;
			}
			Swap(&a[left], &a[right]);//当上述循环均经历之后,说明left和right已经指向了它们该指的位置,此时将它们对应的值进行交换
		}
		Swap(&a[keyi], &a[left]);//当left大于等于right时,说明left与right指向了同一个位置,这时让基准值与该位置的值进行互换
		keyi = left;//交换之后基准值对应的下标发生改变
		QuickSort(a, begin, keyi - 1);
		QuickSort(a, keyi + 1, end);//将序列以基准值为界进行分组,然后分别对这些组进行上述操作
	}
}

int PartSort2(int* a, int begin, int end)
{//快排填坑法
	int mid = GetMidIndx(a, begin, end);//三数取中,找出中间元素然后将其作为基准值的话就可以避免分组的时候一边长一边短的情况,保证每次基本都是对半分
	Swap(&a[begin], &a[mid]);//取中后交换元素位置
	int left = begin, right = end;
	int key = a[begin];//挖坑,将基准值提出来
	int hole = left;//记录坑所在位置
	while (left < right)
	{
		while (left < right && a[right] >= key)
		{//当right一直在left的右边且,right对应的值大于基准值时,让right往前移,因为right是遇到比基准值小的数才停下来的
			right--;
		}
		a[hole] = a[right];//right找到比基准值小的数然后去填之前挖的坑
		hole = right;//当前的数拿来去填坑了,现在right位置就是空的,变成新坑
		while (left < right && a[left] <= key)
		{//同理当left一直在right的左边且,left对应的值小于基准值时,让left往后移,因为left是遇到比基准值大的数才停下来的
			left++;
		}
		a[hole] = a[left];//left找到比基准值大的数然后去填之前挖的坑
		hole = left;//当前的数拿来去填坑了,现在left位置就是空的,变成新坑
	}
	a[hole] = key;//当left>=right时最后将基准元素插入到空缺位置
	return hole;//返回此时的基准值位置,用来做分组的界限
}


void QuickSort2(int* a, int begin, int end)
{//填坑版本快速排序
	if (begin >= end)
		return;//递归终止判断,因为随着不断的分组,数组分的越来越段短,当begin与end相等时数组只剩下一个元素,所以不用再继续递归分组了
	if ((end - begin + 1) <= 8)//当传入的元素小于或等于8的时候我们就可以不用快排来排序了,可以直接用直接插入进行排序,因为此时的数组个数很少了并且经过之前的快排已经近乎有序了所以可以直接用插入排序来提高效率
	{//end - begin+1这里表示数组的元素个数,末尾元素的下标减去首元素的下标然后+1
		InterSort(&a[begin], end - begin + 1);//这里如果是左边的数组则首元素传入的下标是begin但如果传入的是右数组则传入的首元素的下标则是keyi + 1
	}
	else
	{
		int keyi=PartSort2(a, begin, end);
		QuickSort(a, begin, keyi - 1);
		QuickSort(a, keyi + 1, end);//将序列以基准值为界进行分组,然后分别对这些组进行上述操作
	}
}

int PartSort3(int* a, int begin, int end)
{//指针版快速排序
	int mid = GetMidIndx(a, begin, end);//三数取中
	Swap(&a[begin], &a[mid]);//取中后交换元素位置
	int prev = begin, cur = begin + 1, key = a[begin];
	while (cur <= end)
	{
		while (cur <= end && a[cur] >= key)//一直找到比key小的元素
		{
			cur++;
		}
		Swap(&a[++prev], &a[cur++]);//找到了进行交换
	}
	Swap(&a[prev], &a[begin]);//最后进行基准元素与prev所在的元素进行交换
	return prev;
}

void QuickSort3(int* a, int begin, int end)
{//指针版本快速排序
	if (begin >= end)
		return;//递归终止判断,因为随着不断的分组,数组分的越来越段短,当begin与end相等时数组只剩下一个元素,所以不用再继续递归分组了
	if ((end - begin + 1) <= 8)//当传入的元素小于或等于8的时候我们就可以不用快排来排序了,可以直接用直接插入进行排序,因为此时的数组个数很少了并且经过之前的快排已经近乎有序了所以可以直接用插入排序来提高效率
	{//end - begin+1这里表示数组的元素个数,末尾元素的下标减去首元素的下标然后+1
		InterSort(&a[begin], end - begin + 1);//这里如果是左边的数组则首元素传入的下标是begin但如果传入的是右数组则传入的首元素的下标则是keyi + 1
	}
	else
	{
		int keyi = PartSort3(a, begin, end);
		QuickSort(a, begin, keyi - 1);
		QuickSort(a, keyi + 1, end);//将序列以基准值为界进行分组,然后分别对这些组进行上述操作
	}
}

int PartSort3Plus(int* a, int begin, int end)
{//指针版快排升级版
	int mid = GetMidIndx(a, begin, end);//三数取中
	Swap(&a[begin], &a[mid]);//取中后交换元素位置
	int prev = begin, cur = begin + 1, key = a[begin];

	while (cur <= end)
	{
		if (a[cur] < key && ++prev != cur)//找到比key小的元素并且prev和cur不在同一位置时进行交换
		{
			Swap(&a[prev], &a[cur]);//找到了进行交换
		}
		cur++;
	}
	Swap(&a[prev], &a[begin]);//最后进行基准元素与prev所在的元素进行交换
	return prev;
}

void QuickSort3Plus(int* a, int begin, int end)
{//指针版快排升级版
	if (begin >= end)
		return;//递归终止判断,因为随着不断的分组,数组分的越来越段短,当begin与end相等时数组只剩下一个元素,所以不用再继续递归分组了
	if ((end - begin + 1) <= 8)//当传入的元素小于或等于8的时候我们就可以不用快排来排序了,可以直接用直接插入进行排序,因为此时的数组个数很少了并且经过之前的快排已经近乎有序了所以可以直接用插入排序来提高效率
	{//end - begin+1这里表示数组的元素个数,末尾元素的下标减去首元素的下标然后+1
		InterSort(&a[begin], end - begin + 1);//这里如果是左边的数组则首元素传入的下标是begin但如果传入的是右数组则传入的首元素的下标则是keyi + 1
	}
	else
	{
		int keyi = PartSort3Plus(a, begin, end);
		QuickSort(a, begin, keyi - 1);
		QuickSort(a, keyi + 1, end);//将序列以基准值为界进行分组,然后分别对这些组进行上述操作
	}
}

int main()
{

	int i;
	int* a = numberopen(6000);
	int begin = clock();
	QuickSort2(a,0,5999);
	int end = clock();//计时,判断程序排序耗费了多长时间
	for (i = 0; i < 6000; i++)
		printf("%d ", a[i]);   //输出随机数
	printf("\n\n");
	printf("排序所耗时间为:%d ms\n", end-begin);
}

以上是本期博客的主要内容,本期博客主要介绍了八大排序其中的六大排序的一些基本思想和特点以及相应的代码的实现,建议图文一起理解,感谢大家批评指正,我们下次再见!ヾ( ̄▽ ̄)Bye~Bye~

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值