数据结构 java版

Data Structure

Table of contents

Algorithm

  1. 时间复杂度 T(n) = O(f(n))
    • 分析算法的复杂度,关键是要分析循环结构的运行情况;
    • 推到大O阶:
      1. 用常数1取代运行时间中的所有加法常数
      2. 在修改后的运行次数函数中,只保留最高阶项
      3. 如果最高阶项存在且不是1,则去除与这个项相乘的常数;
      得到的结果就是大O阶。
    • 分类:
      1. 常数阶:O(1),1代表常数
      2. 线性阶:O(n)
      3. 对数阶:O(log2n)
      4. 平方阶:O(n2)
    • 循环时间复杂度=循环体复杂度×循环运行次数;
    • 常见的时间复杂度所耗费时间排序:
    	O(1)<O(logn)<O(n)<O(nlogn)<O(n^2)<O(n^3)<O(2^n)<O(n!)<O(n^n)
    
    • 运行时间是最坏情况的运行时间
    • 平均时间复杂度:计算所有情况的平均值
  2. 空间复杂度
    • 计算算法所需的存储空间 S(n)=O(f(n));f为n所占存储空间的函数
    • 原地工作:所需空间相对于输入数据的比是常数

Linked List

基本概念

  1. 线性表:0个或多个数据元素的有限****序列
  2. 元素线性排列,除首末,都有前驱和后继;
  3. 长度:元素个数;n=0为空表
  4. 抽象数据类型的方法
    • 操作表
      1. 初始化,建立空表
      2. 清空表
    • 表属性
      3. 判断表是否为空,返回true/false
      4. 返回元素个数
    • 定位元素(值或位置)
      5. 返回第i个位置的值
      6. 定位与给定值相等的元素;返回序号,0为失败
    • 更新元素
      7. 在第i个位置插入新元素
      8. 删除第i个位置的元素,并返回其值

顺序存储:数组

* 线性表的长度小于等于数组长度
* 地址计算 
LOC(a~i~)=LOC(a~1~) + (i-1)*c (单个元素的存储单元数):存储时间性能为O(1):每个位置存储时间相同
* 存取数据时间复杂度O(1);插入和删除时间复杂度为O(n):适合元素个数不太变化的存取数据应用
1. 优点
	1. 无需为元素逻辑关系额外增加存储空间
	2. 可快速存取元素
2. 缺点
	1. 插入和删除要移动大量元素
	2. 当长度变化较大时,难以确定存储空间容量
	3. 易造成空间“碎片”

链式存储

* 存储单元可以不连续
* 需要额外存储后继元素的存储地址
* 结点:数据域+指针域(指针或链)
* 头结点:数据域可存储长度信息;指针域指向第一个结点(头指针);冠以链表的名字;非必须

单链表:

  • 数据域 + 每个结点只包含一个指针域
  • 对于插入和删除数据越频繁的操作,单链表的效率优势越明显
    | 操作类型 | 时间性能 | 空间性能 |
    | ---------- | ------------ | ---------------------- |
    | 查找 | 顺序结构O(1) | 需预分配空间,不好掌握 |
    | 查找 | 单链表O(n) | 有元素再分配,无限制 |
    | 插入和删除 | 顺序结构O(n) |
    | 插入和删除 | 单链表O(1) |

静态链表

  1. 概念:用数组描述的链表,游标实现法
  2. 元素:data域+cur域(存储指向元素的下标)
  3. 备用链表:未被使用的数组元素a[bi],数组空位
  4. 特点:
    • 数组a[0].cur --> b1(第一个空元素a[b1]的下标);相当于单链表的头结点
    • 数组a[n-1].cur --> v1(第一个有值元素a[v1]的下标)
    • 当静态链表为空时,a[n-1].cur=0
  5. 优点
    * 插入和删除时,只需修改游标,不需移动元素
  6. 缺点
    * 没有解决连续存储分配带来的表长难以确定的问题
    * 失去了顺序存储结构随机存取的特性
  7. 目的:给没有指针的语言以实现单链表的方法

