数据结构_C++语言描述_高教出版社

contents

    • 前言
    • 一、绪论
      • 1.1 数据分析+结构存储+算法计算
        • 1.1.1 逻辑结构
        • 1.1.2 存储结构
        • 1.1.3 算法实现
      • 1.2 数据类型
      • 1.3 算法方法
    • 二、线性表
      • 2.1 线性表的逻辑结构
      • 2.2 线性表的存储结构
        • 2.2.1 顺序存储结构
        • 2.2.2 链式存储结构
      • 2.3 线性表的操作算法
        • 2.3.1 顺序表的操作算法
        • 2.3.2 链表的操作算法
    • 三、栈和队列
      • 3.1 栈
        • 3.1.1 栈的基本概念
        • 3.1.2 栈的存储结构
        • 3.1.3 栈的操作算法
        • 3.1.4 栈的应用
      • 3.2 队列
        • 3.2.1 队列的基本概念
        • 3.2.2 队列的存储结构
        • 3.2.3 队列的操作算法
        • 3.2.4 队列的应用
    • 四、串
      • 4.1 串的基本概念
      • 4.2 串的存储结构
        • 4.2.1 串的顺序存储
        • 4.2.2 串的链式存储
      • 4.3 串的操作算法
        • 4.3.1 串的基本操作算法
        • 4.3.2 串的模式匹配
    • 五、数组和特殊矩阵
      • 5.1 数组
        • 5.1.1 数组的基本概念
        • 5.1.2 数组的存储结构
      • 5.2 特殊矩阵的压缩存储
        • 5.2.1 对称矩阵的压缩存储
        • 5.2.2 三角矩阵的压缩存储
        • 5.2.3 对角矩阵的压缩存储
        • 5.2.4 稀疏矩阵的压缩存储
    • 六、广义表
      • 6.1 广义表的概念
      • 6.2 广义表的存储结构
        • 6.2.1 广义表中结点的结构
        • 6.2.2 广义表的存储结构
      • 6.3 广义表的操作算法
        • 6.3.3 广义表的其他操作算法
    • 七、树和二叉树
      • 7.1 树的概念和性质
        • 7.1.1 树的定义
        • 7.1.2 树的基本术语
        • 7.1.3 树的基本性质
      • 7.2 二叉树的概念和性质
        • 7.2.1 二叉树的定义
        • 7.2.2 二叉树的基本性质
      • 7.3 二叉树的存储结构
        • 7.3.1 二叉树的顺序存储结构
        • 7.3.2 二叉树的链式存储结构
      • 7.4 二叉树的遍历
        • 7.4.1 二叉树遍历的概念
        • 7.4.2 二叉树遍历算法
        • 7.4.3 二叉树的构造和析构:star:
      • 7.5 二叉树的其他操作算法
      • 7.6 线索二叉树:star:
        • 7.6.1 线索二叉树的概念
        • 7.6.2 线索二叉树的存储结构
        • 7.6.3 线索二叉树的操作算法
      • 7.7 树的存储结构与算法
        • 7.7.1 树的存储结构
        • 7.7.2 树的操作算法
      • 7.8 哈夫曼树与哈夫曼编码:star:
        • 7.8.1 哈夫曼树的定义
        • 7.8.2 操作算法
    • 八、图
    • 九、查找
      • 9.1 静态查找表
        • 9.1.1 顺序查找
        • 9.1.2 折半查找
        • 9.1.3 分块查找
      • 9.2 动态查找表
        • 9.2.1 二叉排序树
        • 9.2.2 平衡二叉树
      • 9.3 Hash 查找
        • 9.3.1 构造表
        • 9.3.2 查找表
    • 十、排序
      • 10.1 排序的基本概念
      • 10.2 冒泡排序
      • 10.3 选择排序
      • 10.4 插入排序
      • 10.5 希尔排序
      • 10.6 快速排序
      • 10.7 堆排序
      • 10.8 归并排序

前言

博客起源:本博客记录了个人学习数据结构期间做的一些笔记,其中含有PPT的截图或者个人的一些见解与思考,多有不足还望包含与指出,祝各位学习愉快

参考教材:《数据结构 C++语言描述》高等教育出版社

相关代码:DataStructure

笔记范围:全书

一、绪论

1.1 数据分析+结构存储+算法计算

1.1.1 逻辑结构

对于当前的数据之间的关系进行分析,进而思考应该如何存储

  • 集合
  • 线性结构
  • 树形结构
  • 图结构
1.1.2 存储结构

设计一定的方法存储到程序中

  • 存储内容
    • 数值存储
    • 数据与数据之间关系域的存储
  • 存储方式
    • 顺序存储
    • 链式存储
    • 树形存储
    • 图存储
1.1.3 算法实现

设计算法计算实现

1.2 数据类型

约束:值集 + 运算集

数据类型 D a t a T y p e Data\ Type Data Type(DT):一般编程语言已经实现好了

抽象数据类型 A b s t r a c t D a t a T y p e Abstract\ Data\ Type Abstract Data Type(ADT):数据结构+算法操作

1.3 算法方法

  1. 正确性

  2. 健壮性(鲁棒性):对于不合法、异常的输入也有处理能力

  3. 可读性

  4. 可扩展性

  5. 高效率

    1. 空间复杂度

    2. 时间复杂度 T ( n ) = O ( f ( n ) ) T(n)=O(f(n)) T(n)=O(f(n)),其中有三种表示时间复杂度的公式

      • O ( ) O() O() upper bound:最坏的时间复杂度
      • Ω ( ) \Omega() Ω() lower bound:最好的时间复杂度
      • Θ ( ) \Theta() Θ() average bound:平均时间复杂度

二、线性表

image-20230920231245137

2.1 线性表的逻辑结构

有一个头结点,尾结点,且每一个结点只有一个前驱结点和一个后继结点。

2.2 线性表的存储结构

2.2.1 顺序存储结构

