1 概论

来源:互联网 发布:有没有画画软件 编辑:程序博客网 时间:2024/06/05 03:05

1 概论


1 引子

  • 数据结构(Data Structure)的定义:计算机中存储、组织数据的方式。通常情况下,精心选择的数据结构可以带来最优效率的算法。
  • 数据结构在计算机内部的存储:1、逻辑结构;2、物理存储结构
  • 矩阵的抽象数据类型:AMN=(aij),由MxN个三元集<a,i,j>组成
ElementType GetEntry(Matrix A, int i, int j): //返回矩阵A的第i行第j列元素
  • 为什么要抽象?
    抽象可以过滤掉细节,只研究事物的某一个特定的角度,从而触及到问题的本质。


- 例子1:如何在书架上摆放图书?看规模!摆放书需要考虑:1、如何插入一本新书;2、如何查找到某一本指定的书。
- 1.随便放:新书插入很方便,但是找到某一本书很难!
- 2.按书名拼音字母顺序:二分查找!但是新书插入很难
- 3.划分区域,每个区域摆放一类图书,按照拼音顺序拜访:查找和插入都方便一些。但是事先需要给出很大的空间,否则可能导致没地方放。另外,类别分的粗和细又是一个问题。

- 例子2:实现PrintN函数:打印1~N的全部正整数

//循环实现void PrintN(int N){    int i;    for(i = 1; i <= N; i++){        printf("%d\n", i);    }}
//递归实现void PrintN(int N){    if (N){        PrintN(N-1);        printf("%d\n", N);    }}

  对于递归实现,当N = 100000时,递归程序崩溃。因为递归函数对计算机空间占用很大。这说明,解决问题方法的效率和空间的利用率有关。

  • 例子3:计算给定多项式f(x)=a0+a1x++anxn在点x处的值
double f(int n, double a[], double x){    int i;    double p = a[0];    for(i = 1; i <= n; i++){        p += (a[i] * pow(x,i));    }    renturn p;}

  或者:f(x)=a0+x(a1+x())

double f(int n, double a[], double x){    int i;    double p = a[n];    for(i = n; i > 0; i--){        p = a[i-1] + x*p;    }    renturn p;}

  第二个函数更好,计算效率更高。因此解决问题的效率和算法有关。

  • clock()函数:捕捉程序开始运行到clock()被调用时耗费的时间。单位:clock tick, CLK_TCK:机器时钟每秒所走的时钟打点数。
#include<stdio.h>#include<time.h>clock_t starrt, stop;  //clock_t是clock()函数返回的变量类型double duration;       //以秒为单位int main(){    start = clock();    MyFunction();    stop = clock();    duration = ( (double)(stop - start) ) / CLK_TCK;    return 0;}

2 算法

  • 算法(Algorithm)的定义:一个有限的指令集,接受一些输入并产生输出,一定在有限的步骤之后终止。其中每一条指令有充分目标没有歧义,在计算机的处理范围内,不依赖于任何一种计算机语言以及具体的实现手段。
  • 衡量算法好坏的指标:
    • 1 空间复杂度S(n):占用存储单元的长度
    • 2 时间复杂度T(n):执行耗费时间的长度
//递归实现void PrintN(int N){    if (N){        PrintN(N-1);        printf("%d\n", N);    }}

  例如在递归算法中,假设我们需要输出PrintN(100000),那么函数首先需要调用PrintN(99999),于是100000需要存储在内存中,同样的,99999、99998……、1都需要存储,直到PrintN(0)可以输出。所以S(N)=CN
  而在循环算法中,只涉及到临时变量,所以不管N多大,占用的空间始终是固定的常量。
  在求多项式的值的算法中,由于计算机对于加减法的计算效率要远高于乘除法,pow(x,i)函数中执行了i次乘法,于是乘法的总次数为(n2+n)/2。而第二个算法中,一共执行了n次乘法。因此T1(n)=C1n2+C2nC1,C2分别为乘除法和加减法的复杂度。T2(n)=Cn

  • 什么是好的算法?

    • 最坏情况复杂度:Tworst(n)
    • 平均复杂度:Tavg(n)——最关心的问题
  • 复杂度的渐进表示:

    • T(n)=O(f(n))表示存在常数C>0,n0>0使得当nn0时,T(n)Cf(n)
    • T(n)=Ω(g(n))表示存在常数C>0,n0>0使得当nn0时,T(n)Cg(n)
    • T(n)=Θ(h(n))表示同时有T(n)=O(h(n))T(n)=Θ(h(n))
  • 太大的上界和太小的下界是无意义的。

  • 常见的算法时间复杂度由小到大依次为:O(1)O(logn)O(n)O(nlogn)O(n2)O(n3)O(2n)O(n!)
  • 若两段算法分别有复杂度T1(n)=O(f1(n))T2(n)=O(f2(n)),那么
    • T1(n)+T2(n)=max(O(f1(n))+O(f2(n)))
    • T1(n)T2(n)=O(f1(n)f2(n))
  • T(n)是关于n的k阶多项式,那么T(n)=Θ(nk)
  • for循环的时间复杂度等于循环次数乘循环体代码的复杂度。
  • if-else总体复杂度为三者中最大

