数据结构与算法知识点总结

数据结构相关概念

数据:描述客观事物的符号,在计算机中是能被计算机处理和存储的符号的集合

数据元素:数据处理中的基本单位(包含多个数据项)
数据项:最小的数据单位
数据对象:由类型相似的数据元素组成的集合

数据结构:一组数据对象的逻辑关系以及存储关系

逻辑结构:

  • 集合 set/bag 元素之间不一定有联系
  • 表 list/hash 1:1关系
  • 树 tree 1:n关系
  • 图 graph n:m关系

存储结构(物理结构):

  • 连续存储
  • 链式存储

抽象数据结构ADT包含

  • 字段
  • 函数

算法分析

程序 = 数据结构 + 算法
算法:解决问题的方法步骤描述

  • 输入 0/n
  • 输出 n
  • 有穷性
  • 确定性
  • 可行性

算法评估

  • 事后统计:先实现,记录运行的时间和空间

  • 事前评估:根据程序中指令的数量,不需要运行程序,直接估算*

    • 时间复杂度
    • 空间复杂度

算法分析:高德纳的《计算机程序设计的艺术》中首次提出

时间复杂度

程序数据规模 n 与运行时间 t 构成的函数关系

  • O(1) 常数级:数组下标运算、链表插入删除
  • O(logN) 对数级:二分搜索、二叉树操作
  • O(N) 线性级:线性表插入、删除、查找
  • O(NlogN) 线性对数:快速排序、归并排序
  • O(N^2) 平方级:插入、选择排序
  • O(N^3) 立方级
  • O(2^N) 指数级:斐波那契数列的递归求解

规则:

  • 看结构

    • 一层循环:O(logN)、O(N)
    • 两层循环:O(NlogN)、O(N^2)
    • 一个递归:O(N)
    • 两个递归:O(2^N)
  • 忽略低阶(或常数)项,保留高阶项

  • 忽略系数

使用空间换时间

  • 哈希表
  • 数据库索引

空间复杂度

程序数据规模 n 与 内存开销的关系

表/列表list:多个数据元素,一次存储,每个元素有一个序号index,可以按照序号随机访问,每个元素有唯一的前驱或后继

前驱:元素的前一个元素
后继:元素的后一个元素

线性表:存储结构是连续的,线性表一次性分配连续空间
链表:存储结构是链式的,当没有那么多连续的空间时,链表可以利用零散的空间

链表

  • 单向链表
  • 双向链表
  • 循环链表

    // 单链表
    struct node
    {
        int data;           // 4
        struct node *next;  // 8
    }

    // 双链表
    struct node
    {
        int data;           // 数据域
        struct node *next;  // 后继
        struct node *prev;  // 前驱
    }

单向链表插入

  • 头插法:新节点添加到头指针后面 O(1)
  • 尾插入法:新节点添加到最后一个节点的后面 O(n)

双向链表

  • 方案一:只用头指针
  • 方案二:使用两个指针,头、尾

循环链表

  • 最后一个元素或尾指针的后继指向了头指针

头尾指针都不存数据

    struct stack
    {
        int *data;
        int capacity;
        int top;
    }

栈是一种操作受限的表,只能从一端操作 ,元素进出顺序是LIFO 后进先出

操作

  • push 压栈
  • pop 出栈
  • clear 清空

应用:

  • 浏览器前进后退
  • 编译器,进程、函数、的存储,调用栈
  • 语法检测、括号匹配
  • 中缀转后缀表达式
  • 后缀表达式求值
  • 进制转换

队列

队列是操作受限,两端操作,队头出队,队尾入队的表

应用场景

  • 网络请求存储在队列中
  • 事件处理,GUI中系统中event存入队列进行分发
  • Messege Queue(MQ) 消息对列,同步机制,Kafka
  • 线程池

操作:

  • enqueue/add/put 入队
  • dequeue/poll 出队,轮询
  • peek 读取队列中第一个元素
  • size 队列大小
  • clear 清空
typedef struct node
{
    int data;           // 数据域
    struct node *prev;  // 前驱
    struct node *next;  // 后继
} Node;

// 一个指针
// typedef struct node Link;

// 两个指针
typedef struct queue
{
    struct node *head;  // 头指针
    struct node *tail;  // 尾指针
    int size;           // 大小
} Link;

树是每个节点有至少一个前驱节点(父节点),有0~n个后继节点(子节点)的的数据结构
包含一个根节点,多个子树
有n个节点和n-1条边

