分治法概述

分治法的设计思想

对于一个规模为n的问题:若该问题可以容易地解决(比如说规模n较小)则直接解决,否则将其分解为k个规模娇小的子问题,这鞋子问题互相独立且与原问题形式相同,递归地解这些子问题,然后将各子问题的解合并到原问题的解。
这种算法设计策略叫分治法

适用分治法的问题的特征

(1)该问题的规模缩小到一定的成都就可以容易地解决。
(2)该问题可以分解为若干个规模较小的相同问题。
(3)利用该问题分解出的子问题的解可以合并为该问题的解。
(4)该问题所分解出的各个子问题都是相互独立的,即子问题之间不包含公共的子问题

分治法的求解过程

分治法通常采用递归算法设计技术,在每一层递归上都有3个步骤
1、分解:将原问题分解为若干个规模较小,相互独立,与原问题形式相同的子问题。
2、求解子问题:若子问题规模较小而且容易被解决则直接求解,否则递归地求解各个子问题。
3、合并:将各个子问题的解合并为原问题的解。
在这里插入图片描述
许多问题可以取k=2,成为二分法,这种使子问题规模大致相等的做法是出自一中平衡子问题的思想,它几乎总是比子问题的做好要好。

归并排序

归并排序的基本思想是:首先将a[0,1,…,n-1]看成是n个长度为1的有序表,将相邻的k(k>2)个有序子表成对归并,得到n/k个长度为k的有序子表;然后再将这些有序子表继续归并,得到n/kk个长度为kk的有序子表,如此反复进行下去,最后得到一个长度为n的有序表。
若k=2,即归并在相邻的两个有序子表中进行的,称为二路归并排序。若k>2,即归并操作在相邻的多个有序子表中进行,则叫多路归并排序。

// 二路归并排序算法
#include <stdio.h>
#include <malloc.h>

// 辅助函数, 输出整型数组中所有元素
void disp(int a[], int n)			
{	
	int i;
	for(i = 0; i < n; i ++)
		printf("%d ", a[i]);
	printf("\n");
}

// 将a[low..mid]和a[mid+1..high]两个相邻的有序子序列归并为一个有序子序列a[low..high]
void Merge(int a[],int low,int mid, int high)
{	
	int *tmpa; // 用tmpa所指向的这段内存空间临时保存归并好的序列, 最后拷贝回数组a的对应位置
	int i = low, j = mid + 1, k = 0;		//k是tmpa的下标,i、j分别为两个子表的下标
	
	tmpa = (int *)malloc((high-low+1) * sizeof(int));  //动态分配空间
	while(i <= mid && j <= high)	//在第1子表和第2子表均未扫描完时循环
	{
		// 两个子序列都已经有序
		// 对两个子序列从左到右扫描
		// 每次都挑选最小的那个元素放到tmpa中
		if(a[i] <= a[j])		//将第1子表中的元素放入tmpa中
		{	
			tmpa[k] = a[i];
			i ++;
			k ++; 
		}
		else					//将第2子表中的元素放入tmpa中
		{	
			tmpa[k] = a[j];
			j ++;
			k ++; 
		}
	}
	// 经过上述扫描, 肯定还有一个子序列中还有元素没有放到tempa
	// 下面两个while循环只会有一个执行

	// 如果第一个子序列中还有元素(第二个子序列肯定已扫描完)
	while(i <= mid)			//将第1子表余下部分复制到tmpa
	{	
		tmpa[k]=a[i];
		i ++;
		k ++; 
	}
	// 如果第二个子序列中还有元素(第一个子序列肯定已扫描完)
	while(j <= high)			//将第2子表余下部分复制到tmpa
	{	
		tmpa[k] = a[j];
		j ++;
		k ++;
	}

	for(k = 0, i = low; i <= high; k ++,i ++) 		//将tmpa复制回a中
		a[i] = tmpa[k];
	free(tmpa);						//释放tmpa所占内存空间
}

// 一趟二路归并排序
// a: 待排序的数组
// length: 子序列的长度
// n: 数组a中元素数量
void MergePass(int a[], int length, int n)	
{	
	int i;
	// i = 0时, 归并a[0...length - 1]和a[length...2 * length - 1]
	//     相当于   a[i...i + length - 1]和a[i + length...i + 2 * length - 1]
	//     i = i + 2 * length = 2 * length
	// i = 2 * length时, 归并a[2 * length...3 * length - 1]和a[3 * length...3 * length - 1]
	//	   相当于   a[i...i + length - 1]和a[i + length...i + 2 * length - 1]
	//     i = i + 2 * length = 4 * length
	// ...
	// 所以, 对于每个i值, 
	//		第一个子序列的起始下标为: i
	//		第一个子序列的结束下标为: i + length - 1
	//		第二个子序列的结束下表为: i + 2 * length - 1
	//
	// 此处, 循环条件的判断: i + 2 * length - 1 < n
	// 表示, 对于任何i值, 第二个子序列的下标最多到n - 1(数组最后那个元素的下标)
	//
	// 一旦这个条件不满足, 则可能的情况:
	//		(1) 所有子序列正好合并完, n正好是2*length的整数倍, 循环结束时i的值为n, 无需
	//			继续归并
	//
	//		(2) 数组a中还剩下一个(完整或不完整的)子序列: a[i...n-1], 循环结束时的i值满足
	//			i + length - 1 >= n - 1, 无需继续归并
	//
	//		(3) 数组a中还剩下两个子序列, 第一个子序列完整: a[i...i+length-1], 
	//			第二个子序列不完整: a[i+length...n-1], 循环结束时的i值满足
	//			i + length - 1 < n - 1
	for(i = 0; i + 2 * length - 1 < n; i = i + 2 * length)	//归并length长的两相邻子表
		Merge(a, i, i + length - 1, i + 2 * length - 1);
	
	if(i + length < n)					// 余下两个子表,后者长度小于length
		Merge(a, i, i + length - 1, n - 1);		//归并这两个子表
}


void MergeSort(int a[], int n)			//二路归并算法
{	
	int length;
	// 子序列长度从1开始
	// 每趟归并后, 子序列长度变为原来的2倍
	// 一旦子序列长度length>=n, 说明上一趟归并已经完成排序, 循环结束
	for(length = 1; length < n; length = 2 * length)
		MergePass(a, length, n);

	// length = 2^0, 2^1, ..., 2^k
	// 假设2^(k+1) >= n > 2^k, 则循环了k+1次(从0到k共k+1次)
	// => k + 1 >= logn => k >= logn - 1
	//    k < logn
	// => logn - 1 <= k < logn
	// => logn <= (k+1) < logn + 1
	// 所以循环执行了logn(向上取整)次
	// 每次循环对应的归并过程中, 元素比较次数不超过n-1次
	// 算法复杂度为O(nlogn)
}

int main()
{	
	int n = 10;
	int a[] = {2, 5, 1, 7, 10, 6, 9, 4, 3, 8};
	
	printf("排序前:"); 
	disp(a, n);
	
	MergeSort(a, n);
	
	printf("排序后:"); 
	disp(a, n);

	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

岁月辰星

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

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

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

打赏作者

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

抵扣说明:

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

余额充值