趣味数学和C++
撰文/Zeeshan Amjad 翻译/周翔
原文链接:http://www.codeproject.com/cpp/CPPMathFun.asp
引子
有些人认为自己已经厌倦了数学,因为人们需要灵活的大脑才能领会一些数学问题。但对于每个人来说,并不是数学的每个领域都是那么麻烦和令人厌倦的,即使对数学基础不好的人也是如此。数学的其中一个领域――数论,对数学学的不多的普通人来说,也许会有很大的吸引力。
数论是研究整数性质的一个学科,这也许是数学中的最古老的学科,它被具有“数学王子”之称的数学家高斯称为“数学中的女王”,因为很多数学领域都是为了解决数论上的问题而出现的。实际上,学习数论并不需要有专业的数学基础。你可能会对数学的各个领域都比较了解,如果那样的话,你会发现数论的精彩。数论被认为是“纯粹的数学”,因为在过去的日子里,人们无法将数论应用在除数学之外的其它领域。但是在现在的实际生活中,我们可以看到对数论的很多应用,比如密码的编解码、随机数产生器、错误检查、信息安全等领域,我们都可以看到数论的影子。直至今日,数论仍然是一门令人着迷的数学,因为在这个领域中有很多未解之迷等待着人们的解决。或许,即使随着数学理论研究的发展和相关技术的进步,人们也无法找到这些未解之迷的答案。
对数论的所有研究都是为了漂亮地证明各种数学问题,或者通过已知的基本定理解决更加复杂的问题。有意思的是,即使在数论上你未能证明一个命题是正确的,你仍然可以在实际生活中去应用它。举个例子,大家所熟知的RSA加密算法就是基于一个假设:如果一个数是两个以上的大素数的乘积,那么我们很难将这个大数分解。
详细内容
现在我们要用一个简单的方法来讨论数论。这是关于一个有趣的问题――克拉兹问题(Collatz problem)的一番讨论,在这其中我们会用到很多种编程技术。克拉兹问题的特殊之处在于――尽管我们很容易将这个问题讲清楚,但直到今天我们仍不能保证这个问题的算法对所有可能的输入都管用。这个问题也被叫做hailstone问题、3n+1问题、Hasse算法问题、Kakutani算法问题、Thwaites猜想或者Ulam问题。这个问题是由L. Collatz在1937年提出的。
一句话,这是一个非常简单的问题。取一个数(译者注:严格地说,这个数是正整数)作为输入,如果这个数是偶数,就将它除以2,否则就将它乘以3后再加1。(译者注:这是一个数列计算问题,用数学的语言就是,如果Xn是这个数列的第n项,而且Xn为偶数,那么这个数列的第n+1项Xn+1=(Xn)/2,如果Xn为奇数,则Xn+1=3(Xn)+1,其中n为不小于0的整数,数列的第一项X0可以取任意正整数)。这个猜想的命题是:无论你取哪一个(正整)数作为输入,然后求这个数列后面的项,总会有某些项等于1(译者注:确切的说无论取哪一个正整数,总能使这个数列达成1-4-2的循环,即数列中有一项等于1之后,下一项就是4,然后下一项是2,然后又是1、4、2、……。当然在本文中,作者并没有打算讨论那么多)。时至今日,仍然没有人能证明这个命题是正确的,尽管现在通过计算机,已经用大量的数据检查了这个命题,所有的这些数据都通过了这个命题的测试。而这个命题的证明对人们来说确实是一个挑战,而如果有人能证明这个问题,那么他无疑将会受到整个数学界的青睐。
如果将正整数n作为数列的第一个元素,那么这个数列的元素总能达到1,而数列中元素的个数就是这个数列的周期。比如你输入5的话,那么就有如下的序列:
516 8 4 2 1
那么这个数列的周期是6。这个序列被称为hailstone序列(或3n+1序列)。
用来产生这个数列的算法是非常简单的:
算法A (产生3n+1序列的算法)
A1. [输入n]
A2. [算法结束条件] If n = 1 then exit
A3. [检查n] If n is even then n := n / 2 else n := n * 3 + 1
A4. [得出序列中的下一个元素后] Go to A2.
很好,现在就开始实现这个算法吧。在这里,我尝试着用多种编程方式来实现这个算法,以便使整个过程显得有趣。
使用无结构编程的方法
这也许是在计算机上最基础的编程方法了,因为不需要定义过程(译者注:除了main之外)或是其它的什么东西。使用这种方式的问题是,在对代码的管理和重用方面会有所困难,因为用这种方法编出来的程序没有抽象层,因此如果使用这种方法来进行大规模编程会是十分困难的。
#include <iostream>
using std::cout;
using std::endl;
int main(int argc, char* argv[])
{
int iCount = 1;
int iNo = 22;
int iTemp = iNo;
while (iTemp != 1)
{
// 如果是偶数
if (iTemp % 2 == 0)
{
iTemp /= 2;
}
// 如果是奇数
else
{
iTemp = iTemp * 3 + 1;
}
++iCount;
}
cout << "Cycle length of " << iNo << " is " << iCount << endl;
return 0;
}
使用结构化编程的方法
将程序分为小的模块(函数),会使代码比无结构的程序代码更易于管理和重用。在逻辑上,程序中的一个函数会完成算法中的一部分工作,但这种单元划分可以有很强的主观性。这种编程方法可以将问题进行抽象分层,以便于将一个复杂的问题分为一些较小的部分,然后一一解决。
#include <iostream>
using std::cout;
using std::endl;
int NextTerm(int iNo)
{
// 如果是偶数
if (iNo % 2 == 0)
{
iNo /= 2;
}
// 如果是奇数
else
{
iNo = iNo * 3 + 1;
}
return iNo;
}
int CalculateCycle(int iNo)
{
int iCount = 1;
while (iNo != 1)
{
iNo = NextTerm(iNo);
++iCount;
}
return iCount;
}
int main(int argc, char* argv[])
{
const int iNo = 22;
cout << "Cycle length of " << iNo << " is " << CalculateCycle(iNo) << endl;
return 0;
}
使用递归的方法
你知道什么是GNU吗?GNU就是“GNU's Not Unix”,这不是很奇怪吗?这个名称的首字母就是这个名称本身的缩写,如果你将GNU这个名称展开,那么GNU这个词又会出现在展开后的名称中(比如:GNU's Not Unix 's Not Unix)。这只是递归的一个简单例子。换句话说,一个典型的定义是:递归是通过重复扩展自身的方法来描述一件事物的方法。对编程语言来说,递归就是一个函数调用它自己。使用递归的典型例子是求阶乘、求Fibonacci问题、汉诺(Hanoi)塔问题等等。
#include <iostream>
using std::cout;
using std::endl;
int CalculateCycle(int iNo, int iCount)
{
++iCount;
if (iNo == 1)
{
return iCount;
}
else
{
if (iNo % 2 == 0)
{
iNo /= 2;
iCount = CalculateCycle(iNo, iCount);
}
else
{
iNo = iNo * 3+ 1;
iCount = CalculateCycle(iNo, iCount);
}
}
return iCount;
}
int main(int argc, char* argv[])
{
const int iNo = 22;
cout << "Cycle length of " << iNo << " is = "
<< CalculateCycle(iNo, 0) << endl;
return 0;
}
使用面向对象编程的方法
面向对象编程是一种编程方法,在这种方法中,你可以建立类并且建立这些类的实例,我们也可以称这些实例为“对象”。在技术上,“类”是在一个整体中定义变量和方法的原型。这个问题很小,你并不能通过这个问题看到面向对象编程所有的特点。下面这个程序并不是面向对象的,而是采用基于对象的技术。
#include <iostream>
using std::cout;
using std::endl;
class Collatz
{
private:
int m_iCycle;
int m_iNo;
int NextTerm(int p_iNo);
public:
Collatz(int p_iNo = 0);
void CalculateCycle();
int GetCycle() const;
};
Collatz::Collatz(int p_iNo) : m_iNo(p_iNo), m_iCycle(1)
{
if (m_iNo < 1)
throw std::out_of_range("No should be greater than 0");
}
int Collatz::NextTerm(int p_iNo)
{
// 如果是偶数
if (p_iNo % 2 == 0)
{
p_iNo /= 2;
}
// 如果是奇数
else
{
p_iNo = p_iNo * 3 + 1;
}
return p_iNo;
}
void Collatz::CalculateCycle()
{
while (m_iNo != 1)
{
m_iNo = NextTerm(m_iNo);
++m_iCycle;
}
}
int Collatz::GetCycle() const
{
return m_iCycle;
}
int main(int argc, char* argv[])
{
const int iNo = 22;
try
{
Collatz objCollatz(iNo);
objCollatz.CalculateCycle();
cout << "Cycle length of " << iNo << " is "
<< objCollatz.GetCycle() << endl;
}
catch(std::out_of_range& ex)
{
cout << ex.what() << endl;
}
return 0;
}
使用泛型编程的方法
“泛型的(Generic)”就是“概括的”、“通用的”。在技术上,泛型编程的意思是,以一种方式来编写程序,在这种方式下编出来的程序可以对各种不同的独立的数据类型都能正常的运行。泛型程序应该对所有的数据类型都提供良好的支持,并能完成程序应该完成的功能。使用C++模板是一种泛型编程的途径。
#include <iostream>
using std::cout;
using std::endl;
template <typename T>
class Collatz
{
private:
T m_iCycle;
T m_iNo;
int NextTerm(T p_iNo);
public:
Collatz(T p_iNo = 0);
void CalculateCycle();
T GetCycle() const;
};
template <typename T>
Collatz<T>::Collatz(T p_iNo) : m_iNo(p_iNo), m_iCycle(1)
{
if (m_iNo < 1)
throw std::out_of_range("No should be greater than 0");
}
template <typename T>
int Collatz<T>::NextTerm(T p_iNo)
{
// 如果是偶数
if (p_iNo % 2 == 0)
{
p_iNo /= 2;
}
// 如果是奇数
else
{
p_iNo = p_iNo * 3 + 1;
}
return p_iNo;
}
template <typename T>
void Collatz<T>::CalculateCycle()
{
while (m_iNo != 1)
{
m_iNo = NextTerm(m_iNo);
++m_iCycle;
}
}
template <typename T>
T Collatz<T>::GetCycle() const
{
return m_iCycle;
}
int main(int argc, char* argv[])
{
const int iNo = 22;
try
{
Collatz<int> objCollatz(iNo);
objCollatz.CalculateCycle();
cout << "Cycle length of " << iNo << " is " << objCollatz.GetCycle() << endl;
}
catch(std::out_of_range& ex)
{
cout << ex.what() << endl;
}
return 0;
}
使用模板元编程的方法
模板元编程(Template Meta Programming)是C++中最漂亮的编程技术。在这种方法中,你可以使用编译器来做原本应该由程序本身完成的工作。也可以这样说,它看起来像是根据程序来写程序。在模板元编程中,你可以将C++编译器当做解释器,比如,编译器可以将模板展开并生成程序运行时运行的二进制代码,这样,执行编译后程序的速度会非常快,因为一些数据已经在编译时由编译器计算出来。如果没有消耗资源的话就什么都算不出来,但这种方法可以消耗编译时的资源(通过编译器的计算能力)。编译时耗费的时间会有所增加,这取决于使用这种方法时所用算法的特性。
#include <iostream>
using std::cout;
using std::endl;
template<int N>
class CalculateCycle
{
public:
enum { count = CalculateCycle<N % 2 ? (N * 3 + 1) : (N / 2) >::count + 1;};
};
template <>
class CalculateCycle<1>
{
public:
enum { count = 1 };
};
int main(int argc, char* argv[])
{
const int iNo = 22;
cout << "Cycle length of " << iNo << " is = "
<< CalculateCycle<iNo>::count << endl;
return 0;
}
译者注:实际上,如果使用模板元编程的方法来解决问题的话,代码往往并不能通过编译,因为编译器会提示编译出错信息,比如上面的程序在Visual C++中的编译出错信息是:
--------------------Configuration: tmp_1 - Win32 Debug--------------------
Compiling...
tmp_1.cpp
H:\Documents and Settings\seafrog\桌面\tmp\tmp_1.cpp(9) : error C2760: syntax error : expected ',' not ';'
H:\Documents and Settings\seafrog\桌面\tmp\tmp_1.cpp(9) : see reference to class template instantiation 'CalculateCycle<2>' being compiled
H:\Documents and Settings\seafrog\桌面\tmp\tmp_1.cpp(9) : see reference to class template instantiation 'CalculateCycle<4>' being compiled
H:\Documents and Settings\seafrog\桌面\tmp\tmp_1.cpp(9) : see reference to class template instantiation 'CalculateCycle<8>' being compiled
H:\Documents and Settings\seafrog\桌面\tmp\tmp_1.cpp(9) : see reference to class template instantiation 'CalculateCycle<16>' being compiled
H:\Documents and Settings\seafrog\桌面\tmp\tmp_1.cpp(9) : see reference to class template instantiation 'CalculateCycle<5>' being compiled
H:\Documents and Settings\seafrog\桌面\tmp\tmp_1.cpp(9) : see reference to class template instantiation 'CalculateCycle<10>' being compiled
H:\Documents and Settings\seafrog\桌面\tmp\tmp_1.cpp(9) : see reference to class template instantiation 'CalculateCycle<20>' being compiled
H:\Documents and Settings\seafrog\桌面\tmp\tmp_1.cpp(9) : see reference to class template instantiation 'CalculateCycle<40>' being compiled
H:\Documents and Settings\seafrog\桌面\tmp\tmp_1.cpp(9) : see reference to class template instantiation 'CalculateCycle<13>' being compiled
H:\Documents and Settings\seafrog\桌面\tmp\tmp_1.cpp(9) : see reference to class template instantiation 'CalculateCycle<26>' being compiled
H:\Documents and Settings\seafrog\桌面\tmp\tmp_1.cpp(9) : see reference to class template instantiation 'CalculateCycle<52>' being compiled
H:\Documents and Settings\seafrog\桌面\tmp\tmp_1.cpp(9) : see reference to class template instantiation 'CalculateCycle<17>' being compiled
H:\Documents and Settings\seafrog\桌面\tmp\tmp_1.cpp(9) : see reference to class template instantiation 'CalculateCycle<34>' being compiled
H:\Documents and Settings\seafrog\桌面\tmp\tmp_1.cpp(9) : see reference to class template instantiation 'CalculateCycle<11>' being compiled
H:\Documents and Settings\seafrog\桌面\tmp\tmp_1.cpp(23) : see reference to class template instantiation 'CalculateCycle<22>' being compiled
……
这就是模板元编程的运行结果,我们在CalculateCycle模板后的尖括号中可以看到这个序列中的所有元素:
221134175226134020105168421
而且,如果你把这一句
enum { count = CalculateCycle<N % 2 ? (N * 3 + 1) : (N / 2) >::count + 1;};
中第一个分号(;)改为逗号(,)的话,你会编译成功,运行编译后的可执行文件,从而得到这个序列的周期count:
Cycle length of 22 is = 16
模板元编程确实是一个漂亮的编程方法。但为什么我们可以从编译器得到本该由我们的代码算出的结果呢?大家可以参考《C++模板元编程》(机械工业出版社《软件研发》2004年2月号48页,荣耀著)一文。同时,模板元编程并不一定总是管用,如果你使用其他的编译器(比如Dev-C++),在编译出错信息中就有可能不会显示出运行结果。
(完,2005年8月2日稿)
分享到:
相关推荐
马克思的趣味数学题,用C++编写程序!有30个人,其中有男人,女人,小孩,在一家饭馆吃饭共花50先令,每个男人花3先令,每个女人花2先令,每个小孩花1先令,问男人,女人,小孩各多少人?
标题中的“马克思手稿中的趣味数学题”是指在卡尔·马克思的手稿中发现的一道有趣的数学问题,这道问题融合了实际生活情境与基础代数知识。问题描述了一个包含30个人的群体(男性、女性和小孩)在饭馆用餐,总共花费...
总的来说,《小学生C++趣味编程》不仅教导孩子们掌握C++和Scratch的基本语法,还通过实践项目培养他们的逻辑思维能力和问题解决技巧,为他们将来深入学习编程打下坚实基础。同时,通过结合传统文化、科学知识和趣味...
角谷猜想 C++ 编程实践 角谷猜想是一种数学猜想,指的是对于任何一个正整数n,存在一个正...角谷猜想C++编程实践是《小学生C++趣味编程》中的一节课,旨在帮助学生通过编程来验证角谷猜想,提高编程能力和数学思维。
《小学生C++趣味编程》是一本面向初学者,特别是小学生的教材,旨在通过结合C++编程语言和Scratch可视化编程工具,激发孩子们对编程的兴趣。该书内容涵盖基础的编程概念,逐步引导学生掌握编程思维和技能。以下是书...
标签“小学生C++趣味编程”突出了教育目标群体和编程语言,而“信奥 CSP-J 源代码”则意味着这些课程可能与信息学奥林匹克竞赛(简称信奥)中的初级组(CSP-J)有关。信奥是一项针对中学生的编程竞赛,旨在培养和...
《小学生C++趣味编程》是一本面向初学者,特别是小学生的编程教材,旨在通过C++和Scratch这两种编程语言,让孩子们在娱乐中学习编程基础知识。该书的内容涵盖了从简单的编程概念到逐步复杂的编程思维,旨在培养孩子...
《C/C++趣味编程100例》通过这些丰富多样的实例,不仅传授了编程技巧,还激发了学习者对数学和算法的兴趣。无论是图形绘制、数值计算,还是逻辑推理,每个例子都是一次思维的锻炼和技能的提升。对于希望深入掌握C/...
《小学生C++趣味编程》是一套专为初学者设计...总的来说,这套课程通过结合Scratch和C++,以趣味性和实用性为主导,引导小学生逐步掌握编程基础,培养计算思维和逻辑推理能力,为未来深入学习计算机科学打下坚实基础。
通过分析“C++趣味编程100题”的部分题目,我们了解了C++在图形输出、数学函数应用、控制结构使用以及标准库调用等方面的实践技巧。这些知识不仅有助于提升编程能力,还能激发对C++编程的兴趣,尤其是在解决实际问题...
在本课程资源“第43课 最大公约数 《小学生C++趣味编程》PPT 源码 课后题答案等(2021.08.22).rar”中,主要围绕着计算两个或多个整数的最大公约数(Greatest Common Divisor, GCD)这一主题展开。最大公约数是数学...
C/C++语言经典实用趣味程序设计编程百例精解 <br>C/C++语言经典实用趣味程序设计编程百例精解(1) <br>1.绘制余弦曲线 2.绘制余弦曲线和直线 3.绘制圆 4.歌星大奖赛 5.求最大数 6.高次方数...
3. 控制结构:if...else语句、switch语句和for、while、do...while循环是C语言中的控制结构,它们允许程序根据条件执行不同的分支或重复执行某些代码,这对于解决涉及条件判断和迭代的数学问题非常有用。 4. 函数:...
《C-C++语言趣味程序设计编程百例精解》是一本旨在通过丰富的实例来深入解析C和C++语言的书籍。书中的实例涵盖了基础语法、数据结构、算法、面向对象编程等多个方面,旨在帮助读者在实践中更好地理解和掌握这两种...
从给定的文件标题“用C++编写的常见趣味题”和描述“集成常见的C++程序,如八皇后,回文数,自守数等。”我们可以深入探讨一系列经典的C++编程挑战,这些挑战不仅能够增强程序员的基础技能,还能够激发他们对算法和...
C++趣味程序100例 本资源汇集了100个趣味的C++程序,...C++趣味程序100例资源汇集了丰富的C++知识点,涵盖了图形处理、游戏开发、数学计算、数据处理等多个方面,旨在帮助读者快速掌握C++编程的基本概念和高级技术。
《C++趣味编程题》是一份集合了多个有趣编程挑战的文档,主要针对C++语言进行设计。这些题目旨在帮助学习者提升C++编程技能,同时增加编程的乐趣。本题库涵盖不同难度级别的问题,适合不同程度的C++学习者。 在提供...
【C与C++语言经典、实用、趣味程序设计编程】涉及的知识点广泛,包括基本的程序设计概念、数学运算、图形绘制以及数据处理。下面将逐一展开这些知识点。 1. **绘制余弦曲线** - **数学知识**:余弦函数`cos(x)`,...
这个例子展示了如何使用C++和基本的数学知识来绘制圆形。通过计算每个点与圆心的距离,利用勾股定理判断是否属于圆的一部分,从而实现圆形的绘制。 **关键知识点:** - 利用`sqrt`函数计算距离。 - 使用循环遍历...
在本压缩包“趣味C++小程序.rar”中,包含了多个C++编程的实践项目,旨在帮助初学者深入了解和掌握C++语言。以下是这些小程序所涉及的主要知识点和详细解释: 1. **八皇后问题**:这是一个经典的回溯算法应用,通过...