第一章:绪论
计算机科学的学习重心是:具体地深入思考与分析获得对问题本质的透彻理解,按照长期积淀的框架与模式设计出符合问题内在规律的算法。选用、定制、改进足以支撑算法高效实现的数据结构,并在真实的应用环境中充分测试、调校和改进,构成了计算机高效求解实际问题的典型流程和不二法门。每一位有志于驾驭计算机的学生都应该从这些问题入手,不断学习、反复练习和勤于总结。
1.1 计算机与算法
计算机科学的核心在于研究计算方法的过程与规律,而不仅仅是计算机工具本身。因此,E.Dijkstra以及追随者们更倾斜于这样称呼:计算科学。
1.1.1 埃及人的绳索
问题1: 过直线上一点作直线的垂线。方法如下:最后竖线和横线垂直。
问题2:三等分一条线段。如下:水平线段是要被等分的线段。
1.1.3起泡排序
待解决的问题:对n个元素进行升序排序。
void bubbleSort(int *arr,int n)
{
int compareNum = n - 1;
int i,j;
for(j = 1;j < n;j ++) //共比较 n-1 趟
{
for(i = 0;i < compareNum; i ++) //每趟比较compareNum个元素
{
if(arr[i] > arr[i + 1])
swap(arr[i],arr[i+1]); //c++ 标准库函数swap,作用是交换两个实参
}
compareNum --; //每趟比较的最后compareNum减一,因为下一趟比上一趟少比较一个元素
}
}
1.1.4 算法
1.算法
算法:是指基于特定的计算模型,旨在解决某一信息处理问题而设计的一个指令序列。
算法必须具备的3个要素:
(1)输入输出:
输入:对待求解问题的特定实例的某种描述,统称为输入。
输出:经过计算和处理之后的信息即针对输入问题实例的答案。
(2)基本操作、确定性与可行性:
算法应该描述为由若干语义明确的基本操作组成的指令序列,且每个基本操作在对应的计算模型中均可兑现。
子算法:借助基本操作组合解决子问题的算法,即解决子问题的算法。
对现代计算机而言,一个算法满足确定性与可行性仅当可以通过程序设计语言精确地描述。
(3)有穷性与正确性
任意算法都应该在执行有限次基本操作之后终止并给出输出。此即算法的有穷性(finiteness)。
输出就应该符合问题本身实现确定的条件——正确性。
证明算法的有穷性和正确性的一个重要技巧,就是从适当的角度审视整个计算过程,并找出其所具有的某种不变性和单调性。其中的单调性是指:问题的有小规模会随着算法的推进不断递减。
bubbleSort的不变性和单调性应该如何体现呢?
经过k趟的扫描交换之后,最大的前k个元素必然就位;经过k趟扫描交换之后,待求解问题的有小规模将缩减至n-k。
(4)退化与鲁棒性
算法的鲁棒性:对于退化情况,算法依然可以正确返回不至于出现异常。
(5)重用性
重用性:算法模式可推广并适用不同类型基本元素的这种特性。
1.1.5 算法效率
(1)可计算性:指的是一个实际问题是否能够通过算法在有限时间内被计算机解决。
(2)难解性:问题的最低求解时间成本的高低。
(3)计算效率:算法效率是指算法执行的时间。
(4)数据结构:数据的表现形式。算法的输入和输出,都是以数据结构表示。
1.2 复杂度度量
1.2.1 时间复杂度
1.时间复杂度:随着输入规模的扩大,算法的执行时间的变化趋势可表示为输入规模的一个函数,称为该算法的时间复杂度(time complexity)。算法处理规模为n的的问题所需的时间可记作T(n)。注:在规模是n的所有输入中选择执行时间最长者作为T(n),并以T(n)度量该算法的时间复杂度。
1.2.2 渐进复杂度
着眼长远,更为注重时间复杂度的总体变化趋势和增长速度的策略与方法,即所谓的渐进分析。
1.3.5 求2的n次方的函数:
__int64 power2BF_I(int n) //__int64是有符号64位整数类型,范围是[-2^63,2^63-1]
{
__int64 pow = 1; //sizeof(__int64 = 8),__int64占8个字节,即64位
while(n-- > 0)
{
pow <<= 1;
}
return pow;
}
1.4 递归
如果一个函数直接或者间接地调用自身,称为递归函数。 调用自身的过程叫递归过程。
递归分为直接递归和间接递归。直接递归是指函数直接调用自身,间接递归则指 A 函数调用了其它函数,而其它的函数中又调用了 A 函数。
递归函数通常由以下两个部分组成:
- 基本情况(Base Case):这是递归的终止条件。没有基本情况,递归函数将无限地调用自己,导致栈溢出。基本情况也叫做递归基。
- 递归情况(Recursive Case):在这里,函数将问题分解成更小的子问题,并自我调用来解决这些子问题。
实际场景中,很多问题适合用递归函数来解决,接下来通过两个实例,带读者体会函数的递归过程。
实例:fabonacci序列
#include <iostream>
int fibonacci(int n) {
// 基本情况
if (n == 0) return 0;
if (n == 1) return 1;
// 递归情况
return fibonacci(n - 1) + fibonacci(n - 2);
}
int main() {
int result = fibonacci(5); // 第5个Fibonacci数是5
std::cout << "The 5th Fibonacci number is: " << result << std::endl;
return 0;
}