2024数据结构考研笔记(王道精讲课+天勤高分笔记)_天勤数据结构pdf2024-程序员宅基地

技术标签: 笔记  24考研  数据结构  考研  

推一个usfca的数据结构可视化工具网站

第1章 绪论

1.1 数据结构基本概念

1.1.1 基本概念和术语

  • 数据:信息的载体
  • 数据元素:数据的基本单位,可由若干数据项组成

如:学生记录是一个数据元素,由学号、姓名等数据项组成

  • 数据项:构成数据元素的不可分割的最小单位
  • 数据对象:具有相同性质的数据元素的集合,是数据的一个子集
  • 数据结构:相互之间存在一种或多种特定关系是数据元素的集合。数据元素相互之间的关系称为结构

在这里插入图片描述

  • 抽象数据类型(ADT):定义一个ADT就是定义了数据的逻辑结构、数据的运算,也就是定义了一个数据结构
  • 数据结构三要素:逻辑结构、存储结构、数据的运算
  • 一个算法的设计取决于所选定的逻辑结构,二算法的实现依赖于所用的存储结构

严书拓展

  • 数据类型:一个值的集合和定义在这个值集上的一组操作的总称
    按"值"的不同特性分为:
    • 非结构的原子类型:C语言中的基本类型(int、char、枚举等)、指针类型、空类型
    • 结构类型:数组等
      结构类型可以看成由一种数据结构和定义在其上的一组操作组成
  • 抽象数据类型:指一个数学模型以及定义在该模型上的一组操作
    • 原子类型:变量的值不可分解
    • 固定聚合类型:其值由确定数目的成分按某种结构组成(e.g. 复数由两个实数依据确定的次序关系构成)
    • 可变聚合类型:值的成分的数目不确定

在这里插入图片描述


1.1.2 数据结构三要素

  1. 逻辑结构
  • 与数据的存储无关,是独立于计算机的
  • 分为线性结构和非线性结构
    • 线性结构:数据元素之间只存在一对一的关系
    • 非线性结构:数据元素之间存在一对多的关系

  1. 存储结构
  • 包括数据元素的表示和关系的表示
  • 是用计算机语言实现的逻辑结构,依赖于计算机语言
  • 有顺序存储、链式存储、索引存储、散列存储(哈希存储)
  1. 数据的运算
  • 包括运算的定义和实现
  • 运算的定义是针对逻辑结构的
  • 运算的实现是针对存储结构的

习题

  1. 有序表是指关键字有序的线性表,仅描述元素之间的逻辑关系,既可以链式存储,也可以顺序存储,属于逻辑结构
  2. 逻辑结构独立于存储结构,存储结构不能独立于逻辑结构而存在
  3. 循环队列是用顺序表表示的队列,是一种数据结构
  4. 栈是一种抽象数据类型,可以采用顺序存储或链式存储,只表示逻辑结构
  5. 存储数据要存储数据元素的值和数据元素之间的关系
  6. 链式存储设计时,各个不同结点的存储空间可以不连续,但结点内的存储单元地址必须连续

1.2 算法和算法评价

1.2.1 算法的基本概念

  • 算法是对特定问题求解步骤的一种描述,是指令的有限序列,其中的每条指令表示一个或多个操作

算法的特性:

  1. 有穷性:算法必须是有穷的,而程序可以是无穷的
  2. 确定性:对相同的输入只能得到相同的输出
  3. 可行性
  4. 输入:可以有零个或多个输入
  5. 输出:至少有一个输出

算法的目标:

  1. 正确性
  2. 可读性
  3. 健壮性
  4. 高效率与低存储需求

1.2.2 算法效率的度量

  1. 时间复杂度
  • 依赖于问题的规模n和待输入数据的性质(如输入数据元素的初始状态)

非递归算法的效率

递归算法的效率


在这里插入图片描述

  1. 空间复杂度

习题

  • 求归并排序时间复杂度
    在这里插入图片描述

第2章 线性表

2.1 线性表的定义与基本操作

  • 线性表的定义:具有相同特征的数据元素的一个有限序列

  • 线性表的长度可以为0,表示一个空表

  • 存储结构:顺序存储(顺序表)和链式存储(链表)两种

  • 线性表的基本操作
    在这里插入图片描述


2.2 顺序表

2.2.1 顺序表的定义


2.2.2 顺序表的基本操作

  • 插入
  • 删除
  • 查找

2.3 链表

2.3.1 单链表

顺序单链表(带头结点)A和B合并为一个顺序单链表C
void merge(LNode *A,LNode *B,LNode *&C)
{
    LNode *p = A->next; //p跟踪A的最小值结点
    LNode *q = B->next; //q跟踪B的最小值结点
    LNode *r;   //r跟踪C的终端结点
    C = A;  //将A的头结点设定为C的头结点
    C->next = NULL;
    r = C;
    free(B);    //释放B的头结点
    
    while(p != NULL && q != NULL)
    {
        if(p->data <= q->data)
        {
            r->next = p;
            p = p->next;
            r = r->next;
        }
        else
        {
            r->next = q;
            q = q->next;
            r = r->next;
        }
    }

    r->next = NULL;
    //将剩下的直接连接到C的末尾
    if(p != NULL)
        r->next = p;
    if(q != NULL)
        r->next = q;
}
尾插法建立链表
  • 将数组a中的n个元素用尾插法建立链表C

