c语言实现数据结构---排序(下)

快速排序

我们首先来看一组数据
在这里插入图片描述
我们这里的快排的思想就是先找到一个标记点,那么我们这里就将标记点设定为最左边的元素,然后我们再创建两个变量,分别指向最左边,和最右边
在这里插入图片描述
那么我们接下来要做的事情就是先让我们这里的R向左边移动,边移动变查看数据,当遇到比标记的数据小的时候就停下来,那么我们这里就是R走到了5的时候就停下来:
在这里插入图片描述
等R走完之后就开始走这里的L,L是向右出发当遇到比比较的数据大的元素就停下来,那么我们这里的L就是在元素7的下面停了下来:
在这里插入图片描述
然后当两个标记的点都停下来之后我们就交换标记的两个点的值:
在这里插入图片描述
交换完之后我们就继续移动这里的R直到遇到了比标记点小的地方停止,那么我们这里就是在4下面停止:
在这里插入图片描述
再移动这里的L,那么同样的道理他会在9的下面停下来:
在这里插入图片描述
然后我们再交换这里的L和R指向的两个值:
在这里插入图片描述
然后同样的道理我们再走这里R,然后在3的下面就停了下来:
在这里插入图片描述
那么这时按照规律来看的话我们这里就会再移动L,然后遇到比标记点大的值再停下来,但是在这里我们移动的过程中会与R相遇,所以我们这里就会提前截至,这时的L和R都会指向同一个元素
在这里插入图片描述
然后当两个变量都指向同一个元素之后我们就让指向的元素与我们标记的元素交换:
在这里插入图片描述
那么看到这里我们的第一趟排序就结束了,我们可以先将这里的代码写出来,首先我们可以知道的一件事就是我们这里的肯定是一个循环的嵌套内部循环用来找到左边比标记点大的值,和右边比标记点小的值,那么我们这里就采用两个while循环,等这两个循环都找到之后我们就执行一下交换函数

	int right = n - 1;
	int left = 1;
	int flag = 0;
while (a[flag] < a[right]&&(left<right))//从右边找小的
		{
			right--;
		}
		while (a[flag] > a[left]&&(left<right))//从左边找小的
		{
			left++;
		}
		swap(&a[right], &a[left]);

大家注意一下我们这里的循环结束的条件有两个一个是找到了我们想要的值,另外一个就是当我们的left<right的时候就结束循环,好那么这里就是我们的内部的循环,因为我们要交换的数据不止一个,所以我们这里还得在外部加一个while循环,用来不停的执行内部循环,这个外部循环结束的条件就是当left>right的时候我们就结束这个整个循环,因为我们循环结束的时候两个指针都指向了同一个元素,所以我们这里在循环的外部还得将指向的元素与我们标记的元素进行一下交换,那么我们完整的代码就如下:

void part1sort(int* a, int n)
{
	int right = n - 1;
	int left = 1;
	int flag = 0;
	while (left < right)
	{
		while (a[flag] <= a[right]&&(left<right))//从右边找小的
		{
			right--;
		}
		while (a[flag] >= a[left]&&(left<right))//从左边找小的
		{
			left++;
		}
		swap(&a[right], &a[left]);
	}
	swap(&a[right], &a[flag]);
}

我们来测试一下我们这里函数写的是否正确:

int main()
{
	int a[10] = { 6,1,2,7,9,3,4,5,10,8 };
	part1sort(a, sizeof(a) / sizeof(a[0]));
	for (int i = 0; i < sizeof(a) / sizeof(a[0]);i++)
	{
		printf("%d ", a[i]);
	}
	return 0;
}