根节点:树中唯一没有父节点的节点

子节点:一个节点的后继节点成为该节点的子节点

叶子结点:没有子节点的节点

路径:一个节点到另一个节点的边构成的集合,任意两个节点的路径唯一

深度:根节点到节点的边数的最大值

高度:叶子结点到节点的节点边的数量

兄弟节点:有共同父节点的节点,互为兄弟节点

    //普通树的定义
    struct node
    {
        int data;                 // 数据域
        struct node *first_child; // 第一个子节点
        struct node *next_brother;// 下一个兄弟节点
    }

二叉树

二叉树是节点子树最多有两个的树

完全二叉树:除了最后一层,每一层节点数都是2n(层数)个

叶子节点为n,完全二叉树的最小最大节点数为n-1,n

满二叉树:每一层节点数都是2i(i为层数,根为0层)个,总共2n+1-1个节点(n为深度)
最大(小)堆:父节点数据都比子节点大(小)的完全二叉树

完全二叉树、满二叉树和堆都可以基于数组实现,由于数组下标访问时间复杂度是O(1),可以很方便地定位指定节点n的左子节点(2i+1)、右子节点(2i+2)、父节点((i-1)/2)

二叉搜索树:为了实现快速搜索,一般左子树比右子树小的二叉树

数的遍历:

  • 先序遍历 根->左—>右
  • 后序遍历 左—>右->根
  • 中序遍历 左->根—>右
  • 层序遍历 每层依次从左至右遍历
先序:a b d h i e j k c f g
后序:h i d j k e b f g c a
中序:h d i b j e k a f c g

实现方式

  1. 顺序存储,基于数组tree[]实现,数组下标访问时间复杂度是O(1)
  • 根节点存储在tree[0]
  • 节点n的相关节点的索引:
    • 父节点 (n-1)/ 2
    • 子节点 2n+1, 2n+2
  1. 链式实现
    struct node
    {
        int data;                 // 数据域
        struct node *left_child; // 左子节点
        struct node *right_child;// 右子节点
    }

  • 入堆:每次添加在数组末尾,然后从下往上调整,比父节点大(小)就与其互换
  • 出堆:根节点出堆,取数组末尾放在根节点,然后从上往下调整,每次与较大(小)的子节点换位置
12 9 23 5 17 42 56 11 20 3

                    56 
              20             42
        17         9      12        23
     5     11    3

二叉搜索树

为了实现快速搜索,一般左子树比右子树小的二叉树

12 9 23 5 17 42 56 11 20 3

                    12
            9                 23
        5     11        17           42
    3     _          _    20     _      56

二叉搜索树的左右子树的高度相差太大,就会造成操作的时间复杂度降低到O(n),所以需要构造平衡二叉树,降低高度差

平衡二叉树

平衡二叉树是根节点的左右两个子树的高度最多相差1的二叉搜索树

实现:

  • AVL树
  • 红黑树
  • 数堆

AVL 树

AVL树是能自适应的平衡二叉树
每个节点都有一个平衡因子(左右子树的高度差)
不平衡树转换成平衡树:

  • 左左 新加的节点加在子树的子节点,右旋(根节点变成左子树的右节点)
  • 左右 新加的节点加在子树的子节点,先变成左左(左旋,左子树根节点变成左子树的左子节点,左子树的右节点变成左子树的根),再右旋
  • 右右 新加的节点加在子树的子节点,左旋(根节点变成右子树的左节点)
  • 右左 新加的节点加在子树的子节点,选变成右右(右旋,右子树根节点变成有子树的右子节点,右子树的左节点变成左子树的根),再左旋

红黑树

红黑树满足一下条件:

  1. 每个节点要么是红色,要么是黑色。
  2. 根节点是黑色。
  3. 每个叶子节点(NIL 节点,空节点)是黑色的。
  4. 如果一个节点是红色的,则它的两个子节点都是黑色的
  5. 从每个叶子到根的所有路径上不能有两个连续的红色节点
  6. 从任一节点到其每个叶子的所有简单路径都包含相同数目的黑色节点。

哈夫曼树

用于文本数据编码、压缩的数据结构

常用编码
定长编码:

  • ANSI 1字节
  • GB2312 2字节

变长编码:

  • UTF-8 英文1字节 中文3字节

短信:140字节

ANSI 8bit * 140
SMS 7bit * 160

哈夫曼树构建