void creatlistR(LNode *&C,int a[],int n)
{
    LNode *s,*r;    //s指向新申请的节点,r指向C的终端结点
    int i;
    C = (LNode *)malloc(sizeof(LNode)); //申请C的头结点空间
    C->next = NULL;
    r = C;
    for(i = 0; i < n; i++)
    {
        s = (LNode *)malloc(sizeof(LNode));
        s->data = a[i];
        r->next = s;
        r = r->next;
    }
    r->next = NULL;
}
头插法建立链表

void creatlistF(LNode *&C,int a[],int n)
{
    LNode *s;
    int i;
    C = (LNode *)malloc(sizeof(LNode)); //申请C的头结点空间
    C->next = NULL;
    for(i = 0; i < n; i++)
    {
        s = (LNode *)malloc(sizeof(LNode));
        s->data = a[i];
        //下面两句是头插法的关键
        s->next = C->next;  //s所指新节点的指针域next指向C中的开始结点
        C->next = s;    //头结点的指针域next指向s结点,是的s成为新的开始结点
    }
    r->next = NULL;
}
尾插法与头插法的图解

【数据结构】:单链表之头插法和尾插法(动图+图解)

其他基本操作
  • 插入
  • 后插
    在这里插入图片描述
  • 前插
    在这里插入图片描述
  • 删除
    在这里插入图片描述

2.3.2 双链表

尾插法建立双链表
void creatDlistR(LNode *&C,int a[],int n)
{
    LNode *s,*r;    //s指向新申请的节点,r指向C的终端结点
    int i;
    C = (LNode *)malloc(sizeof(LNode)); //申请C的头结点空间
    C->prior = NULL;
    C->next = NULL;
    r = C;
    for(i = 0; i < n; i++)
    {
        s = (LNode *)malloc(sizeof(LNode));
        s->data = a[i];
        r->next = s;
        //开始与单链表不同
        s->prior = r;
        r = s;
    }
    r->next = NULL;
}
双链表的插入

s->next = p->next;
s->prior = p;
p->next = s;
s->next->prior = s;
双链表的删除

在这里插入图片描述

q = p->next;
p->next = q->next;
q->next->prior = p;
free(q);

2.3.3 循环链表

比较简单,此处省略


2.3.4 静态链表

在这里插入图片描述


2.4 顺序表与链表的比较

  1. 存储分配的方式:
    顺序表的存储空间是一次性分配的
    链表的存储空间是多次分配的
  2. 存储密度:
    顺序表存储密度 = 1
    链表存储密度 > 1
  3. 存取方式:
    顺序表可随机存取
    链表只能顺序存取
  4. 插入/删除
    顺序表需要移动多个元素
    链表不需要移动元素,只用修改指针

2.5 逆置问题(重点)

天勤上的,王道没有

  1. 将长度为n的数组的前端k个元素逆序后移动到数组后端,要求原数组中的数据不丢失,其余元素位置无关紧要

逆转整个数组

void reverse(int a[], int left, int right, int k)
{
    int temp;
    for(int i = left, j = right; i < left + k && i < j; ++i, --j)
    {
        temp = a[i];
        a[i] = a[j];
        a[j] = temp;
    }
}
  1. 将长度为n的数组的前端k个元素保持原序移动到数组后端,要求原数组中的数据不丢失,其余元素位置无关紧要

将前k个元素逆置,再将整个数组逆置

void moveToEnd(int a[], int k, int n)
{
    reverse(a, 0, k - 1, k);
    reverse(a, 0, n - 1, k);
}
  1. 循环左移p个位置

将0p-1位置的元素逆置,再将pn-1位置的元素逆置,最后将整个数组逆置

void moveP(int a[], int n, int p)
{
    reverse(a, 0, p - 1, p);
    reverse(a, p, n - 1, n - p);
    reverse(a, 0, n - 1, n);
}

习题

  1. 线性表的顺序结构是一种随机存取的存储结构(不是顺序存取的存储结构)

存取方式即为读写方式

  1. 链式存储结构比顺序存储结构更能方便的表示各种逻辑结构

  2. 队列需要在表头删除元素,表尾插入元素,用带尾指针的循环链表更方便

  3. 用给定的n个元素的以为数组,建立一个有序单链表的最低时间复杂度是O(nlogn)
    在这里插入图片描述

  4. 删除链表的最后一个元素的时间复杂度是O(n),就算有尾指针也是,因为要遍历到其前驱结点,把next指向NULL

  5. 从顺序表汇总删除其值在给定值s到t之间(包含s和t,s<t)的所有元素,若s和t不合理或顺序表为空,则显示出错信息并退出运行
    在这里插入图片描述

  6. 空间换时间的思想
    在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述


第3章 栈和队列

3.1 基本概念

  • n个不同元素进展,不同的出栈顺序有 1 n + 1 C 2 n n \frac{1}{n+1}C^n_{2n} n+11C2nn

  • 进栈:先动指针再进元素

stack[++top] = x;
  • 出栈:先取元素再动指针
x = stack[top--];
  • 入队
rear = (rear + 1) % maxSize;
data[rear] = x;
  • 判断队空/满

一般情况下front所指的位置为空,所以先变指针再出队
rear所指的位置为尾元素,所以先变指针再进队
在这里插入图片描述

  • 双端队列
    在这里插入图片描述

3.2 栈的应用

括号匹配问题

在这里插入图片描述


