(2013.03.08) 求最大公约数_3种算法
一。实验题目
求两个自然数m和n的最大公约数。(连续整数检测法,欧几里得算法,质因数分解法)
二。实验目的
1. 复习数据结构课程的相关知识,实现课程间的平滑过渡;
2. 掌握并应用算法的数学分析和后验分析方法;
3. 理解这样一个观点:不同的算法能够解决相同的问题,这些算法的解题思路不同,复杂程度不同,解题效率也不同。
三。实验内容
1. 算法相关代码(伪代码与C++代码)
1.1 连续整数检测法
1. t = min{m, n};
2. m 除以 t,如果余数为0,则执行步骤3,否则,执行第4步;
3. n除以t,如果余数为0,返回t的值作为结果,否则,执行第4步;
4. t = t – 1, 转第2步。
// 连续整数检测
int CommonDivisor::continuousIntCheck()
{
int t = min();
for (int i = t; i > 0; --i){
if(0 == m % i && 0 == n % i){
return i;
}
}
return -1;
}
1. 2 欧几里得算法
1. r = m % n;
2. 循环直到r = 0;
2.1 m = n;
2. 2 n = r;
2.3 r = m % n;
3. 输出n.
// 欧几里得算法(辗转相除法)
int CommonDivisor::euclideanAlgorithm()
{
int divisor = min(); // 除数
int dividend = max(); // 被除数
int mod = dividend % divisor;
while(0 != mod){
dividend = divisor; // 除数作为被除数
divisor = mod; // 余数作为除数
mod = dividend % divisor;
}
if(0 == mod){
return divisor;
}
else{
return -1;
}
}
1.3 质因数分解法
0. 分解质因数
1. 将m分解质因数;
2. 将n分解质因数;
3. 提取m和n中的公共质因数;
4. 将m和n中的公共质因数相乘,乘积作为结果输出
// 分解质因数求最大公约数
int CommonDivisor::breakPrimeFactor()
{
// 获取各质因数
vector<int> nVector = getPrimeFactorSet(m);
vector<int> mVector = getPrimeFactorSet(n);
// 找出公共的质因数并相乘
int result = 1;
unsigned i1 = 0;
unsigned i2 = 0;
while(i1 < nVector.size() && i2 < mVector.size()){
while(nVector[i1] != mVector[i2]){
if(nVector[i1] < mVector[i2]){++i1;}
if(i1 >= nVector.size()){break;}
if(nVector[i1] > mVector[i2]){++i2;}
if(i2 >= mVector.size()){break;}
}
// 越界判断
if(i1 >= nVector.size() || i2 >= mVector.size()){
break;
}
// nVector[i1] == mVector[i2])
result *= nVector[i1];
++i1;
++i2;
}
return result;
}
2. 完整代码:(包含语句执行次数检则方法及时间计算)
/**
* 文件名:CommonDivisor.cpp
* 程序功能:以三种方法求两数的最大公约数,并对三种方法进行算法复杂度计算。
* 编译环境:VS2005,Win7
* 程序结构:包含两个类:
* MyClock:用于记录程序运行所需时间,程序语句运行次数;
* CommonDivisor:包含两个数字及三种求最大公约数的方法。
* 1. 连续整数检测;
* 2. 欧几里得算法;
* 3. 分解质因数方法。
* 作者名:Neicole
* 编写时间:2013.03.05
* 联系方式:http://blog.csdn.net/neicole
**/
#include <iostream>
#include <vector>
#include <cstdlib> // rand()
#include <math.h> // sqrt(double)
#include <fstream> // 读写文件
using namespace std;
#define DEBUGMODE
// 定义宏和全局变量以作算法效率测试
#ifdef DEBUGMODE
#include <ctime> // clock() 函数返回自程序开始运行的处理器时间
class MyClock
{
private:
clock_t startTime; // 记录开始时间
clock_t endTime; // 记录结束时间
long runNum; // 记录运行次数
public:
MyClock::MyClock(){
clear();
}
inline int start(){
startTime = clock();
return startTime;
}
inline int end(){
endTime = clock();
return endTime;
}
inline int getRunTime(){
return (endTime-startTime)/CLOCKS_PER_SEC*1000.0;
}
inline int add(){
++runNum;
return runNum;
}
inline int getRunNum(){
return runNum;
}
void clear(){
runNum = 0;
startTime = 0;
endTime = 0;
}
};
#endif
/**************************** 最大公约数的类声明 **********************************/
class CommonDivisor
{
private:
int m; // 测试数据
int n;
public:
#ifdef DEBUGMODE
MyClock myClock;
#endif
public:
explicit CommonDivisor(int);// 构造函数(指定范围内的随机数据)
CommonDivisor(int, int); // 构造函数(指定数据)
inline int getM(); // 获取m的值
inline int getN(); // 获取n的值
inline int min(); // 求出最小值
inline int max(); // 求出最大值
int continuousIntCheck(); // 连续整数检测
int euclideanAlgorithm(); // 欧几里得算法
bool isPrime(int); // 判断是否质数
vector<int> getPrimeFactorSet(int); // 获取某个数的质因数集
int breakPrimeFactor(); // 分解质因数
};
/************************************ 类定义 **********************************/
// 构造函数(随机数据)
CommonDivisor::CommonDivisor(int maxTestValue)
{
m = rand() % maxTestValue + 1; // 大于等于1,小于 maxTestValue 的随机数
n = rand() % maxTestValue + 1; // 大于等于1,小于 MAX_TEST_VALUE 的随机数
}
// 构造函数(指定数据)
CommonDivisor::CommonDivisor(int m, int n)
{
this -> m = m;
this -> n = n;
}
// 获取m的值
int CommonDivisor::getM()
{
return m;
}
// 获取n的值
int CommonDivisor::getN()
{
return n;
}
// 求出最小值
int CommonDivisor::min()
{
return m < n ? m : n;
}
// 求出最小值
int CommonDivisor::max()
{
return m > n ? m : n;
}
// 连续整数检测
int CommonDivisor::continuousIntCheck()
{
#ifdef DEBUGMODE
myClock.clear();
myClock.start();
myClock.add();
#endif
int t = min();
for (int i = t; i > 0; --i){
#ifdef DEBUGMODE
myClock.add(); // 运行次数加1,用以估计时间复杂性
#endif
if(0 == m % i && 0 == n % i){
#ifdef DEBUGMODE
myClock.end();
#endif
return i;
}
}
#ifdef DEBUGMODE
myClock.end();
#endif
return -1;
}
// 欧几里得算法(辗转相除法)
int CommonDivisor::euclideanAlgorithm()
{
#ifdef DEBUGMODE
myClock.clear();
myClock.start();
myClock.add();
#endif
int divisor = min(); // 除数
int dividend = max(); // 被除数
int mod = dividend % divisor;
while(0 != mod){
dividend = divisor; // 除数作为被除数
divisor = mod; // 余数作为除数
mod = dividend % divisor;
#ifdef DEBUGMODE
myClock.add(); // 运行次数加1,用以估计时间复杂性
#endif
}
#ifdef DEBUGMODE
myClock.end();
#endif
if(0 == mod){
return divisor;
}
else{
return -1;
}
}
// 判断是否质数
bool CommonDivisor::isPrime(int source)
{
#ifdef DEBUGMODE
myClock.add(); // 运行次数加1,用以估计时间复杂性
#endif
if(2 == source || 3 == source){
return true;
}
// 判断大于3的数
int sqrtSou = (int)sqrt((double)source);
for(int i = sqrtSou; i >= 2; --i){
#ifdef DEBUGMODE
myClock.add(); // 运行次数加1,用以估计时间复杂性
#endif
if(0 == source % i){
return false;
}
}
return true;
}
// 获取某个数的质因数集
vector<int> CommonDivisor::getPrimeFactorSet(int source)
{
#ifdef DEBUGMODE
myClock.add(); // 运行次数加1,用以估计时间复杂性
#endif
int compareSize = source;
vector<int> result; // 初始化使元素1于向量组中
bool canDiv = true; // 可被整除
while(canDiv){
bool hasNotDiv = true;;
for(int i = 2; hasNotDiv && i <= compareSize; ++i){
#ifdef DEBUGMODE
myClock.add(); // 运行次数加1,用以估计时间复杂性
#endif
if(isPrime(i) && 0 == source % i){
result.push_back(i);
source = source / i;
hasNotDiv = false;
}
}
if(hasNotDiv){ // 终止条件,已找不出能被整除的数
canDiv = false;
}
}
return result;
}
// 分解质因数求最大公约数
int CommonDivisor::breakPrimeFactor()
{
#ifdef DEBUGMODE
myClock.clear();
myClock.start();
myClock.add();
#endif
// 获取各质因数
vector<int> nVector = getPrimeFactorSet(m);
vector<int> mVector = getPrimeFactorSet(n);
// 找出公共的质因数并相乘
int result = 1;
unsigned i1 = 0;
unsigned i2 = 0;
while(i1 < nVector.size() && i2 < mVector.size()){
while(nVector[i1] != mVector[i2]){
#ifdef DEBUGMODE
myClock.add(); // 运行次数加1,用以估计时间复杂性
#endif
if(nVector[i1] < mVector[i2]){++i1;}
if(i1 >= nVector.size()){break;}
if(nVector[i1] > mVector[i2]){++i2;}
if(i2 >= mVector.size()){break;}
}
// 越界判断
if(i1 >= nVector.size() || i2 >= mVector.size()){
break;
}
// nVector[i1] == mVector[i2])
#ifdef DEBUGMODE
myClock.add(); // 运行次数加1,用以估计时间复杂性
#endif
result *= nVector[i1];
++i1;
++i2;
}
#ifdef DEBUGMODE
myClock.end();
#endif
return result;
}
/************************************ 测试函数 **********************************/
#ifdef DEBUGMODE
// 验证三种算法的结果正确性
int valueCheck()
{
int maxTestValue = 100;
for(int i = 0; i < 30; ++i, maxTestValue += 100){
CommonDivisor testObj(maxTestValue); // 产生两个随机数
int val1 = testObj.euclideanAlgorithm();
int val2 = testObj.continuousIntCheck();
int val3 = testObj.breakPrimeFactor();
cout << "第" << i+1 << "组: "
<< "m: " << testObj.getM() << "\tn: " << testObj.getN() << "\tTestFunction: "
<< val1 << "\t" << val2 << "\t" << val3 << endl;
if(val1 != val2 || val1 != val3){
cout << "请注意,算法结果有误!\n";
return -1;
}
}
cout << "算法结果无误!\n";
return 0;
}
// 记录三种算法的运算时间,语句执行次数
/** 数据记录顺序如下,每个数据间使用空格间开:
随机数最大值 最大公约数 m值 n值
连续整数法执行时间 欧几里得算法执行时间 分解质因数法执行时间
连续整数法语句执行次数 欧几里得算法语句执行次数 分解质因数法语句执行次数
**/
int clockCheck()
{
int maxTestValue = 100;
for(int i = 0; i < 100; ++i, maxTestValue += 30){
CommonDivisor testObj(maxTestValue); // 产生两个随机数
int valM = testObj.getM();
int valN = testObj.getN();
int resVal1 = testObj.continuousIntCheck();
int resValRunTime1 = testObj.myClock.getRunTime();
int resValRunNum1 = testObj.myClock.getRunNum();
int resVal2 = testObj.euclideanAlgorithm();
int resValRunTime2 = testObj.myClock.getRunTime();
int resValRunNum2 = testObj.myClock.getRunNum();
int resVal3 = testObj.breakPrimeFactor();
int resValRunTime3 = testObj.myClock.getRunTime();
int resValRunNum3 = testObj.myClock.getRunNum();
cout << maxTestValue << " " << resVal1 << " " << valM << " " << valN << " "
// 由于在我的电脑上面测试的时间间隔太短(为零),因此这里不做输出与测试
// << resValRunTime1 << " " << resValRunTime2 << " " << resValRunTime3 << " "
<< resValRunNum1 << " " << resValRunNum2 << " " << resValRunNum3 << endl;
ofstream myTestResult("myTestResult.txt", ios_base::out | ios_base::app);
myTestResult << maxTestValue << " " << resVal1 << " " << valM << " " << valN << " "
// 由于在我的电脑上面测试的时间间隔太短(为零),因此这里不做输出与测试
// << resValRunTime1 << " " << resValRunTime2 << " " << resValRunTime3 << " "
<< resValRunNum1 << " " << resValRunNum2 << " " << resValRunNum3 << endl;
myTestResult.close();
}
return 0;
}
#endif
/************************************ 主函数 **********************************/
int main(void)
{
#ifdef DEBUGMODE
// 功能测试函数调用
valueCheck();
cout << endl;
system("pause");
cout << endl;
clockCheck();
#endif
system("pause");
return 0;
}
3. 运行结果:
四。实验结果及简要分析
上图为由本实验所示代码所生成的部分数据,将需要求解的数值的最大值设为100, 130, 160... 每次增加30,这次实验的最大值最大增长至3000,观察分别使用连续整数法,欧几里得算法,分解质因数法求两数的最大公约数的语句执行次数,下面先逐个算法结果绘图观察。
如上图,可更清晰地看出连续整数法求最大公约数的语句执行规律,可以看出,整体而言,求最大公约数所产生随机数的最大值基本上是与语句执行次数成正比的,语句执行次数会随着随机数的最大值的增大而增大,产生随机数最大值为500以内的语句执行次数都不会超过500,而随着随机数的增大,在最大值增长至2600时,语句执行次数已上升至2000次以上,而在随机数值最大值为3000内的数使用连续整数法求最大公约数,语句执行次数未发现超过3000次。
如上图,可更清晰地看出欧几里得算法的语句执行规律,可以看出,整体而言,求最大公约数所产生随机数的最大值基本上是与语句执行次数成正比的,语句执行次数会随着随机数的最大值的增大而增大,但增长幅度还是比较小的,产生随机数最大值为由500增长到3000间,语法执行次数会在12次内。
如上图,可更清晰地看出分解质因数语句执行次数走向,可以看出,求最大公约数所产生随机数的最大值基本上是与语句执行次数成正比的,语句执行次数会随着随机数的最大值的增大而增大,增长幅度还是比较大的,而且语句执行次数相当的大,随机数最大值于2800附近时语句执行次数更是超过了150000次,产生随机数最大值为由3000的语法执行次数会在200000以下。
上图为求最大公约数的算法执行次数走向比较图,横坐标为求最大公约数所随机数的最大值,纵坐标为使用某种算法时语句的执行次数。由图可知,分解质因数法求最大公约数远比连续整数法和分解质因数法的语句执行次数要多,分解质因数法和欧几里得算法语句执行次数相对要少。
而结合以上所有分析图可知,使用欧几里得算法求最大公约数的语句执行数次是相对最少的,在随机数最大值为3000的测试数据中,欧几里得算法的语句执行次数的上限为12,连续整数检测算法上限达到3000,比欧几里得算法多出200倍以上,而分解质因数算法语法执行次数上限竟高达200000,比欧几里得算法多出10000倍以上。
五。参考书籍
《算法设计与分析》 王红梅 编,清华大学出版社