存储在一维地址连续的存储单元里

特点:逻辑位置相邻,物理位置也相邻

数据结构:一个一个一维数组 + 一个长度变量 n

template<class T, int MaxSize>
class SeqList
{T data[MazSize];int length;
public:...
}

顺序表可以直接存储元素与关系

链表的元素存储也是可以直接实现的,但是关系要通过指针域来实现

2.2.2 链式存储结构
  1. 单链表:默认有一个头结点,不存储数据

  2. 循环链表

  3. 双向链表

2.3 线性表的操作算法

2.3.1 顺序表的操作算法
  1. 顺序表初始化构造

  2. 求顺序表长度

  3. 按位查找

  4. 按值查找

  5. 遍历顺序表

  6. 插入

  7. 删除

2.3.2 链表的操作算法
  1. 单链表初始化构造

    head = new Node<T>;
    head->next = nullptr;
    
    • 头插法

      head = new Node<T>;
      head->next = nullptr;
      
    • 尾插法

      head = new Node<T>;
      rear = head;
      
  2. 求单链表长度

  3. 按位查找

  4. 按值查找

  5. 遍历单链表

  6. 插入

  7. 删除

  8. 单链表的析构函数

  9. 其他操作

  10. 双向链表操作

    • 插入

      image-20230925092012868
      // 插入当前结点 s
      s->prior = p;
      s->next = p->next;
      p->next->prior = s;
      p->next = s;
      
    • 删除

      image-20230925091955866
      // 删除当前结点 p
      p->next->prior = p->prior;
      p->prior->next = p->next;
      

三、栈和队列

3.1 栈

3.1.1 栈的基本概念
image-20230928095521363

卡特兰数:假设 f ( k ) f(k) f(k) 表示第 k 个数最后一个出栈的总个数,则 f ( k ) = f ( k − 1 ) f ( n − k ) f(k)=f(k-1)f(n-k) f(k)=f(k1)f(nk)
f ( n ) = ∑ k = 1 n f ( k − 1 ) f ( n − k ) = 1 n + 1 C 2 n n f(n) = \sum_{k=1}^{n} f(k-1) f(n-k)=\frac{1}{n+1} C_{2n}^{n} f(n)=k=1nf(k1)f(nk)=n+11C2nn

3.1.2 栈的存储结构
  1. 顺序存储

    image-20230928095919380
  2. 链式存储

    image-20230928100849337
3.1.3 栈的操作算法
  1. 顺序栈的操作
  2. 链栈的操作
3.1.4 栈的应用
  1. 括号匹配

  2. 算数表达式求值

    • 中缀表达式求值

      双栈思路,算符优先法

      • 遇到数字,直接入数栈

      • 遇到符号

        • 如果是括号,左括号直接入栈,右括号进行运算直到遇到左括号
        • 如果是算符,在入算符栈之前,需要进行运算操作直到算符栈顶元素等级小于当前算符等级
    • 中缀表达式转后缀表达式

      算符栈即可

      后缀先遇到就直接计算的运算符 → \to 中缀表达式需要先算的运算符,于是转化思路就是:

      • 遇到数字,直接构造后缀表达式
      • 遇到算符
        • 如果是括号,左括号直接入栈,右括号进行后缀表达式构造直到遇到左括号
        • 如果是算符,在入算符栈之前,需要进行后缀表达式构造操作直到算符栈顶元素等级小于当前算符等级
    • 后缀表达式求值

      数栈即可

      遇到数字直接入数栈,遇到算符直接进行运算

  3. 栈与递归

    递归工作栈

3.2 队列

3.2.1 队列的基本概念

先进先出

3.2.2 队列的存储结构
  1. 顺序存储

    image-20231007091939711
    image-20231007091951161
  2. 链式存储

    image-20231007090752380
3.2.3 队列的操作算法
  1. 循环队列的操作

    循环队列的三个注意点

    • 解决假溢出:采用循环队列,即在入队的时候不是单纯的指针 +1,而是+1后 % MaxSize
    • 解决队空队满的冲突(真溢出):
      1. 浪费一个元素空间:测试rear+1是否等于head,
      2. 设置一个辅助标志变量
      3. 设置一个计数器
    1. 初始化:头尾全部初始化为0
    2. 入队push
    3. 出队pop
    4. 取队头front
    5. 长度size
    6. 队空empty
  2. 链队列的操作

3.2.4 队列的应用
  1. 报数问题:报到 0 的出队,报到 1 的重新入队,求解出队顺序

  2. 迷宫最短路问题:开一个记忆数组 d [ i ] [ j ] d[i][j] d[i][j] 表示从起点 ( 0 , 0 ) (0,0) (0,0) 到终点 ( i , j ) (i,j) (i,j) 点的最短路径的长度。可以将求最短路看做一个波心扩散的物理场景,队列中的每一个点都可以作为一个波心,从而实现“两点之间线段最短”的物理场景

    • 为什么用队列:逐层搜索,每次搜素到的点就是当前点可以搜索到的最短的点,先搜到的点先扩展,于是就是队列的数据结构
    • 为什么最短:对于每一个点探索到的点都是最短的点,最终的搜索出来的路径就是最短的路径

四、串

4.1 串的基本概念

由字符组成的串

  • 子串(连续)
  • 主串
  • 位置

4.2 串的存储结构

4.2.1 串的顺序存储

使用固定长度的数组来存储,3种存储字符串长度的方法如下:

image-20231012095542232
image-20231012095512268
image-20231012095528625
4.2.2 串的链式存储

存储密度 = 串值所占的内存 一个结点的总内存 存储密度 = \frac {串值所占的内存}{一个结点的总内存} 存储密度=一个结点的总内存串值所占的内存

  1. 非压缩形式:一个结点存一个字符

    // 存储密度为:1/9 (64位操作系统)
    struct String {char data;String* next;
    };
    
  2. 压缩形式(块链):一个结点存储指定长度的字符

    // 存储密度为:4/12 (64位操作系统)
    const int MaxSize = 4;
    struct String {char data[MaxSize];String* next;
    }
    