循环链表

  1. 将单链表中终结点的指针指向头结点;
  2. 目的:从当中结点出发,访问链表的全部结点
  3. 头指针:使空链表和非空链表处理一致二增加的表头,不存储数据
  4. 尾指针:指向终点结点;rear.next.next即为开始结点,跳过头指针;方便快速查询开始和结尾两个结点;

双向链表

  1. 在单链表的结点中,增加指向前驱结点的指针域
  2. 也有带头指针的循环链表
  3. 目的:便于往前遍历元素;用空间换时间,便于对某个结点的前后结点操作

Stack Queue

栈:LIFO

  1. 限定仅在表尾进行插入和删除操作的线性表;后进先出,LIFO结构
  2. 栈顶:允许插入和删除的一端;
    栈底:另一端
    空栈:不含任何元素的栈
    进栈/压栈/入栈Push:插入操作
    出栈/弹栈Pop:删除操作
  3. 栈方法:
    1. InitStack(*S):初始化 ,建立一个空栈S
    2. DestroyStack(*S):若栈存在则销毁
    3. ClearStack(*S):清空栈
    4. StackEmpty(S):若栈为空返回true;反之
    5. GetTop(*S,e):若栈存在且非空,用e返回S的栈顶元素
    6. Push(*S,e):若栈S存在,插入新元素e到栈S中并成为栈顶元素
    7. Pop(S,e):删除栈S中栈顶元素,并用e返回其值
    8. StackLength(S):返回栈的元素个数
  4. 顺序存储栈:顺序栈
    1. 空栈时top置为-1;top必须小于stacksize;
    2. 数组末尾为栈顶,进出栈时间复杂度均为O(1);
    3. 两栈共享空间:
      1. 前提:数据类型一致;
      2. 应用情况:此消彼长的两个栈
      3. 逻辑:左栈底为数组始端,下标为0;右栈底为数组末端,下标为n-1;进出栈时,多加一个参数,指定栈号;
      4. 判断两栈全满:top1 + 1 = top2
  5. 链式存储结构:链栈
    1. 单链表中,栈顶指针top指向头部第一个元素;不要头指针;
    2. 空栈:top指向NULL
    3. 进出栈时间复杂度均为O(1);
    4. 与顺序栈对比:
      1. 顺序栈需提前确定存储空间大小,可能会浪费;但存取时定位方便;
      2. 链栈长度无限制,但增加指针,增加了开销
      3. 应用:如果元素变化大,用链栈;
  6. 栈的作用:对先进先出应用场景的封装
  7. 递归:类似两面镜子里的自己;
    1. 斐波那契递归:第n项等于第n-1和n-2项的和;
    2. 递归函数定义:直接或间接调用自己函数的函数
    3. 递归条件:满足条件时,递归不再引用自己而退出
    4. 抽象:存储某些数据,再以存储的逆序恢复这些数据供后续使用;所以编译器使用栈实现递归
  8. 应用
    后缀(逆波兰)表示法定义,数学表达式的求值
    1. 后缀表达式:符号在运算数字后面出现
    2. 规则:由左至右遍历表达式;遇到数组就进栈,遇到符号就让栈顶的两个数字出栈,进行运算,运算结果进栈;直到获得最终结果;
    3. 后缀表达式的推到:
      • 符号在中间的表达式为中缀表达式;
      • 规则:由左至右遍历中缀表达式;遇到数字就输出为后缀表达式;若是左括号直接进栈,直到遇到右括号则输出两个最近括号之间的运算符;若是运算符,则判断其与栈顶符号的优先级;优先级(乘除优先于加减)低于栈顶运算符,则栈顶元素依次出栈并输出,并将当前符号进栈;直到最终输出为止;

