数据结构与算法:分治法应用总结

本文介绍了分治法的基本概念和步骤,并详细解析了其在归并排序、二分查找、求数值的整数次方及斐波那契数列中的应用。通过对不同算法的对比分析,展示了分治法的优势。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

介绍分治法应用之前首先介绍分治法的概念和步骤:

分治策略:将原问题划分为n个规模较小而结构与原问题相似的子问题;递归地解决这些子问题,然后再合并其结果,就得到原问题的解。

步骤:

1、分解(Divide):将原问题分解成一系列子问题;

2、解决(Conquer):递归地解各子问题。若子问题足够小,则直接求解;

3、合并(Combine):将子问题的结果合并成原问题的解。

下面主要介绍分治法的几种应用:

一、归并排序

归并排序按照上述的步骤可如下操作:

1、分解:将n个元素分成各含n/2个元素的子序列;

2、解决:用归并排序法对两个子序列递归地排序;

3、合并:合并两个已排序的子序列以得到排序结果。

归并排序的具体步骤和实现代码已经在前面的博客中详细介绍

博客地址:http://blog.youkuaiyun.com/dabusideqiang/article/details/23474963


二、二分查找

二分查找法:

在有序表中,把待查找数据值与查找范围的中间元素值进行比较:

1)待查找数据值与中间元素值正好相等,则放回中间元素值的索引;

2)待查找数据值比中间元素值小,则递归查找整个查找范围的前半部分;

3)待查找数据值比中间元素值大,则递归查找整个查找范围的后半部分;

4)如果最后找不到相等的值,则返回错误提示信息。

对应代码如下:

/*****************二分查找*******************/

/**
* BinarySearch函数:在数组a中查找数据e (非递归法)
* @param [in] a:查找数组
* @param [in] e:待查找数据
* @param [in] first:起始位置
* @param [in] last:结束位置
* @return int:待查找数据所在的位置
*/ 

int BinarySearch(int a[], int e, int first, int last)
{
	if (a==NULL || first > last)
	{
		return -1;
	}
	int mid;//中间位置
	while(first <= last)
	{
		mid = (first + last) / 2;
		if (e == a[mid] )
		{
			return mid;
		}
		else if (a[mid] < e)
		{
			first = mid + 1;
		}
		else if (a[mid] > e)
		{
			last = mid - 1;
		}
	}
	return -1;
}

/**
* BinarySearch_Recursion函数:在数组a中查找数据e (递归法)
* @param [in] a:查找数组
* @param [in] e:待查找数据
* @param [in] first:起始位置
* @param [in] last:结束位置
* @return int:待查找数据所在的位置
*/ 
int BinarySearch_Recursion(int a[], int e, int first, int last)
{
	if (a==NULL || first > last)
	{
		return -1;
	}
	int mid = (first + last) / 2;
	if (e == a[mid])
	{
		return mid;
	}
	else if (e < a[mid])
	{
		return BinarySearch_Recursion(a, e, first, mid - 1);
	}
	else if (e > a[mid])
	{
		return BinarySearch_Recursion(a, e, mid + 1, last);
	}
	return -1;
}
二分查找时间复杂度为O(logn)

三、求数值的整数次方(xn)x是浮点数。

1、朴素算法:

针对这个问题要考虑以下几方面:

1)要考虑指数是0或负数的情况

2)要考虑指数为负数且底数是0的情况

3)由于底数是double型,当判断底数是否等于0时,不能直接用==,而是判断它们之差的绝对值是不是在一个很小的范围内。

对应代码如下:

bool g_InvalidInput = false;
bool equal(double num1, double num2);
double PowerWithUnsignedExponent(double base, unsigned int exponent);
/**
* Power函数:求数值的整数次方
* @param [in] base:底数
* @param [in] exponent:指数
* @return double:返回结果
*/ 
double Power(double base, int exponent)
{
    g_InvalidInput = false;
    if(equal(base, 0.0) && exponent < 0)
    {
        g_InvalidInput = true;
        return 0.0;
    }
    unsigned int absExponent = (unsigned int)(exponent);
    if(exponent < 0)
        absExponent = (unsigned int)(-exponent);
    double result = PowerWithUnsignedExponent(base, absExponent);
    if(exponent < 0)
        result = 1.0 / result;
    return result;
}
 
/**
* PowerWithUnsignedExponent函数:求数值的非负整数次方(朴素法)
* @param [in] base:底数
* @param [in] exponent:非负指数
* @return double:返回结果
*/
double PowerWithUnsignedExponent(double base, unsigned int exponent)
{
    double result = 1.0;
    for(int i = 1; i <= exponent; ++i)
        result *= base;
    return result;
}
/**
* equal函数:判断两个浮点数是否相等
* @param [in] num1:浮点数1
* @param [in] num2:浮点数2
* @return bool:返回结果
*/
bool equal(double num1, double num2)
{
    if((num1 - num2 > -0.0000001)
        && (num1 - num2 < 0.0000001))
        return true;
    else
        return false;
}

注:程序中定义了全局变量g_InvalidInput,是为了标识程序是否出错,因为程序出错时返回0,而底数为0时程序正常运行也返回0,为了区分这个,设置一个全局变量g_InvalidInput,当程序出错时设置为true。

朴素算法的复杂度为O(n)

2、分治法

上述算法中需要循环做n-1次乘法。这里我们用分治法可以将n次方分解,比如求x8,我们只需要求x4就可以了,对x4再求平方就可以得到x8

上述表述可由下面的公式表示:


这个公式就可以通过递归实现了,实现代码如下:

/**
* PowerWithUnsignedExponent函数:求数值的非负整数次方(分治法)
* @param [in] base:底数
* @param [in] exponent:非负指数
* @return double:返回结果
*/
double PowerWithUnsignedExponent(double base, unsigned int exponent)
{
    if(exponent == 0)
        return 1;
    if(exponent == 1)
        return base;
    double result = PowerWithUnsignedExponent(base, exponent >> 1);
    result *= result;
    if((exponent & 0x1) == 1)
        result *= base;
    return result;
}
分治法复杂度为O(logn)

四、斐波那契数列(Fibonacci)

Fibonacci数列是一个比较经典的例子,很多书中介绍递归时都用它作为例子。

  • F_0=0
  • F_1=1
  • F_n = F_{n-1}+ F_{n-2}(n≧2)
那么首先我们介绍最为熟悉的递归法

1、递归法

大家对递归法应该都很熟悉,直接上代码:

/****************递归法*******************/
/**
* Fbi1函数:求斐波那契数列
* @param [in] n:第n项
* @return long long:返回结果
*/ 

long long Fbi1(int n) 
{
	if( n < 2 )
		return n == 0 ? 0 : 1;  
	return Fbi1(n - 1) + Fbi1(n - 2);  /* 这里Fbi就是函数自己,等于在调用自己 */
}

我们以求f(10)为例分析求解过程,求f(10),需要求f(9)和f(8),求f(9)需要求f(8)和f(7),这样以此类推,可由下图表示依赖关系。

由图可以发现,递归时会重复计算一些数据,这样会增加复杂度。下面介绍一种迭代法,避免重复计算。

2、迭代法

为了避免重复计算,我们可以从下往上计算,可以将前一次计算的结果保存起来用于下一次计算,如先根据f(0)和f(1)求出f(2),然后利用f(1)和f(2)求出f(3)。以此类推、、、

对应代码如下:

/****************迭代法*******************/
/**
* Fbi2函数:求斐波那契数列
* @param [in] n:第n项
* @return long long:返回结果
*/ 

long long Fbi2(int n) 
{
	if( n < 2 )
		return n == 0 ? 0 : 1;  
	long long fb1=1;
	long long fb2=0;
	long long fbn=0;
	for(int i=2;i<=n;i++)
	{
		fbn=fb1+fb2;
		fb2=fb1;
		fb1=fbn;
	}
	return fbn;
} 
上述的迭代法已经比较高效了,时间复杂度为0(n);下面来介绍今天的主题方法,分治法,时间复杂度达到O(logn),可能比较生僻,当时可以用来理解分治法。

3、基于矩阵的分治法

介绍该方法之前,首先要介绍一个数学公式:

这个公式很容易用归纳法证明,这里就不再证明了。

根据上述公式求f(n)可以转化为求矩阵的n-1次方。求数值的n次方是不是很熟悉,本文的分治法第三种应用就是求数值的整数次方,只不过这里的数值换成了矩阵。具体方法不再赘述。

对应代码如下:

/****************基于矩阵的分治法*******************/
/**
* 矩阵结构体Matrix2By2,用于创建和初始化矩阵
*/ 
struct Matrix2By2
{
	Matrix2By2
		(
		long long m00 = 0, 
		long long m01 = 0, 
		long long m10 = 0, 
		long long m11 = 0
		)
		:m_00(m00), m_01(m01), m_10(m10), m_11(m11) 	{
	}
	long long m_00;
	long long m_01;
	long long m_10;
	long long m_11;
};
/**
* MatrixMultiply函数:矩阵相乘
* @param [in] matrix1:矩阵1
* @param [in] matrix2:矩阵2
* @return Matrix2By2:返回矩阵结果
*/ 
Matrix2By2 MatrixMultiply
(
 const Matrix2By2& matrix1, 
 const Matrix2By2& matrix2
 )
{
	return Matrix2By2(
		matrix1.m_00 * matrix2.m_00 + matrix1.m_01 * matrix2.m_10,
		matrix1.m_00 * matrix2.m_01 + matrix1.m_01 * matrix2.m_11,
		matrix1.m_10 * matrix2.m_00 + matrix1.m_11 * matrix2.m_10,
		matrix1.m_10 * matrix2.m_01 + matrix1.m_11 * matrix2.m_11);
}
/**
* MatrixPower函数:求矩阵的整数次方
* @param [in] n:指数
* @return Matrix2By2:返回矩阵结果
*/ 
Matrix2By2 MatrixPower(unsigned int n)
{
	assert(n > 0);
	Matrix2By2 matrix;
	if(n == 1)
	{
		matrix = Matrix2By2(1, 1, 1, 0);
	}
	else if(n % 2 == 0)//偶数
	{
		matrix = MatrixPower(n / 2);
		matrix = MatrixMultiply(matrix, matrix);
	}
	else if(n % 2 == 1)//奇数
	{
		matrix = MatrixPower((n - 1) / 2);
		matrix = MatrixMultiply(matrix, matrix);
		matrix = MatrixMultiply(matrix, Matrix2By2(1, 1, 1, 0));
	}
	return matrix;
}
/**
* Fbi3函数:求斐波那契数列
* @param [in] n:第n项
* @return long long:返回结果
*/ 
long long Fbi3(unsigned int n)
{
	if( n < 2 )
		return n == 0 ? 0 : 1;  
	Matrix2By2 PowerNMinus2 = MatrixPower(n - 1);
	return PowerNMinus2.m_00;
}
针对上述的三种方法求Fibonacci数列,可以运行试试,会发现递归法速度明显慢于其他两种方法。递归法由于是函数调用自身,而函数调用是有时间和空间的消耗,每一次调用都需要在内存栈中分配空间以保存参数、返回地址及临时变量,而往栈里压入和弹出数据都需要时间。递归本质是将一个问题分解成两个或多个小问题,如果小问题存在重复,那么就会重复计算增加复杂度。第一种递归法就是由于重复计算导致速度很慢。



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值