4.3 串的操作算法

4.3.1 串的基本操作算法
  1. 串连接
  2. 串比较
  3. 串拷贝
4.3.2 串的模式匹配
  1. BF算法(Brute - Force)

    image-20231012105536936
    // 返回匹配上的所有位置下标(下标从0开始)
    vector<int> BF(string& s, string& t) {vector<int> res;int i = 0, j = 0, n = s.size(), m = t.size();while (i < n && j < m) {if (s[i] == t[j]) i++, j++;else i = i - j + 1, j = 0;if (j == m) {res.emplace_back(i - j);j = 0;}}return res;
    }
    
  2. KMP算法

    image-20231012194432323

    优化思想

    先看暴力思想,我们需要每次将模式串 t 后移一位重新进行比较,其中浪费了已匹配的串数据,优化就是从这块已匹配的串数据入手。而已匹配的串数据就是模式串本身的串数据,因为我们可以直接从模式串本身入手。

    初步猜想

    根据模式串的性质,构造一个数表 next,存储模式串应该后移的指针数 k

    算法实现

    1. 递推求 next 数组
    2. KMP 中 i 指针不回溯,j 回溯到 next[j]
    // 求 next 数组	下标从1开始
    for (int i = 2, j = 0; i <= m; i++) {while (j && t[i] != t[j + 1])// 未匹配上则不断回溯j = ne[j];if (t[i] == t[j + 1])// 匹配上了则j指针后移一位j++;ne[i] = j;
    }
    
    // KMP 匹配		 下标从1开始
    for (int i = 1, j = 0; i <= n; i++) {while (j && news[i] != newt[j + 1])// 未匹配上则不断回溯j = ne[j];if (news[i] == newt[j + 1])// 匹配上了则j指针后移一位j++;if (j == m) {// 匹配完全,则统计并且回溯cnt++;j = ne[j];}
    }
    

五、数组和特殊矩阵

5.1 数组

5.1.1 数组的基本概念
typedef int arr[m][n];
// 等价于
typedef int arr1[n];
typedef arr1 arr2[m];
5.1.2 数组的存储结构
  1. 行优先:按行存储
  2. 列优先:按列存储

可以按照下标的关系,只需要知道第一个元素的地址,通过矩阵的大小关系即可直接计算出 a i j ⋯ a_{ij\cdots} aij 的地址

5.2 特殊矩阵的压缩存储

对于多个相同的非零元素只分配一个存储空间,对零元素不分配空间

5.2.1 对称矩阵的压缩存储
image-20231016092313887

假设现在有一个 n*n 的对称矩阵

存:行优先存储 data[n * (n + 1) / 2]

取:我们如果要取 data[i][j]

  • 对于上三角

    • i >= jdata[i * (i + 1) / 2 + j]

    • i < jdata[j * (j + 1) / 2 + i]

  • 对于下三角

    • i >= jdata[i * (i + 1) / 2 + j]

    • i < jdata[j * (j + 1) / 2 + i]

5.2.2 三角矩阵的压缩存储
image-20231019094830937

假设现在有一个 n*n 的三角矩阵(上三角或下三角为常数c)

存:行优先存储,常数 c 存储到最后 data[n * (n + 1) / 2 + 1]

5.2.3 对角矩阵的压缩存储
image-20231019094859766

假设现在有一个 n*n 的对角矩阵(围绕主对角线有数据,其余数据均为0)

5.2.4 稀疏矩阵的压缩存储

假设现在有一个 n*m 的稀疏矩阵(很多零的一个矩阵)

  1. 三元组顺序表

    按行存储两个信息,一个是非零元素的数值,还有一个是具体的坐标 (i, j)

  2. 十字链表

    定义两个指针数组,定义两个指针数组,存储行列的头指针即可 vector<CrossNode<T>*> cheads, rheads

六、广义表

6.1 广义表的概念

  • 与以往的线性表的区别在于:线性表的元素只能是DT或ADT。而对于广义表,元素还可以是一个广义表,即可递归结构
  • 表头、表尾:对于当前序列,第一个元素就是表头,其余元素的集合就是表尾
  • 特点:层次结构、共享结构、递归结构

6.2 广义表的存储结构

6.2.1 广义表中结点的结构

采用联合结构体存储结点类型

image-20231023084427729
6.2.2 广义表的存储结构
image-20231023090301534

6.3 广义表的操作算法

  • 直接递归法 - 原子直接操作,子表循环成原子进行操作
  • 减治法 - 先处理第一个元素(原子:直接操作 o r or or 子表:递归操作),最后递归操作剩余的元素
6.3.3 广义表的其他操作算法
  1. 复制广义表
  2. 计算广义表的长度
  3. 计算广义表的深度 - 原子深度为0,空表深度为1
  4. 释放广义表的存储空间

七、树和二叉树

7.1 树的概念和性质

7.1.1 树的定义
7.1.2 树的基本术语
  1. 结点的度和树的度
    • 结点的度:每一个结点孩子结点的数量
    • 树的度:一棵树中结点度数的最大值
  2. 孩子、双亲、兄弟结点
  3. 路径和路径长度
  4. 子孙结点和祖先结点
  5. 结点的层次和树的高度
  6. 有序树和无序树
    • 有序树:子集不可以随意交换
    • 无序树:子集可以随意交换
  7. 森林
    • 多棵树
7.1.3 树的基本性质

7.2 二叉树的概念和性质