3 最大子列和问题

  最大子列和问题:给定N个整数的序列{A1,A2,,AN},求函数f(i,j)=max{0,ijAk}的最大值。

  • 算法1:暴力搜索,T(N)=O(N3)
int MaxSubseqSum1( 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(n2)
int MaxSubseqSum2( int A[], int N){    int ThisSum, MaxSum = 0;    int i, j;    for(i = 0; i < N; i++){        ThisSum = 0;        for( j = i; j < N; j++){   //对于相同的i,不同的j,只在j-1次循环的基础上累加一项即可             ThisSum += A[j];            if (ThisSum > MaxSum)                MaxSum = ThisSum;        }    }    return MaxSum;}
  • 算法3:分而治之,将序列在中间切分,左边有一个最大子列和,右边有一个最大子列和,跨越中间线的有一个最大子列和,取三者中最大的即可。T(n)=O(nlogn)
    • 时间复杂度分析:T(1)=O(1),左右两边问题为T(N/2),中间为O(N),所以T(N)=2T(N/2)+O(N),因此T(N)=O(N)+O(NlogN)=O(NlogN)
    • 空间复杂度分析:左右两部分空间复杂度为S(N/2),跨边界空间复杂度为O(N)。故整个空间复杂度S(N)=2S(N/2)+O(N)=O(NlogN)
int Max3( int A, int B, int C ){ /* 返回3个整数中的最大值 */    return A > B ? A > C ? A : C : B > C ? B : C;}int DivideAndConquer( int List[], int left, int right ){ /* 分治法求List[left]到List[right]的最大子列和 */    int MaxLeftSum, MaxRightSum; /* 存放左右子问题的解 */    int MaxLeftBorderSum, MaxRightBorderSum; /*存放跨分界线的结果*/    int LeftBorderSum, RightBorderSum;    int center, i;    if( left == right )  { /* 递归的终止条件,子列只有1个数字 */        if( List[left] > 0 )  return List[left];        else return 0;    }    /* 下面是"分"的过程 */    center = ( left + right ) / 2; /* 找到中分点 */    /* 递归求得两边子列的最大和 */    MaxLeftSum = DivideAndConquer( List, left, center );    MaxRightSum = DivideAndConquer( List, center+1, right );    /* 下面求跨分界线的最大子列和 */    MaxLeftBorderSum = 0; LeftBorderSum = 0;    for( i=center; i>=left; i-- ) { /* 从中线向左扫描 */        LeftBorderSum += List[i];        if( LeftBorderSum > MaxLeftBorderSum )            MaxLeftBorderSum = LeftBorderSum;    } /* 左边扫描结束 */    MaxRightBorderSum = 0; RightBorderSum = 0;    for( i=center+1; i<=right; i++ ) { /* 从中线向右扫描 */        RightBorderSum += List[i];        if( RightBorderSum > MaxRightBorderSum )            MaxRightBorderSum = RightBorderSum;    } /* 右边扫描结束 */    /* 下面返回"治"的结果 */    return Max3( MaxLeftSum, MaxRightSum, MaxLeftBorderSum + MaxRightBorderSum );}int MaxSubseqSum3( int List[], int N ){ /* 保持与前2种算法相同的函数接口 */    return DivideAndConquer( List, 0, N-1 );}
  • 算法4:在线处理,T(N)=O(N)。“在线”:每输入一个数据就进行即时处理,在任何一个地方终止输入,算法都能给出正确的解。
int MaxSubseqSum4( int A[], int N){    int ThisSum = 0, MaxSum = 0;    int i;    for(i = 0; i < N; i++){        ThisSum += A[i];        if (ThisSum > MaxSum)            MaxSum = ThisSum;        else if(ThisSum < 0)    //如果当前子列和为负,不可能通过累加后面的数达到最大。             ThisSum = 0;    }    return MaxSum;}
0 0
原创粉丝点击