数据结构与算法(1)- 基础概念

1,038 阅读10分钟

数据结构概念

程序设计 = 数据结构 + 算法.

  • 数据: 程序的操作对象,用于描述客观事物.数据是信息的载体,在计算机科学中是指所有能输入到计算机中并能被计算机程序识别和处理的符号集合。可以将数据分为两大类:一类是整数、实数等数值数据;另一类是文字、声音、图形和图像等非数值数据。

    数据的特点: 可以输入到计算机,可以被计算机处理

  • 数据结构: 指数据对象中的数据元素之间的关系。数据结构是带有结构的各『数据元素』的集合

  • 数据对象: 多个数据元素的集合(类似于数组)

  • 数据元素: 组成数据对象的基本单位。『数据元素是数据的基本单位』,『数据项是数据的最小单位』。

  • 数据项: 一个数据元素由若干数据项组成。数据项是组成数据元素的、有独立含义,不可分割的最小单位。 同一个逻辑结构中的数据元素所包含的数据项的个数要相同,且要求对应的数据项的类型也要一致。

结构体Teacher就是一种数据结构,定义的name、age是数据项,Teacher 创建的 t1、t2... 是具体的数据元素,由多个t1、t2...构成的数组tArray[10] 即是数据对象

  • 关系图

逻辑结构

描述数据与数据之间逻辑关系的数据结构.是从具体问题抽象出来的数学模型,从逻辑关系上来描述数据,与数据的储存无关,也就是说与数据本身的具体形式、内容、相对位置、个数无关。所以一些表面上很不相同的数据也可以有相同的数据逻辑结构。例如,学生表和图书表都可以看做线性结构。

集合结构

所有元素同属一个集合,没有先后顺序,也不存在其他关系

线性结构

数据元素之间存在着“一对一”的线性关系,它是一种有序数据的集合。

常用的线性结构有:线性表,栈,队列,双队列,数组,字符串。

特点:
1. 集合中必存在唯一的一个"第一个元素";
2.集合中必存在唯一的一个"最后的元素";
3.除最后元素之外,其它数据元素均有唯一的"后继";
4.除第一元素之外,其它数据元素均有唯一的"前驱"

非线性结构

每个数据元素可能与零个或者多个其他数据元素发生联系。即“一对多”关系。

常见的非线性结构有:二维数组,多维数组,广义表,树(二叉树等),图。

存储结构

数据的逻辑结构在计算机中的表示。数据最终要存储到计算机内存空间中,根据存储方式的不同分为顺序存储结构链式存储结构

顺序存储结构

数据元素存放在地址连续的存储单元里,其数据间的逻辑关系和物理关系是一致的。例如数组。 需要在内存中先开辟一块连续的存储空间。

链接存储结构

数据元素存放在任意的存储单元中,这组存储单元可以是连续的,也可以不是不连续的,数据元素的存储关系并不能反映其逻辑关系,需要借助指针来表示数据元素之间的逻辑关系。每一个元素包含了下一个元素的地址信息,通过指针保存。物理上不是一一对应的,而逻辑上是一一对应的。

除了顺序存储方法和链式存储方法外,有时为了查找方便还采用 索引存储方法(关键字与地址一一对应,属于广义上的映射) 和 散列表存储方法(Hash哈希-->存在函数(映射)关系,通过关键字查询)。

讨论数据结构的目的就是在计算机中实现对数据的操作,因此在讨论数据的组织结构时必然要考虑在该结构上进行的操作(或称运算)。

算法

算法(algorithm)是解决特定问题求解步骤的描述方法,在计算机中表现为有限的操作序列。算法与数据结构是相辅相成的。

解决某一类特定问题的算法可以选定不同的数据结构,而且选择恰当与否直接影响算法的效率。

算法特性

  1. 有穷性:一个算法必须在有穷步之后结束,即必须在有限时间内完成。
  2. 确定性:算法的每一步必须有确切的定义,无二义性,且在任何条件下算法只有唯一一条执行路径,即对于相同的输入只能得出相同的输出。
  3. 可行性:算法中的每一步都可以通过已经实现的基本运算的有限次执行得以实现。
  4. 输入:一个算法具有零个或多个输入,这些输入取自特定的数据对象集合。
  5. 输出:一个算法具有一个或多个输出,这些输出同输入之间存在某种特定的关系。

设计要求

  1. 正确性:算法的执行结果应当满足预先规定的功能和性能要求。正确性要求表明算法必须满足实际需求,达到解决实际问题的目标。
  2. 可读性:一个算法应当思路清晰、层次分明、简单明了、易读易懂。可读性要求表明算法主要是人与人之间交流解题思路和进行软件设计的工具,因此可读性必须要强。同时一个可读性强的算法,其程序的可维护性、可扩展性都要好得多,因此,许多时候人们往往在一定程度上牺牲效率来提高可读性。
  3. 健壮性:当输入不合法数据时,应能适当处理,不至于引起严重后果。健壮性要求表明算法要全面细致地考虑所有可能的边界情况,并对这些边界条件做出完备的处理,尽可能使算法没有意外的情况。
  4. 高效性:时间效率高和存储量低。高效性主要是指时间效率,即解决相同规模的问题时间尽可能短。

算法效率衡量方法

一个算法的优劣,可以从该算法在计算机上运行的时间所占存储空间来衡量和评判。

时间复杂度

大O表示法