7.2.1 二叉树的定义
7.2.2 二叉树的基本性质
  1. 根是第一层。第 i i i 层最多有 2 i − 1 2^{i - 1} 2i1 个结点

  2. 树中叶子结点的个数为 n 0 n_0 n0,度数为2的结点的个数为 n 2 n_2 n2。已知树的点数为 n n n,边数为 m m m,则 n = m + 1 n = m + 1 n=m+1。而 n = n 0 + n 1 + n 2 n=n_0+n_1+n_2 n=n0+n1+n2 m = n 1 + 2 n 2 m=n_1+2n_2 m=n1+2n2,则 n 0 + n 1 + n 2 = n 1 + 2 n 2 + 1 n_0+n_1+n_2 = n_1+2n_2 +1 n0+n1+n2=n1+2n2+1,则
    n 0 = n 2 + 1 n_0=n_2 + 1 n0=n2+1

  3. 满二叉树:每一层都是满结点

    image-20231030085031394
  4. 完全二叉树:对于一个 k k k 层的二叉树, 1 → k − 1 1\to k-1 1k1 都是满的,第 k k k 层从左到右连接叶子结点

    image-20231030085057862

    结点数固定,则完全二叉树的形状唯一

    image-20231030090400650

    i i i 为奇数,且 i ≠ 1 i\neq1 i=1,则左兄弟就是 i − 1 i-1 i1

    i i i 为偶数,则右兄弟就是 i + 1 i+1 i+1

7.3 二叉树的存储结构

7.3.1 二叉树的顺序存储结构
  • 对于一般的二叉树,将其转化为完全二叉树进行存储即可
  • 插入删除操作都不方便
7.3.2 二叉树的链式存储结构

7.4 二叉树的遍历

7.4.1 二叉树遍历的概念
7.4.2 二叉树遍历算法
  1. 先中后遍历
    1. 递归遍历
    2. 栈式遍历
  2. 层序遍历
7.4.3 二叉树的构造和析构⭐️
  1. 由含空指针标记的单个遍历序列构造二叉树

    可以从遍历的逻辑进行逆推。在遍历到空指针的时候输出一个编制符号,然后在构造的时候按照遍历序列进行递归构造即可,如图

    先序序列进行构造:
    按照遍历的思路来,对于先序序列而言,第一个元素一定是根元素,因此首先根据“当前局面”的第一个元素创建根结点,接着递归创建左子树和右子树即可。注意传递的序列起始下标是引用类型的变量

    image-20231102112448133
    image-20231102112515427

    中序序列进行构造:
    不可以,因为不能确定根节点以及左子树和右子树的部分

    后序序列进行构造:
    与上述先序序列进行构建的逻辑一致,只不过有一个小 trick,即我们从后序序列的最后一个元素开始创建,那么得到的第一个元素就是根结点的值,然后首先递归创建右子树,再递归创建左子树即可。同样需要注意的是传递参数时,序列起始下标是引用类型的变量与先序序列构造逻辑相同,只是递归的顺序需要调整一下

  2. 由两个遍历序列构造二叉树

    • 先+中:构造逻辑与上述带标记的序列构造逻辑几乎一致,只不过区别在于如何进行递归中参数的传递。传递的参数除了先序和中序的字符串,还有当前局面先序序列的起始下标与当前局面中序序列的起始下标,以及以当前序列进行构造时子树的结点个数。很容易就可以找到当前序列的根结点,接着就是利用很简单的下标关系得到上述的三个参数的过程,最后将新得到的三个参数传递给递归函数进行递归构建左右子树即可,当前的根结点是 pre[ipre]
    • 后+中:逻辑与上述一致,只不过当前的根结点是 post[ipost+n-1]
  3. 由顺序结构构造链式结构

  4. 拷贝构造

  5. 析构

7.5 二叉树的其他操作算法

  1. 计算二叉树的结点数
    • 有返回值的递归
    • 无返回值的递归
  2. 计算二叉树的高度
    • 有返回值的递归
    • 无返回值的递归
  3. 根据关键值查找结点
  4. 查找结点的父结点

7.6 线索二叉树⭐️

7.6.1 线索二叉树的概念

将空指针域用前驱 or 后继结点的地址进行覆盖

7.6.2 线索二叉树的存储结构

依旧是链式存储,只不过增加了结点域中的指针类型,分为链接类型Link与线索类型Thread

7.6.3 线索二叉树的操作算法

中序线索化的二叉树

  1. 线索化算法

    设置一个全局变量 pre,为了简化思维,我们可以将一个中序遍历的过程想象成一个线性结构。前驱为pre,当前为p。

    • p的左子树为空,则p的前驱为pre
    • pre的右子树为空,则pre的后继为p
  2. 求后继结点和前驱结点的算法

  3. 遍历算法

  4. 求父结点的算法

    • 首先,若已知当前是左子树,则父结点一定是当前右孩子的中序前驱线索;若已知当前是右子树,则父结点一定是当前左孩子的中序前驱线索
    • 但是在未知当前结点的位置(未知左右子树)时,同时搜索两边的父结点,然后根据试探出来的父结点,特判父结点的子结点是否是当前结点即可

7.7 树的存储结构与算法

7.7.1 树的存储结构
  1. 多叉链表表示法

    将每一个结点的子结点都预设置为一个定值(树的最大度数):浪费空间

  2. 孩子链表表示法

    自顶向下存储边的信息

    template<class T>
    struct CTBox {T data;CTNode* firstchild;
    };
    struct CTNode {int child;CTNode* next;
    };
    
    image-20231109115729850
  3. 双亲表示法

    自下向上存储边的信息

    image-20231109115759641
  4. 孩子兄弟表示法

    左结点存储孩子,右结点存储兄弟

7.7.2 树的操作算法
  1. 构造
  2. 计算树的高度
  3. 计算树中所有结点的度

7.8 哈夫曼树与哈夫曼编码⭐️

7.8.1 哈夫曼树的定义