我们代码运行的结果就如下:
在这里插入图片描述
那么看到这个结果跟我们之前推导的一模一样,那么这就说明我们这里的代码写的是正确的,好看到上面的代码大家有没有想过我们这些操作的目的是什么?如果大家将这里的数组自行地排成有序地话,应该不难发现我们这一步地操作就是将我们这里标志地那个元素放到了他应该存在地位置,这里我将这个数组进行认为地排序大家来看看:
在这里插入图片描述
在这里插入图片描述
大家可以看到我们之前标记地数据就是6,而经过我们这次调整就可以看到我们的6来到了他应该所在的位置,并且在6的左边都是比6小的值,在6的右边都是比6大的值,那么这就是我们第一次循环的结果,但是看到这里大家有没有想过一个问题就是为什么我们最后一次与标记的元素进行交换的时候,我们两个指针指向的元素一定是比标记的元素小啊,大家有没有想过这个问题?那么这个元素可能比标记的元素大吗?答案是不可能的,因为我们这里两个变量的相遇就分为两个情况,第一个就是当left开始移动撞到right停下来,那么这种情况我们可以这么理解,我们的right是找比标记的元素小的位置,而我们的left是找比标记元素大的位置,而且我们这里是right先走,所以当我们的left撞到right的时候我们的lright已经找到了比标记元素小的位置,所以这时候交换元素的话是一定比标记元素小的,那么第二种情况就是当我们的right移动撞到我们的left的时候,这种情况就可以这么理解我们是right先走所以这时候撞到了我们的left说明我们在走的过程中没有找到比标记元素小的位置,但是这里的left已经进行了元素的交换,在上一次的交换过程中我们这里的left已经变成了比标记元素小的元素,所以这时候再与标记元素进行交换也是一定比标记元素小,所以综上所述不会出现什么问题,好既然我们这些问题都解决了,我们就来看看接下来该怎么走,我们这里经过上面的操作将所有比6小的元素放到6的左边将所有比6大的元素放到了6的右边,这样的话我们6的位置就确定了,那我们这里想要整个数组都有序的话我们是不是就只需要让我们这里6的左边的元素都有序,让6的右边的元素都有序,那我们整个数组是不是就有序起来了,那么这时我们的目标的就变成了让下面的两组元素变的有序:
在这里插入图片描述
那么这时我们再对这两个数组进行同样的操作我们是不是又可以再确定两个元素的位置,并且确定这两个元素之后我们的两个数组就变成了4个数组,那么我们要让这两个数组变的有序的话是不是就只用将这里的4个数组变的有序起来就可以了,同样的道理我们再对这4个数组进行如上的操作是不是又可以确定4个元素的位置,那么这样以此类推我们是不是就可以确定所有元素的位置了,那么看了上面的描述想必大家应该知道了我们这里的快速排序的原理和实现方式,我们这里就是通过递归来实现这个功能,那么既然是递归的话我们这里就要有递归结束的条件,我们这里的每次递归的目的就是确定一个元素的位置,那如果我们这里的递归只有一个元素了呢?那是不是一定就确定了不需要再执行其他的操作了,那如果我们这里的递归没有元素了呢?就是传过来的left的值比right还大,那是不是也不需要执行剩下的操作了啊,那么如果上面不用递归的条件都没有满足的话我们这里的就得对我们传过来的范围执行一下上面的part1sort函数,但是我们这里得对这个函数进行一下修改,我们得让这个函数有个返回值,返回他调整后已经确定的那个元素的位置,因为他的位置已经确定了不需要再进行调整了所以我们在下面的递归中就可以依次作为分割点,那么我们这里修改后的函数就如下:

int part1sort(int* a, int left,int right)
{
	int flag = left;
	while (left < right)
	{
		while (a[flag] <= a[right]&&(left<right))//从右边找小的
		{
			right--;
		}
		while (a[flag] >= a[left]&&(left<right))//从左边找小的
		{
			left++;
		}
		swap(&a[right], &a[left]);
	}
	swap(&a[right], &a[flag]);
	return right;
}

那么这个函数就是我们分部函数的一部分,那么我们的主函数就是这样:

void quicksort(int* a, int left, int right)
{
	if (left >= right)
	{
		return;
	}
	int mid=part1sort(a,left,right);
	quicksort(a, left, mid - 1);
	quicksort(a, mid+1, right);
}

那么我们再来看看这个函数是否能将我们这个数组排序正确,我们来看看上面的那个代码的运行结果:
在这里插入图片描述
我们发现我们这里排序的结果就是正确的,但是我们这里的代码并没结束,我们还得对其进行优化。

快排的优化

大家看了上面的代码有没有注意到这个点就是在这里插入图片描述
我们这里找元素的时候只找比目标数组小的元素,不找跟他相等的元素,大家有没有想过为啥?那么我们这里就直说了我们这里就是为了防止我们这里出现了死循环,啊这里会出现死循环可能很多小伙伴们无法理解这里为啥会出现死循环呢?那么有这个问题的小伙伴们可以看看这个数组6 6 6 6 6把这个数组带进去跑一下的话你就会发现这里出现了死循环所以我们这里找元素就只找小不找相等的。另外还有一点就是大家有没有发现我们这里有个非常奇怪的地方就是我们这里选择的标记数据总是在我们这里的最左边,然后拿其他的数据与这个数据与这个元素进行比较最后移动这个数据到其他的位置上去,然后在以这个其他位置为分割点将我们的数组分成两块,再对剩下的两块采用相同的操作,但是这里就有个问题我们这里最好的情况是我们的分割点在我们的数据的中间部位,那要是最坏呢?是不是就是我们数组的最右边或者最左边啊,那这一下子是不是就变慢了很多啊,比如说我们一个有序的数组
在这里插入图片描述
我们这个数组本来就是有序的,那我们还对这个数组进行一下排序呢?那根据我们上面的运算他的分割点是不是就一下子来到了1这里,那我们剩下的数组就是这样
在这里插入图片描述
然后我们又要对这个数组进行同样的操作那么我们这里的分割点找到的地方就又是最左边,那么分割之后我们的数组就成了这样:
在这里插入图片描述
那要是出现这样的情况的话我们这个排序是不是就一下子变的特别的慢了啊,所以我们这里就得采用另一种方法来解决上述的问题,我们这里之所以出现这个问题其根本原因就是我们这里的标记元素取的太具有可能性了,我们总是以原本数组的最左边的元素作为标记元素,这样一遇到有序的数组那就寄了,所以我们这里就取一个三数取中的一个算法,我们将这个数组中的中间值,最右边和最左边的值进行比较,选出其中间值,再将这个值与我们最左边的那个标记元素进行互换这样我们就可以有效的避免这里的偶然性,那么我们这里就得再写出一个函数用来取得这里的中间值那么这个函数就非常的简单我们就不多废话:

int GetMidIndex(int* a, int left, int right)
{
	int mid = left + (right - left) / 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 // a[left] >= a[mid]
	{
		if (a[mid] > a[right])
		{
			return mid;
		}
		else if (a[left] < a[right])
		{
			return left;
		}
		else
		{
			return right;
		}
	}
}

那么我们柘林再对我们的分函数进行一下更改

int part1sort(int* a, int left,int right)
{
	int mid = GetMidIndex(a, left, right);
	swap(&a[left], &a[mid]);
	int flag = left;
	while (left < right)
	{
		while (a[flag] <= a[right]&&(left<right))//从右边找小的
		{
			right--;
		}
		while (a[flag] >= a[left]&&(left<right))//从左边找小的
		{
			left++;
		}
		swap(&a[right], &a[left]);
	}
	swap(&a[right], &a[flag]);
	return right;
}

