数据结构与算法概念

数据结构小白入门

数据结构指一组相互之间存在一种或多种特定关系的数据元素的集合,
当咱们须要在计算机中存储这些数据时,还涉及到数据的,组织方式,在计算机中的存储方式,以及定义在该数据上的一组操做;算法

  • 一组数据相互之间有某种关系
  • 组织方式
  • 存储方式
  • 以及可对其进行的一组操做
理解:

咱们学习的最终目的是要在计算机中存储一组数据,可是不得不先考虑数据的组织方式,在计算机中的存储方式,以及能够对这些数据进行的一组操做,固然了既然是一组数据必然代表了这写数据之间是存在想换的关联关系的;关系可能还会有多种;数据结构

例如:

一组数据:12345函数

组织方式:从小到大学习

存储方式:可以使用线性存储结构设计

操做:要取出最大的一个指针

数据结构研究方向

问题:code

  • 机外处理
  • 处理要求

建模:索引

  • 逻辑结构
  • 基本运算

实现:内存

  • 存储结构
  • 算法

基本术语

数据(Data):

​ 全部能被计算机处理的符号的集合element

数据元素(DataElement):

​ 是数据集合中的一个 个体,即数据的基本单位

数据项(DataItem):

​ 数据元素经常可分为若干个数据项,数据项是数据具备意义的最小单位

组织数据的三个层次:

数据(表)->数据元素(行)->数据项(字段)

实际问题中的数据成为原始数据

逻辑结构(LogicalStructure)

​ 数据元素之间的结构关系,如从小到大/一对一/一对多

物理结构(PhysicalStructure)

​ 也会叫作存储结构,指数据在计算机内的表示,逻辑结构在计算机中的具体实现

逻辑结构

常见的逻辑结构以下:

集合:

数据元素属于同一个集合,表示为R{}; 数据之间不存在特定关系

组织结构松散,任意两节点之间都没有邻接关系

线性:

除了起始节点d1和终端阶段dn外,每一个节点都有一个前驱和一个后继,表示为R={d1,d2...dn},数据之间存在先后顺序关系

各节点按逻辑关系排列,造成一条'链'

树状:

每一个元素最多有一个前驱,能够有多个后继,表示为(D,{R}),就像一个树干长了多个树枝

具有分支,层次特性,上层节点能够和下层度哦哦哦个节点相邻接,可是下层节点只能和一个上层节点邻接

图状:

任何两个元素之间均可以相邻接,表示为(D,{R})

注意:

逻辑结构

  • 与元素自己的形式,内容,无关

  • 元素的相对位置,无关

  • 与包含的节点个数,无关

存储结构

存储结构由 存储节点(每一个存储节点存放一个数据元素)节点之间的逻辑关系共同组成

反过来讲,一个完整的存储结构必须能够存储数据元素,以及元素之间的逻辑关系

存储结构分类 (缺图)

  • 顺序存储

    使用索引(相对起始位置)来表示数据的逻辑结构,数据被存储在一组连续的存储单元中

    特色:

    • 需预先分配长度,
    • 插入和删除慢,须要移动其余元素
    • 存取数据快捷,属于随机存储结构(可经过索引直接访问任意位置数据)
  • 链式存储

    借助元素地址指针表示数据的逻辑结构,每一个元素都会包含指向下一个元素的指针

    这种结构须要在节点上附加一个指针项,指出后继节点的位置,即每一个节点存储单元包含两个部分:[数据项,指针项]

    特色:

    • 动态分配内容,不须要预先分配内存
    • 插入删除快捷,不须要移动其余元素
    • 非随机存取结构(获取数据必须遍历前面的全部节点)
  • 索引存储(Map是否属于索引结构 很疑惑?)

    借助索引表来指示数据元素的存储位置

    索引表中包含了全部数据元素的地址,查询索引表可以快速的定位到须要的数据

    特色:

    • 索引是一份独立于实际存放数据,的数据结构(就像书的目录都在正文前面)
    • 索引须要占用额外的存储空间
    • 当实际数据发生改变时须要重建索引
    • 查询数据快
    • 插入修改,删除慢
  • 散列存储(哈希表)

    经过散列函数计算得出元素的位置

    特色:

    • 在散列函数不变时,相同数据会得出相同的位置
    • 存入顺序和取出顺序一般不一致
    • 没法完成随机存取(指定获取某个元素)