树的路径长度:叶子结点到根结点的路径之和

  1. 树的带权路径长度 W P L WPL WPL :叶子结点到根结点的路径之和 × \times × 叶子结点的权重,整体之和
  2. W P L WPL WPL 最小的树就叫做哈夫曼树:对于一个结点序列 n,每次选择其中的两个权值最小的两个结点进行合并,在进行了 n-1 次以后,得到的二叉树就是哈夫曼树
  3. 哈夫曼编码:
    • 编码:利用二叉树进行前缀编码 - 避免解码时的二义性
    • 解码:根据编码的二叉 trie 树,进行解码
7.8.2 操作算法
  • 构造 Huffman 树
  • 编码
  • 解码

八、图

8.1 图的基本概念

8.1.1 图的定义

G r a p h = ( V , E ) Graph = (V,E) Graph=(V,E)

完全无向图: e d g e = n ( n − 1 ) / 2 edge=n(n-1)/2 edge=n(n1)/2

完全有向图: e d g e = n ( n − 1 ) edge=n(n-1) edge=n(n1)

8.1.2 图的基本术语
  • 带权图称为网

  • 连通图和连通分量:

    • 无向图

    • 连通图:每一个顶点之间都有路径可达

    • 连通分量:极大连通子图

  • 强连通图和强连通分量:

    • 有向图

    • 强连通图:每一个顶点之间都有路径可达

    • 强连通分量:极大强连通子图

8.2 图的存储结构

教材中的点编号统一从 0 0 0 开始

8.2.1 邻接矩阵
  • 无向图的度:第 i i i 行(列)的非标记数的个数

  • 有向图的度:

    • 入度:第 i i i 行的非标记数的个数
    • 出度:第 i i i 列的非标记数的个数
  • 类定义:

    image-20231123111517468
    image-20231123111541444
8.2.2 邻接表
  • 存储出边表(邻接表)

  • 存储入编表(逆邻接表)

  • 类定义:

    image-20231123113744911

8.3 图的遍历

8.3.1 图遍历的概念

每个结点只能访问一次,故需要开启标记数组用来记录是否访问的情况

8.3.2 深度优先搜索
image-20231127083412848
  1. 邻接矩阵:

    • 时间复杂度: O ( n 2 ) O(n^2) O(n2)

    • 针对邻接矩阵的一个无向连通图的搜索代码示例

      image-20231127083039909
  2. 邻接表:

    • 时间复杂度: O ( n + e ) O(n+e) O(n+e)

    • 针对邻接表的一个无向连通图的搜索代码示例

      template<class T>
      void ALGraph::DFS(int v, bool* visited) {cout << vexs[v];visited[v] = true;// 遍历所有的边
      }
      
8.3.3 广度优先搜索
  • 通过队列实现
  • 时间复杂度与上述 DFS 算法类似
8.3.4 图遍历算法的应用
  1. (u,v) 的所有简单路径

    dfs+回溯法的简单应用

  2. 染色法求二部图

    bfs的简单应用

    当然dfs也是可以的,只需要在染色之后判断是否有相同颜色的邻接点即可

8.4 最小生成树

8.4.1 最小生成树的概念及其性质

M i n i m u m S p a n n i n g T r e e ( M S T ) Minimum\ Spanning\ Tree(MST) Minimum Spanning Tree(MST)

证明:

image-20231130114331973

对于上述的一个割,选择其中权值最小的交叉边。从而对于所有的状态,每次选择最小交叉边即可。

8.4.2 Prim算法

算法标签: g r e e d y greedy greedy

  • 构造 n − 1 n-1 n1 个割的状态

  • 起始状态为:顶点集合 U U U 1 1 1 个顶点,顶点集合 V − U V-U VU n − 1 n-1 n1 个顶点

  • 状态转移为:

    • 选完最小交叉边之后,将这条边在集合 V − U V-U VU 中的顶点加入到最小生成树集合 U U U
    • 更新最小交叉边数组 m i n i e d g e s [ ] miniedges[\ ] miniedges[ ]
  • 时间复杂度: O ( n 2 ) O(n^2) O(n2)

8.4.3 Kruskal算法

算法流标签: g r e e d y 、 d s u greedy、dsu greedydsu

  • 初始化 n n n 个顶点作为 n n n 个连通分量
  • 按照边的权值升序进行选择
    • 如果选出的边的两个顶点不在同一个集合,则加入最小生成树
    • 如果选出的边的两个顶点在同一个集合,则不选择(如果选了就会使得生成树形成回路)
  • 时间复杂度: O ( e log ⁡ e ) O(e\log e) O(eloge)

8.5 最短路径

8.5.1 最短路径的概念

单源最短路

D i j k s t r a Dijkstra Dijkstra 算法无法求解含负边权的单源最短路

B e l l m a n − F o r d Bellman-Ford BellmanFord 算法支持负边权的单源最短路求解

S p f a Spfa Spfa 算法同样支持负边权的单元最短路,属于 B e l l m a n − F o r d Bellman-Ford BellmanFord 算法的优化

多源最短路

F l o y d Floyd Floyd 适用于求解含负边权的多源最短路

8.5.2 单源最短路径 - D i j k s t r a Dijkstra Dijkstra 算法

算法标签: g r e e d y greedy greedy d p dp dp

其实就是 P r i m Prim Prim 的另一种应用

  • P r i m Prim Prim 是只存储交叉边的最小值
  • D i j k s t r a Dijkstra Dijkstra 是存储交叉边的最小值 + + + 这条边在集合 S 中的点已经记录的值
  1. 朴素版:

    • 邻接矩阵

    • 定义 d [ i ] d[i] d[i] 表示从起点到当前i号点的最短路径的长度

    • 将顶点分为两个集合, S S S V − S V-S VS,其中 S S S 表示已经更新了最短路径长度的顶点集合

    • 迭代更新过程:依次更新每一个结点,对于当前结点 v i v_i vi,在集合 S S S 中的所有结点中,选择其中到当前结点路径最短的顶点 v j v_j vj,则 d[i]=d[j]+edges[j][i]

    • 时间复杂度: O ( n 2 ) O(n^2) O(n2)

  2. 堆优化:

    • 邻接表

    • 时间复杂度: O ( e log ⁡ e ) O(e \log e) O(eloge)