表达式问题

在这里插入图片描述

计算

在这里插入图片描述


在这里插入图片描述

转换


递归问题

在这里插入图片描述

3.3 队列的应用

  1. 树的层次遍历
  2. 图的广度优先遍历
  3. 操作系统中的FCFS

3.4 特殊矩阵的压缩存储

对称矩阵

三角矩阵


带状矩阵



稀疏矩阵



习题

  1. 链表不带头结点且所有操作均在标头进行,只有表头结点指针,没有表尾指针的单向循环链表最不适合做为链栈(删除需要找到前一个结点,即表尾结点,时间复杂度为O(N))

在这里插入图片描述

  1. 栈只可能发生上溢
  2. 用数组A[0…5]实现循环队列,当前raer和front值分别为1和5,若进行两次入队操作和一次出队操作后,rear和front的值为:3和0(不是4!)
  3. 对链表实现的队列来说,若头尾指针都有,则非循环的比循环的更好

第4章 串

4.1 朴素模式匹配算法


4.2 KMP算法

在这里插入图片描述

求next数组(重点)

  1. 方法一:划线试探法
    详情见24王道课视频
    在这里插入图片描述

  2. 方法二:最长公共前后缀法
    同样先划线,next数组的值等于其前最长公共前后缀数+1

    KMP算法之求next数组代码讲解

void get_next(SString T, int next[]){
    
    int i = 1, j = 0;
    next[1] = 0;
    while(i < T.length)
    {
    
        if(j == 0 || T.ch[i] == T.ch[j])
        {
    
            i++;
            j++;
            next[i] = j;    //若Pi=Pj,则next[j+1]=next[j]+1
        }
        else
            j = next[j];    //否则令j=next[j]
    }
}

优化(nextval)

在这里插入图片描述


第5章 树与二叉树

5.1 基本概念与性质(重点)

  • 除根节点外,任何结点有且仅有一个前驱
  • 结点的度:有几个分支
  • 树的度:各结点度的最大值

常考性质

  1. 结点数 = 总度数+1
  2. 度为m的树,第i层最多有 m i − 1 m^{i-1} mi1个结点
  3. m叉树第i层最多有 m i − 1 m^{i-1} mi1个结点
  4. 高度为h的m叉树最多有 m h − 1 m − 1 \frac{m^h-1}{m-1} m1mh1个结点
  5. 高度为h的m叉树至少有h个结点
  6. 高度为h,度为m的树至少有h+m-1个结点
  7. 具有n个结点的m叉树的最小高度为 ⌈ l o g m ( n ( m − 1 ) + 1 ) ⌉ \lceil log_m(n(m-1)+1) \rceil logm(n(m1)+1)⌉

5.2 二叉树

5.2.1 几种常见的二叉树

满二叉树

叶子结点都在最后一层,不存在度为1的结点

  • 若高度为h,则有 2 h − 1 2^h-1 2h1个结点
  • 按层序从1开始编号,结点i的左孩子为2i,右孩子为2i+1;结点i的父节点为 ⌊ i 2 ⌋ \lfloor \frac{i}{2} \rfloor 2i

完全二叉树

每个结点都与高度为h的满二叉树中的编号一一对应

  • 叶子结点只能出现在最后两层
  • 最多只有一个度为1的结点(只有左孩子)

  • 若共有n个结点,则 i ≤ ⌊ n 2 ⌋ i \leq \lfloor \frac{n}{2} \rfloor i2n为分支结点,为叶子结点 i > ⌊ n 2 ⌋ i > \lfloor \frac{n}{2} \rfloor i>2n

二叉排序树
  • 左子树上的所有结点的关键字都小于根节点的关键字
  • 右子树上的所有结点的关键字都大于根节点的关键字


平衡二叉树
  • 任一结点的左子树和右子树的深度之差不超过1

5.2.2 二叉树的性质(重点)

  1. 度为0的结点 = 度为2的结点 + 1

  2. 2叉树第i层最多有 2 i − 1 2^{i-1} 2i1个结点

  3. 高度为h的2叉树最多有 2 h − 1 2^h-1 2h1个结点

  4. 有n个结点的完全二叉树的高度为 ⌈ l o g 2 ( n + 1 ) ⌉ \lceil log_2(n+1) \rceil log2(n+1)⌉ ⌊ l o g 2 n ⌋ + 1 \lfloor log_2n \rfloor + 1 log2n+1(计算i所在的层次也是一样)
    在这里插入图片描述

  5. 完全二叉树若已知结点数为n,可推出度为0/1/2的结点数的个数


5.2.3 二叉树的遍历

先序遍历
  • 根左右
Status PreOrderTraverse(BiTree T){
    if(T == NULL)
        return OK;
    else
    {
        visit(T);  //访问根节点
        PreOrderTraverse(T->lchild);  //递归遍历左子树
        PreOrderTraverse(T->rchild);  //递归遍历右子树
    } 
}
中序遍历
  • 左根右
Status InOrderTraverse(BiTree T){
    if(T == NULL)
        return OK;
    else
    {
        InOrderTraverse(T->lchild);  //递归遍历左子树
        visit(T);  //访问根节点
        InOrderTraverse(T->rchild);  //递归遍历右子树
    } 
}
后续遍历
  • 左右根
