首页 > 其他分享 >归并和快速排序的递归实现

归并和快速排序的递归实现

时间:2023-06-27 20:31:58浏览次数:22  
标签:begin 归并 right end 递归 int arr 排序 left

最近学习了归并排序和快速排序,在这里写一篇博客用于复习并且检验自己是否有遗漏知识点的情况。

归并排序

归并排序的思想

归并排序使用的思想为分治法。分治思想分为两部分第一部分为:分解,第二部分为合并。

首先,将待排序的序列分成若干个子序列,每个子序列都是有序的。然后,再将这些有序的子序列合并成一个大的有序序列。其中合并过程是重点,需要使用额外的空间。合并过程可以借助两个指针和一个辅助数组,将两个有序的子序列合并成一个有序的序列。

归并排序的基本思想是,在进行排序的过程中,先将数据分成两个部分,然后对这两部分分别进行排序,最后将这两个已经有序的部分合并成一个有序的整体。这种分治思想的好处是,可以将一个大的复杂问题分解成多个小的简单问题来解决,使得算法的实现更加容易,也更加高效。

如果这里有一个无序的数组那么归并排序首先就会将整个数组分成一个一个单独的有序序列,然后将这些有序序列合并到一起,这也就是归并排序的大体思路。

这是图解

归并和快速排序的递归实现_归并排序

那么下面来写代码

归并函数1:分割

首先归并排序要完成的函数有两个函数1的功能为不断地分割元素,函数2的功能为合并有序的区间

//函数1:通过不断二分的方法将数组中的元素分为有序的序列
void Merge(int* arr, int* tmp, int begin, int end)
{
	if (begin < end)//确定递归的出口,当begin和end相等时不再细分
	{
		int mid = (begin + end) >> 1;
		Merge(arr, tmp, begin, mid);//分割左半区域
		Merge(arr, tmp, mid + 1, end);//分割右半区域
		Mergesort(arr, tmp, begin,mid, end);//合并左右区域
	}
}

归并函数二:合并

void Mergesort(int* arr, int* tmp, int begin, int mid, int end)
{
	int k = begin;//确定临时数组的开始下标
	//首先确定左半区域的开始坐标
	int leftbegin = begin;
	//确定右半区域的开始坐标
	int rightbegin = mid + 1;
	while (leftbegin <= mid && rightbegin <= end)//当左半部分和右半部分都还存在元素的时候
	{
		if (arr[leftbegin] < arr[rightbegin])
		{
			tmp[k++] = arr[leftbegin++];
		}
		else
		{
			tmp[k++] = arr[rightbegin++];
		}
	}//到这里还需要考虑一种特殊的情况也就是当左半区域没有元素了,或是右半区域没有元素了
	//但是另外一个区域还有元素,就需要将另一个区域的元素按顺序移动到tmp数组中
	//因为此时的左半和右半区域已经是有序的了
	while (leftbegin <= mid)
	{
		tmp[k++] = arr[leftbegin++];
	}
	while (rightbegin <= end)
	{
		tmp[k++] = arr[rightbegin++];
	}
	//最后将tmp数组中的元素重新赋值给arr数组
	for (int i = begin; i <= end; i++)
	{
		arr[i] = tmp[i];
	}//因为合并的就是从begin到end区域的元素,所以arr赋值也就是这些位置的元素
}

执行结果:

归并和快速排序的递归实现_数组_02

快速排序

快速排序的思想

快速排序(QuickSort)是一种高效的排序算法,基于分治的思想。具体实现思路如下:

  1. 选择一个基准元素,将待排序序列分成两部分,使基准元素左边的所有元素都比基准元素小,右边的所有元素都比基准元素大。
  2. 对左右两部分递归地进行快速排序,直到序列只剩一个元素或为空。

在实现上,一般选择待排序序列的第一个元素作为基准元素,通常称之为“枢轴值”(pivot)。然后从序列的两端开始向中间扫描,找到一个比枢轴值大的元素和一个比枢轴值小的元素,将它们交换位置。不断重复这个过程,直到两个指针相遇,此时将枢轴值和相遇的位置的元素进行交换。这个过程称为一次划分(Partition),划分后基准元素在序列中的最终位置也就确定下来了。然后对划分出来的左右子序列进行递归处理,直到整个序列有序。

而寻找枢轴值的方法也有三种。

寻找枢轴值的方法

方法一:挖坑法

挖坑法首先就要确定一个key值,一般是拿无序数组的第一个数当作key值,然后定义一个begin等于数组第一个元素的下标,一个end等于数组最后一个元素的下标,再定义一个pivot当作坑所在的下标,默认key所在的位置也就是第一个坑。然后从右端开始从右往左寻找小于key的值,找到之后赋值给坑,然后这个右端end的位置也就成为了新坑,再从左往右寻找大于key的值,找到之后赋值给新坑,然后这个位置再次新成新的坑。最后当begin和end相等的时候这个位置也就是key的位置,那么此时key的左端全部都是小于它的元素,右端全部是大于key的元素,这样key就待在了正确的位置。

归并和快速排序的递归实现_数组_03

下面是代码实现:

int key = arr[begin];
	int left = begin;
	int right = end;
	int pivot = begin;//确定坑所在的位置
	//左半区域存放的是小于key的值,右半区域存放的是大于key的值
	while (left < right)
	{
		while (left<right && arr[right]>= key)
		{
			right--;
		}//当出循环的时候也就意味着在右半区域找到了小于key的值
		arr[pivot] = arr[right];//使用这个值填到这个坑里
		pivot = right;
		while (left < right && arr[left] <= key)
		{
			left++;
		}
		arr[pivot] = arr[left];//再次填坑
		pivot = left;//出现新的坑
	}//到这里此时left指向的位置也就是key所应该在的位置
	arr[left] = key;

那么接下来要怎么让整个数组都变成有序呢?那么就将左半部分再次使用挖坑法,让左半的有一个元素变得有序,最后和归并一样让整个数组变得有序。下面便是图解。

归并和快速排序的递归实现_归并排序_04

代码实现:

int Get_Mid(int* arr, int left, int right)//三数取中法优化选取过程
{
	int mid = (left + right) >> 1;//找到中间下标
	if (arr[mid] > arr[left])//如果中间值大于左端值
	{
		if (arr[mid] < arr[right])//如果在中间值大于左端值的情况下,中间值又小于了右端值
    //那么中间值也就是大小为中
		{
			return mid;//返回下标
		}
		else if (arr[left] > arr[right])//如果mid大于right那么mid也就是最大的值,下面只需要在
        //left和right中寻找一个较大的值,返回的下标也就是中间的下标了
		{
			return right;
		}
		else
			return left;
	}
	else//如果mid<left
	{
		if (arr[mid] > arr[right])//mid小于了left又大于了right
		{
			return mid;//返回mid下标
		}
		else if (arr[left] < arr[right])//在这里mid也就是最小值
        //下面只需要取left和right中较小的值也就可以得到
        //中间下标了
		{
			return left;
		}
		else
			return right;
	}
}
int partsort1(int* arr, int begin, int end)//使用挖坑法来实现快排的第一次排序
{
	int index = Get_Mid(arr,begin,end);//确定key值,这里的key为下标
	//这里确定就可以使用三数取中法去确定
	swap(&arr[index], &arr[begin]);//这里让首元素和找到的中间值交换
  //让坑位置依旧默认在首位
	int pivot = begin;//确定第一个坑的位置
	int key = arr[begin];//因为begin和index所在位置的元素交换了所有这里要使用begin所在的元素为key
	while (begin < end)
	{
		while (begin < end && arr[end] >= key)
		{
			end--;
		}
		arr[pivot] = arr[end];
		pivot = end;
		while (begin < end && arr[begin] <= key)
		{
			begin++;
		}
		arr[pivot] = arr[begin];
		pivot = begin;
	}//这里的循环不能修改,即必须先去右侧寻找大于key的数,并进行填坑
	pivot = begin;
	arr[pivot] = key;
	return pivot;//这里返回的就是分割线元素所在的位置
}
void quicksort(int* arr, int begin, int end)
{
	if (begin >= end)
		return;
	int mid = partsort1(arr, begin, end);//算出分割元素
	quicksort(arr, begin, mid - 1);//递归左半区域
	quicksort(arr, mid + 1, end);//递归右半区域
}

下面是运行结果:

归并和快速排序的递归实现_快速排序_05

方法二:左右指针法

此方法和挖坑法的思路很相似,也是先找到基准值,然后定义一个right,一个left,让right从右端开始寻找小于right的值,然后让right停止在小于基准值的那个位置,再让left从左端开始从左往右寻找大于基准值的位置,再让left和right所在的元素交换,这样不断继续,直到最后当left和right指向同一个位置的时候,让left或是right和基准值交换,这样也就完成了基准值左端为小于它的元素,右端为大于它的元素。下面是代码实现

int partsort2(int* arr, int left, int right)//快速排序使用左右指针法完成第一躺排序
{
	int begin = left;
	int end = right;
	//首先先确定key
	int index = Get_Mid(arr, left, right);
	swap(&arr[begin], &arr[index]);
	int keyi = begin;
	while (begin < end)
	{
		while (begin < end && arr[end] >= arr[keyi])
		{
			end--;
		}
		while (begin < end && arr[begin] <= arr[keyi])
		{
			begin++;
		}
		swap(&arr[begin], &arr[end]);
	}
	//内部的循环必须先从右边部分找寻
	// 再去左边部分找寻
	//当begin和end指向同一个位置的时候这个位置也就是key所应该在的位置
	swap(&arr[keyi], &arr[begin]);
	return begin;//返回分割元素的下标
}
void quicksort(int* arr, int begin, int end)
{
	if (begin >= end)
		return;
	int mid = partsort2(arr, begin, end);//算出分割元素
	quicksort(arr, begin, mid - 1);
	quicksort(arr, mid + 1, end);
}

方法三:前后指针法

首先依旧是找到一个基准值

使用一个prev,一个cur,让cur等于left(最左端的下标)prev等于left+1,然后prev往右,当遇到小于基准值的元素时,先让cur向前进一位,再让cur和prev所在的元素交换,直到最后prev超出范围的时候cur所在的位置也就是基准值应该在的位置。画图表示