8.5.3 多源最短路径 - F l o y d Floyd Floyd 算法

算法标签: d p dp dp

多阶段决策共 n n n 个阶段,dp[i][j] 表示每一个阶段 k k k,从 i i i j j j 的选择前 k k k 个顶点后的最短路径的长度

对于当前阶段 k k k,我们利用阶段 k − 1 k-1 k1 的状态进行转移更新,其实就是对于新增加的顶点 v k v_k vk 是否选择的过程

  • 选择 v k v_k vk,则 dp[i][j] = dp[i][k] + dp[k][j]
  • 不选 v k v_k vk,则 dp[i][j] 就是 k − 1 k-1 k1 状态下的 dp[i][j]

8.6 AOV网与拓扑排序

8.6.1 有向无环图与AOV网的概念
  • 有向无环图: D A G DAG DAG
  • AOV网: ( a c t i v i t y o n v e r t e x n e t w o r k ) (activity\ on\ vertex\ network) (activity on vertex network)
  • 应用场景:在时间先后上有约束关系的工程管理问题
8.6.2 拓扑排序
  • 定义:顶点线性化
  • 应用:判环、判断一个图是否可以进行动态规划
  • 算法设计:对于有向图,从所有的入度为 0 的点开始删点删边,到最后判断有多少点被删除即可
  • 算法实现:可以采用 dfs 进行缩点删边,也可以采用 bfs 进行缩点删边
  • 时间复杂度: O ( n + e ) O(n+e) O(n+e)

九、查找

9.1 静态查找表

定义:只支持查询和修改,不支持删除与插入

9.1.1 顺序查找
9.1.2 折半查找
9.1.3 分块查找

结合顺序查找与分块查找的一种方法

image-20231225093347357
  • 索引表可以折半或者顺序查找
  • 块内部只能顺序查找

9.2 动态查找表

9.2.1 二叉排序树

定义:根结点比左子树所有结点的值都大,比右子树所有结点的值都小。关键字唯一

操作:查找、插入、删除

判定:想要判定一棵二叉树是否为二叉排序树,只需要判断中序遍历的结果是不是递增的即可,可以采取中序遍历序列比对的方法,也可以在递归遍历二叉树的过程中通过记录前驱结点的值直接进行比较判断。时间复杂度: O ( n ) O(n) O(n)

9.2.2 平衡二叉树

定义:平衡因子为左子树的高度 - 右子树的高度,平衡二叉树的平衡因子绝对值 <= 1

构建:当插入结点进行构建时出现了有结点平衡因子的绝对值超过了1,则进行“旋转”调整,旋转共分为4种

image-20240104125111608
image-20240104125140305
image-20240104125159728
尝试模拟一遍下列序列的构造过程就可以理解了
image-20240114231458389

9.3 Hash 查找

定义:装填因子 α = n m \alpha=\frac{n}{m} α=mn,其中 n n n 表示待填入表中的结点数, m m m 表示哈希表的空间大小

哈希函数应该满足以下两点:第一、映射出来的地址不会越界;第二、映射出来的地址是唯一的

9.3.1 构造表

常用的哈希函数

  1. 直接地址法 - 线性函数一对一映射

    优点。计算简单且不可能产生冲突

    缺点。对于空间的要求极高,如果数据过于离散,则会造成很大的空间浪费

  2. 数字分析法 - 按照数位中的数值分布情况进行哈希

    缺点。需要预先知道数据的数字分布情况

  3. 平方取中法 - 对于 1 0 m 10^m 10m 的哈希空间,可以将数字平方后取中间 m m m 位进行哈希存储

  4. 折叠法

    • 移位法:将一个数字按照数位拆分为几个部分,然后将几个部分的数值累加出一个数即可,高位抹去不用

    • 间隔法:与移位法几乎一致,只不过将其中的部分意义间隔的进行数值反转,最后累计即可,高位抹去不用

  5. 除留余数法 - 按照数值 mod  p \text{mod}\ p mod p 后的数值进行哈希,假设哈希表空间大小为 m m m ,则 p p p 一般取 ≤ m \le m m 的质数

处理冲突

  1. 开放定址法 - 探测开放地址,一般有三种
    • 连续序列进行线性探测
    • 左右倍增序列进行探测
    • 伪随机序列进行探测
    • 双 hash 探测法
  2. 拉链法
    • 定义:将产生 hash 冲突的元素放入同一个子集,通过单链表进行存储
    • 优点:没有堆积现象,从而减少了很多不必要的比价,提升比较效率;适合一开始不知道表长的情况;除结点更加容易。
9.3.2 查找表

按照构造相同的逻辑进行查找即可

十、排序

10.1 排序的基本概念

关键字:

  • 主关键字:每一个待排序的该关键字是独一无二的
  • 次关键字:每一个待排序的该关键字可能是重复的

稳定性:

  • 场景:只针对次关键字的情况
  • 稳定:按照次关键字排序后,原来相同关键字的顺序不变
  • 不稳定:按照次关键字排序后,原来相同关键字的顺序可能会改变

内外排序:

  • 内排序:数据全部存放在内存
  • 外排序:数据量过大时,待排序的数据在内存与外存之间不断转换

10.2 冒泡排序

基于交换的思路进行

稳定的

10.3 选择排序

  • 选择第 1 小的数放在第一个位置,…,选择第 i 小的数放在第 i 个位置

  • 共选择 n-1 次

10.4 插入排序

  • 直接插入排序:依次向前缀已经排好序的序列中进行插入 - O ( n 2 ) O(n^2) O(n2)
  • 折半插入排序:同上,只是选择插入位置的使用二分 - O ( n log ⁡ n ) O(n\log n) O(nlogn)
  • 递归插入排序:排序 [1,i] 等价于先排好 [1,i-1],然后插入当前 num[i] 即可