Status PostOrderTraverse(BiTree T){
    if(T == NULL)
        return OK;
    else
    {
        PostOrderTraverse(T->lchild);  //递归遍历左子树
        PostOrderTraverse(T->rchild);  //递归遍历右子树
        visit(T);  //访问根节点
    } 
}
层序遍历(队列)

在这里插入图片描述

根据遍历序列构造二叉树
  1. 前序+中序

  2. 后序 + 中序
    在这里插入图片描述

  3. 层序 + 中序


5.2.4 线索二叉树

概念



线索化

在这里插入图片描述

  • 先序遍历线索化会有"爱的魔力转圈圈"问题,要加个判断语句

在这里插入图片描述

在这里插入图片描述


在线索二叉树中找前驱后继(重点)

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述


5.3 树与森林

5.3.1 存储结构

  1. 双亲表示法(顺序存储)
    在这里插入图片描述

  2. 孩子表示法(顺序+链式存储)
    在这里插入图片描述

  3. 孩子兄弟表示法(链式存储)

在这里插入图片描述

在这里插入图片描述


5.3.2 树、森林与二叉树的转换

  1. 树→二叉树
    在这里插入图片描述

  2. 森林→二叉树
    在这里插入图片描述

  3. 二叉树→树
    在这里插入图片描述

  4. 二叉树→森林
    在这里插入图片描述


5.3.3 树与森林的遍历

树的遍历
  1. 先根遍历
    在这里插入图片描述

  2. 后根遍历
    在这里插入图片描述

  3. 层序遍历
    在这里插入图片描述

森林的遍历
  1. 先序遍历
    在这里插入图片描述

在这里插入图片描述

  1. 中序遍历
    在这里插入图片描述

在这里插入图片描述


5.4 树与二叉树的应用

5.4.1 Huffman树(最优二叉树)

在含有n个带权叶结点的二叉树中,WPL最小的二叉树即为Huffman树

  • 结点的带权路径长度:从树的根到该结点的路径长度 × 该结点的权值
  • 树的带权路径长度(WPL):树中所有叶子结点的带权路径长度之和

在这里插入图片描述

在这里插入图片描述


5.4.2 并查集

在这里插入图片描述

在这里插入图片描述

对Union优化
  • 思路:合并时小树进大树,使得树尽量"矮"
  • Find的时间复杂度从O(n)变为O(logn)

对Find优化
  • 思路:压缩路径,先找到根节点,再将查找路径上的所有结点都挂到根节点下,使得树尽量"矮"

在这里插入图片描述

优化总结

在这里插入图片描述

在这里插入图片描述


习题

  1. 结点按完全二叉树层序编号的二叉树中,第i个结点的左孩子编号为2i:错,不一定有左孩子

  2. 设二叉树有2n个结点,m < n,则不可能存在2m个度为1的结点
    在这里插入图片描述

  3. 三叉树的结点数为50,则最小高度为5
    在这里插入图片描述

  4. 任意一颗高度为5且有10个结点的二叉树,若采用顺序存储结构保存,每个结点占1个存储单元(仅存放结点的数据信息),则存放该二叉树所需的存储单元数至少 为31 (任意→考虑最坏情况)

  5. 二叉树中可用后续遍历找到祖先到子孙的路径

  6. 线索二叉树是一种物理结构
    在这里插入图片描述

  7. 线索化后仍不能有效求解后序线索二叉树中求后序后继

  8. 后序线索树的遍历需要栈的支持
    在这里插入图片描述

  9. 一棵有2011个结点的树,其中叶结点个数为116,则对应的二叉树中无右孩子的结点个数为?
    在这里插入图片描述

  10. 设哈夫曼编码的长度不超过4,若已对两个字符编码为1和01,则最多还能对4个字符编码
    在这里插入图片描述

  11. 度为m的哈夫曼树中,叶结点为n,则非叶结点个数为?
    在这里插入图片描述

在这里插入图片描述

在这里插入图片描述


第6章 图

6.1 图的基本概念(重点)

  • 图不可为空(边可以没有,一定有一个顶点)

在这里插入图片描述

在这里插入图片描述

  • 并非任意挑几个边几个顶点就是子图
  • 生成子图:包含所有顶点
  • 连通分量:无向图的极大连通子图
  • 强连通分量:有向图的极大连通子图

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

n个顶点的树有n-1条边
n个顶点的图若边 >n-1则一定有回路

在这里插入图片描述


6.2 图的存储

6.2.1 邻接矩阵法

在这里插入图片描述

回顾:对称矩阵的压缩

在这里插入图片描述


6.2.2 邻接表法

在这里插入图片描述


6.2.3 十字链表法(有向图)与邻接多重表(无向图)

在这里插入图片描述

空间复杂度O(V+E)

在这里插入图片描述

在这里插入图片描述


6.3 图的遍历

6.3.1 广度优先搜索(BFS)

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述


6.3.2 深度优先搜索(DFS)

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述


6.4 图的应用(重点)

6.4.1 最小生成树

问题模型:道路规划,使每个城市都联通,且花销最少

Prim算法
  • 以顶点为主
    时间复杂度: O ( V 2 ) O(V^2) O(V2)

在这里插入图片描述

在这里插入图片描述

Kruskal算法
  • 以边为主
    时间复杂度: O ( E l o g 2 E ) O(Elog_{2}{E}) O(Elog2E)

在这里插入图片描述


6.4.2 最短路径

问题模型:寻找两个顶点之间的最短路径

BFS算法


Dijkstra算法

