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

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

归并排序

归并排序的思想

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

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

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

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

这是图解

那么下面来写代码

归并函数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赋值也就是这些位置的元素
}

执行结果:

快速排序

快速排序的思想

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

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

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

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

寻找枢轴值的方法

方法一:挖坑法

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

下面是代码实现:

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;

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

代码实现:

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);//递归右半区域
}

下面是运行结果:

方法二:左右指针法

此方法和挖坑法的思路很相似,也是先找到基准值,然后定义一个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所在的位置也就是基准值应该在的位置。画图表示

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

最后交换基准值和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;
}

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

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值