稳定的

10.5 希尔排序

基于插入直接排序的优点:

  1. 当序列基本有序时,效率很高
  2. 当待排序数很少时,效率很高

于是希尔(Shell)就得出来以下的希尔排序算法:

  1. 将序列划分一定次数,从 d<n 到 1
  2. 每次划分都对组内的元素进行直接插入排序
  3. 最后分为 1 组时,直接排序一趟以后就可以得到 sortrd sequence

不稳定

10.6 快速排序

分治法三步骤:divide、conquer and combine

每次选择一个 pivot 进行 partition,递归两个 partition

void Sort(int l, int r) {if (l >= r) return;int i = l - 1, j = r + 1, x = a[l + r >> 1];while (i < j) {while (a[++i] < x);while (a[--j] > x);if (i < j) swap(a[i], a[j]);            }Sort(l, j), Sort(j + 1, r);
}

不稳定

10.7 堆排序

堆与堆排序的定义

首先我们得知道什么是堆结构。堆是具有下面性质(对于任意的 1 ≤ i ≤ n / 2 1\le i \le n/2 1in/2 )的完全二叉树

  • k i ≤ k 2 i 、 k i ≤ k 2 i + 1 k_i \le k_{2i}、k_i \le k_{2i+1} kik2ikik2i+1 叫做 小顶堆

  • k i ≥ k 2 i 、 k i ≥ k 2 i + 1 k_i \ge k_{2i}、k_i \ge k_{2i+1} kik2ikik2i+1 叫做 大顶堆

因此一个堆结构可以采用线性的单元进行存储与维护

而堆排序利用堆顶是最值这一性质,通过不断的取堆顶,调整堆的方式获得最终的排好序的序列

建立初始堆

由于完全二叉树中,每一个叶子结点都已经是堆结构,因此直接从第一个非叶子结点开始建堆即可。对每一个元素与左孩子、 右孩子进行比较

  • 如果当前结点的值比左右孩子都大,那么无需修改,当前位置就是堆顶
  • 如果当前结点的值比左孩子或者右孩子中的最大值小,则将最大的孩子作为堆顶,并将当前值不断的“下沉”即可

交换堆顶与记录位置后重新建堆

交换记录值获取当前堆中最值以后,需要将除了已记录的值的结点以外的所有结点重新调整为堆结构

  • 调整为堆结构的过程与上述初始建堆的过程完全一致,只是结点数每次 -1

时间复杂度 O ( n log ⁡ n ) O(n \log n) O(nlogn)

不稳定

10.8 归并排序

递归

同样采用分治法,我们按照分治法的三个步骤进行讨论

  • divide: 将当前序列划分为左右两部分
  • conquer: 递归处理上述划分出来的两部分
  • combine: 归并上述递归完的两部分

时间复杂度 O ( n log ⁡ n ) ← T ( n ) = 2 T ( n 2 ) + O ( n ) O(n \log n)\leftarrow T(n)=2T(\frac{n}{2}) + O(n) O(nlogn)T(n)=2T(2n)+O(n)

非递归

就是模拟上述递归的过程,可以拆分为三步

  • 归并
  • 按照指定的长度处理整个序列
  • 划分局部排序的长度

稳定的

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/637910.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【c++函数重载】

文章目录 一. 命名空间二 .全缺省参数和半缺省参数三 . 函数重载 一. 命名空间 1.不指定域&#xff1a;先在局部找&#xff0c;再全局。 2. 指定域&#xff1a;到指定的命名空间去找。 3. 当把指定命名空间放开时&#xff0c;即using namespace std&#xff1b;例如放开标准c库…

5.2 基于深度学习和先验状态的实时指纹室内定位

文献来源 Nabati M, Ghorashi S A. A real-time fingerprint-based indoor positioning using deep learning and preceding states[J]. Expert Systems with Applications, 2023, 213: 118889.&#xff08;5.2_基于指纹的实时室内定位&#xff0c;使用深度学习和前一状态&…

让uniapp小程序支持多色图标icon:iconfont-tools-cli

前景&#xff1a; uniapp开发小程序项目时&#xff0c;对于iconfont多色图标无法直接支持&#xff1b;若将多色icon下载引入项目则必须关注包体&#xff0c;若将图标放在oss或者哪里管理&#xff0c;加载又是一个问题&#xff0c;因此大多采用iconfont-tools工具&#xff0c;但…

【php】php去除excel导入时的空格

背景 PHPExcel_1.8.0导入excel&#xff0c;遇到trim无法处理的空格。 解决方案 $excelVal preg_replace(“/(\s| | |\xc2\xa0)/”, ‘’, $excelVal); 完整代码 thinkphp5代码 function readExcel($file) {require_once EXTEND_PATH . PHPExcel_1.8.0/Classes/PHPExcel.p…

靶场实战(18):OSCP备考之VulnHub MY CMSMS

打靶思路 资产发现 主机发现服务发现漏洞发现&#xff08;获取权限&#xff09; 80端口/HTTP服务 组件漏洞URL漏洞3306端口/MySQL服务 组件漏洞口令漏洞80端口/HTTP服务 URL漏洞URL漏洞提升权限 www-data用户 sudosuidcron内核提权信息收集armour用户 sudo 1、资产发现 1.1…

考研C语言刷编程题篇之分支循环结构基础篇(一)

目录 第一题 第二题 方法一&#xff1a;要循环两次&#xff0c;一次求阶乘&#xff0c;一次求和。 注意&#xff1a;在求和时&#xff0c;如果不将sum每次求和的初始值置为1&#xff0c;那么求和就会重复。 方法二&#xff1a; 第三题 方法一&#xff1a;用数组遍历的思想…

【大数据处理技术实践】期末考查题目:集群搭建、合并文件与数据统计可视化

