基本概念

关于数据组织 #

笔记 #

具象例子:

  • 如何在书架上摆放图书:把书架划分为几块区域,每块区域指定拜访某种类别的书,每个类别内用书名的拼音排序
    问题:①如何分配空间?②类别要分多细?
    结论:解决问题方法的效率与数据的组织方式有关。

关于空间使用 #

笔记 #

案例问题:实现一个函数,输入一个正整数N后可以顺序打印从1到N的全部正整数。

循环实现: #

//循环实现
 static void PrintN(int N)
    {
        int i = 1;
        for (i = 1; i <= N; i++)
        {
            Console.Write(i);
        }
    }

递归实现: #

//递归实现
static void PrintN(int N)
    {
        if (N>0)
        {
            PrintN(N - 1);
            Console.Write(N);
        }
    }

令N=100,1000,10000,100000…分别测试两种实现方式的效果:
前面都没事差不多,但是在100000时,递归就罢工了。
递归的程序对空间的占用有时会过多。
结论:解决问题方法的效率和空间的利用效率有关

关于算法效率 #

笔记 #

案例问题:写程序计算给定多项式在给定点x处的值
多项式(打这个用了[[Latex语法]]):$f(x)=a_{0}+a_{1}x+···+a_{n-1}x^{n-1}+a_{n}x^{n}$

//不好的写法
static double F(int n, double a[], double x)
{
    int n=1;
    double fx=a[0];
    for(i=1,i<=n,i++)
    {
        fx +=(a[i]*pow(x,i));
    }
    return fx;
}

计算前首先把多项式换个形式:
$$f(x)=a_{0}+x(a_{1}+x(…(a_{n-1}+x(a_{n}))…))$$

//更好的写法
static double F(int n, double a[], double x)
{
    int i;
    double fx=a[n];
    for(i=n,i>0,i--)
    {
        fx=a[i-1]+x*p;  
    }
    return fx;
}

结论:解决问题方法的效率,和算法的巧妙程度有关

抽象数据类型 #

笔记 #

数据结构是数据对象在计算机中的组织方式

  • 逻辑结构
    • 线性结构
    • 树形结构
    • 图形结构
  • 物理存储结构
    数据对象必定与一系列加在其上的操作相关联
    完成这些操作所用的方法就是算法

抽象数据类型(Abstract Data Type)
只描述数据对象集和相关操作集“是什么”,并不涉及“如何做到”的问题。
数据类型:

  • 数据对象集
  • 数据集合相关联的操作集

抽象:描述数据类型的方法不依赖于具体实现

  • 与存放数据的机器无关
  • 与数据存储的物理结构无关
  • 与实现操作的算法和编程语言无关

例:“矩阵”的抽象数据类型定义(其中何种矩阵、元素是何种数据类型、具体实现方法、语言均为抽象)

  • 类型名称:矩阵(Matrix)
  • 数据对象集:一个$M\times N$的矩阵$A_{M\times N}=(a_{ij})(i=1,…,M;j=1,…,N)$由$M\times N$个三元组$$构成,其中$a$是矩阵元素的值,$i$是元素所在的行号,$j$是元素所在的列号。
  • 操作集:对于任意矩阵$A、B、C\in Matrix$,以及整数$i、j、M、N$
    • Matrix Create(int M, int N):返回一个$M\times N$的空矩阵
    • int GetMaxRow(Matrix A):返回矩阵A的总行数
    • int GetMaxCol(Matrix A):返回矩阵A的总列数
    • ElementType GetEntry(Matrix A, int i, int j):返回矩阵A的第一行、第j列的元素
    • Matrix Add(Matrix A, Matrix B):如果A和B的行、列数一致,则返回矩阵C=A+B,否则返回错误标志
    • Matrix Multiply(Matrix A, Matrix B):如果A的列数等于B的行数,则返回矩阵C=AB,否则返回错误标志

算法的定义 #

笔记 #

算法(Algorithm)

  • 一个有限指令集
  • 接受一些输入(有些情况下不需要输入)
  • 产生输出
  • 一定在有限步骤之后终止
  • 每一条指令必须
    • 有充分明确的目标,不可以有歧义
    • 计算机能够处理的范围之内
    • 描述应不依赖于任何一种计算机语言以及具体的实现手段

例1:选择排序算法的伪码描述

void SelectionSort(int List[], int N)
{ 
/*将N个整数List[0]...List[n-1]进行非递减排序*/
    从List[i]到List[n-1]中找最小元,并将其位置赋给MinPosition;
    将未排序部分的最小元换到有序部分的最后位置

}

