zoukankan      html  css  js  c++  java
  • 重温数据结构01 基本概念

    什么是数据结构

    关于数据组织

    数据结构没有官方的统一定义。

    例1:如何在书架上摆放图书?

    图书的摆放应使2个相关操作方便实现:

    • 新书如何插入?
    • 怎么找到某本指定的书?
    1. 随便放
      • 操作1:哪里有空放哪里
      • 操作2:一本一本找,图书数量多的话会很累
    2. 按照书名的拼音字母顺序排放
      • 操作1:
      • 操作2:二分查找
    3. 把书架划分成几个区域,每块区域摆放指定类别的书籍;在每种类别内按照书名拼音字母顺序排放
      • 操作1:确定类别,二分查找确定位置,移出空位
      • 操作2:先确定类别,在二分查找

    那么如何分配空间?类别应该分多细?

    关于空间使用

    例2 实现PrintN函数,使得传一个正整数为N的参数后,能顺序打印从1到N的全部正整数。

    循环实现:

    void PrintN(int N){
        int i;
        for(i = 1;i <= N;i++){
            printf("%d
    ", i);
        }
        return;
    }
    

    递归实现:

    void PrintN(int N){
        if(N){
            PrintN(N - 1);
            printf("%d
    ", N);
        }
        return;
    }
    

    当输入为10、100、1000、10000时两个函数都可以正常输出,但是当输入为100000时,递归实现会内存溢出,所以只输出了100000。这就说明了:

    解决问题方法的效率跟空间的利用效率有关。

    关于算法效率

    例3:计算给定多项式在定点 (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));
        }
        return p;
    }
    

    标准多项式形式为:

    [f left(x ight) = a_0 + a_1x + cdotcdotcdot + a_{n-1}x^{n-1} + a_nx^n ]

    根据秦九韶算法可变形为:

    [f left(x ight) = a_0 + xleft(a_1 + xleft( cdotcdotcdot left(a_{n-1} + xleft(a_n ight) ight) cdotcdotcdot ight) ight) ]

    实现代码为:

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

    利用 clock() 函数可以计算从程序运行到 clock() 被调用时所耗费的时间,这个时间单位是 clock tick ,即时钟打点。常数 CLK_TCK 即表示机器时钟每秒所走的时钟打点数。常用的代码模板:

    #include <stdio.h>
    #include <time.h>
    
    clock_t start, stop;
    
    //记录被测函数的运行时间
    double duration;
    
    int main(){
        start = clock();//开始计时
        MyFunction();
        stop = clock();//结束计时
        duration = ((double)(stop - start)) / CLK_TCK;
        
        return 0;
    }
    

    以多项式 (f(x)=sum_{9}^{i=0} i cdot x^i) 为例,可用以下代码进行测试:

    #include <stdio.h>
    #include <time.h>
    #include <math.h>
    
    clock_t start, stop;
    
    //记录被测函数的运行时间
    double duration;
    
    #define MAXN 10
    #define MAXK 1e7
    
    double f1(int n, double a[], double x){
        int i;
        double p = a[0];
        for(i = 1 ; i <= n ; i++){
            p += (a[i] * pow(x, i));
        }
        return p;
    }
    double f2(int n, double a[], double x){
        int i;
        double p = a[n];
        for(i = n ; i > 0 ; i--){
            p = a[n - 1] + x * p;
        }
        return p;
    }
    
    int main(int argc, char const *argv[])
    {
        int i;
        double a[MAXN];
        for(i = 0 ; i < MAXN ; i++){
            a[i] = (double)i;
        }
    
        start = clock();
        for (int i = 0; i < MAXK; i++){
            f1(MAXN - 1, a , 1.1);
        }
        stop = clock();
        double duration1 = (((double)(stop - start)) / CLK_TCK) / MAXK;
        printf("ticks1 = %f
    ", (double)(stop - start));
        printf("duration1 = %6.2e
    ", duration1);
    
        start = clock();
        for (int i = 0; i < MAXK; i++)
        {
            f2(MAXN - 1, a , 1.1);
        }
        stop = clock();
        double duration2 = (((double)(stop - start)) / CLK_TCK) / MAXK;
        printf("ticks1 = %f
    ", (double)(stop - start));
        printf("duration2 = %6.2e
    ", duration2);
    
        return 0;
    }
    

    解决问题的效率与所使用的方法有关。

    抽象数据类型

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

    数据对象必定与一系列加在其上的操作相关联。

    完成这些操作的方法被称为算法

    抽象数据类型(Abstract Data Type,ADT) 用于描述数据结构。

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

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

    例4:“矩阵”的抽象数据类型定义

    • 类型名称:矩阵(Matrix)
    • 数据对象集:一个 (M imes N) 的矩阵 (A_{M imes N} = left(a_{ij} ight)(i=1,...,N))(M imes N) 个三元组 (<a, i, j>) 构成,其中 (a) 是矩阵元素的值, (i) 是元素所在的行号, (j) 是元素所在的列号。
    • 操作集:对于任意矩阵 (A、B、C in Matrix) ,以及整数 (i、j、M、N)
      • Matrix Create( int M, int N) :返回一个 (M imes N) 的空矩阵;
      • (cdotcdotcdot)

    算法(Algorithm)

    算法(Algorithm)的定义:

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

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

    void SelectionSort(int List[], int N){
        //将N个整数List[0]...List[N]进行非递减排序
        for(i = 0;i < N;i++){
            //从 List[i] 到 List[N-1] 中找到最小值,并将其所在位置赋值给MinPosition;
            MinPosition = SacnForMin(List, i, N-1);
            //将未排序部分的最小元素换到有序部分的最后位置;
            Swap(List[i], List[MinPosition]);
        }
    }
    

    算法的衡量指标

    • 空间复杂度 (Sleft(n ight)) ——根据算法写成的程序在执行时占用存储单元的长度
    • 时间复杂度 (Tleft(n ight)) ——根据算法写成的程序在执行时耗费的时间长度

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

    • 最坏情况复杂度 (T_{worst} (n))
    • 平均复杂度 (T_{avg} (n))

    [T_{avg} (n) leqslant T_{worst} (n) ]

    复杂度的渐进表示法

    • (T(n) = O(f(n))) 表示存在常数 (C > 0, n_0 > 0) 使得当 (n geqslant n_0) 时有 (T(n) leqslant C cdot f(n))
    • (T(n) = Omega (g(n))) 表示存在常数 (C > 0, n_0 > 0) 使得当 (n geqslant 0) 时有 (T(n) geqslant C cdot g(n))
    • (T(n) = Theta(h(n))) 表示同时有 (T(n) = O(h(n)))(T(n) = Omega(h(n)))

    复杂度分析技巧:

    • 若两段算法分别有复杂度 (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) imes T_2(n) = O(f_1(n) imes f_2(n)))
    • (T(n)) 是关于 (n)(k) 阶多项式,那么 (T(n) = Theta(n^k))
    • for 循环的时间复杂度 = 循环次数 ( imes) 循环体时间复杂度
    • if-else 结构的复杂度取决于 if 的条件判断复杂度和两个分支部分的复杂度,总体复杂度取三者中最大

    算法举例

    例1:最大子列和问题:给定 (N) 个整数的序列 ({A_1,A_2,cdotcdotcdot,A_n}) ,求函数 (f(i, j) = max{0, sum_{k=1}^{j}A_k }) 的最大值

    算法1(时间复杂度为 (T(n) = O(n^3))):

    int  MaxSubSeqSum1(int A[], int N){
        int ThisSum, MaxSum = 0;
        int i, j, k;
        //i为子列左端位置
        for(i = 0;i < N;i++){
            //j为子列右端位置
            for(j = i;j < N;j++){
                ThisSum = 0;
                // ThisSum为A[i]到A[j]的子列和
                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;
        //i为子列左端位置
        for(i = 0;i < N;i++){
            //ThisSum为A[i]到A[j]的子列和
            ThisSum = 0;
            //j为子列右端位置
            for(j = i;j < N;j++){
                //对于相同的i,不同的j,只要在j-1次循环的基础上累加1项即可
                ThisSum += A[j];
                if(ThisSum > MaxSum)
                    MaxSum = ThisSum;
            }
        }
        return MaxSum;
    }
    

    算法3 分而治之(时间复杂度为 (T(n) = O(nlog n))):

    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; /* 发现更大和则更新当前结果 */
              if( ThisSum < 0 ) /* 如果当前子列和为负 */
                      ThisSum = 0; /* 则不可能使后面的部分和增大,抛弃之 */
        }
        return MaxSum;  
    }
    

    作者: 暮颜 —— 衣带渐宽终不悔
    出处:https://www.cnblogs.com/vocaloid-fan1995/
    版权归作者和博客园共有,欢迎转载。但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
    转载请注明原文链接:https://www.cnblogs.com/vocaloid-fan1995/p/DataStructure01.html
    本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议知识共享许可协议进行许可。

  • 相关阅读:
    EXT性能优化(转载)
    xampp 下配置mail()函数发邮件
    从存储过程中返回结果 (转载)
    HDU1394 Minimum Inversion Number
    HDU4414 Finding crosses
    POJ1328 Radar Installation
    HDU3308 LCIS
    POJ2352 Stars
    POJ2513 Colored Sticks
    HDU4027 Can you answer these queries?
  • 原文地址:https://www.cnblogs.com/vocaloid-fan1995/p/DataStructure01.html
Copyright © 2011-2022 走看看