不断加顶点,算新的最短路径



Floyd算法

尝试以所有结点为中转点,路径是否变小





6.4.3 有向无环图(DAG)描述表达式




6.4.4 拓扑排序

  • AOV网:用有向无环图表示一个工程,顶点表示活动,有向边<Vi,Vj>表示活动Vi必须先于Vj进行



逆拓扑排序



6.4.5 关键路径

  • AOE网:以顶点表示时间,有向边表示活动,边上的权值表示完成该活动的开销

  • 正看最大,反看最小
  • 活动的最早发生时间就是该结点所连的弧尾的事件的最早发生时间
  • 活动的最迟发生时间 = 该活动所连弧头时间的最晚发生时间 - 该活动所花费的时间






习题

  1. 路径:由顶点和相邻顶点序偶构成的边所形成的的序列
  2. 一个有n个顶点和n条边的无向图一定是有环的(n-1条边必连通)
  3. 连通分量:极大连通子图(连通且尽量有多的边)
  4. 生成树:包含所有顶点的极小连通子图(连通且边尽可能少)
  5. 简单路径:没有重复的顶点
  6. V < C E − 1 2 + 1 V < C_{E-1}^{2} + 1 VCE12+1时无向图必连通; V > E + 1 V > E + 1 VE+1时无向图必不连通
  7. 最小生成树的n-1条边中并不能保证是n-1条最小的边

第7章 查找

7.1 查找的基本概念

  • 关键字:唯一标识该元素的某个数据项的值
  • 平均查找长度


7.2 顺序查找与折半查找

7.2.1 顺序查找

哨兵:保证查找时不会越界



7.2.2 折半查找

仅适用于有序表,适合顺序存储结构,不适合链式存储结构

  • 对比失败后low = mid +1或high = mid - 1

性质

  1. 对任何一个结点:右子树结点数 - 左子树结点数 = 0或1
  2. 折半查找树预定是平衡二叉树,结点个数为n时,树高为 ⌈ l o g 2 ( n + 1 ) ⌉ \lceil log_{2}^{(n+1)} \rceil log2(n+1)
  3. 若折半查找树中共有n个成功结点,则失败结点有n+1个
  4. 折半查找的时间复杂度

7.2.3 分块查找

  • 块内无序,块间有序

low>high,在low所在的分块查找




7.3 树形查找(重点)

7.3.1 二叉排序树(BST)

  • 左子树结点值 < 根节点值 < 右子树结点值
  • 用中序遍历可得到一个递增的有序序列


  • 平均查找长度(ASL)

二叉排序树的删除
  1. 删除叶结点:直接删除
  2. 目标结点只有左子树/右子树,则让子树直接代替准备删除结点的位置
  3. 目标结点有两颗子树
  • 法一:用右子树中最小的结点代替

  • 法二:用左子树中最大的结点代替


7.3.2 平衡二叉树(AVL)

  • 任一结点的左子树和右子树的高度之差不超过1
  • 结点平衡因子 = 左子树高度 - 右子树高度

平衡二叉树的插入(保持平衡)

  1. LL

  2. RR

  • 代码实现
  1. LR


  2. RL


练习



平衡二叉树的删除

  1. 用前驱替代


  1. 用后继替代



7.3.3 红黑树(RBT)

  • 考纲新添内容,插入和删除考的可能性不大,了解大致过程即可
红黑树的概念与性质




性质:

  1. 从根节点到叶结点的最长路径不大于最短路径的2倍
  2. 若有n个内部结点的红黑树高度 h ≤ O ( l o g 2 ( n + 1 ) ) h \leq O(log_{2}^{(n+1)}) hO(log2(n+1))


红黑树的插入





3直接插


25、18直接插




红黑树的删除(不重要)


7.4 B树和B+树

7.4.1 B树

基本概念



B树的高度



对于含有n个关键字的m阶B树 l o g m ( n + 1 ) ≤ h ≤ l o g ⌈ m 2 ⌉ n + 1 2 + 1 log_{m}{(n+1)} \leq h \leq log_{\lceil \frac{m}{2} \rceil}{\frac{n+1}{2}} + 1 logm(n+1)hlog2m2n+1+1


B树的插入




B树的删除
  1. 删除非终端结点:用直接前驱(左子树中最右下的结点)/直接后继(右子树中最左下的结点)替代。即转换为删除终端结点
  2. 删除终端结点
  • 兄弟够借:

借右兄弟:

借左兄弟:

  • 兄弟不够借



7.4.2 B+树

  • B+树的查找,无论是否找到,最终一定要走到最下面一层结点(B树可能不用)


7.5 散列表

7.5.1 散列表的基本概念


  • 装填因子: α = 表中记录数 n 散列表长度 m \alpha = \frac{表中记录数n}{散列表长度m} α=散列表长度m表中记录数n

α \alpha α 越大说明表中记录数越多,说明表装的越满,冲突的可能性越大,查找的次数越多

  • 散列表的平均查找长度依赖于散列表的装填因子
  • 散列表的查找效率取决于:散列函数、冲突处理、装填因子

7.5.2 散列函数

  1. 除留余数法
  2. 直接定址法
  3. 数字分析法
  4. 平方取中法


7.5.3 拉链法处理冲突



拉链法的平均查找长度