若存在函数 f(n),使得当n趋近于无穷大时,T(n)/ f(n)的极限值为不等于零的常数,则称 f(n)是T(n)的同数量级函数。记作 T(n)= O(f(n)),称O(f(n)) 为算法的渐进时间复杂度,简称时间复杂度。

一套图 搞懂“时间复杂度”

推导时间复杂度遵守以下原则:

  • 如果运行时间是常数量级,用常数1表示;3->1 O(1)
  • 只保留时间函数中的最高阶项;n^3+2n^2+5 -> O(n^3)
  • 如果最高阶项存在,则省去最高阶项前面的系数。2n^3 -> n^3

一般情况下,随着规模n的增大,次数T(n)的增长较慢的算法为最优算法。
常见时间复杂度从小到大依次排列:
常数阶O(1)

/* 
    常数阶O(1) 无论代码执行了多少行,
    只要是没有循环等复杂结构,
    那这个代码的时间复杂度就都是O(1)
*/
// 
void testSum1(int n){
    int sum = 0;                //执行1次
    sum = (1+n)*n/2;            //执行1次
    printf("testSum1:%d\n",sum);//执行1次
}

void add(int x){
    x = x+1;// 执行1次
}

线性阶O(n)

//执行n次 
int sum = 0;               
for (int i = 0; i < n; i++) {
       sum += i;
}

平方阶O(n^2)

//x=x++; 执行n*n次 
void add3(int x,int n){
    for (int i = 0; i< n; i++) {
        for (int j = 0; j < n ; j++) {
            x=x+1;
        }
    }
}
   
//1+(n+1)+n(n+1)+n^2+n^2 = 2+3n^2+2n -> O(n^2)
void testSum5(int n){
    int i,j,x=0,sum = 0;           //执行1次
    for (i = 1; i <= n; i++) {     //执行n+1次
        for (j = 1; j <= n; j++) { //执行n(n+1)
            x++;                   //执行n*n次
            sum = sum + x;         //执行n*n次
        }
    }
    printf("testSum5:%d\n",sum);
}

立方阶(n^3) 相当于三层n循环

void testB(int n){
    int sum = 1;                         //执行1次
    for (int i = 0; i < n; i++) {        //执行n次
        for (int j = 0 ; j < n; j++) {   //执行n*n次
            for (int k = 0; k < n; k++) {//执行n*n*n次
                sum = sum * 2;          //执行n*n*n次
            }
        }
    }
}

对数阶 O(logn)

/*2的x次方等于n x = log2n  ->O(logn)*/
void testA(int n){
    int count = 1;         //执行1次
    while (count < n) {
        count = count * 2;
    }
}

线性对数阶O(nlogN)

/*将时间复杂度为O(logn)的代码循环N遍的话,那么它的时间复杂度就是 n * O(logN),也就是了O(nlogN)*/
void testA(int n){
    for(m=1; m<n; m++)
    {
        i = 1;
        while(i<n)
        {
            i = i * 2;
        }
    }
}

空间复杂度

程序运行从开始到结束所需的存储量与问题规模的对应关系,记做: S(n)=Ο(f(n)) 其中n为问题的规模(或大小)。 算法的时间复杂度和空间复杂度没有直接关系。

程序空间计算因素:
 1. 寄存本身的指令
 2. 常数
 3. 变量
 4. 输入
 5. 对数据进行操作的辅助空间
 
 考量算法的空间复杂度,主要考虑算法执行时所需要的辅助空间.
 int n = 5;
 int a[10] = {1,2,3,4,5,6,7,8,9,10};
 int temp;
 for(int i = 0; i < n/2 ; i++){
        temp = a[i];
        a[i] = a[n-i-1];
        a[n-i-1] = temp;
 }
以上例子只需要用到辅助空间temp,所以空间复杂度为O(1)

int b[10] = {0};
for(int i = 0; i < n;i++){
    b[i] = a[n-i-1];
 }
for(int i = 0; i < n; i++){
        a[i] = b[i];
}
这个例子中需要用到b(n)的辅助空间,所以空间复杂度为O(n)

练习题

计算下面程序的时间复杂度

1. 设n是非负整数
x = 2;
while (x < n/2)
    x = 2*x;

2. 
count = 0;
for(k=1;k<=n;k*=2)
    for(j=1;j<n;j++)
        count++

3.某算法的语句执行频度为:(3n + nlog2n + n^2 + 8),求时间复杂度。

 在计算时间复杂度时,可以忽略所有低次幂和最高次幂项的系数。时间复杂度为O(n^2)
4.  一维数组中有n个元素,则读取第i个数组元素的平均时间复杂度?

5. 下面算法将一维数组a中的n个数逆序存放到原数组中,求空间复杂度
for(i=0;i<n;i++)
    b[i]  = a[n-i-1];
for(i=0;i<n;i++)
    a[i]  = b[i];

6. 下面算法将一维数组a中的n个数逆序存放到原数组中,求空间复杂度
for(i=0;i<n/2;i++)
{
    t = a[i];
    a[i] = a[n-i-1];
    a[n-i-1] = t;
}

7. 
for(i=0;i<n;i++)
    for(j=0;j<m;j++)
        a[i][j] = 0;

8.
i = 1;
while(i<=n)
    i = i*3;

9. 
x = n;
y = 0;
while(x>= (y+1)*(y+1))
    y++;