数据结构与算法 第一周

发布于 2020-02-29  279 次阅读


基本概念

什么是数据结构

在计算机科学中,数据结构(data structure)是计算机中存储、组织数据的方式。

clock() 捕捉从程序开始运行到clock()被调用时所花费的时间。这个时间单位是clock tick,即“时钟打点”。 常数CLK_TCK:机器时钟每秒所走的时钟打点数。
使用该函数需要引入<time.h>

#include <stdio.h>
#include <time.h>
clock_t start,stop;     //clock_t是clock()函数返回的变量类型
double duration;        //记录被测函数运行时间,以秒为单位
int main(){
    //不在测试范围内的准备工作写在clock()调用之前
    start = clock();    //开始计时
    MyFunction();       //测试的函数
    stop=clock();       //停止计时
    //其他不在测试范围内的处理写在其后,例如输出duration的值
    duration = ((double)(stop-start))/CLK_TCK;

    return 0;
}

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

  • 逻辑结构
  • 物理存储结构

数据对象必定与一系列加在其上的操作相关联
完成这些操作所用的方法就是算法
抽象数据类型 Abstract Data Type

  • 数据类型
    1. 数据对象集
    2. 数据集合相关联的操作集
  • 抽象:描述数据类型的方法不依赖于具体实现
    1. 与存放数据的机器无关
    2. 与数据存储的物理结构无关
    3. 与现实操作的算法和编程语言均无关

只描述数据对象集和相关操作“是什么”,并不涉及“如何做到”的问题


什么是算法

算法 Algorithm 定义

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

什么是好的算法

空间复杂度S(n)--根据算法写成的程序在执行时占用存储单位的长度。这个长度往往与输入数据的规模有关。空间复杂度过高的算法可能会导致使用的内存超限,造成程序非正常中断。
时间复杂度T(n)--根据算法写成的程序在执行时耗费时间的长度。这个长度往往也与输入数据的规模有关。时间复杂度过高的低效算法可能导致我们在有生之年都等不到运行的结果。

在分析一般算法的效率时,经常关注以下两种复杂度

  • 最坏情况复杂度 Tworst(n)
  • 平均复杂度Tavg(n)
    Tavg(n) ≤ Tworst(n)

复杂度的渐进表示法

O(大O符号):上界
定义:若存在两个正的常数 c 和 n0 , 对于任意 n≥n0 , 都有 T( n)≤cf( n) ,则称T( n) = O( f( n) )(或称算法在 O( f( n))中)。
大 O 符号用来描述增长率的上限,表示 T( n)的增长最多像 f( n)增长的那样快,也就是说, 当输入规模为 n时, 算法消耗时间的最大值,这个上限的阶越低, 结果就越有价值。上界是对算法效率的一种承诺。
Ω(大Ω符号):下界
定义:若存在两个正的常数 c和 n0 ,对于任意 n≥ n0 , 都有 T( n)≥cg( n) ,则称T( n) = Ω( g( n) )(或称算法在 Ω( g( n) )中)。
大 Ω符号用来描述增长率的下限, 也就是说, 当输入规模为 n 时,算法消耗时间的最小值。与大 O 符号对称, 这个下限的阶越高,结果就越有价值。
Θ(theta) :紧确界
定义:若存在 3 个正的常数 c1 、 c2 和 n0 ,对于任意 n≥ n0 , 都有 c1f(n)≥ T(n)≥ c2f(n) , 则称 T( n) = Θ( f(n))。
Θ符号意味着 T( n)与 f( n)同阶, 用来表示算法的精确阶。

输入规模
输入规模

每秒10亿指令计算机的运行时间表
每秒10亿指令计算机的运行时间表

复杂度分析小窍门

  • 若两段算法分别有复杂度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结构的复杂度取决于if的条件判断复杂度和两个分枝的复杂度,总体复杂度取三者中最大

应用实例:最大子列和问题

给定N个整数的序列{A1,A2,......,,AN,},求函数f(i,j)=max{0,Σ(j,k=i)Ak} 的最大值。

算法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++ ) { /* i是子列左端位置 */
          for( j = i; j < N; j++ ) { /* j是子列右端位置 */
                  ThisSum = 0;  /* ThisSum是从A[i]到A[j]的子列和 */
                  for( k = i; k <= j; k++ )
                            ThisSum += A[k];
                            if( ThisSum > MaxSum ) /* 如果刚得到的这个子列和更大 */
                                      MaxSum = ThisSum;    /* 则更新结果 */
          } /* j循环结束 */
     } /* i循环结束 */
     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++ ) { /* i是子列左端位置 */
          ThisSum = 0;  /* ThisSum是从A[i]到A[j]的子列和 */
          for( j = i; j < N; j++ ) { /* j是子列右端位置 */
                  ThisSum += A[j];        /*对于相同的i,不同的j,只要在j-1次循环的基础上累加1项即可*/ 
                  if( ThisSum > MaxSum ) /* 如果刚得到的这个子列和更大 */
                            MaxSum = ThisSum;    /* 则更新结果 */
          } /* j循环结束 */    
     } /* i循环结束 */    
     return MaxSum;  
}

算法3:分而治之 T(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, MaxSum;
    int i;
    ThisSum = MaxSum = 0;
    for( i = 0; i < N; i++ ) {
          ThisSum += A[i]; /* 向右累加 */
          if( ThisSum > MaxSum )
                  MaxSum = ThisSum; /* 发现更大和则更新当前结果 */
          else if( ThisSum < 0 ) /* 如果当前子列和为负 */
                  ThisSum = 0; /* 则不可能使后面的部分和增大,抛弃之 */
    }
    return MaxSum;  
}

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


我们都要做生活的高手。