9.5.4 开放定址法处理冲突

  • 开放定址情况下不能随便删除表中已有元素
  1. 线性探测法
    在这里插入图片描述
  2. 平方探测法
    在这里插入图片描述
    在这里插入图片描述
  3. 双散列法
    在这里插入图片描述
  4. 伪随机序列法: d i = 伪随机数序列 d_i = 伪随机数序列 di=伪随机数序列

在这里插入图片描述

线性探测法的查找效率
在这里插入图片描述
在这里插入图片描述

聚集(堆积)现象
  • 线性探测法容易引起聚集问题
  • 堆积问题是由于同义词之间与非同义词之间发送冲突引起的

习题

  1. 在这里插入图片描述

  2. 判断能否构成折半查找中关键字比较序列

  3. 高度为h的AVL至少有多少结点

  4. 正常AVL中序遍历得到的是升序序列,而这道题说的是降序序列。坑…

  5. 高度为5的3阶B树至少有31个结点,最多有121个结点 (注意问的是结点数还是关键字数)

  6. 线性探测法的ASL


第8章 排序

8.1 插入排序

直接插入排序

在这里插入图片描述

void InsertSort(int A[], int n)
{
   int i, j;
   for(i = 2; i <= n; i++)
   {
      if(A[i] < A[i - 1])
      {
         A[0] = A[i];  //复制为哨兵
         for(j = i - 1; A[0] < A[j]; j--)
         {
             A[j + 1] = A[j];  //记录后移
         }
         A[j + 1] = A[0];  //插入到正确位置
     }
   }
}

ps.对链表进行插入排序,移动元素的次数变少,但关键字对比次数仍为 O ( n 2 ) O(n^2) O(n2)数量级

折半插入排序

  • 用折半查找的方式找到应插入的位置
  • 时间复杂度和稳定性同直接插入

void InsertSort(int A[], int n)
{
   int i, j, low, high, mid;
   for(i = 2; i <= n; i++)
   {
      low = 1;
      high = i - 1;
      while(low <= high)   //折半查找
      {
         mid = (low + high)/2;
         if(A[mid] > A[0]) //找左半边
            hgih = mid - 1;
         else  //找右半边(保持稳定性)
            low = mid + 1;
      }
      for(j = i - 1; j >= high + 1; j--)  //后移
         A[j + 1] = A[j];
      A[high + 1] = A[0];  //插入到正确位置
   }
}

希尔排序

在这里插入图片描述

void ShellSort(int A[], int n)
{
   int d, i ,j;
   for(d = n / 2; d >= 1; d = d / 2)   //步长变化
   {
      for(i = d + 1; d <= n; i++)
      {
         if(A[i] < A[i - d])  //将A[i]插入有序增量子表
         {
            A[0] = A[i];   //暂存在哨兵位置
            for(j = i - d; j > 0 && A[0] < A[j]; j -= d) //插入排序
               A[j + d] = A[j];
            A[j + d] = A[0];  //插入到正确位置
         }
      }
   }
}

8.2 交换排序

  • 根据序列中两个元素的关键字的比较结果来兑换这两个记录在序列中的位置

冒泡排序

void BubbleSort(int A[], int n)
{
   for(int i = 0; i < n - 1; i++)
   {
      bool flag = false;   //本次冒泡排序是否发生交换
      for(int j = n - 1; j < i; j-->)  //一趟冒泡过程,从后往前找
      {
         if(A[j - 1] > A[j])
         {
            swap(A[j - 1], A[j]);   //交换
            flag = true;
         }
      }
      if(flag = false)
         return;
   }
}


快速排序

ps.上图最好和最坏复杂度写反了


//用第一个元素将序列划分为左右两部分
int Partition(int A[], int low. int high)
{
   int pivot = A[low];  //将第一个元素作为基准
   while(low < high)
   {
      while(low < high && A[high] >= pivot)
         --high;  //从后往前找比基准小的元素
      A[low] = A[high]; //比基准小的移动到左端
      while(low < high && A[low] <= pivot)
         ++low;   //从前往后找比基准大的元素
      A[high] = A[low]; //比基准大的移动到右端
   }
   A[low] = A[high]; //将基准放到最终位置
   return low; //返回基准所在的位置
}

//快速排序
void QuikSort(int A[], int low, int high)
{
   if(low < high)
   {
      int pivotpos = Partition(A, low, high);   //划分
      QuikSort(A, low, pivotpos - 1);  //划分左子表
      QuikSort(A, pivotpos + 1, high); //划分右子表
   }
}

8.3 选择排序

简单选择排序


void SelectSort(int A[], int n)
{
   for(int i = 0; i < n - 1; i++)   //共n-1趟
   {
      int min = i;   //记录最小元素的位置
      for(int j = i + 1; j < n; j++)
         if(A[j] < A[min]) //找最小元素
            min = j; //更新最小元素位置
      if(min != i)
         swap(A[i], A[min]);  //交换
   }
}

堆排序

在这里插入图片描述

在顺序存储的完全二叉树中,非终端节点编号 i ≤ ⌊ n / 2 ⌋ i \leq \lfloor n/2 \rfloor in/2
在这里插入图片描述

在这里插入图片描述

//建立大根堆
void BuildMaxMap(int A[], int len)
{
   for(int i = len / 2; i > 0; i--) //从后往前调整所有非终端结点
      HeadAdjust(A, i ,len);
}