集群搭建、合并文件与数据统计可视化 实验目的任务一&#xff1a;任务二&#xff1a; 实验平台实验内容及步骤任务一&#xff1a;搭建具有3个DataNode节点的HDFS集群集群环境配置克隆的方式创建 Slave 节点修改主机名编辑 hosts 文件生成密钥免认证登录修改 hadoop 的配置文件编…

Java并发编程: 并发编程中的ExecutionException异常

一、什么是ExecutionException 在并发编程中在执行java.util.concurrent.Future实现类的get方法时&#xff0c;需要捕获java.util.concurrent.ExecutionException这个异常。Future.get()方法通常是要获取任务的执行结果&#xff0c;当执行任务的过程中抛出了异常&#xff0c;就…

ThinkPad T14/T15/P14s/P15s gen2电脑原厂Win10系统镜像 恢复笔记本出厂时预装自带OEM系统

lenovo联想原装出厂Windows10系统&#xff0c;适用型号&#xff1a; ThinkPad T14 Gen 2&#xff0c;ThinPad T15 Gen 2&#xff0c;ThinkPad P14s Gen 2&#xff0c;ThinkPad P15s Gen 2 &#xff08;20W1,20W5,20VY,20W7,20W0,20W4,20VX,20W6&#xff09; 链接&#xff1…

Redis在Windows10中安装和配置

1.首先去下载Redis 这里不给出下载地址&#xff0c;自己可以用去搜索一下地址 下载 下载完成后解压到D盘redis下&#xff0c;本人用的是3.2.100 D:\Redis\Redis-x64-3.2.100 2.解压完成后需要设置环境变量&#xff0c;这里新建一个系统环境变量中path 中添加一个文件所…

WCP知识分享平台的容器化部署

1. 什么是WCP? WCP是一个知识管理、分享平台,支持针对文档(包括pdf,word,excel等)进行实时解析、索引、查询。 通过WCP知识分享平台进行知识信息的收集、维护、分享。 通过知识创建、知识更新、知识检索、知识分享、知识评价、知识统计等功能进行知识生命周期管理。 wcp官…

第04章_IDEA的安装与使用(上)(认识,卸载与安装,JDK相关设置,详细设置,工程与模块管理,代码模板的使用)

文章目录 第04章_IDEA的安装与使用&#xff08;上&#xff09;本章专题与脉络1. 认识IntelliJ IDEA1.1 JetBrains 公司介绍1.2 IntelliJ IDEA 介绍1.3 IDEA的主要优势&#xff1a;(vs Eclipse)1.4 IDEA 的下载 2. 卸载与安装2.1 卸载过程2.2 安装前的准备2.3 安装过程2.4 注册2…

【小笔记】算法训练基础超参数调优思路

【学而不思则罔&#xff0c;思维不学则怠】 本文总结一下常见的一些算法训练超参数调优思路&#xff08;陆续总结更新&#xff09;&#xff0c;包括&#xff1a; batchsize学习率epochsdropout&#xff08;待添加&#xff09; Batch_size 2023.9.29 简单来说&#xff0c;较…

学习笔记之——3D Gaussian SLAM,SplaTAM配置(Linux)与源码解读

SplaTAM全称是《SplaTAM: Splat, Track & Map 3D Gaussians for Dense RGB-D SLAM》&#xff0c;是第一个&#xff08;也是目前唯一一个&#xff09;开源的用3D Gaussian Splatting&#xff08;3DGS&#xff09;来做SLAM的工作。 在下面博客中&#xff0c;已经对3DGS进行了…

基于springboot+vue的宠物领养系统(前后端分离)

博主主页&#xff1a;猫头鹰源码 博主简介&#xff1a;Java领域优质创作者、CSDN博客专家、公司架构师、全网粉丝5万、专注Java技术领域和毕业设计项目实战 主要内容&#xff1a;毕业设计(Javaweb项目|小程序等)、简历模板、学习资料、面试题库、技术咨询 文末联系获取 背景及意…

C++参悟:正则表达式库regex

正则表达式库regex 一、概述二、快速上手Demo1. 查找字符串2. 匹配字符串3. 替换字符串 三、类关系梳理1. 主类1. basic_regex 2. 算法1. regex_match2. regex_search3. regex_replace 3. 迭代器4. 异常5. 特征6. 常量1. syntax_option_type2. match_flag_type3. error_type 一…

Unity animator动画倒放的方法

在Unity中&#xff0c; 我们有时候不仅需要animator正放的效果&#xff0c;也需要倒放的效果。但我们在实际制作动画的时候可以只制作一个正放的动画&#xff0c;然后通过代码控制倒放。 实现方法其实很简单&#xff0c;只需要把animator动画的speed设置为-1即为倒放&#xff…

科技护航 智慧军休打通医养结合最后一公里

“小度小度&#xff0c;请帮我打电话给医生。” “好的&#xff0c;马上呼叫植物路军休所医生。” 2023年9月25日&#xff0c;常年独居、家住广西南宁市植物路军休所的军休干部程老&#xff0c;半夜突发疾病&#xff0c;让他想不到的是&#xff0c;这个常年伴他左右的“小度”…

Centos 8 安装 Elasticsearch

简介&#xff1a;CentOS 8是一个基于Red Hat Enterprise Linux&#xff08;RHEL&#xff09;源代码构建的开源操作系统。它是一款稳定、可靠、安全的服务器操作系统&#xff0c;适合用于企业级应用和服务的部署。CentOS 8采用了最新的Linux内核和软件包管理系统&#xff0c;提供…

Vue3新特性defineModel()便捷的双向绑定数据

官网介绍 传送门 配置 要求&#xff1a; 版本&#xff1a; vue > 3.4(必须&#xff01;&#xff01;&#xff01;)配置&#xff1a;vite.config.js 使用场景和案例 使用场景&#xff1a;父子组件的数据双向绑定&#xff0c;不用emit和props的繁重代码 具体案例 代码实…