队列:FIFO

  1. 概念
    只允许在队尾进行插入操作(进),只允许在队头进行删除操作(出)的线性表
  2. 方法类似栈
  3. 循环队列
    1. 概念:把队列头尾相接的顺序存储结构
    2. 目的:解决假溢出(尾部下标溢出,头部却还有空位置)
    3. 规则:
      1. 当数组只有1个空元素时判断为满(不让头尾指针相等)
      2. 判断满的算法:(rear+1)%QueueSize == front
      3. 计算队列长度公式:(rear-front+QueueSize)%QueueSize
      4. 空队列判断:rear == front
    4. 缺点:
      1. 顺序结构时间性能不高,涉及元素移位
      2. 循环结构数组长度固定,容易满(溢出)
  4. 链式存储结构
    1. 链队列:只能尾进头出的线性表的单链表
    2. front指向头结点(无数据);rear指向队尾结点
    3. 空队列:front = rear --> 头结点
    4. 与循环队列比较:
      1. 循环队列长度固定,空间有时浪费
      2. 链队列长度不限,动态申请和释放结点存在时间开销;额外的指针域存在空间开销
      3. 队列长度确定用循环队列;反之

String

  1. 概念:由0个或多个字符组成的有限序列,即字符串
    1. 序列:串的相邻字符之间有前驱和后继的关系
    2. 空串:null string;即"";
    3. 空格串:只包含空格的串,有长度的
    4. 子串位置:子串第一个字符在主串中的序号
  2. 串的比较
    • 依次比较字符的编码
  3. 抽象类型
    • 类似于线性表,针对的是字符集
    • 针对子串的操作
  4. 方法
    1. 抽象方法:
    • 主串操作:创建、拷贝、清空、判断空、求长度
    • 子串操作:对比、联接、求截断、子串定位、用子串替换、插入子串、删除子串
    1. 高级语言扩展方法
    • 转大小写、从两端定位子串、去两边空格等
  5. 存储结构
    1. 顺序存储:类似于线性表,长度一般固定(堆可动态分配)
    2. 链式存储:连接串和串操作方便点,性能不如顺序结构
  6. 串的模式匹配
    1. 指子串的定位操作
    2. 朴素匹配算法:逐个字符比较,效率低 O((n-m+1)*m)
    3. KMP模式匹配:
      1. 去掉了主串回溯,复杂度为O(n+m);
      2. 适用于两串存在许多部分匹配的情况,效率会好些
    4. KMP改进算法

Tree

  1. 概念
    0个以上结点的有限集。0个结点为空树。
  2. 特点
    • 有且仅有一个特定根结点
    • 其余结点可分为m>0个互不相交的有限集,其本身是一棵树,成为根的子树
  3. 结点分类
    1. 结点的度Degree:结点拥有的子树数
    2. 叶结点Leaf/终端结点:度=0;
    3. 非终端结点/分支结点(内部结点):度!=0
    4. 树的度=树内各结点的度的最大值
  4. 结点间的关系
    1. 结点的孩子(Child):结点子树的根
    2. 孩子的双亲(Parent):孩子的上级结点(父母同体)
    3. 结点的祖先:从根到该结点所经分支上的所有结点
    4. 兄弟(Sibling):同一个双亲的孩子之间互称
    5. 结点的子孙:以某结点为根的子树中的任一结点都为该结点的子孙
    6. 特点:
      1. 根结点:无双亲,唯一
      2. 叶结点:无孩子,可多个
      3. 中间结点:一个双亲多个孩子
  5. 其他概念
    1. 层次level:从根开始定义起,根为第一层,根的孩子为第二层
    2. 树的深度Depth/高度:最大层次
    3. 有序树:将树中结点的各子树看成从左至右是有次序的,不能互换的;否则为无序的
    4. 森林Forest:m>=0颗互不相交的树的集合。对树中每个结点而言,其子树的集合即为森林
  6. 抽象类型
    1. 树中结点有相同的数据类型及层次关系
    2. 方法:
      1. 构造/销毁树;
      2. 创建/清空树;
      3. 判断空/求深度/求根结点
      4. 返回/设置结点值
      5. 求双亲/最左孩子/右兄弟
      6. 插入/删除子树
  7. 存储结构
    1. 双亲表示法:数据域+指针域(双亲在数组中的下标)
      1. 根结点指针域:置为-1
      2. 增加长子域:用于快速找到孩子结点;无孩子指针设为-1
      3. 增加右兄弟域:关注各兄弟之间的关系;无兄弟指针设为-1
    2. 孩子表示法
      1. 多重链表表示法:每个结点有多个指针域,每个指针域指向一棵树的根结点;子树数不定,所以指针域个数不固定
      2. 指针域不固定的解决办法
        1. 指针域的个数 = 树的度;各结点的度相差较大时浪费空间
        2. 指针域的个数 = 该结点的度;取专门位置存储该结点的指针域个数:运算上有时间损耗
      3. 孩子表示法
        1. 把每个结点的孩子结点排列起来,以单链表存储;则n个结点有n个孩子链表;
        2. 叶子结点单链表为空;
        3. n个头指针组成一个线性表,用顺序结构存储,一维数组
        4. 双亲孩子表示法:在孩子表示法基础上,表头结点中增加parent指针存储双亲结点所在的下标;方便找双亲结点
      4. 孩子兄弟表示法
        1. 结点的第1个孩子和右兄弟如果存在,一定是唯一的;
        2. 结点采用两个指针域,指向左孩子和右兄弟的下标
        3. 缺点:找双亲费劲;需增加双亲指针
        4. 优点:分解为二叉树