顺序和链式是最基本的也是最经常使用的存储结构,须要重点掌握,包括各自的优缺点,使用场景等

链式存储结构可实现树结构(逻辑结构)

运算

运算指的是某种逻辑结构上能够进行的操做;

运算分为两类:

  • 加工型运算

    会改变原逻辑结构的内容,顺序,个数等的操做

  • 引用型运行

    与加工型运算相反

常见运算:

创建,查找,读取,插入,删除

加工型:创建,插入,删除

引用型:读取,查找

算法

算法字面意思,计算方法;

算法规定了求解给定类型问题所需的全部处理步骤以及执行顺序,使得问题能在优先时间内机械的求解,一个算法就是对特定问题求解步骤的一种描述,再具体一点,算法是一段有穷的指令序列;算法必须能使用某种语言描述;

例如:

计算1到5的和 ,这个需求,如何来实现,第一步作什么,第二步作什么,整个计算步骤和执行顺序统称为算法,若是最终可以在有限的步骤下求出正确的和,那这就是一个合格的算法;

算法的特色:

  • 有穷性

    算法必须在执行有穷步后结束

  • 肯定性

    算法的每个步骤都必须是明肯定义的,

  • 可行性

    算法中的每一步都是能够经过已实现的操做来完成的

  • 输入

    一个算法具有0或多个输入

  • 输出

    一个算法有一个或多个输出,它们与输入有着特定的关系

算法与程序的区别,算法只是一种描述,可使用任何语言,可是一般不能直接被计算机运行,而程序则是算法的具体实现,使用某种计算机语言;

算法设计应知足的要求

  • 正确性:对于合法的输入产生符合要求的输出

  • 易读性:算法应该尽量易读,便于交流,这也是保证正确性的前提(注释可提升易读性)

  • 健壮性:当输入非法数据时,算法可做出适当反应而不至于崩溃(例如输出错误缘由);

  • 时空性:指的是算法的时间复杂度和空间复杂度,算法分析主要也是分析算法的时间复杂度和空间复杂的,其目的是提升算法的效率;

算法分析

解决同一问题的算法可能有多种,咱们但愿从中选出最优的算法,效率高的,占用空间小的,为此咱们就须要对算法进行评估和分析;

一般评估算法根据两个度量

  • 时间复杂度:算法运行完成所需的总步数(标准操做),一般是问题规模的函数

  • 空间复杂度:算法执行时所占用的存储空间,一般是问题规模的函数

肯定算法的计算量

  • 合理选择一种或几种操做做为'标准操做',无特殊说明默认以赋值操做做为标准操做;
  • 肯定算法共执行多少次标准操做,并将这次数规定为算法的计算量
  • 以算法在全部时输入下的计算量最大值做为算法的最坏状况时间复杂度
  • 以算法在全部时输入下的计算量最小值做为算法的最好状况时间复杂度
  • 以算法在全部时输入下的计算量平均值做为算法的平均状况时间复杂度
  • 最坏/平均状况时间复杂度均可做为时间复杂度,一般以最坏状况衡量;

注意:时间复杂度一般以量级来衡量,也就是说不须要精确的计算到底执行了几步,而是得出其计算量的数量级便可,并忽略常数,由于当数量级足够大时,常数对于计算量的影响能够忽略不计;

如: (n-1)(n-2) 数量级为 n^2

时间复杂度使用大O表示,如O(1)

案例:

1.
void aFunction(){
    int c = 10 + 20;
    int d = c * c;
        printf(d);
}