按频率构建,每次将最小的两个子树合并,到左子树的度编号0,右子树的度编号1,从根到叶子节点上的编号序列就是该叶子结点对应字符的编码

b树与b+树

b树

一个节点存储多个索引值(键值),n个键n+1个分支,大幅降低树的高度,适合做硬盘的数据加载
缺点是每个节点的比较次数较多

构造过程:二叉树是自上而下,b树是自下而上

b+树

b树的扩展,在分裂时保留分出的数据,适合用作数据库的索引(MySQL InnoDB引擎)

b树b+树
数据所有节点都有数据数据在叶子结点,非叶子节点只是保存索引值
叶子结点节点之间无指向叶子结点之间构成双向列表
访问随机范围访问

HashTable

基于数组定义的散列表

哈希函数:

  • md5
  • sha1
  • sha256
  • 自定义散列函数:由键生成尽量不重复的整数,作为索引

冲突处理:

  • 线性探测
  • 平方探测
  • 二次hash
  • 链表分离 * 链表太大就转用二叉搜索树

负载因子:0.6~0.8,索引达到容量的指定比例时,扩容,扩容后所有数据重新hash

redis数据库本质上就是一个hashtable

图是一组节点V和边E组成的集合G(V,E)

图的的分类:

  • 无向图

  • 有向图

  • 带权图

  • 无权图

一个图有n个顶点,最多n(n-1)/2条边

图的表示

  1. 邻接矩阵 n * n的矩阵,n为顶点个数,适合稠密图
  A B C D
A 0 3 0 1
B 3 0 1 0
C 0 1 0 1
D 1 0 1 0
  1. 邻接表 由节点组成的数组,数组元素是指向节点的链表,链表存储节点的度(边),适合稀疏图
|0 A| -> B -> C -> D
|1 B| -> C
|2 C| -> D
|3 D| -> A

稠密图|E|趋近于 |V|2
稀疏图|E|远小于 |V|2

  1. 二元组
    表示:
    (前驱, 后继)无向边
    <前驱, 后继> 有向边

拓扑排序

有向无环图中,多个任务中存在依赖关系,如何确定执行顺序?
拓扑排序:每次删除并输出一个入度为0的顶点,并删除所有与之相连的边,直到所有顶点都被删除,由此形成的线性排序序列就是拓扑排序

V1 v2 v3 v4
0  2  2   1

Queue: v1

最小生成树

使用n-1条边连接n个顶点,且边权总和最小

  1. prime算法
  2. kruskal算法 贪心

最短路径

  1. dijkstra算法 单源最短路径
  2. floyd算法

稀疏矩阵转换

int a[4][5]:
 0 0 0 1 0
 3 0 0 0 0
 0 0 0 0 0
 0 0 1 0 0

n * m -> (n + 1) * 3

int a[5][3]:
 4 5 3 // 行 列 值的数量
 0 3 1 // 行号 列号 值
 1 0 3
 3 2 1

排序

  1. 冒泡排序
  2. 插入排序
  3. 选择排序
  4. 希尔排序 插入排序的优化,通过选择一个增量序列来实现的,使得数据项跳跃式地比较和交换,从而减少了数据移动的次数,常见的增量序列有每次折半
  5. 归并排序
  6. 堆排序
  7. 快速排序

快速排序的优化

  1. 三数取中法:中间、开头、末尾3个数取中值
  2. 随机数取中法 随机取三个数取中值

串操作

KMP算法

用于匹配字符串(模式匹配)O(n+m)
关键是构建前缀表/next数组:记录了模式串中每个I子串的最大前缀后缀公共序列的长度,表示每次匹配失败时,应该跳转多少位置继续匹配

前缀:不包括最后一个字符的所有子串
后缀:不包括第一个字符的所有子串

目标串  aabaabacxyz
模式串  aabac
前缀表:01230
       模式串
       aabac

next表的构建

  1. 初始化数组为零,从next[1]开始计算next[i],len记录当前最长公共前后缀长度
  2. 如果模式串的i位置字符和模式串的第len位置字符相等(a[len - 1] == a[ i]),则next[i] = len + 1(等同于next[i-1]+1),len++,++i,否则看len的值操作
  3. 如果len = 0,则next[i] = 0,++i
  4. 否则,len = next[len - 1](缩小范围),继续循环

随机不重复数

洗牌算法:

  1. 初始化一个1~n的数组a[]
  2. 随机生成一个[0,n-1]的数作为索引,与第n一个数交换
  3. 递减n,重复2,直到n=1
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值