分治法求解排序问题

快速排序

基本思想

在待排序的n个元素中任取一个元素(通常取第一个元素)作为基准,把该元素放入最终位置后,整个数据序列被基准分割成两个子序列,所有小于基准的元素放置在前子序列中,所有大于基准的元素放置在后子序列中,并把基准排在这两个子序列的中间,这个过程称作划分。然后对两个子序列分别重复上述过程,直至每个子序列内只有一个记录或空为止。
在这里插入图片描述

分治策略

① 分解:将原序列a[s…t]分解成两个子序列a[s…i-1]和a[i+1…t],其中i为划分的基准位置。
② 求解子问题:若子序列的长度为0或为1,则它是有序的,直接返回;否则递归地求解各个子问题。
③ 合并:由于整个序列存放在数组中a中,排序过程是就地进行的,合并步骤不需要执行任何操作。

代码

//通过排序其他元素使得划分元素的最后位置确定
int Partition(int a[], int s, int t)//划分算法
{
	int i = s, j = t;
	int tmp = a[s];
	
	while (i != j)//从序列两端向中间进行扫描
	{
		while (j > i&&a[j] >= tmp)//寻找比划分元素小的数
			j--;
		a[i] = a[j];//将小的数放置到划分元素前
		while (i < j&&a[i] <= tmp)//寻找比划分元素大的数
			i++;
		a[j] = a[i];//将大的数放置到划分元素后
	}

	a[i] = tmp;//将划分元素最后的位置确定并替换为相应的划分元素
	return i;//返回划分元素的位置
}

void QuickSort(int a[], int s, int t)
{
	if (s < t)
	{
		int i = Partition(a, s, t);
		QuickSort(a, s, i - 1);//对左子序列递归排序
		QuickSort(a, i + 1, t);//对右子序列递归排序
	}
}

算法分析

快速排序的时间主要耗费在划分操作上,对长度为n的区间进行划分,共需n-1次关键字的比较,时间复杂度为O(n)。
对n个记录进行快速排序的过程构成一棵递归树,在这样的递归树中,每一层至多对n个记录进行划分,所花时间为O(n)。
当初始排序数据正序或反序时,此时的递归树高度为n,快速排序呈现最坏情况,即最坏情况下的时间复杂度为O(n2);
当初始排序数据随机分布,使每次分成的两个子区间中的记录个数大致相等,此时的递归树高度为log2n,快速排序呈现最好情况,即最好情况下的时间复杂度为O(nlog2n)。快速排序算法的平均时间复杂度也是O(nlog2n)。

归并排序

基本思想

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

自底向上的二路归并排序算法

在这里插入图片描述

分治策略

循环log2n次,length依次取1、2、…、log2n。每次执行以下步骤:
① 分解:将原序列分解成length长度的若干子序列。
② 求解子问题:将相邻的两个子序列调用Merge算法合并成一个有序子序列。
③ 合并:由于整个序列存放在数组中a中,排序过程是就地进行的,合并步骤不需要执行任何操作。

代码

void Merge(int a[], int low, int mid, int high)
{
	int *tmpa;//临时数组
	int i = low, j = mid + 1, k = 0;
	tmpa = (int *)malloc((high - low + 1) * sizeof(int));

	while (i <= mid && j <= high)//将两个子表的数据合并到一个数组
	{
		if (a[i] <= a[j])
		{
			tmpa[k] = a[i];
			i++;
			k++;
		}
		else
		{
			tmpa[k] = a[j];
			j++;
			k++;
		}
	}

	//将表中剩余元素添加到临时数组
	while (i <= mid)
	{
		tmpa[k] = a[i];
		i++;
		k++;
	}
	while (j <= high)
	{
		tmpa[k] = a[j];
		j++;
		k++;
	}

	//将临时数组的元素复制到原数组
	for (k = 0, i = low; i <= high; k++, i++)
	{
		a[i] = tmpa[k];
	}
	free(tmpa);
}

void MergePass(int a[], int length, int n)
{
	int i;
	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 - 1 < n)//若有余下的子表则进行归并
		Merge(a, i, i + length - 1, n - 1);
}

void MergeSort(int a[], int n)
{
	int length;
	for (length = 1; length < n; length = 2 * length)
		MergePass(a, length, n);
}

算法分析

对于上述二路归并排序算法,当有n个元素时,需要log2n趟归并,每一趟归并,其元素比较次数不超过n-1,元素移动次数都是n,因此归并排序的时间复杂度为O(nlog2n)。

自顶向下的二路归并排序算法

例如,对于{2,5,1,7,10,6,9,4,3,8}序列,说明其自顶向下的二路归并排序的过程。
在这里插入图片描述

分治策略

① 分解:将序列a[low…high]一分为二,即求mid=(low+high)/2;递归地对两个子序列a[low…mid]和a[mid+1…high]进行继续分解。其终结条件是子序列长度为1(因为一个元素的子表一定是有序表)。
② 求解子问题:排序两个子序列a[low…mid]和a[mid+1…high]。
③ 合并:与分解过程相反,将已排序的两个子序列a[low…mid]和a[mid+1…high]归并为一个有序序列a[low…high]。

代码

void Merge(int a[], int low, int mid, int high)
{
	int *tmpa;//临时数组
	int i = low, j = mid + 1, k = 0;
	tmpa = (int *)malloc((high - low + 1) * sizeof(int));

	while (i <= mid && j <= high)//将两个子表的数据合并到一个数组
	{
		if (a[i] <= a[j])
		{
			tmpa[k] = a[i];
			i++;
			k++;
		}
		else
		{
			tmpa[k] = a[j];
			j++;
			k++;
		}
	}

	//将表中剩余元素添加到临时数组
	while (i <= mid)
	{
		tmpa[k] = a[i];
		i++;
		k++;
	}
	while (j <= high)
	{
		tmpa[k] = a[j];
		j++;
		k++;
	}

	//将临时数组的元素复制到原数组
	for (k = 0, i = low; i <= high; k++, i++)
	{
		a[i] = tmpa[k];
	}
	free(tmpa);
}

void MergeSort(int a[]int low,int high)
//二路归并算法
{   int mid;
  if (low<high)			//子序列有两个或以上元素
  {	mid=(low+high)/2;		//取中间位置
    MergeSort(a,low,mid);	//对a[low..mid]子序列排序
    MergeSort(a,mid+1,high);	//对a[mid+1..high]子序列排序
    Merge(a,low,mid,high);	//将两子序列合并
  }
}

算法分析

设MergeSort(a,0,n-1)算法的执行时间为T(n),显然Merge(a,0,n/2,n-1)的执行时间为O(n),所以得到以下递推式:
在这里插入图片描述
容易推出,T(n)=O(nlog2n)。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值