上列算法若以赋值运算做为标准操做,则该算法的计算量为2,其时间复杂度记为O(1),为何是O(1)呢,是由于2是一个常数,常数对于函数的增加影响并不大,因此计算量为常数时表示为O(1),按照这种方式,即便计算量为2000,一样记为O(1),称为常数

2.
void bFunction(int n){
  for(int i = 0;i < n;i++){ // n
    int c = 2 * i;// 1
    int d = 3 * i;// 2
  }
}

此时函数的循环次数由未知数n来决定,循环体内计算量为2,当n是一个天然数时,函数的计算量等于(n)(2),此时时间复杂度为O(n),不管用常数2对n进行加减乘除对于n的指数都没有影响,当n足够大时,内部的2次计算量能够忽略,因此记为O(n),称为线性阶

更粗陋的度量方法是函数体包含一层循环时记为O(n)

3.
void bFunction(int n){
  for(int i = 0;i < n;i++){
      for(int j = 0;j < i;j++){
      }
  }
}

外层循环次数为n,内层循环次数随着n的增加而增加且最大值为n-1次,那么整个函数的计算量为(n)(n-1),常数能够忽略,因此时间复杂度记为O(n^2) ,称为平方阶

粗陋的方法是有两层嵌套循环,且循环次数都随着n的增加而增加,因此是O(n^2),以此类推,可是要注意下面这种状况

4.
void bFunction(int n){
  for(int i = 0;i < n;i++){
      for(int j = 0;j < 3;j++){
      }
  }
}

此时内层循环的循环次数是固定(常数)因此不会影响计算量的数量级,时间复杂度记为O(n)

5.
void bFunction(int n){
  for(int i = 3;i < n;){
      i *= 3;
  }
}

此时函循环次数会随着3循环体中的常数3的的变化而变化,咱们能够用对数来表示,

假设循环次数为s,循环条件可表示为 s = 3^s < n;(即i自己为3,其次幂会不断的增加,但结果要小于n)

固然这里有个条件是i的初值必须和每次乘等的值相同,造成次幂的增加;

用对数表示为s = log3n,时间复杂度记为O(log3n),常数能够忽略,因此最后是O(logn)称之为对数阶

对数阶的数量级小于线性阶,由于若n的值相同,对数阶计算量必然小于线性阶

6.
void bFunction(int n){
 for(int i = 0;i < n;i++){
     for(int j = 0;j < n;j++){
         for(int k = 0;k < n;k++){
       }
     }   
 }
}

上述算法时间复杂度为O(n^3),3为常数,对应着循环的嵌套层数;也能够用O(n^C)表示,称为多项式阶

7.
void bFunction(int n){
  int num = 2;
  for(int i = 0;i < n;){ //O(n)
        num *= 2;
  }
  for (int j = 0;j<num;j++){ //O(n)
  }
}

上述函数输入的参数n将做为循环次数的指数,假设循环次数为s, s = 2^n,那么时间复杂度为O(2^n),

称之为指数阶,可记为O(C^n)

8.
void bFunction(int n)
{
    for(int i=0;i<n;i++){
          for(int j=0;j<n;j++){
          }
    }   
  
      for(int i=0;i<n;i++){
      }             
}

对于顺序运行的算法,总时间复杂度等于算法中最大时间复杂度,即O(n^2)

9.
void bFunction(int n)
{
  if(n % 2 ==0){
    for(int i=0;i<n;i++){
          for(int j=0;j<n;j++){
          }
    }   
  }else{
      for(int i=0;i<n;i++){
      }             
  } 
}

对于具有分支结构的算法,总时间复杂度等于算法中时间复杂度最大路径的复杂度,即O(n^2)

时间复杂度大小顺序

常数 < 对数阶 < 线性阶 < 平方阶 < 多项式阶 < 指数阶

O(1) < O(logn) < O(n) < O(n^2) < O(n^C) < O(C^n)

我的观点,如有错误敬请指出,谢谢!

相关文章
相关标签/搜索