//将以k为根的子树调整为大根堆
void HeadAdjust(int A[], int k, int len)
{
   A[0] = A[k];   //暂存
   for(int i = 2 * k; i <= len; i *= 2)
   {
      if(i < len && A[i] < A[i + 1])   //选左右孩子中更大的
         i++;
      if(A[0] > A[i])   //满足大根堆,退出
         beak;
      else  //不满足大根堆,小元素下坠
      {
         A[k] = A[i];   //将A[i]放到上面
         k = i;   //修改k值,继续向下比较
      }
   }
   A[k] = A[0];   //最终位置
}

//堆排序
void HeapSort(int A[], int len)
{
   BuildMaxHeap(A, len);   //初始建堆
   for(i = len; i > 1; i--)   //n-1趟交换和建堆过程
   {
      swap(A[i], A[1]); //每次把最大的元素放在最后
      HeadAdjust(A, 1, i - 1);   //声誉元素调整为堆
   }
}

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述


8.4 归并排序

  • 归并太虚的性能与输入元素序列无关
  • m路归并每次选出一元素需要对比关键词m-1次

int *B = (int *)malloc(n * sizeof(int));  //辅助数组B

//将A[low..mid]和A[mid+1..high]归并
void Merge(int A[], int low, int mid, int high)
{
   int i, j, k;
   for(k = low; k <= high; k++)
      B[k] = A[k];   //将A数组复制到B中
   for(i = low, j = mid + 1, k = i; i<= mid && j <= high; k++)
   {
      if(B[i] < B[k])   //将小的放到A中
         A[k] = B[i++];
      else
         A[k] = B[j++];
   }
   //将数组中剩余部分直接放入
   while(i <= mid)
      A[k++] = B[i++];
   while(j <= high)
      A[k++] = B[j++];
}

//归并排序
void MergrSort(int A[], int low, int high)
{
   if(low < high)
   {
      int mid = (low + high) / 2;
      MergeSort(A, low, mid); //归并左半部分
      MergeSort(A, mid + 1, high); //归并右半部分
      Merge(A, low, mid, high);  //归并
   }
}

8.5 基数排序

在这里插入图片描述



8.6 各种内部排序的比较

十大基础排序算法动画,看我就够了!!!



8.7 外部排序

8.7.1 外部排序基本原理





在这里插入图片描述




8.7.2 外部排序的优化




在这里插入图片描述

  • 多路平衡归并
    在这里插入图片描述

败者树

  • 减少多路归并时的对比次数

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述


置换-选择排序

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述


最佳归并树

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述


习题

  1. 对任意n个关键字进行基于比较的排序,至少要进行 l o g 2 ( n ! ) log_{2}^{(n!)} log2(n!) 次关键字之间的两两比较

例如,对1,2,3(n=3)进行从小到大排序,即序列有6(3!)种可能,我们要从这6种可能中找到唯一的一个序列1,2,3。针对3个元素中的任意两个元素,比如1和2,一定要求1在2前面,对此只剩3个序列满足要求,之后依次进行对数折半筛选,只剩唯一一个序列。注意到每次筛选都是折半的,因此是以2为底的对数运算。

  1. 在这里插入图片描述

  2. 在这里插入图片描述

  3. n

  4. n个关键字的小根堆中,关键字最大的记录可能存储在 ⌊ n / 2 ⌋ + 1   n \lfloor n/2 \rfloor + 1 ~ n n/2+1 n

满二叉树最后一个非叶结点为 ⌊ n / 2 ⌋ \lfloor n/2 \rfloor n/2

  1. 在这里插入图片描述

  2. 堆排序和希尔排序用到了顺序表随机存储的特点,用链表实现更慢

  3. 设有5个初始归并段,每个归并段有20个记录,采用5路平衡归并的方法,若不采用败者树,总的比较次数为369次;若采用败者树,总的比较次数为300次
    在这里插入图片描述

  4. 在这里插入图片描述

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/weixin_51252109/article/details/131534761

智能推荐

从零开始搭建Hadoop_创建一个hadoop项目-程序员宅基地

文章浏览阅读331次。第一部分:准备工作1 安装虚拟机2 安装centos73 安装JDK以上三步是准备工作,至此已经完成一台已安装JDK的主机第二部分:准备3台虚拟机以下所有工作最好都在root权限下操作1 克隆上面已经有一台虚拟机了,现在对master进行克隆,克隆出另外2台子机;1.1 进行克隆21.2 下一步1.3 下一步1.4 下一步1.5 根据子机需要,命名和安装路径1.6 ..._创建一个hadoop项目

心脏滴血漏洞HeartBleed CVE-2014-0160深入代码层面的分析_heartbleed代码分析-程序员宅基地

文章浏览阅读1.7k次。心脏滴血漏洞HeartBleed CVE-2014-0160 是由heartbeat功能引入的,本文从深入码层面的分析该漏洞产生的原因_heartbleed代码分析

java读取ofd文档内容_ofd电子文档内容分析工具(分析文档、签章和证书)-程序员宅基地

文章浏览阅读1.4k次。前言ofd是国家文档标准,其对标的文档格式是pdf。ofd文档是容器格式文件,ofd其实就是压缩包。将ofd文件后缀改为.zip,解压后可看到文件包含的内容。ofd文件分析工具下载:点我下载。ofd文件解压后,可以看到如下内容: 对于xml文件,可以用文本工具查看。但是对于印章文件(Seal.esl)、签名文件(SignedValue.dat)就无法查看其内容了。本人开发一款ofd内容查看器,..._signedvalue.dat