那么这里就是我们的优化的一部分,我们接下来还要进行一下另一个方面的优化,我们都知道函数的调用其实是非常的消耗性能的我们要不停的开辟函数的栈帧和函数栈帧的销毁,那我们这里是采用递归的方式来实现这个函数,而且我们这个函数还跟二叉树十分的相似,而我们又知道二叉树的最后一层占的整个函数调用的50%,但是这个最后一层却只有一个元素的或者没有元素,而我们这里倒数第二层的函数调用占我们整个调用次数的25%,但是这个倒数第二层所操作的函数就3个到4个,我们倒数第三层的函数调用占我们整个调用次数的12.5%但是他这一层也就只管了我们8个元素,那么这么
看的话大家发现了一个问题没有就是我们最后几层的函数调用操作的数据非常的少,但是他要调用的函数次数却非常的多,而多次的调用函数又会导致函数性能的下降,那么这里就是我们需要优化的地方,我们这里就采用这种方式来进行优化,当我们的end-begin小于8的时候我们就不在使用快速排序来我们采用其他的函数来帮我们完成这个任务,那我们这里使用啥呢?用堆排序吗?但是堆排序的实现起来太复杂了不好实现,选择排序又太慢了,希尔排序得数据特别多才能体现出来优势,冒泡排序提前结束的条件太苛刻了不然跑起来也慢,那么综上所述我们就只好选择插入排序来帮我们完成任务,那么我们修改之后的代码就如下:

void quicksort(int* a, int left, int right)
{
	if (left >= right)
	{
		return;
	}
	if (right - left <= 8)
	{
		Insertsort(a + left, right - left + 1);
	}
	else
	{
		int mid = part1sort(a, left, right);
		quicksort(a, left, mid - 1);
		quicksort(a, mid + 1, right);
	}
}

那么我们这里的优化就结束了,我们接下来来看看我们这里快速排序的改进性。

挖坑法实现快排

我们之前调整的方法是两个变量一个找比标记元素大的值,一个变量找比标记元素小的值,找到之后再将两个元素进行交换,那么我们这里就可以对其进行一下修改改成挖坑法,那么这里挖坑法就跟我们的名字所所述挖个坑嘛,那么我们这里还是来结合图来进行讲解,首先我们的数据是这样:
在这里插入图片描述
我们还是创建两个变量用来指向我们这里的最左边和最右边,然后我们再创建一个变量用来记录我们这里的标记元素,然后我们右边的变量就开始往左走开始寻找比标记元素小的值,那么这里我们就来到了5下面
在这里插入图片描述
然后我们接下来的操作就是将这里right指向的5放到left指向的6里面去
在这里插入图片描述
那么这时我们的right指向的就是一个坑了,我们再来移动我们这里的left往右找比标记函数大的值,那么这里我们就来到了7的下面,并且将这个7放到我们right指向的坑里面,那么这里我们的图就成了这样:
在这里插入图片描述
我们的left走完了之后就轮到我们的right走,我们的right就会来到4的下面并且将这个4放到left指向的坑里面:
在这里插入图片描述
那么同样的道理我们的left就来到了9的下面并且将放到right的坑里面
在这里插入图片描述
然后再移动right来到了3下面并且移动3:

那么我们这里再走left这时我们再走就会发现我们这里的两个变量相遇了。那么这里的相遇肯定就是一个坑的位置,那么我们这里就将我们之前创建的tem变量放到我们这里的两个变量指向的同一个的坑位里面:
在这里插入图片描述

那么这里就是我们挖坑法的全部过程,而且我们发现我们这里也是和我们上面的那个过程达到了同样的目的,那么我们这里就来想一下我们这里的代码应该如何来写,首先我们知道的一点就是我们这里肯定是一个循环的嵌套,我们内部循环干的事情就是找到比他小的值和找到比他大的值跟我们上面的代码一样,但是我们这里有个不同的地方就是我们这里找到最小值和最大值之后我们不是将这里的最大值和最小值进行交换而是将这里的值与坑里面的值进行交换,那么我们这里就来创建一个变量用来记录这里的坑,然后每一次交换这里的值就改变这里坑的位置,然后我们再加一个外部的循环用于多次的交换,最后再将我们的tem的值填入坑中,那么我们这里的代码就如下:

int part2sort(int* a, int left, int right)
{
	int mid = GetMidIndex(a, left, right);
	swap(&a[left], &a[mid]);
	int tem = a[left];
	int hole = left;
	while (left < right)
	{
		while (tem <= a[right] && (left < right))//从右边找小的
		{
			right--;
		}
		swap(&a[hole],  &a[right]);
		hole = right;
		while (tem >= a[left] && (left < right))//从左边找小的
		{
			left++;
		}
		swap(&a[hole] , &a[left]);
		hole = left;
	}
	swap(&a[hole], &tem);
	return left;
	
}

我们将这个分函数放到我们的主函数里面来测试一下代码:
在这里插入图片描述
我们发现这个代码的实现确实是真确的,那么我们这里就接着来看下一个实现方法:前后指针法。

前后指针法实现快排

那么我们这个方法的实现过程就是创建两个指针一个prev一个cur,同样再找到一个标记元素key,那么这两个指针一开始都指向我们这里的数组的最左边,然后我们通过循环不停的让cur往后走,如果在走的过程中遇到了比key小的值我们就让prev++再将两个值进行一下交换,当没有遇到比key小的值的时候我们就让cur的值不停的往后走,这时我们的cur和prev之间都是比key大的值,而我们这里的交换就是让小的值放到前面讲大的值放到后面,而当我们循环结束之后我们就将key的值与prev的值进行交换这样我们的key的值就来到了正确的位置,那么看到了我们上面的描述大家是不是感觉我们这种方法特别的好理解那么我们的代码也很好写,我们的代码就如下:

int PartSort3(int* a, int left, int right)
{
	// 三数取中
	int mid = GetMidIndex(a, left, right);
	Swap(&a[left], &a[mid]);

	int keyi = left;
	int prev = left;
	int cur = left + 1;
	while (cur <= right)
	{
		// 找小
		if (a[cur] < a[keyi] && ++prev != cur)//避免对相同的值进行交换
			Swap(&a[cur], &a[prev]);

		++cur;
	}

	Swap(&a[keyi], &a[prev]);

	return prev;
}

我们测试一下就可以看到我们这里的函数实现是正确的:
在这里插入图片描述

将快排改成非递归的形式

那么我们上面是以递归的形式来实现的快排,但是我们的递归是有个坏处的就是当我们递归的程度过深的话我们这里会导致栈溢出的现象,所以我们这里就对其做出改进将我们这里的递归改成非递归的形式,那么我们这里如何来进行改进呢?我们大家仔细的观察一下我们这里的代码

void quicksort(int* a, int left, int right)
{
	if (left >= right)
	{
		return;
	}
	int mid=part1sort(a,left,right);
	quicksort(a, left, mid - 1);
	quicksort(a, mid+1, right);
}

我们这是通过递归的方式来实现我们这里的不断地分组,但是我发现如果是递归地话我们这里好像每次都是先调用这个函数来进行递归quicksort(a, left, mid - 1);那这不就好比我们的二叉树总是先遍历左子树等左子树全部都遍历完之后再遍历右子树是一样的嘛,比如说我们这里数组的下标是1~10,那么经历这第一次函数的调用我们开始递归到1~5,然后在我们的1~5的函数里面我们又开始调用1~2,在1~2里面就开始调用1~1,又因为我们这里就一个元素不用再进行操作所以我们这里就可以结束左子树来到我们这里的右边就是2~2,因为这就是一个元素所以我们的右子树就不用进行其他的操作,然后我们就回到1~5的右边就是3~5,那么就是这么依次类推,但是这样推理的话我们会发现一个特点就是我们这里总先对左边的数据进行操作等所有左边的数据都操作完之后我们才对右边的数据进行操作,而且这里的对右边的数据进行操作并不是从一开始的右边进行操作,而是对递归的最深处的那个左边的右边进行操作,然后慢慢的一层一层的回到最上面的右边再递归这个右边的左子树,那么我们这里大家想一下谁可以做到这一点,是不是我们这里的栈啊,我们栈有个特点就是后进先出,我们可以每次入两个元素,再出一个元素,并且我们是先入右边再入左边这样的话,这样我们每次出的话是不是就是出的左边等我们的左边都出完了就轮到我们这里的右边,那么我们这里的代码就是这样:

void QuickSortNonR(int* a, int begin, int end)
{
	ST st;
	StackInit(&st);
	StackPush(&st, begin);
	StackPush(&st, end);

	while (!StackEmpty(&st))
	{
		int right = StackTop(&st);
		StackPop(&st);

		int left = StackTop(&st);
		StackPop(&st);

		/*if (left >= right)
		{
			continue;
		}*/

		int keyi = PartSort3(a, left, right);
		// [left, keyi-1] keyi [keyi+1,right]

		if (keyi + 1 < right)
		{
			StackPush(&st, keyi + 1);
			StackPush(&st, right);
		}
	
		if (left < keyi- 1)
		{
			StackPush(&st, left);
			StackPush(&st, keyi - 1);
		}
	}

	StackDestroy(&st);
}

归并排序

我们这里还有一个非常重要的排序就是归并排序,这个排序我们如何来理解呢?我们这里就可以来看看下面的这张图片:

在这里插入图片描述
这是我们这里的数据,然后我们首先要做的第一步就是对这个数据进行拆分,我们将这里的数据拆分成两组:
在这里插入图片描述
但是这里拆分完之后还得对其进行拆分:

在这里插入图片描述
因为这里的每个数组还是有两个元素,所以我们这里还得对其进行拆分:
在这里插入图片描述
那么拆到这里我们的拆分过程就结束了,我们将这里的整个数组拆分成一个个元素,那么接下来我们要干的事情就是将这里的一个个元素全部都合并起来,但是这里的合并并不是简单的合并,我们还得按照一定的顺序进行归并,我们这里是按照一半的数据进行拆分所以我们这里归并的时候就得按照同样的数据进行归并,所以我们这里第一步的归并就让这里的每两个元素都归并到了一起,并且这里的四个数组还都是有序的:
在这里插入图片描述那么我们这里的归并还并没有结束,我们继续归并并且使得归并之后的元素有序起来:
在这里插入图片描述
那么归到这里我们这里就只剩下两个数组了,那么我们这里就再执行一下最后依次归并再让这里的数组变的有序起来那么我们这里的图就成了这样:
在这里插入图片描述
那么这就是我们完整的归并排序的过程,那么我们这里要做的就是两件事情,第一个就是拆分第二件就是归并,那么我们这里的拆分就可以采用递归来实现,我们创建一个变量叫mid这个变量就是记录数组中间的位置的值,然后我们再通过这里的递归来不停的缩小我们这里的区间,然后还有一点的就是我们这里的归并是要改变这里的原数组的值的,所以我们这里得提前创建一个数组用来记录我们这里有序后的数组然后将这个数组的值拷贝到我们的原数组里面去,那么我们这里就创建一个主函数和一个分函数,主函数创建一个数组,分函数就使用这里的排序,那么我们这里的代码就是这样的:

void _MergeSort(int* a, int begin, int end, int* tem)
{
	if (begin > end)
	{
		return;
	}
	int mid = begin + (end - begin) / 2;
	_MergeSort(a, begin, mid, tem);
	_MergeSort(a, mid, end, tem);
}
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;
}

那么这就是我们拆分的过程,那么接下来我们要做的就是合并,因为我们这里拆分的时候有两个数组,所以我们这里就就创建两个变量用来记录开始的位置,再创建两个变量来记录我们这里结束的位置,因为经历了上面的两个递归我们这里已经将数组中的数据分成了两个数组,所以我们这里就创建的变量就如下:

	int begin1 = begin;
	int begin2 = mid+1;
	int end1 = mid;
	int end2 = end;