Binary Tree 二叉树

  1. 概念
    n>=0个结点的有限集合;空树为空集;非空树由一个根结点和两颗互不相交的、分别称为根结点的左子树和右子树的二叉树组成(一对多)
  2. 特殊二叉树
    1. 斜树:所有结点只有左子树或只有右子树;
      • 特点:每一层只有一个结点,结点个数与二叉树的深度相同
    2. 满二叉树:所有分支结点都有左子树和右子树,并且所有叶子都在同一层上
      • 特点:
        • 叶子只能出现在最下层
        • 非叶子结点的度为2
        • 同样深度的二叉树,满二叉树结点数最多,叶子数最多
    3. 完全二叉树:按层序编号,所有结点位置与满二叉树对应编号的结点位置相同;简言之,满二叉树中层序编号在完全二叉树中不能断;
      • 特点
        1. 同样结点数的二叉树,完全二叉树深度最小
        2. 叶子结点只能出现在最下面两层
        3. 最下层叶子一定集中在左部连续位置
        4. 倒数第二层若有叶子结点,一定在右部连续位置
  3. 二叉树的性质
    1. 第i层上至多有2i-1个结点
    2. 深度为k的二叉树至多有2k-1个结点
    3. 终端结点数 = 度为2的结点数 + 1
    4. n结点的完全二叉树的深度为|log2n|+1(|x|表不大于X的整数)
    5. 对层序编号i,
      1. 若i>1,双亲是结点|i/2|;
      2. 若2i>n,结点i无左孩子,为叶子结点,否则左孩子是结点2i
      3. 若2i+1>n,结点i无右孩子,否则右孩子是结点2i+1
  4. 存储结构
    1. 顺序存储结构:只用于完全二叉树
    2. 二叉链表:1个数据域+2个指针域(左孩子和右孩子)
      1. 三叉链表:增加双亲的指针域
  5. 遍历二叉树
    1. 从根结点出发,按某种次序访问所有结点,只访问1次且不重复
    2. 思路
      1. 前序遍历:若二叉树为空,则空操作返回;否则先访问根结点,然后前序遍历左子树,再前序遍历右子树
      2. 中序遍历:若二叉树为空,则空操作返回;否则先从根结点开始,中序遍历根结点左子树,然后访问根结点,再中序遍历右子树。
      3. 后序遍历:若二叉树为空,则空操作返回;否则从左到右,先叶子后结点的方式遍历访问左右子树;最后访问根结点。
      4. 层序遍历:若二叉树为空,则空操作返回;否则从树的第1层,即根结点开始访问;从上而下逐层遍历,在同一层中,按从左至右的顺序对结点逐个访问
    3. 遍历性质
      1. 已知前序和中序遍历,可以唯一确定二叉树
      2. 已知后续和中序遍历,同上
  6. 二叉树的建立
    1. 扩展二叉树:将二叉树每个结点的空指针引出虚结点,赋值为特定符号,如#;再遍历扩展二叉树;
    2. 建立:同上述遍历思路,运用递归,把访问改成写入;
  7. 线索二叉树
    1. 线索:指向前驱和后继的指针
    2. 线索链表:加上线索的二叉链表;
    3. 线索二叉树(Threaded Binary Tree):线索链表对应的二叉树
    4. 线索化:对二叉树以某种次序遍历使其成为线索二叉树;实质是将二叉链表中的空指针改为指向前驱或后继的线索;在遍历过程中修改空指针
    5. 左右标志域:存布尔值0/1;
      • 作用:区分左右指针的类型:是左、右孩子还是前驱、后继
    1. 添加头结点:等同于操作双向链表;时间复杂度为O(n)
    1. 适用于频繁遍历或查找时需要某种遍历中的前驱和后继的情况
  1. 树、森林和二叉树的转换
    1. 树转换为二叉树
      1. 在所有兄弟结点间加一条线
      2. 对每个结点,只保留长子连线,去掉其他孩子连线
      3. 层次调整,按层展示
    2. 森林转换为二叉树
      1. 森林由若干树组成,可理解为每棵树为兄弟
      2. 步骤:
        1. 把每棵树转换为二叉树
        2. 第一颗二叉树不动,从第2棵开始,依次把后一棵二叉树的根结点作为前一棵二叉树的根结点的右孩子,用线连起来;
    3. 二叉树转换为树(1的反向)
      1. 加线。若某结点的左孩子存在,则将该左孩子的n个右孩子结点都作为此结点的孩子,用线连接起来
      2. 去线。删除二叉树中所有结点与其右孩子的连线
      3. 层次调整。
    4. 二叉树转换为森林
      1. 判断二叉树能够转换成树还是森林:看这课二叉树根结点有没有右孩子;有就是森林,没有就是树
      2. 步骤
        1. 从根结点开始,若右孩子存在,则删除与右孩子的连线;依次删除剩下二叉树的右孩子连线;
        2. 再将分离后的二叉树转换为树即可
    5. 树与森林的遍历
      1. 树的遍历
        1. 先根遍历:即先访问树的根结点,然后依次先根遍历根的每颗子树
        2. 后根遍历:先依次后根遍历每颗子树,然后访问根结点
    6. 森林的遍历
      1. 前序遍历:先访问森林中第1棵树的根结点,然后再依次先根遍历根的每颗子树,在依次同样遍历剩下树的构成的森林;
        • 与二叉树的前序遍历结果相同,可借用其算法
      2. 后序遍历:先访问森林的第1棵树,后根遍历的方式遍历每颗子树,然后再访问根结点;再依次同样遍历剩下的森林
        • 与二叉树的中序遍历结果相同,可借用其算法
    7. 赫夫曼树
      1. 赫夫曼树
        • 涉及概念
          • 路径长度:两个结点间的路径上的分支数目(连线数目)
          • 树的路径长度 = 从树根到每个结点的路径长度之和
          • 结点的带权路径长度 = 结点到树根的路径长度 * 结点权重
          • 树的带权路径长度 = 所有叶子结点的带权路径长度之和
          • 赫夫曼树: 带权路径长度WPL最小的二叉树
        • 构造方法
          1. 先把有权值的叶子结点按照从小到大的顺序排成序列;
          2. 取头两个最小权值的结点作为新结点N1的子结点,小的为左孩子,大的为右孩子;N1结点的权值为两个叶子结点权值的和;
          3. 将N1替换前两个较小结点,插入序列中,从小到大排序;
          4. 重复步骤2 ,直到完成构造树
      2. 赫夫曼编码
        1. 前缀编码:任一字符的编码都不是另一个字符编码的前缀;长度不等;
        2. 解码:依赖赫夫曼树
          • 方法:
            1. 要编码的字符集为{d1,d2,d3...dn},各字符在电文中出现的频率集合为{w1,w2...wn},以d1...dn为叶子结点,以w1...wn为对应权值来构造赫夫曼树;
            2. 规定赫夫曼树左分支代表0,有分支代表1;
            3. 则从根结点到叶子结点所经过路径分支组成的0和1的序列即为该结点对应字符的编码