基于FPGA的数据采集系统(一)_基于fpga的信息采集-程序员宅基地

文章浏览阅读1.8w次,点赞29次,收藏313次。整体系统设计本设计主要是对ADC和DAC的使用,主要实现功能流程为:首先通过串口向FPGA发送控制信号,控制DAC芯片tlv5618进行DA装换,转换的数据存在ROM中,转换开始时读取ROM中数据进行读取转换。其次用按键控制adc128s052进行模数转换100次,模数转换数据存储到FIFO中,再从FIFO中读取数据通过串口输出显示在pc上。其整体系统框图如下:图1:FPGA数据采集系统框图从图中可以看出,该系统主要包括9个模块:串口接收模块、按键消抖模块、按键控制模块、ROM模块、D.._基于fpga的信息采集

微服务 spring cloud zuul com.netflix.zuul.exception.ZuulException GENERAL-程序员宅基地

文章浏览阅读2.5w次。1.背景错误信息:-- [http-nio-9904-exec-5] o.s.c.n.z.filters.post.SendErrorFilter : Error during filteringcom.netflix.zuul.exception.ZuulException: Forwarding error at org.springframework.cloud..._com.netflix.zuul.exception.zuulexception

邻接矩阵-建立图-程序员宅基地

文章浏览阅读358次。1.介绍图的相关概念  图是由顶点的有穷非空集和一个描述顶点之间关系-边(或者弧)的集合组成。通常,图中的数据元素被称为顶点,顶点间的关系用边表示,图通常用字母G表示,图的顶点通常用字母V表示,所以图可以定义为:  G=(V,E)其中,V(G)是图中顶点的有穷非空集合,E(G)是V(G)中顶点的边的有穷集合1.1 无向图:图中任意两个顶点构成的边是没有方向的1.2 有向图:图中..._给定一个邻接矩阵未必能够造出一个图

随便推点

MDT2012部署系列之11 WDS安装与配置-程序员宅基地

文章浏览阅读321次。(十二)、WDS服务器安装通过前面的测试我们会发现,每次安装的时候需要加域光盘映像,这是一个比较麻烦的事情,试想一个上万个的公司,你天天带着一个光盘与光驱去给别人装系统,这将是一个多么痛苦的事情啊,有什么方法可以解决这个问题了?答案是肯定的,下面我们就来简单说一下。WDS服务器,它是Windows自带的一个免费的基于系统本身角色的一个功能,它主要提供一种简单、安全的通过网络快速、远程将Window..._doc server2012上通过wds+mdt无人值守部署win11系统.doc

python--xlrd/xlwt/xlutils_xlutils模块可以读xlsx吗-程序员宅基地

文章浏览阅读219次。python–xlrd/xlwt/xlutilsxlrd只能读取,不能改,支持 xlsx和xls 格式xlwt只能改,不能读xlwt只能保存为.xls格式xlutils能将xlrd.Book转为xlwt.Workbook,从而得以在现有xls的基础上修改数据,并创建一个新的xls,实现修改xlrd打开文件import xlrdexcel=xlrd.open_workbook('E:/test.xlsx') 返回值为xlrd.book.Book对象,不能修改获取sheett_xlutils模块可以读xlsx吗

关于新版本selenium定位元素报错:‘WebDriver‘ object has no attribute ‘find_element_by_id‘等问题_unresolved attribute reference 'find_element_by_id-程序员宅基地

文章浏览阅读8.2w次,点赞267次,收藏656次。运行Selenium出现'WebDriver' object has no attribute 'find_element_by_id'或AttributeError: 'WebDriver' object has no attribute 'find_element_by_xpath'等定位元素代码错误,是因为selenium更新到了新的版本,以前的一些语法经过改动。..............._unresolved attribute reference 'find_element_by_id' for class 'webdriver

DOM对象转换成jQuery对象转换与子页面获取父页面DOM对象-程序员宅基地

文章浏览阅读198次。一:模态窗口//父页面JSwindow.showModalDialog(ifrmehref, window, 'dialogWidth:550px;dialogHeight:150px;help:no;resizable:no;status:no');//子页面获取父页面DOM对象//window.showModalDialog的DOM对象var v=parentWin..._jquery获取父window下的dom对象

什么是算法?-程序员宅基地

文章浏览阅读1.7w次,点赞15次,收藏129次。算法(algorithm)是解决一系列问题的清晰指令,也就是,能对一定规范的输入,在有限的时间内获得所要求的输出。 简单来说,算法就是解决一个问题的具体方法和步骤。算法是程序的灵 魂。二、算法的特征1.可行性 算法中执行的任何计算步骤都可以分解为基本可执行的操作步,即每个计算步都可以在有限时间里完成(也称之为有效性) 算法的每一步都要有确切的意义,不能有二义性。例如“增加x的值”,并没有说增加多少,计算机就无法执行明确的运算。 _算法

【网络安全】网络安全的标准和规范_网络安全标准规范-程序员宅基地

文章浏览阅读1.5k次,点赞18次,收藏26次。网络安全的标准和规范是网络安全领域的重要组成部分。它们为网络安全提供了技术依据,规定了网络安全的技术要求和操作方式,帮助我们构建安全的网络环境。下面,我们将详细介绍一些主要的网络安全标准和规范,以及它们在实际操作中的应用。_网络安全标准规范