归并和快速排序的递归实现_数组_06

这里cur遇到的值为1的时候先让prev加1让其指向了8,然后交换这两个元素。按照这个规则不断进行下去直到最后当cur超出范围之后就会出现下面的情况

归并和快速排序的递归实现_快速排序_07

最后交换基准值和prev也就完成了第一躺快排,之后思路同上。

代码实现:

int partsort3(int* arr, int left, int right)//使用前后指针的方法去解决快速排序的第一躺排序
{
	int index = Get_Mid(arr, left, right);
	swap(&arr[left], &arr[index]);//先将中间数交换到数组首位
	int keyi = left;
	int prev = left;//前指针用于寻找小于key的值
	int cur = left+1;
	while (cur <= right)
	{
		if (arr[cur] < arr[keyi])
		{
			//先让prev加加
			//再将prev和cur所在地位置进行交换
			//当然是在prev和cur指向地不是同一个位置地情况下
			++prev;
			if (prev != cur)
			{
				swap(&arr[prev], &arr[cur]);
			}
		}
		cur++;
	}//循环结束的时候,prev指向的就是key元素的位置
	swap(&arr[keyi], &arr[prev]);
	return prev;
}

希望这篇文章对你能有所帮助。

标签:begin,归并,right,end,递归,int,arr,排序,left
From: https://blog.51cto.com/u_15838996/6565514

相关文章

  • 30.快速排序
    算法思想时这样的:1.每次选取第一个数为基准数;2.然后使用“乾坤挪移大法”将大于和小于基准的元素分别放置于基准数两边;3.继续分别对基准数两侧未排序的数据使用分治法进行细分处理,直至整个序列有序。对于下面待排序的数组:第一步:先选择第一个数163为基准数,以163为基准将小......
  • Python 选择排序
    思路:首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾重复第二步,直到所有元素均排序完毕 Code:1defselectSort(arr):2foriinrange(0,len(arr)):#i表示多少......
  • 如何实现递归的
    在Java中,递归是一种通过方法调用自身来解决问题的编程技巧。实现递归需要满足两个条件:定义基本情况:在递归方法内部,需要定义一个或多个基本情况,当满足基本情况时,递归将停止,并返回结果。调用自身:递归方法内部需要调用自身,传递参数,以解决规模更小的相同问题。下面是一个简单的示......
  • 如何实现递归的
    在Java中,递归是一种通过方法调用自身来解决问题的编程技巧。实现递归需要满足两个条件:定义基本情况:在递归方法内部,需要定义一个或多个基本情况,当满足基本情况时,递归将停止,并返回结果。调用自身:递归方法内部需要调用自身,传递参数,以解决规模更小的相同问题。下面是一个简单的示......
  • List排序
    List排序//按照某个字段进行正序排序list.sort((x,y)->Integer.compare(Integer.valueOf(x.getCourseDuration()),Integer.valueOf(y.getCourseDuration())));//按照某个字段进行倒序排序list.sort((x,y)->Integer.compare(Integer.valueOf(y.getCourseDuration()),Integer.......
  • 【算法】根据整数数组,生成正的素因子二位数组,并排序
    给定一个正整数或负整数的数组,I=[i1,..,in] 生成一个形式为的排序数组P [[p,I数组的所有ij的和,其中p是ij的素因子(p为正)]…]P将按素数的递增顺序进行排序。 示例:I={12,15};//结果=“(212)(327)(515)”[2,3,5]是I的元素的所有素因子的列表,因此是结果。 注意事项: 如果某些数字为......
  • 29.归并排序
    研究了这么多算法以后,小桂子颇有收获,基本自认为排序算法已经全部掌握,于是就想卖弄一下自己的“算法内功”,另一方面为了交流推广,把这些算法传播出去,就召开一个全国算法大赛,集思广益,征集更牛逼的算法!在算法大赛上,有两位白发葱葱的老者提出的算法让小桂子自惭形秽,感叹良多。。。其......
  • 递归,计算器
    递归递归A方法调用B方法,我们很容易理解!递归就是:A方法调用A方法!就是自己调用自己利用递归可以用简单的程序来解决一些复杂的问题。它通常把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解,递归策略只需少量的程序就可描述出解题过程所需要的多次......
  • java递归,数组
    递归在Java中,递归是一种方法或函数调用自身的技术。使用递归可以解决那些可以被分解成相同问题的子问题的情况。以下是有关使用递归的一些基本信息:递归的基本原理:找到问题的基本情况(递归终止条件)。找到问题的规模减少的方式,将其转化为更小的子问题。通过调用自身来解......
  • 34. 在排序数组中查找元素的第一个和最后一个位置
    给你一个按照非递减顺序排列的整数数组nums,和一个目标值target。请你找出给定目标值在数组中的开始位置和结束位置。如果数组中不存在目标值target,返回[-1,-1]。你必须设计并实现时间复杂度为O(logn)的算法解决此问题。示例1:输入:nums=[5,7,7,8,8,10],target=......