Graph

  1. 概念:多对多
    1. 图G由顶点的有穷非空集合V和顶点之间边的集合E组成,G(V,E)
    2. 不允许没有顶点Vertex(元素/结点)
    3. 顶点之间的逻辑关系用边来表示
  2. 各种图定义
    1. 无向图
      • 无向边Edge:没有方向的边;用无序偶对(Vi,Vj)表示
      • 无向图Undirected graphs:图中所有边都是无向边
    2. 无向完全图:无向图中,任意两个顶点之间都有边;
      • n个顶点对应n*(n-1)/2条边;
    3. 有向图
      • 有向边/弧Arc:边有方向;用有序偶对<Vi,Vj>表示
      • 有向图Directed graphs:所有边都是有向边的图
      • 弧<A,B>:从A指向B,A是弧尾,B是弧头;
    4. 有向完全图
      • 有向图中,任意两个顶点之间都存在方向互为相反的两条弧
    5. 简单图:不存在顶点到自身的边,且边不重复出现;n*(n-1)条边
    6. 稀疏图/稠密图:边/弧较少/多;
    7. 网Network:边带权Weight的图
    8. 子图Subgraph:顶点和边都为父图的子集
  3. 顶点和边的关系
    1. 无向图
      • 领节点Adjacent:某边(v,v')是图边的子集;边与两个顶点相关联;
      • 顶点v的度Degree:与v相关联的边的数目,记为TD(v)
      • 边数是顶点度总和的一半
      • 生成树:连通且n个顶点n-1条边
    2. 有向图
      • 图中的弧<v,v'>:v邻接到v';v'邻接自v;互相关联
      • 入度InDegree:以v为头的弧的数目,记为ID(v)
      • 出度OutDegree:以v为尾的弧的数目,记为OD(v)
      • TD(v) = ID(v) + OD(v)
      • 弧数 = 入度和 = 出度和
      • 有向树:一顶点为0,其余顶点入度为1
      • 有向图的生成森林:由若干棵有向树组成,含有图中全部顶点,但只有足以构成若干棵不相交的有向树的弧
    3. 路径Path
      • 顶点序列,序列中任一点和下一点相邻
      • 路径长度:路径上的边/弧的数目
      • 简单路径:路径序列中顶点不重复出现
    4. 回路/环Cycle:第一个和最后一个顶点相同的路径;
    5. 简单回路/简单环:除首末顶点,其余顶点不重复的回路
  4. 连通图 Connected Graph
    1. 对于无向图:
      • 连通的:两个顶点间有路径
      • 连通图:任意两个顶点都是连通的
      • 连通分量:无向图中的极大顶点数的连通子图
    2. 对于有向图:
      • 强连通图:两个顶点双向都有路径
      • 强连通分量:极大强连通子图
    3. 连通图的生成树:是极小的连通子图,含有图中全部顶点,但只有足以构成一棵树的n-1条边
  5. 抽象数据类型
    1. 方法
      1. 构造/销毁
      2. 定位顶点,返回/设置顶点值,返回邻接顶点,返回下个顶点
      3. 增加/删除顶点/弧
      4. 深度/广度优先遍历
  6. 存储结构
    1. 邻接矩阵Adjacency Matrix
      • 一个一维数组存储顶点信息,一个二维数组(邻接矩阵)存储图中的边或弧的信息;
      • 邻接矩阵为n*n方阵,若边/弧属于图,则方阵值为1,反之为0;
      • 无向图:
        • 边数组为对称矩阵,即aij=aji
        • 顶点的度 = 第i行/列的元素的和
        • 邻接顶点:第i行中元素为1对应的列号即为邻接顶点,即aij=1;
      • 有向图
        • 边数组为非对称矩阵
        • 入度 = 第i列元素的和
        • 出度 = 第i行元素的和
        • 是否存在弧:同无向图
      • 网图
        • 邻接矩阵为n*n方阵,若边/弧属于图,则方阵值为权重值;若边i=j,则方阵值为0;若边/弧不属于图,方阵值为大于所有权重值的不可能的值(代表不存在)
        • 邻接矩阵初始化复杂度 O(n^2)
    2. 邻接表
      1. 数组和链表结合的存储方法
      2. 目的:适用于稀疏图,节省空间
      3. 处理方法:
        1. 顶点用一维数组存储,数据域+指针域-->第1个邻接点的指针
        2. 顶点的所有邻接点构成线性表,用单链表存储
          1. 无向图即为顶点的边表:邻接点adjvex在顶点表中的下标+next边表中下个结点的指针
          2. 有向图称为顶点的弧尾的出边表:邻接表以顶点为弧尾来存储;可增加逆邻接表,建立以顶点为弧头的表
          3. 网图:在边表结点中增加weight数据域
    3. 十字链表Orthogonal List
      1. 顶点表:数据域+入边表头指针+出边表头指针
      2. 边表:弧起点的下标+弧终点下标+入边表指针域+出边表指针域
      3. 入边表指针域-->终点相同的下一条边;出边表指针域-->起点相同的下一条边;对网图,可增加权重域;
      4. 优点:结合邻接表和逆邻接表,方便查询顶点的弧尾和弧头;创建图的时间复杂度与理解表相同。优化有向图
    4. 邻接多重表
      1. 目的:优化无向图对边的操作
      2. 边表结点结构:某边依附的两个顶点下标+分别指向依附对应两个顶点的下一条边;顶点下标+指针+顶点下标+指针
    5. 边集数组
      1. 两个一维数组组成;一个存顶点,一个存边信息;
      2. 边数组元素:起点下标+终点下标+权
      3. 应用:对边依次进行处理的操作,不适合对顶点的操作
  7. 图的遍历 Traversing Graph
    从某顶点出发遍历其他顶点,不重复
    1. 深度优先搜索 Depth-First_Search DFS:递归过程,相当于树的前序遍历;对于非连通图:对所有连通分量进行DFS;对于有向图,算法可通用;
    • 适用于目标明确,找到目标为主要目的;
  2. 广度优先遍历 Breadth_First_Search BFS  
     * 类似于层序遍历,按层先进先出队列,逐层遍历
     * 适用于:不断扩大遍历范围以找到相对最优解
  1. 最小生成树 Minimum Cost Spanning Tree
    1. 概念:构造连通网的最小代价生成树
    2. 算法:
      1. 普利姆Prim算法
        1. 算法步骤:从顶点u0开始,找到代价最小的边(顶点为ui)并入最小生成树的边集合,同时将该对应顶点并入u0所在的顶点集合,直至顶点集合包含所有顶点。
        2. 时间复杂度为O(n2)
        3. 简述:进门找一点,按最近邻点走下一步,直至出口;
      2. 克鲁斯卡尔Kruskal算法
        1. 应用边集数组:数组元素有起始下标、终点下标、权
        2. 算法步骤
          1. 初始状态为n个顶点0条边的非连通图E,每个顶点自成一个连通分量
          2. 在E中选择代价最小的边,若该变依附的顶点落在E中不同的连通分量上,则将此边加入E,否则舍去选下一条代价最小的边;
          3. 直到所有的顶点都在同一连通分量上。
  2. 最短路径
    3. 对于网图,最短路径指两定点间经过的边上权值和最少的路径
    4. 对于非网图,最短路径指权值都为1时,权值和最少;即边数最少
    5. 起点叫源点,最后一个点叫终点
    6. 算法:
    1. 迪杰斯特拉Dijkastra算法:按路径长度递增的次序产生最短路径:每增加一个顶点,计算最短路径;然后计算下一条边;
      • 时间复杂度为O(n^3)
      • 适用:某顶点到所有顶点的最小路径,对有向图也适用
    2. 弗洛伊德Floyd算法:
      1. 结构:邻接矩阵+对应顶点的最小路径的前驱矩阵
      2. vw的权重 = min(vni,niw)
      3. 前驱矩阵下标 = 权小的边对应的起点下标
      4. 时间复杂度为O(n^3)
      5. 适用:所有顶点到所有顶点的最小路径,对有向图也适用
  3. 拓扑排序:解决一个工程能否顺利进行的问题
    1. 无环的图应用
    2. 顶点表示某活动的网 AOV网 Activity On Vertex Network:表示工程的有向图中,用顶点表示活动,用弧表示活动之间的优先/制约关系;
    3. 特点:
      1. 弧表示活动之间的制约关系
      2. 不存在回路(活动完成不是活动开始的前提条件,制约条件不成立)
    4. 定义:n个顶点的有向图中,若i,j之间有路径,则顶点序列中i必在j之前。则成为拓扑序列;
    5. 拓扑排序:对有向图构造拓扑序列的过程;若全部顶点都输出,则说明没有回路,为AOV网;若输出顶点少了,则说明有环路,不是AOV;
    6. 拓扑排序算法
      1. 基本思路:从AOV网中选择入度为0的顶点输出;删除此顶点及其弧;重复此步骤直至输出全部顶点或不存在入度为0的顶点为止;
      2. 数据结构:邻接表,增加入度值域,即入度+顶点值+边指针;
        • 边表:邻边下标+指向next邻边的指针
      3. 时间复杂度为O(n+e)
  4. 关键路径:工程完成需要的最短时间问题
    1. AOE: Activity On Edge Network;在一个表示工程的带权有向图中,用顶点表示事件,用有向边表示活动,用边上的权值表示活动的持续时间,这种有向图的边表示活动的网叫AOE网
    2. 始点/源点:没有入边的顶点;终点/汇点:没有出边的顶点
    3. 前提:活动之间制约关系没有矛盾
    4. 路径长度:各个活动持续的时间之和
    5. 关键路径:从源点到汇点具有最大长度的路径;在关键路径上的活动叫关键活动;缩短关键路径上的长度才能缩短整体工期;
    6. 关键路径算法思路:
      1. 事件的最早发生时间etv:earliest time of vertex:顶点vk的最早发生时间;
      2. 事件的最晚发生时间ltv:latest time of vertex:顶点vk的最早发生时间;超过此时间就会延误工期;
      3. 活动的最早开工时间ete:earliest time of edge:弧ak的最早发生时间
      4. 活动的最晚开工时间lte:latest time of edge:弧ak的最晚发生时间;不推迟工期的最晚开工时间
      5. 利用1、2可求得3、4;再比较ete[k] == lte[k]?来判断ak是否是关键活动;
    7. 算法
      1. 数据结构:邻接表+弧链表(增加权域,存放弧的权值)
      2. 时间复杂度为O(n+e)
      3. 深入学习***

Searching

  1. 查找概论
    • 查找表
    • 关键码(字段):关键字key(键值,数据项)
    • 主关键码:主关键字Primary key(数据项);次关键码:次关键字
    • 类型:
      • 静态查找表Static Search Table:只查找
      • 动态查找表Dynamic - -:删除或插入
    • 对应的数据结构 -- 查找结构
  2. 顺序查找 sequential search
    • 概念:线性查找:从头到尾逐一对比
    • 顺序表查找优化:设置哨兵,用while循环;复杂度为O(n)
  3. 有序表查找
    1. 折半查找binary search(二分查找):每次取中间记录查找;
      1. 前提:关键码有序,顺序存储
      2. 分成两个子树,查找其中一颗,提高效率O(logn)
      3. 对频繁变动的表不建议用,维护有序比较麻烦
    2. 插值查找
      1. 插值计算公式:根据查找值在有序列中的位置估算最近的下标,从下标开始重复
      2. 对元素大小极度分布不均的比较困难
    3. 斐波那契查找 Fibonacci Search
      1. 利用黄金分割原理
      2. F[k] = F[k-1] + F[k-2]来分割
  4. 线性索引查找
    1. 索引:把关键字和对应的记录相关联
    2. 分类:线性、树形、多级索引
    3. 线性索引:线性结构,即索引表--> 稠密、分块、倒排索引
    4. 稠密索引:一一对应,存储关键码要有序,便于插值索引;大数据不适用
    5. 分块索引:
      • 块间有序,块内可无序;
      • 每块对应一个索引项;索引项结构包括:最大关键码、块内项个数、块首指针
      • 适用于细分快无需有序的情况
    6. 倒排索引
      1. 索引项:次关键码+记录号表
      2. 记录号表-->相同次关键字的多有记录的记录号
      3. 本质:根据属性值来确定记录的位置
      4. 优点:查找记录快;缺点:记录号不定长
    7. 二叉排序树
      1. 动态查找表:需要在查找时插入和删除的查找表

Sort

posted @ 2022-02-27 15:22  蓝天可乐  阅读(170)  评论(0)    收藏  举报