抽象:
List到底是数组还是链表?
切换位置用函数/宏都可以

什么是好的算法 #

笔记 #

衡量算法的指标: #

空间复杂度$S(n)$ #

根据算法写成的程序在执行时占用存储单元的长度。这个长度往往与输入数据的规模有关。空间复杂度过高的算法可能导致使用的内存超限,造成程序非正常中断。
$S(n)=C\times N$

时间复杂度$T(n)$ #

根据算法写成的程序在执行时耗费时间的长度。这个长度往往也与输入数据的规模有关。时间复杂度过高的低效算法可能导致运行花费太久的时间。
(可以通过算机器做了几次乘除法来判断)

什么是好的算法? #

  • 在分析一般算法的效率时,经常关注下面两种复杂度
    • 最坏情况复杂度$T_{worst}(n)$
    • 平均复杂度$T_{avg}(n)$

$T_{avg}(n)\leqslant T_{worst}(n)$
分析平均复杂度比较难,更关心最坏情况复杂度

复杂度的渐进表示 #

笔记 #

复杂度的渐进表示法

  • 算法的上界:$T(n)=O(f(n))$表示存在常数$C>0,n_{0}>0$使得当$n\geq n_{0}$时有$T(n)\leqslant C\cdot f(n)$
  • 算法的下界:$T(n)=\Omega(g(n))$表示存在常数$C>0,n_{0}>0$使得当$n\geq n_{0}$时有$T(n)\geq C\cdot g(n)$
  • $T(n)=\Theta(h(n))$表示同时有$T(n)=O(h(n))$和$T(n)=\Omega(h(n))$

不同函数根据输入规模n的变化复杂度的变化:
【输入规模变化图1】
【输入规模变化图2】

复杂度分析小窍门 #

  • 若两端算法分别有复杂度$T_{1}(n)=O(f_{1}(n))$和$T_{2}(n)=O(f_{2}(n))$,则
    • $T_{1}(n)+T_{2}(n)=max(O(f_{1}(n)),O(f_{2}(n)))$
    • $T_{1}(n)\times T_{2}(n)=O(f_{1}(n)\times f_{2}(n))$
  • 若$T(n)$是关于n的k阶多项式,那么$T(n)=\theta(n^{k})$
  • 一个循环结构的时间复杂度等于循环次数乘以循环体代码的复杂度
  • if else语句结构的复杂度取决于if的条件判断复杂度和两个分支部分的复杂度,总体复杂度取三者中最大

应用实例-最大子列和问题 #

应用实例-题目 #

给定N个整数的序列$\left{A_1,A_2,…,A_N \right}$,求函数$f(i,j)=max\left{0,\sum_{k=i}^{j}A_K\right}$的最大值

应用实例-算法一【复杂度$T(N)=O(N^3)$】: #

int MaxSubseqSum(int A[], int N)
{
    int ThisSum, MaxSum=0;
    int i,j,k;
    for(i=0;i<N;i++)
    {
        for(j=i;j<N;j++)
        {
            ThisSum=0;
            for(k=i;k<=j;k++)
            {
                ThisSum+=A[k];
                if(ThisSum>MaxSum)
                MaxSum=ThisSum;
            }
        }
    }
    return MaxSum;
}

应用实例-算法2【复杂度$T(N)=O(N^2)$】: #

int MaxSubseqSum2(int A[],int N)
{
    int ThisSum,MaxSum=0;
    int i,j;
    for(i=0;j<N;i++)
    {
        ThisSum=0;
        for(j=i;j<N;j++)
        {
            ThisSum+=A[j];
            if(ThisSum>MaxSum)
            MaxSum=ThisSum;
        }
    }
    return MaxSum;
}

应用实例-算法3:分而治之 #

笔记 #

【分而治之图】

应用实例-算法4:在线处理 #

笔记【复杂度$T(N)=O(N)$】 #

int MaxSubseqSum4(int A[],int N)
{
    int ThisSum,MaxSum;
    int i;
    ThisSum=MaxSum=0;
    for(i=0;i<N;i++)
    {
        ThisSum+=A[i];//向右累加
        if(ThisSum>MaxSum)
            MaxSum=ThisSum;//发现更大和则更新当前结果
        elseif(ThisSum<0)//如果当前子列和为负
            ThisSum=0;//则不可能使后面的部分和增大,抛弃原有结果
    }
    return MaxSum;
}

“在线”指每输入一个数据就进行即时处理,在任何一个地方中止输入,算法都能正确给出当前的解。

更新 2023年 2月 10日