既然变量创建完了之后我们接下来要干的事情就是比较两个数组中的元素,然后按照升序的规律将这些数据放到我们的tem数组里面,那么我们这里就用while循环来干这件事,因为我们这里要方便以后的数据拷贝我们这里就再创建的一个变量i将这个i的值初始化为begin,然后我们每插入一个数据都将这里的i的值加加,因为我们这里是两个数组我们不知道到底是哪个数组先结束的,所以我们这里在循环的外面就得再来判断一下哪个数组还没有拷贝完并且将剩下的元素拷贝到外面的目的数组里面去,最后再将目的数组拷贝回原数组,那么我们这里的代码就如下:

while (begin1 <= end1 && begin2 <= end2)
	{
		if (a[begin1] <= a[begin2])
		{
			tmp[i++] = a[begin1++];
		}
		else
		{
			tmp[i++] = a[begin2++];
		}
	}

	while (begin1 <= end1)
	{
		tmp[i++] = a[begin1++];
	}

	while (begin2 <= end2)
	{
		tmp[i++] = a[begin2++];
	}

	// 拷贝回原数组 -- 归并哪部分就拷贝哪部分回去
	memcpy(a+begin, tmp+begin, (end-begin+1)*sizeof(int));

那么我们这里完整的代码就如下:

void _MergeSort(int* a, int begin, int end, int* tem)
{
	if (begin > end)
	{
		return;
	}
	int mid = begin + (end - begin) / 2;
	_MergeSort(a, begin, mid, tem);
	_MergeSort(a, mid, end, tem);
	int begin1 = begin;
	int begin2 = mid+1;
	int end1 = mid;
	int end2 = end;
	int i = begin;
	while (begin1 <= end1 && begin2 <= end2)
	{
		if (a[begin1] <= a[begin2])
		{
			tem[i++] = a[begin1++];
		}
		else
		{
			tem[i++] = a[begin2++];
		}
	}

	while (begin1 <= end1)
	{
		tem[i++] = a[begin1++];
	}

	while (begin2 <= end2)
	{
		tem[i++] = a[begin2++];
	}

	// 拷贝回原数组 -- 归并哪部分就拷贝哪部分回去
	memcpy(a + begin, tem + 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, 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;
	while (gap < n)
	{
		// gap个数据  gap个数据归并
		for (int j = 0; j < n; j += 2 * gap)
		{
			// 归并 取小的尾插
			int begin1 = j, end1 = j + gap - 1;
			int begin2 = j + gap, end2 = j + 2 * gap - 1;

			// 第一组越界
			if (end1 >= n)
			{
				printf("[%d,%d]", begin1, n-1);
				break;
			}

			// 第二组全部越界
			if (begin2 >= n)
			{
				printf("[%d,%d]", begin1, end1);
				break;
			}

			// 第二组部分越界
			if (end2 >= n)
			{
				// 修正一下end2,继续归并
				end2 = n - 1;
			}

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

			int i = j;
			while (begin1 <= end1 && begin2 <= end2)
			{
				if (a[begin1] <= a[begin2])
				{
					tmp[i++] = a[begin1++];
				}
				else
				{
					tmp[i++] = a[begin2++];
				}
			}

			while (begin1 <= end1)
			{
				tmp[i++] = a[begin1++];
			}

			while (begin2 <= end2)
			{
				tmp[i++] = a[begin2++];
			}

			// 拷贝回原数组 -- 归并哪部分就拷贝哪部分回去
			memcpy(a+j, tmp+j, (end2-j+1)*sizeof(int));
		}

		gap *= 2;
		printf("\n");
	}

	free(tmp);
	tmp = NULL;
}

哥们写不动了,先发布以后再补。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

叶超凡

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值