• 数据结构笔记——自用


    请善用目录Ctrl+F

    Chp1-1 数据结构的基本概念

    image-20220601164032186 image-20220601164130990

    数据概念:

    image-20220601164241116

    早期的计算机一一只用于处理纯数值型问题

    现代计算机一一经常处理非数值型问题

    对于非数值型的问题:
    1.我们关心每个个体的具体信息
    2.我们还关心个体之间的关系

    数据项、数据元素之间的关系

    image-20220601164431996 image-20220601164541973

    数据对象

    • 数据对象是具有相同性质的数据元素的集合,是数据的一个子集。
    • 数据结构是相互之间存在一种或多种特定关系的数据元素的集合。

    即,数据元素与数据结构之间没有指定的关心:

    不同的数据类型可以组成相同的数据结构;相同的数据类型可以组成不同数据结构。

    数据结构三要素

    image-20220601165138020 image-20220601171011611 image-20220601171407525
    • 线性结构

      • 逻辑结构image-20220601165317790

      • 物理结构–链式存储:image-20220601170505773

        • 优点:不会出现碎片现象
        • 缺点:每个元素因存储指针而占用额外的存储空间,且只能顺序存放
      • 物理结构–索引存储: image-20220601170733757

        • 优点:检索速度快

        • 缺点:附加的索引表额外占用存储空间,且增减和删除数据时也要修改索引表,因此会花费较多时间

      • 物理结构–散列存储:image-20220601170913893

        • 优点:检索、增加和删除节点的操作很快

        • 缺点:若散列函数不好,则可能出现元素存储单元冲突,而解决冲突会增加时间和空间的开销

    • 树形结构:image-20220601165345459

    • 图形结构:image-20220601165413012

    • 集合结构:image-20220601170040823

    数据类型、抽象数据类型

    数据类型是一个值的集合和定义在此集合上的一组操作的总称。
    1)原子类型。其值不可再分的数据类型
    2)结构类型。其值可以再分解为若干成分(分量)的数据类型

    **抽象数据类型(Abstract Data Type, ADT)😗*是抽象数据组织及与之相关的操作。

    • ADT用数学化的语言定义数据的逻辑结构、定义运算。与具体的实现无关
    • 确定了ADT的存储结构,才 能“实现”这种数据结构

    例题

    03 以下属于逻辑结构的是:

    A.顺序表

    B.哈希表

    C.有序表

    D.单链表

    image-20220602144549885

    04 以下与数据的存储结构无关的术语是:

    A.循环队列

    B.链表

    C.哈希表

    D.栈

    image-20220701142635782

    06 在存储数据时,通常不仅要存储各数据元素的值,而且要存储:

    A.数据的操作方法

    B.数据元素的类型

    C.数据元素之间的关系

    D.数据的存取方式

    image-20220602144946879

    Chp1-2 算法的基本概念

    程序 = 数据结构 + 算法

    算法的特性

    1. 有穷性。一个算法必须总在执行有穷步之后结束,且每一步都可在有穷时间内完成。
      注:算法必须是有穷的,而程序可以是无穷的(如微信是程序不是算法)
    2. 确定性。算法中每条指令必须有确切的含义,对于相同的输入只能得出相同的输出
    3. 可行性。算法中描述的操作都可以通过己经实现的基本运算执行有限次来实现。
    4. 输入。一个算法有零个或多个输入,这些输入取自于某个特定的对象的集合。
    5. 输出。一个算法有一个或多个输出,这些输出是与输入有着某种特定关系的量。

    好算法的特性

    1. 正确性。算法应能够正确地解决求解问题。

    2. 可读性。算法应具有良好的可读性,以帮助人们理解。

    3. 健壮性。输入非法数据时,算法能适当地做出反应或进行处理,而不会产生莫名其妙的输出结果。

    4. 高效率低存储量需求

      (时间复杂度低)+(空间复杂度低)

    算法的时间复杂度

    image-20220602140519820

    image-20220602140608631

    image-20220602140740831

    结论

    • 结论1:可以只考虑阶数高的部分
    • 结论2:问题规模足够大时,常数项系数也可以忽略

    运算规则

    image-20220602140927553

    趋向于无穷的速度::

    O ( 1 ) < O ( l o g 2 n ) < O ( n ) < O ( n ∗ l o g 2 n ) < O ( n 2 ) < O ( n 3 ) < O ( 2 n ) < O ( n ! ) < O ( n n ) Ο(1)<Ο(log_2n)<Ο(n)<Ο(n*log_2n)<Ο(n^2)<Ο(n^3)<Ο(2^n)<Ο(n!)<Ο(n^n) O(1)<O(log2n)<O(n)<O(nlog2n)<O(n2)<O(n3)<O(2n)<O(n!)<O(nn)

    类型–顺序执行型:

    image-20220701142959310

    类型–循环嵌套型:

    image-20220602143140635

    类型–指数循环型:

    image-20220602143312747

    类型–条件搜索型:

    注:很多算法执行时间与输入的数据有关

    image-20220701143058271 image-20220701143145839

    最坏时间复杂度:最坏情况下算法的时间复杂度
    平均时间复杂度:所有输入示例等概率出现的情况下,算法的期望运行时间
    最好时间复杂度:最好情况下算法的时间复杂度

    小节总结

    image-20220701143218929

    例题

    08 设n是描述问题规模的非负整数,下面程序片段的时间复杂度是:

    x = 2;
    while(x<n/2)	x = 2*x;
    
    • 1
    • 2

    A. O ( l o g 2 n ) Ο(log_2n) O(log2n)

    B. O ( n ) Ο(n) O(n)

    C. O ( n l o g 2 n ) Ο(nlog_2n) O(nlog2n)

    D. O ( n 2 ) Ο(n^2) O(n2)

    [解]

    执行频率最高的语句为 x = 2*x 每执行一次,x乘2。假设该语句最终执行t次,又因x初始值为2,所以有:

    2 t + 1 < n / 2 2^{t+1}2t+1<n/2=>

    ( t + 1 ) l o g 2 2 < l o g 2 ( n / 2 ) (t+1)log_22(t+1)log22<log2(n/2)=>

    ( t + 1 ) < l o g 2 n − 1 (t+1)(t+1)<log2n1=>

    t < l o g 2 n − 2 tt<log2n2 因此有 T ( n ) = O ( l o g 2 n ) T(n)=Ο(log_2n) T(n)=O(log2n)

    10 已知两个长度分别为 m 和 n 的升序两边,若将他们合并为长度为 m + n 的一个降序链表,则最坏情况下的时间复杂度是:

    A. O ( n ) Ο(n) O(n)

    B. O ( m n ) Ο(mn) O(mn)

    C. O ( m i n ( m , n ) ) Ο(min(m,n)) O(min(m,n))

    D. O ( m a x ( m , n ) ) Ο(max(m,n)) O(max(m,n))

    [解]

    一开始我的想法是,先将这两个升序链表合并成一个升序链表花费 O ( m i n ( m , n ) ) Ο(min(m,n)) O(min(m,n)) ,然后再整体使用头插法实现逆序,花费 O ( m + n ) Ο(m+n) O(m+n) 两者相加: O ( m + n + m i n ( m , n ) ) Ο(m+n+min(m,n)) O(m+n+min(m,n))

    • 当 m>>n时: O ( m + n + m i n ( m , n ) ) Ο(m+n+min(m,n)) O(m+n+min(m,n))~ O ( 2 m ) Ο(2m) O(2m) ~ O ( m ) Ο(m) O(m) > > O ( n ) >>Ο(n) >>O(n)

    A错;B: O ( n ) Ο(n) O(n) 故C错;而D: O ( m ) Ο(m) O(m) 满足条件

    • 当 m~n时 : O ( m + n + m i n ( m , n ) ) Ο(m+n+min(m,n)) O(m+n+min(m,n)) ~ O ( n + m ) Ο(n+m) O(n+m)~ O ( 2 n ) Ο(2n) O(2n) ~ O ( n ) Ο(n) O(n)

    B~ O ( n 2 ) Ο(n^2) O(n2) 因此B错;而D: O ( n ) Ο(n) O(n) ~ O ( m ) Ο(m) O(m) 满足条件

    • 当 m< O ( m + n + m i n ( m , n ) ) Ο(m+n+min(m,n)) O(m+n+min(m,n))~ O ( 2 n ) Ο(2n) O(2n) ~ O ( n ) Ο(n) O(n)

    而 D: O ( n ) Ο(n) O(n) 满足条件

    • 综上,D满足所有情况

    12 下列函数的时间复杂度是:

    int func(int n){
     int i = 0,sum = 0;
     while(sum<n)	sum += ++i;
     return i;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    A. O ( l o g n ) Ο(logn) O(logn)

    B. O ( n 1 / 2 ) Ο(n^{1/2}) O(n1/2)

    C. O ( n ) Ο(n) O(n)

    D. O ( n l o g n ) Ο(nlogn) O(nlogn)

    [解]

    基本运算 sum += ++i,等价于 ++i;sum = sum+i,每执行一次 i 自增1。

    不难发现这是一个公差为1的等差数列求和,起始项为1,尾项为n,共有n项。可知循环次数满足: ( 1 + t ) ∗ t / 2 < n (1+t)*t/2(1+t)t/2<n => 时间复杂度为 O ( n 1 / 2 ) Ο(n^{1/2}) O(n1/2)

    扩展:

    求解 递归与非递归算法下的斐波那契数列算法时间复杂度
    F ( n ) = { 1 , if  n = 0 , 1 ; F ( n − 1 ) + F ( n − 2 ) , if  n > 1.

    F(n)={1,if n=0,1;F(n1)+F(n2),if n>1." role="presentation" style="position: relative;">F(n)={1,if n=0,1;F(n1)+F(n2),if n>1.
    F(n)={1,F(n1)+F(n2),if n=0,1;if n>1.

    • 递归求解:
    int dfs(n){
       if(n<=1)	return 1;
       return dfs(n-1)+dfs(n-2);
    }
    
    • 1
    • 2
    • 3
    • 4

    执行图:

    image-20220603160916329

    即, 2 0 + 2 1 + 2 2 + . . + 2 n = 1 − 2 n 1 − 2 = 2 n − 1 2^0+2^1+2^2+..+2^n=\frac{1-2^n}{1-2}=2^n-1 20+21+22+..+2n=1212n=2n1 故时间复杂度为: O ( 2 n ) Ο(2^n) O(2n)

    • 非递归求解
    int dfs(int n){
       if(n<=1)	return 1;
       int[] tmp = new int[n+1];//初始化数组
       tmp[1] = 1;
       for(int i = 2;i<=n;i++){
           //其过程只依靠与前两个元素
           temp[i] = temp[i-1] + temp[i-2];
       }
       return tmp[n];
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    不难看出,时间复杂度为 O ( n ) Ο(n) O(n)

    空间复杂度

    image-20220603163418331

    Chp2-1 线性表

    线性表的定义

    注:数据结构三要素一一逻辑结构、数据的运算、存储结构(物理结构);储存的方式不同,运算实现的方式不同

    image-20220702153510177

    小节回顾

    chp2-2顺序表

    定义

    顺序表一一用顺序存储(一般使用数组)的方式实现线性表顺序存储。把逻辑上相邻的元素存储在物理
    位置上也相邻
    的存储单元中,元素之间的关系由存储单元的邻接关系来体现。

    而在数组分配上又可再进一步的分为动态数组分配与静态数组分配。

    顺序表的特点

    image-20220604180908974

    小节总结

    2-2.1实现–静态\动态数组分配

    代码实现
    #include
    #include
    
    #define MaxiSize  10    //默认顺序表的最大长度
    
    //使用静态数组分配顺序表
    /* Static Arrays*/
    typedef struct SAList
    {
        int data[MaxiSize];     //使用静态数组存放数据
        int length;                 //顺序表当前长度
    } SASqList;                     //顺序表的定义(静态分配方式)
    
    //使用动态分配顺序表
    /* Malloc Arrays*/
    typedef struct MAList
    {
        int *data;                  //指示动态分配数组的指针
        int MaxSize;            //顺序表的最大容量
        int length;             //顺序表的当前长度
    }MASqList;
    
    //静态数组顺序表初始化函数
    void SAInitList(SASqList *L){
        printf("<-------静态顺序表初始化函数------->\n");
        for (int i = 0; i < MaxiSize; i++)
        {
            //全部数组赋初值为0
            L->data[i] = 0;
        }
        //顺序表初始长度为0
        L->length = 0;
        printf("<-------静态顺序表初始化结束------->\n");
    }
    
    void MAInitList(MASqList *L){
        printf("<-------动态顺序表初始化函数------->\n");
        //用malloc申请一片连续的数组空间
        L->data = (int *)malloc(MaxiSize * sizeof(int));
        //顺序表初始长度为0
        L->length = 0;
        L->MaxSize = MaxiSize;
        printf("<-------静态顺序表初始化结束------->\n");
    }
    
    void MAIncreaseSize(MASqList *L,int len){
        int *p = L->data;                               //用一个指针暂存当前顺序表的数组指针
        //重新给L顺序表分配空间(变大了)
        L->data = (int *)malloc((L->MaxSize + len) * sizeof(int));
        for (int i = 0; i < L->MaxSize; i++)
        {
            L->data[i] = p[i];                          //将原来L顺序表中的元素赋值到新的区域
        }
        L->MaxSize = L->MaxSize + len;  //顺序表最大长度增加 len
        free(p);                                        //释放原来的内存空间
    }
    
    int main(){
        SASqList SAL;            //声明一个静态顺序表
        SAInitList(&SAL);       //初始化静态顺序表
    
        MASqList MAL;       //声明一个动态顺序表
        MAInitList(&MAL);
        return 0;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66

    2-2.2顺序表的基本功能–插入

    功能代码是在静态分配方式下实现的,动态分配也雷同

    image-20220604183237450
    代码实现
    /** 静态顺序表的插入 在L的位序 i 处插入元素 e
     * @param  L 需要操作的链表
     * @param i   需要插入的位置(表L中第i个位置对应于下标i-1)
     * @param e  需要插入的元素
     *  这里需要注意的是,最后e应该放到 i -1 = 2 的数组下标位置
     *  如 i = 2    j最后一进入 for 循环值为 2 则data[2] = data[1]
     *  0   1   2   3   4  5    index
     *  3   5   7   9   0       values
     *  3   5   5   7   9   0    将 下标>=1的元素全部后移一位
     *  3   e   5   7   9   0    将元素放置i-1=1处
     */
    bool SAListInsert(SASqList *L,int i,int e){
        if(i<1||i>(L->length)+1)    //判断 i 的合法性
            return false;
        if(L->length>=MaxiSize) //如果空间已满则也不能插入
            return false;
        //将第 i 个元素及之后的元素后移
        for (int j = L->length; j >= i; j--)
        {
            L->data[j] = L->data[j - 1];
        }
        L->data[i - 1] = e;     //在位序 i 处放入e
        L->length++;           //长度+1
        return true;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    时间复杂度分析
    image-20220702154426621

    2-2.3顺序表的基本功能–删除

    image-20220604192533991

    代码实现
    /** 静态顺序表的删除 将L的位序为 i 的元素删除 并将其赋至e
     *@param L 需要操作的表
     @param i   需要删除的位序
     @param e  对应为删除的元素带回来
     *  如 i = 2
     * 有 e = data[1] =5
     * j第一轮进入  for 循环值为 i=2                      则data[1] = data[2]
     * j最后一进入 for 循环值为 length-1=5-1=4 则data[3] = data[4]
     *  0   1   2   3   4  5    index
     *  3   5   7   9   0       values
     *  3   7   9   0   0       将 下标>=2的元素全部前移一位
     * */
    bool SAListDelete(SASqList *L,int i , int *e){
        if(i<1||i>L->length)                        //判断 i 的合法性
            return false;
        *e = L->data[i-1];                          //将需要删除的元素的值赋到e
        for (int j = i; j < L->length;j++){
            L->data[j - 1] = L->data[j];    //将第 i 各位之后的元素前移
        }
        L->length--;                               //线性表长度-1
        return true;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    时间复杂度分析
    image-20220604195217959
    小节总结
    image-20220702154505827

    2-2.4顺序表的查找

    按位查找
    代码实现
    /** 获取链表L中位序为 i 的元素
     *  @param L 需要操作的链表
     *  @param i 需要取值的位序
     * */
    int SAGetElem(SASqList *L,int i ){
        if(i<1||i>L->length)    //判断位序的合法性
            return -1;
        return L->data[i - 1]; //与数组的使用无异
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    时间复杂度
    image-20220605141100271
    按值查找
    代码实现
    /** 按值查找    在顺序表中获取值 e 第一次出现的位序
     *  @param L 需要操作的链表
     *  @param e 需要查询的数值
     * */
    int SALocateElem(SASqList *L,int e){
        for (int i = 0; i < L->data;I++){
            if(L->data[i]==e)       	//如果匹配到对应的值
                return i + 1;           //范围对应的位序
        }
        return -1;                     //如果没有查询到,则返回-1
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    时间复杂度
    image-20220702154635444

    chp2-3链表

    知识总览

    image-20220702154723323

    2-3.1单链表

    单链表与顺序表的对比

    image-20220702154744812
    单链表的创建

    链表结构体声明

    typedef struct Node
    {
        int data;                       //数据域 每个节点存放一个数据
        struct Node *pNext;    //指针域 指针指向下一个节点
    } LNode, *LinkList;
    //NODE *    声明指向单链表的第一个节点的指针
    //PNODE    声明指向单链表的第一个节点的指针--可读性更强
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    不带头结点与带头结点的空链表初始化
    //不带头结点的空链表初始化
    LinkList NHInitList(){
        return NULL;    //空表,没有数据
    }
    
    //带头结点的空链表初始化
    LinkList HInitList(){
        LinkList L = NULL;
        L = (LinkList)malloc(sizeof(LNode));
        if(L=NULL){
            printf("创建失败!\n");
            exit(-1);
        }
        L->pNext = NULL;
        return L;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    头插法创建单链表

    代码实现

    //带头结点的头插法创建单链表
    LinkList ListCreatHeadInsert()
    {
        int len;                    //所需创建的链表长度
        LNode *s;               //暂存新建节点指针
        LinkList pHead;     //返回的创建的链表头节点指针
        //创建头节点
        pHead = (LNode *)malloc(sizeof(LNode));
    
        if(pHead==NULL)
        {
            printf("分配内存失败!\n");
            exit(-1);
        }
    
        printf("<=========头插法=========>\n");
        printf("请输入需要生产的节点数:\n");
        scanf("%d", &len);
        for (int val,i = 0; i < len;i++)
        {
            printf("请输入第%d个节点的值:", i + 1);
            scanf("%d", &val);
            s = (LinkList)malloc(sizeof(LNode));  //分配空间给新节点
            if(s==NULL)
            {
                printf("分配内存失败!\n");
                exit(-1);
            }
            s->data = val;                                  //赋值给新创建的节点
            s->pNext = pHead->pNext;          //新节点接管头节点的后继节点
            pHead->pNext = s;                       //新节点成为头节点的后继节点
        }
        return pHead;                                   //返回头节点
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    重要应用:链表逆序

    //利用头插法逆序带头结点的链表
    LinkList Reverse(LinkList pHead){
        if(HEmpty(pHead))
            return pHead;
        LinkList p = HInitList();           //初始化空的带头结点链表
        while(!HEmpty(pHead)){
            pHead = pHead->pNext;  //链表指向下一个节点,注意,一开始进来需要跳过头节点,
            					   //因为头节点不带数据
            LNode *s = (LNode *)malloc(sizeof(LNode));//为新节点分配空间
            s->data = pHead->data;//赋值
            s->pNext = p->pNext;
            p->pNext = s;
        }
        return p;                  //返回逆序后的头节点
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    尾插法创建单链表

    代码实现

    // 带头节点的尾插法创建单链表
    LinkList ListCreatTailInsert(){
        int len;                    //所需创建的链表长度
        LNode *s, *r;          // s:暂存新建节点指针,r保存当前的尾节点的指针
        LinkList pHead;     //返回的创建的链表头节点指针
        //创建头节点
        pHead = (LNode *)malloc(sizeof(LNode));
        r = pHead;                                          //尾指针指向pHead节点,因为是新建立的,故为最后一个节点
        if(pHead==NULL)
        {
            printf("分配内存失败!\n");
            exit(-1);
        }
        printf("<=========尾插法=========>\n");
        printf("请输入需要生产的节点数:\n");
        scanf("%d", &len);
        for (int val,i = 0; i < len;i++)
        {
            printf("请输入第%d个节点的值:", i + 1);
            scanf("%d", &val);
            s = (LinkList)malloc(sizeof(LNode));  //分配空间给新节点
            if(s==NULL)
            {
                printf("分配内存失败!\n");
                exit(-1);
            }
            s->data = val;                                  //赋值给新创建的节点
            s->pNext = NULL;                           //新节点的后继节点置空
            r->pNext = s;                                  //尾节点的后继节点指向新节点
            r = s;                                              //尾节点指向新节点
        }
        /*
            也可以将上面的语句 s->pNext = NULL 从循环中删除
            改成在循坏外最后执行:r->pNext = NULL;
        */
        return pHead;                                   //返回头节点
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    小节总结

    image-20220605154923264

    单链表的插入与删除

    知识总览

    image-20220605170604374

    按位序插入(带头结点)

    image-20220605171328495

    代码实现

    /** 带头结点的单链表按位序插入
     * @param L 操作的链
     * @param i 需要插入的位序
     * @param e 需要插入的元素
     * 头节点作为第0号节点,而插入第 i 位节点可看成插入第 i -1 节点之后
     * 故我们需要定位到 第 i - 1 节点(因为链表插入到某个节点后,需要要知道该节点的前继节点)
     * 下面定位节点的 for 循环功能每次执行完一次,指针都会指向到下一个下一个节点
     * 因此如果我们想要当最后一次执行完for循环之后能正确的指向我们所需的节点位置( 第i - 1节点)
     * 我们就得需要却确保最后一次进入for循环时遍历到的是第 i -2 个节点
     * 故下面for循环的终止条件为 j< i-1 <=> j<= i - 2
     * */
    bool HListInsert(LinkList *L, int i, int e){
        if(i<i)                 //判断位序的合法性
            return false;
        LNode *p;      		   //指针p记录当前扫描到的节点
        int j = 0;            //当前p指向的是第几个节点
        p = L;               //L指向头节点,头节点是第0个节点(不存数据)
        //循环找到第 i-1 个节点
        while(p!=NULL&&j<i-1){
            p = p->pNext;
            j++;
        }
    
        if(p==NULL)//i值不合法
            return false;
        //分配新节点空间
        LNode *s = (LNode *)malloc(sizeof(LNode));
        s->data = e;                    //赋值给新节点
        s->pNext = p->pNext;   //将p节点看成头节点
        p->pNext = s;                //使用 头插法
        return true;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    几种情况:

    • 插入表头: i = 0

      image-20220606171955686

    • 插入表中:0

      image-20220606173542456

    • 插入表尾:i=length

      其逻辑与插入表中类似

      image-20220606174020179

    • 插入表外:i>length

      image-20220606174522173

    按位序插入(不带头节点)

    image-20220607135722737

    代码实现

    /** 不带头结点的按序插入
     * @param L 需要操作的链表                    //L指向的是第一个节点元素指针的指针
     * @param i 需要插入的位序
     * @param e 需要插入的元素
     * 注意,由于不带头结点,插入第一位时需要修改头指针指向,所以传参数时
     * 不能值传递,只能指针传递,即传递指针的指针
     * */
    bool NHListInsert(LinkList *L, int i, int e)  {
        if(i<1)
            return false;                                                           //判断位序的合法性
        //因为不带头结点,故需指要单独判断和操作。具体的,让新节点插入表头
        //并让表头指针指向该节点
        LNode *s = (LNode *)malloc(sizeof(LNode));      //给新结点分配空间
        if(i==1){
            s->data = e;                                                        //给新结点赋值
            s->pNext = (*L);                                                     //将头节点插入表头
            (*L) = s;                                                                  //将头指针的指向新节点
            return true;
        }
    
        /* 以下代码段同头插法的寻找第 i-1 个节点 */
        LNode *p = *(L);                                                         //指针p指向当前扫描到的节点
        int j = 1;                                                                //当前p指向的第几个几点,注意,由于是不带头节点的,故从1开始
        while(p!=NULL&&j<i-1){
            p = p->pNext;
            j++;
        }
        if(p==NULL)                                                        //i值不合法,即i大小超过当前链表长度
            return false;
        s->data = e;
        s->pNext = p->pNext;
        p->pNext = s;
        return true;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    需要特别注意的是:i=1插入表首的情况

    image-20220607161747971

    其他情况与带头结点一致:

    image-20220607161917729
    指定节点的后插操作
    image-20220607162019473

    代码实现

    /** 插入到指定节点 p 后面
     * @param P 指定的节点
     *  @param e 需要插入的元素
     * */
    bool HInsertNextNode(LNode *p, int e){
        if(p==NULL)
            return false;
        LNode *s = (LNode *)malloc(sizeof(LNode));
        if(s==NULL)
            return false;
        s->data = e;
        s->pNext = p->pNext;        //后插
        p->pNext = s;
        return true;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    指定节点的前插操作

    解法一:

    /** 将元素 e 前插到指定节点 p 之前
     * @param L 需要菜操作的链表头指针
     * @param P 指定的节点
     * @param e 需要插入的元素
     **/
    bool HInsertPriorNode(LinkList L, LNode *p, int e){
        //如果需要插入到头节点之后,即需要充当第一个节点
        if(L==p){
            LNode *s = (LNode *)malloc(sizeof(LNode));
            s->data = e;
            s->pNext = L->pNext;        //1-1使用头插法将s插到头节点L之后
            L->pNext = s;                     //1-2使用头插法将s插到头节点L之后
            return true;
        }
        LNode *r = L->pNext;                   //创建尾指针 初始化为指向头节点
        /* r指针寻找目标节点:p  ;L指针充当r的前继结点的指针   */
        while(r!=NULL){
            //如果 r 指针找到目标节点 p
            if(r==p){
                LNode *s = (LNode *)malloc(sizeof(LNode));
                s->data = e;
                s->pNext = L->pNext;                //  1-1将s节点接到L节点后面
                L->pNext = s;                             //  1-2将s节点接到L节点后面
                return true;
            }
            //如果没有找到,继续进行指针的传递
            else{
                L = r->pNext;
                r = r->pNext;
            }
        }
        //没有找到p,结束
        return false;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    image-20220607170510011

    解法二:

    /** 将元素 e 前插到指定节点 p 之前
     * @param   p 指定的节点
     * @param e 需要插入的元素
     * */
    bool HInsertPriorNodeC(LNode *p, int e){
        if(p==NULL)
            return false;
        LNode *s = (LNode *)malloc(sizeof(LNode));
        if(s==NULL)
            return false;
        s->pNext = p->pNext;        //1.1将新节点插入到指定节点p之后
        p->pNext = s;                     //1.2将新节点插入到指定节点p之后
        s->data = p->data;            //2.1交换s与p两节点的数据
        p->data = e;                      //2.2交换s与p两节点的数据
        return true;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    image-20220607170637272

    按位序删除(带头结点/不带头结点)
    image-20220702155308860

    代码实现

    /** 带头结点删除位序为 i 的节点 并将删除的元素赋给 e
     * @param L 需要操作的链表
     * @param i 需要删除的节点位序
     * @param e 将删除的值赋给e
     * */
    bool HListDelete(LinkList L, int i, int *e){
        if(i<1)                 //判断i的范围合法性
            return false;
        LNode *p;           //指针p指向的是当前扫描的节点
        p = L;                 //开始指向头节点
        int j = 0;            //L指向头节点,头结点是第0个节点,不存数据
        //循环找到第 i-1 个节点
        while(p!=NULL&&j<i-1){
            p = p->pNext;
            j++;
        }
        if(p==NULL)                             //如果 i 值不合法
            return false;
        if(p->pNext==NULL)              //第 i-1 个节点之后已无其他节点
            return false;
        LNode *q = p->pNext;          //令q指向被删除的节点
        *e = q->data;
        p->pNext = q->pNext;        //将*q节点从从链中“断开”
        free(q);                                 //释放节点的存储空间
        return true;                          //删除成功
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    image-20220608145012819

    不带头结点

    /** 不带头结点删除位序为 i 的节点 并将删除的元素赋给 e
     *  @param L 需要操作的链表
     * @param i 需要删除的节点位序
     * @param e 将删除的值赋给e
     * */
    bool NHListDelete(LinkList *L, int i, int *e){
        if(i<1||L==NULL)                 //判断 i 与 指针 的合法性
            return false;
        if(i==1){
            LNode *q = (*L);
            *e = q->data;
            *L = q->pNext;
            free(q);
            return true;
        }
        //<<=====余下操作同带头结点的====>>//
        LNode *p;           //指针p指向当前扫描的节点
        p = *L;                //开始指向首节点
        int j = 1;             //L指向首节点,首节点是第1个节点
        //循环找到第 i-1 个节点
        while(p!=NULL&&j<i-1){
            p = p->pNext;
            j++;
        }
        if(p==NULL)                             //如果 i 值不合法
            return false;
        if(p->pNext==NULL)              //第 i-1 个节点之后已无其他节点
            return false;
        LNode *q = p->pNext;          //令q指向被删除的节点
        *e = q->data;
        p->pNext = q->pNext;        //将*q节点从从链中“断开”
        free(q);                                 //释放节点的存储空间
        return true;                          //删除成功
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    指定节点的删除
    image-20220608152147697

    方法一:传入头指针,查找所需删除节点p的前继结点

    代码实现:

    /** 删除指定节点 方法一:查找前继结点
     * @param L 需要操作的链表
     *  @param p 需要删除的节点
     * */
    bool HDeleteNode(LinkList L, LNode *p){
        LNode *r = L->pNext;                //设置扫描尾指针,指向当前扫描到的值,初始化为第一个节点
        //查找需要删除的节点的前继结点 用L指向;此时r匹配到需要删除的节点
        while(r!=NULL&&r!=p){
            L = r;                                //1-1进行指针传递
            r = r->pNext;                         //1-2进行指针传递
        }
        if(r==NULL)
            return false;
        L->pNext = r->pNext;           			//将指针r从链表中“断开”
        free(r);
        return true;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    方法二:与后继节点交换数据,并将后继节点删除

    代码实现:

    /** 删除指定节点
     * 交换p节点与其后继节点之间的数据,然后将后继节点删除
     * @param p 需要删除的节点
     * */
    bool HDeleteNode1(LNode *p){
        if(p==NULL)
            return false;
        LNode *q = p->pNext;            //令q指向*p的后继节点
        p->data = p->pNext->data;   //和后继节点交换数据   注意,如果p节点为最后一个节点的话,该语句会出错
        p->pNext = q->pNext;            // 将*q节点从链中"断开"
        free(q);                                     //释放后继节点的存储空间
        return true;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    image-20220608153639317

    注意,该方法不能删除最一个节点

    image-20220608154219747
    小节总结
    image-20220702155343279
    单链表的查询

    注意,以下代码是建立在包含头节点的链表中实现的

    image-20220608155609406
    按位查询

    代码实现;

    image-20220608160618905
    /** 删除指定节点
     * 交换p节点与其后继节点之间的数据,然后将后继节点删除
     * @param p 需要删除的节点
     * */
    bool HDeleteNode1(LNode *p){
        if(p==NULL)
            return false;
        LNode *q = p->pNext;            //令q指向*p的后继节点
        p->data = p->pNext->data;   //和后继节点交换数据        注意,如果p节点为最后一个节点的话,该语句会出错
        p->pNext = q->pNext;            // 将*q节点从链中"断开"
        free(q);                                     //释放后继节点的存储空间
        return true;
    }
    
    /**  按位查找,返回第 i 个节点元素(包含头节点)
     * @param L 需要操作的链表
     *  @param i 需要返回的节点位序
     * */
    LNode *HGetElem(LinkList L, int i){
        if(i<1)                             //判断位序的合法性
            return NULL;
        LNode *p = L;               //p指针指向当前扫描到的节点元素
        int j = 0;                      //p初始化为指向头节点,头节点属与第0号节点
        //循环找到第 i 个节点
        while(p!=NULL&&j<i){
            p = p->pNext;
            j++;
        }
        return p;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    按值查询
    image-20220608160710352
    /**  按位查找,返回第 i 个节点元素(包含头节点)
     * @param L 需要操作的链表
     *  @param i 需要返回的节点位序
     * */
    LNode *HGetElem(LinkList L, int i){
        if(i<1)                             //判断位序的合法性
            return NULL;
        LNode *p = L;               //p指针指向当前扫描到的节点元素
        int j = 0;                      //p初始化为指向头节点,头节点属与第0号节点
        //循环找到第 i 个节点
        while(p!=NULL&&j<i){
            p = p->pNext;
            j++;
        }
        return p;
    }
    
    /** 按值查找,返回值=e的节点(包含头节点)
     *  @param L 需要操作的链表
     * @param e 需要查找的元素
     * */
    LNode *HLocateElem(LinkList L, int e){
        LNode *p = L->pNext;
        //从第1个节点开始查找数据域为e的节点
        while(p!=NULL&&p->data!=e){
            p = p->pNext;
        }
        return p;       //      若找到则返回对应的节点,否则返回NULL
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    求表长
    int Length(LinkList L){
        int len = 0;
        LNode *p = L;
        while(p->pNext!=NULL){
            len++;
            p = p->pNext;
        }
        return len;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    2-3.5双链表

    单链表vs双链表
    image-20220626165408268
    双链表初始化
    bool InitDLinkList(DLinkList *L){
        (*L) = (DNode *)malloc(sizeof(DNode));      //分配一个头结点
        if((*L) == NULL)
            return false;
        (*L)->prior = NULL;                                     //头节点的prior永远指向NULL
        (*L)->pNext = NULL;                                   //头节点之后展示还没有节点
        return true;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    双链表的插入

    image-20220701151409297

    /** 在节点 p后面插入s新节点
     * @param p 需要在该节点之后插入新节点
     * @param s 需要新插入的节点
     */
    bool InsertNextDNode(DNode *p, DNode *s){
        if(p==NULL||s==NULL)                    //非法参数
            return false;
        s->pNext = p->pNext;                   //①将新节点s的后指针指向p的后继节点
        s->prior = p;                          //②将新节点s的前指针指向p节点
        if(p->pNext!=NULL)
            p->pNext->prior = s;               //③将p的后继节点的前指针指向新节点s
        p->pNext = s;                          //④将p后指针指向新节点s
        return true;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    双链表批量插入
    //批量添加节点
    bool Add(DLinkList *L){
        int len, i = 0;
        DNode* node = (*L);
        printf("请输入需要创建的节点个数:\n");
        scanf("%d", &len);
        while(--len>=0&&++i>0){
            int val;
            printf("请输入当前第 %d 个元素的数值:", i);
            scanf(" %d", &val);
            DNode* newNode = (DNode *)malloc(sizeof(DNode));
            if(newNode == NULL)     // 内存空间分配失败
                return false;
            newNode->data = val;
            InsertNextDNode(node, newNode);
            node = newNode;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    双链表的删除
    image-20220701153400429
    /** 删除p节点的后继节点
     * p需要操作的节点
     */
    bool DeleteNextDNode(DNode *p){
        if(p==NULL)
            return false;
        DNode *q = p->pNext;            //找到p的后继节点q
        if(q==NULL)
            return false;                          //p没有后继节点
        p->pNext = q->pNext;
        free(q);                                    //释放p的后继节点的空间
        return true;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    双链表的遍历
    image-20220701153710400

    双链表不可随机存取,按位查找、按值查找操作都只能用遍历的方式实现。时间复杂度O(n)

    /** 从节点p开始遍历双链表
     *  @param p 起始节点
     * @param e 选项,1:后向遍历、2:前向遍历、3:前向遍历(跳过头节点)
     */
    void traverse_DList(DNode *p,int e){
        if(p==NULL)
            return;
        switch (e)
        {
        case 1:	//后向遍历
            while(p!=NULL){
                printf("%d\n", p->data);
                p = p->pNext;
            }
            break;
        case 2://前向遍历
            while(p!=NULL){
                printf("%d\n", p->data);
                p = p->prior;
            }
            break;
        case 3: //前向遍历(跳过头节点)
            while (p->prior != NULL)
            {
                printf("%d\n", p->data);
                p = p->prior;
            }
            break;
        default:
            break;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    小节总结
    image-20220701154920939

    2-3.6循环链表

    循环单链表
    image-20220701174209088
    定义结构体
    typedef struct CNode{
        int data;                       //定义单链表节点类型
        struct CNode *pNext;    //每个节点存放一个数据元素
    } CNode, *CLinkList;       //指针指向下一个节点
    
    • 1
    • 2
    • 3
    • 4
    初始化
    //初始化一个循环单链表
    CLinkList InitList(){
        CLinkList L = (CLinkList)malloc(sizeof(CLinkList));       //分配一个头结点
        if(L==NULL)
            exit - 1;                                                                      //内存分配不足
        L->pNext = L;                                                               //头结点pnext指向头结点
        return L;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    判空
    //判断循环单链表是否为空
    bool Empty(CLinkList L){
        if(L->pNext==L)
            return true;
        else
            return false;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    判断节点是否为表尾节点
    //判断节点p是否为循环单链表的表尾节点
    bool isTail(CLinkList L ,CNode *p){
        if(p->pNext == L)
            return true;
        else
            return false;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    image-20220701205352903
    循环双链表
    image-20220701205524910
    定义结构体
    typedef struct CDNode{
        int data;
        struct CDNode *prior, *pNext;
    } CDNode, *CDLinkList;
    
    • 1
    • 2
    • 3
    • 4
    初始化循环双链表
    //初始化孔的循环双链表
    CDLinkList  InitCDLinkList(){
        CDNode *L = (CDNode *)malloc(sizeof(CDNode));       // 分配一个头结点
        if(L==NULL)
            exit - 1;           //内存不足,分配失败
        L->prior = L;      //头结点的prior指向头结点
        L->pNext = L;   //头结点的pNext指向头结点
        return L;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    判空
    //判断循环双链表是否为空
    bool Empty(CDLinkList L){
        if(L->pNext == L)
            return true;
        else
            return false;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    判断节点是否为表尾节点
    //判断p节点是否为循环双链表的尾结点
    bool isTail(CDLinkList L, CDNode *p){
        if(p->pNext == L)
            return true;
        else
            return false;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    节点后插
    image-20220701211730739
    //将节点 *s插入到节点 *p之后
    bool InsertNextCDNode(CDNode *p, CDNode *s){
        //过程与双链表的插入类似,只是少了判断p节点后面是否为空这一步
        s->pNext = p->pNext;
        s->prior = p;
        p->pNext->prior = s;
        p->pNext = s;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    节点删除

    image-20220701211905752

    //删除节点p的后继节点
    bool delNextCDNode(CDNode *p){
        if(p == NULL)
            return false;
        CDNode *q = p->pNext;
        p->pNext = q->pNext;
        q->pNext->prior = p;
        free(q);
        return true;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    小节回顾
    image-20220701212557120

    2-3.7静态链表

    定义结构体
    image-20220702163545285

    方法一:

    struct Node{
        int data;
        int next;
    };
    typedef struct Node SLinkList[MaxSize];
    
    int main(void){
        SLinkList sl;
        sl[0].data = 0;
        sl[0].next = 1;
        printf("%d\t%d", sl[0].data, sl[0].next);
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    方法二:

    struct Node{
        int data;
        int next;
    };
    
    int main(void){
        struct Node sl[MaxSize];
        sl[0].data = 0;
        sl[0].next = 1;
        printf("%d\t%d", sl[0].data, sl[0].next);
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    方法三:

    typedef struct {
        int data;           //存放数据元素
        int next;           //下一个元素的数组下标
    } SLinkList[MaxSize];
    
    int main(void){
        SLinkList sl;
        sl[0].data = 0;
        sl[0].next = 1;
        printf("%d\t%d", sl[0].data, sl[0].next);
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    基本操作
    image-20220702202312010
    初始化
    //初始化一个空的静态链表,
    //初始空状态,头结点0号节点next = -1表示该节点为尾结点,其余节点 next =-2表示空闲
    bool InitSList(SLinkList *sl){
        if(MaxSize<1)//最大长度不足
            return false;
        int cnt = 1;
        (*sl)[0].next = -1;
        while (cnt < MaxSize)
        {
            (*sl)[cnt].next = -2;
            cnt++;
        }
        return true;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    判空
    //判空
    bool Empty(SLinkList sl){
        return MaxSize == 0 || sl[0].next == -1;
    }
    
    • 1
    • 2
    • 3
    • 4
    遍历
    //遍历静态链表
    void traverse_SList(SLinkList sl){
        int index = 0;
        while(index!=-1&&index != -2){  //-1:表示表尾;-2:表示空闲结点
            printf("index is : %d \t value is : %d \n", index, sl[index].data);
            index = sl[index].next;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    按位序插入
    /** 插入val元素至位序为i的节点中
     * @param sl 需要操作的链表
     * @param i 需要插入的位序
     * @param vla 需要插入的节点
     */
    bool Insert(SLinkList sl, int i,int val){
        if(i<0||i>MaxSize)                                      //参数非法
            return false;
        int cnt = 0;                                            // 0号头结点不存放数据
        while (sl[++cnt].next != -2);               //寻找一个空闲的节点:next = -2
        sl[cnt].data = val;                             //给新节点存入数据
        int preIndex = FindPreIndex(sl, i); //查找位序为 i-1 的结点的下标
        sl[cnt].next = sl[preIndex].next;   //修改新节点的next为 i-1 节点的next
        sl[preIndex].next = cnt;              //修改i-1 号节点的next指向新节点
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    查找前继节点
    /** 查找后继节点为"位序i的节点"的next
     *  @param sl 需要操作的链表
     * @param i 需要差查找的位序
     * @return -1 :查找失败 其他数值:index对应的前继节点的下标
     */
    int FindPreIndex(SLinkList sl, int i){
        if(i < 1||i>MaxSize)                        //数值非法
            return -1;
        int pindex = 0;
        int index = sl[pindex].next, cnt = 1;  //双指针查找前置节点
        while (index != -1 && cnt++ < i)
        {
            pindex = index;
            index = sl[pindex].next;
        }
        return pindex;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    按位序删除
    //删除位序为i的结点
    bool Del(SLinkList sl, int i){
        if(i<1||i>MaxSize)
            return false;
        int preIndex = FindPreIndex(sl, i);                     //查找位序为 i-1 的结点的下标
        int iNext = sl[sl[preIndex].next].next;
        sl[sl[preIndex].next].next = -2;                        //置被删除的结点next = -2,表示空闲
        sl[preIndex].next = iNext; //将i-1的结点的next的修改为被删除的结点的next
        return true;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    小节回顾
    image-20220702202356608

    2-3.8顺序表与链表比较

    • 逻辑结构:都属于线性表,属于线性结构

    • 存储结构:

      • 顺序表:
        1. 优点:支持随机存取、存储密度高
        2. 缺点:大片连续空间分配不方便
      • 链表:
        1. 优点:离散的小空间分配方便,改变容量方便
        2. 缺点:不可随机存取,存储密度低
    • 基本操作:

      image-20220703143051445 image-20220703143126533 image-20220703143214556 image-20220703143228110
    image-20220703143516962

    chp3-1栈

    3-1.1栈的基本概念

    栈的定义
    image-20220712145819941

    特点:后进先出(LIFO)

    image-20220712145936775

    栈常考题型:合法的出栈顺序

    image-20220712150238798
    小节回顾
    image-20220712150344927

    顺序栈与链式栈的实现

    image-20220712150622178 image-20220713144657723
    顺序栈的定义
    #define MaxSize 10          //定义栈中元素的最大个数
    
    typedef struct{
        int data[MaxSize];      //静态数组存放栈中元素
        int top;                       //栈顶指针
    } SqStack;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    image-20220712151642794
    顺序栈的一些操作

    初始化

    //初始化栈
    void InitStack(SqStack *s){
        (*s).top = -1;          //初始化栈顶指针
    }
    
    • 1
    • 2
    • 3
    • 4

    判空

    //判空
    bool StackEmpty(SqStack s){
        return s.top == -1;
    }
    
    • 1
    • 2
    • 3
    • 4

    入栈

    //入栈
    bool Push(SqStack *s, int x){
        if((*s).top==MaxSize-1)         //栈满
            return false;
        (*s).top = (*s).top + 1;         //指针先+1
        (*s).data[(*s).top] = x;        // 新元素入栈
        return true;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    出栈

    //出栈
    bool Pop(SqStack(*s), int *x){
        if((*s).top==-1)        //  栈空
            return false;
        (*x) = (*s).data[(*s).top];//先出栈
        (*s).top = (*s).top - 1;    //栈顶指针-1
        return true;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    读取栈顶元素

    //读栈顶元素
    bool GetTop(SqStack s, int *x){
        if(s.top==-1)
            return false;
        (*x) = s.data[s.top];
        return true;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    顺序栈的另外一种形式:共享栈
    //共享栈
    typedef struct{
        int data[MaxSize];        //静态数组中存放栈中元素
        int top0;                      //0号栈顶指针
        int top1;                      //1号栈顶指针
    } ShStack;
    
    //初始化栈
    void InitStack(ShStack *s){
        (*s).top0 = -1;             //初始化栈顶指针
        (*s).top1 = MaxSize;
    }
    //共享栈判满条件:top0+1==top1
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    小节回顾
    image-20220712160421310
    链式栈的实现
    typedef struct Node{
        int data;//数据域
        struct Node *pNext;//指针域
    } SNode, *LinkStack;//栈类型定义
    
    • 1
    • 2
    • 3
    • 4

    基本操作

    //初始化栈
    void InitStack(LinkStack *s){
        (*s) = NULL;
    }
    
    //入栈
    void Push(LinkStack *s, int x){
        if((*s)==NULL){
            printf("Is NUll Stack , will create a node\n");
            (*s) = (SNode *)malloc(sizeof(SNode));
            (*s)->data = x;
            (*s)->pNext = NULL;
        }
        else{
            SNode *p = (SNode *)malloc(sizeof(SNode));
            if(p==NULL) //内存空间不足
                exit(-1);
            p->data = x;
            p->pNext = (*s);
            (*s) = p;
        }
    }
    
    //出栈
    bool Pop(LinkStack *s, int *x){
        if(s==NULL){//栈空
            printf("Is NUll Stack \n");
            return false;
        }
        SNode *p = (*s);
        (*x) = p->data;
        (*s) = p->pNext;
        free(p);
        return true;
    }
    
    //读栈顶元素
    bool GetTop(LinkStack s, int *x){
        if(s==NULL){//栈空
            printf("Is NUll Stack \n");
            return false;
        }
        (*x) = s->data;
        return true;
    }
    
    //遍历栈
    void Traverse(LinkStack s){
        while(s!=NULL){
            printf("%d\t", s->data);
            s = s->pNext;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    image-20220713152816687
    小节回顾
    image-20220713152704566

    ch3-2队列

    3-2.1队列的基本概念

    image-20220714165606404 image-20220714165707545
    小节回顾
    image-20220714165744488

    3-2.2队列的顺序实现

    image-20220714165908339
    顺序队列的定义
    image-20220714170547443
    顺序队列的一些操作
    //初始化队列
    void InitQueue(SqQueue *Q){
        //初始化时 队头队尾指针指向0
        (*Q).front = (*Q).rear = 0;
    }
    
    //判断队列是否为空
    bool QueueEmtpy(SqQueue Q){
        return Q.front == Q.rear;
    }
    
    //入队
    bool EnQueue(SqQueue *Q, int x){
        if(((*Q).rear+1)%MaxSize==(*Q).front)
            return false;                       //队列满
        (*Q).data[(*Q).rear] = x;               //将x插入队尾
        (*Q).rear = ((*Q).rear + 1) % MaxSize; //队尾指针+1取模
        return true;
    }
    
    //出队 (删除一个元素并用x返回)
    bool DeQueue(SqQueue *Q, int *x){
        if(QueueEmtpy(*Q))
            return false;   //队列空
        (*x) = (*Q).data[(*Q).front];
        (*Q).front = ((*Q).front + 1) % MaxSize;
        return true;
    }
    
    //取出对头元素
    bool GetHead(SqQueue Q, int *x){
        if(QueueEmtpy(Q))
            return false;   //对空
        (*x) = Q.data[Q.front];
        return true;
    }
    
    //求队列长度
    int LenQueue(SqQueue Q){
        return (Q.rear - Q.front + MaxSize) % MaxSize;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    顺序队列的三种判空\满办法

    方法一:

    image-20220715152317321

    方法二:

    image-20220715152405179

    方法三:

    image-20220715152621996

    关于顺序队列的两指针的不同定义情况

    上述的关于队头队尾指针定义:队头指针front指向对头元素;队尾指针rear指向队尾元素的后一个位置。

    但是还可定义:队头指针front指向对头元素;队尾指针rear指向队尾位置。

    image-20220715154243488

    初始化时:front = 1;rear = MaxSize-1;

    判空:(rear+1)%MaxSize == front;

    判满:(rear+2)%MaxSize == front;

    小节回顾
    image-20220715160712492

    3-2.3队列的链式实现

    链式队列的定义
    image-20220715162216870
    链式队列的一些操作
    //初始化队列(带头结点)
    void HInitQueue(LinkQueue *Q){
        //初始化时 front 与 rear都指向头结点
        (*Q).front = (*Q).rear =
            (LinkNode *)malloc(sizeof(LinkNode));
        //头结点的后继节点为空
        (*Q).front->pNext = NULL;
    }
    
    //判断队列是否为空
    bool HIsEmpty(LinkQueue Q){
        return Q.front == Q.rear;
    }
    
    //初始化队列(不带头节点)
    void NHInitQueue(LinkQueue *Q){
        //初始化时 front 、rear 均指向NUll
        (*Q).front = (*Q).rear = NULL;
    }
    
    //判断队列是否为空(带不头结点)
    bool NHIsEmpty(LinkQueue Q){
        return Q.front == NULL;
    }
    
    //入队(带头结点)
    void HEnQueue(LinkQueue *Q,int x){
        LinkNode *s = (LinkNode *)malloc(sizeof(LinkNode));
        s->data = x;
        s->pNext = NULL;
        (*Q).rear->pNext = s;  //新节点插入到rear之后
        (*Q).rear = s;        //修改表尾指针
    }
    
    //入队(不带头结点)
    void NHEnQueue(LinkQueue *Q,int x){
        LinkNode *s = (LinkNode *)malloc(sizeof(LinkNode));
        s->data = x;
        s->pNext = NULL;
        //不带头节点的队列第一个元素入队需要特殊处理
        if((*Q).front==NULL){   //在空队列中插入第一个元素
            (*Q).front = s;    //修改队头队尾指针
            (*Q).rear = s;
        }
        else{
            (*Q).rear->pNext = s;//新节点插入到 rear 节点之后
            (*Q).rear = s;      //修改 rear 指针
        }
    }
    
    //出队(带头结点)
    bool HDeQueue(LinkQueue *Q, int *x){
        if(HIsEmpty(*Q))
            return false;//堆空
        LinkNode *p = (*Q).front->pNext;
        (*x) = p->data;    //用变量x返回队头元素
        (*Q).front->pNext = p->pNext;//修改头结点的 pNext 指针
        if((*Q).rear==p)  //如果要出队的结点为最后一个
            (*Q).rear = (*Q).front;//则修改队尾指针,指向头结点(因为队内没有节点了)
        free(p);
        return true;
    }
    
    //出队(不带头结点)
    bool NHDeQueue(LinkQueue *Q, int *x){
        if(NHIsEmpty(*Q))
            return false;         //空队列
        LinkNode *p = (*Q).front;//P指向此次出队的结点
        (*x) = p->data;         //用变量x返回对头元素
        (*Q).front = p->pNext; //修改 front 指针
        if((*Q).rear==p){     //此次是最后一个结点出队
            (*Q).front = NULL;//front 与 rear 指针指向 NULL
            (*Q).rear = NULL;
        }
        free(p);
        return true;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    小节总结
    image-20220715170745167

    3-2.4双端队列

    输入受限的双端队列
    image-20220716150137852

    如何判断此种队列的序列合法性?因为只能从一端输入,故当某个点输出时,他之前的序列是已经在队内按照指定的输入顺序确定了,此时看能否通过两端的删除构造出对应的序列。

    输出受限的双端队列
    image-20220716150222452

    如何判断此种队列的序列合法性?因为只能从一端输出,故当某一个点输出时,他之前入队的序列是已经在队内确定的了。因此看能否通过两遍的输入来拼凑出对应的队列。

    小节回顾
    image-20220716145759313

    3-3.4栈的应用

    括号匹配的应用

    流程图

    image-20220717142605302

    算法实现

    bool BracketCheck(char str[], int length){
        LinkStack s;
        InitStack(&s);          //初始化一个栈
        for (int i = 0; i < length;i++){
            if(str[i]=='('||str[i]=='['||str[i]=='{'){ //若为左括号 则入栈
                Push(&s, str[i] - '0');
            }
            else{
                if(StackEmpty(s))                   //若为右括号 且左括号的栈空 则返回false
                    return false;
                else{
                    int top;
                    Pop(&s, &top);                 //获取左括号的栈顶元素
                    if(str[i]==')'&&(top+'0')!='(')
                        return false;
                    if(str[i]=='}'&&(top+'0')!='{')
                        return false;
                    if(str[i]==']'&&(top+'0')!='[')
                        return false;
                }
            }
        }
        return StackEmpty(s);//检索完全部括号之后左括号栈为空(没有多余的左括号)这说明匹配成功
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    小节回顾
    image-20220717151129579
    表达式求值

    知识总览

    image-20220717160549928

    中缀、后缀、前缀表达式

    image-20220717151656487
    中缀转后缀(手算)方法
    image-20220717152158933 image-20220717152632803
    中缀转后缀表达式(机算)
    image-20220717161800054

    image-20220717164437800

    后缀表达式计算(手算)
    image-20220717152906844
    后缀表达式计算(计算机)
    image-20220717153637798 image-20220717155250344
    中缀转前缀(手算)方式
    image-20220717155600856
    前缀表达式计算(手算)
    image-20220717155842164
    前缀表达式(计算机)
    image-20220717155957321
    中缀表达式的实现

    用栈实现,中缀转后缀+后缀表达式求值 两算法结合

    image-20220717165002153 image-20220717170809696
    小节回顾
    image-20220717160044762
    递归

    利用栈,将函数递归调用转变为非递归调用

    image-20220717171327561

    3-3.5队列的应用

    1. 树的层次遍历
    2. 图的广度优先遍历
    3. OS的先来先服务

    3-4.0特殊矩阵的压缩存储

    知识总览

    image-20220719160427955

    一维数组存储
    image-20220719160808556
    二维数组存储

    image-20220719160952585

    存储地址的计算

    行优先:

    image-20220719163945499

    列优先:

    image-20220719164033395
    普通矩阵的存储
    image-20220719164341816
    对称矩阵的压缩存储
    对称矩阵定义以及压缩策略
    image-20220719164922580
    压缩下三角
    image-20220720143124235

    image-20220720144120037

    在按行存储且只存储了下半三角的情况利用对称性求上半三角元素

    image-20220720144252367

    压缩策略

    B数组多留出一个来存储常数C

    image-20220720151316055

    三对角矩阵的压缩存储

    三对角矩阵性质&坐标映射

    二维转一维:

    image-20220720153656463

    除去第一行与最后一个每行均有两个元素之外其余的每行都有三个元素:总共有 ( n − 2 ) ∗ 3 + 2 ∗ 2 = 3 n − 2 (n-2)*3+2*2=3n-2 (n2)3+22=3n2个元素 因为B数组是从0开始存储的,故B最后一个元素下标为 3 n − 3 3n-3 3n3

    另外, 2 i + j − 2 2i+j-2 2i+j2是由: 3 ( i − 1 ) − 1 + j − i + 2 3(i-1)-1+j-i+2 3(i1)1+ji+2而来 ,同样的,因为B数组下标从0开始,故k要再多-1: k = 2 i + j − 3 k=2i+j-3 k=2i+j3

    image-20220721152945850

    一维转二维:

    image-20220721154300433

    因为B的下标是从0开始的,故第 k + 1 k+1 k+1个对应的是下标为第 k k k个,假设按照从二维转换一维的对应关系,则有:k对应的是二维数组中的下标 i,j 。又因为一行有2、3个的元素,而一个k对应的i对应某一行。因此k位于这i行全部的元素个数与前总i-1行元素个之间。 3 ( i − 1 ) − 1 < k + 1 ≤ 3 i − 1 3(i-1)-13(i1)1<k+13i1 第一个不取等号是因为第i行的元素最小值一定会大于前i-1行总元素;一定会小于等于第i行最大总元素。image-20220721160621583 ,等价于 i = ⌈ ( k + 2 ) / 3 ⌉ ⇔ ( k + 2 ) / 3 + 1 i=\lceil(k+2)/3\rceil\Leftrightarrow(k+2)/3+1 i=⌈(k+2)/3(k+2)/3+1

    上面求得了i的表达式,再代入二维转一维的表达式可得余下的j :image-20220721160542297

    稀疏矩阵压缩存储

    失去随机存储特性

    使用三元组存储
    image-20220721160904295
    使用链式存储–十字链表、
    image-20220721160954508
    小节回顾
    image-20220721161734545 image-20220721161755310

    ch4串

    4-1.1串的定义和基本操作

    串的定义
    image-20220722155958761
    串的基本操作
    image-20220722160042856
    小节回顾
    image-20220722160121850

    4-2.1 朴素模式匹配算法

    what
    image-20220722161814065
    image-20220722164241091
    image-20220722164326408
    image-20220722164352260
    image-20220722164409932
    image-20220722164424211
    image-20220722164439433
    image-20220722164513543
    image-20220722164901607
    image-20220722164916918
    image-20220722164932124
    image-20220722164949175
    image-20220722165014482
    image-20220722165027676
    image-20220722165051250
    image-20220722165116165

    枚举主串 S 中的每个字符作为「发起点」,每次从原串的「发起点」和匹配串的「首位」开始尝试匹配:

    匹配成功:返回本次匹配的原串「发起点」。
    匹配失败:枚举原串的下一个「发起点」,重新尝试匹配。

    代码实现:

    王道书上

    image-20220722165241995

    Java实现

    class Solution {
        public int strStr(String ss, String pp) {
            int n = ss.length(), m = pp.length();
            char[] s = ss.toCharArray(), p = pp.toCharArray();
            // 枚举原串的「发起点」
            for (int i = 0; i <= n - m; i++) {
                // 从原串的「发起点」和匹配串的「首位」开始,尝试匹配
                int a = i, b = 0;
                while (b < m && s[a] == p[b]) {
                    a++;
                    b++;
                }
                // 如果能够完全匹配,返回原串的「发起点」下标
                if (b == m) return i;
            }
            return -1;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    KMP算法见附件

    ch5树

    5-2.1树的常考性质

    1. image-20220725153013305
    2. image-20220725153133488
    3. image-20220725153330994
    4. image-20220725153615458
    5. image-20220725154116973
    6. image-20220725154154908
    小节回顾
    image-20220725154230357

    5-2.2二叉树常考性质

    一般二叉树性质
    1. image-20220726152243366
    2. image-20220726152307050
    3. image-20220726152329380
    完全二叉树性质
    1. image-20220726152653768 image-20220726152742387
    2. image-20220726155501280

    5-2.3二叉树存储结构

    二叉树的顺序存储
    image-20220726161051817 image-20220726161212606
    二叉树的链式存储
    image-20220726161528144

    5-3.1二叉树的先中后序遍历

    先序遍历

    image-20220729143538711
    void PreTraverse(PNodeBintree P)
    {
        if(P!=NULL)
        {
            visit(P);
            PreTraverse(P->LeftChild);
            PreTraverse(P->RightChild);
        }
    }
    //非递归版
    void PreTraverse2(PNodeBintree P){
        if(!P)
            return;
        LinkStack s;
        InitStack(&s); //初始化栈
        while(P||!IsEmptyStack(s)){
            if(P){                  //节点非空
                Visit(P);           //先序访问根节点
                Push(&s, P);        //访问完,根节点入栈
                P = P->LeftChild;   //左孩子不空,一直往左走
            }
            else{
                Pop(&s, &P);        //出栈,并转向栈节点的右子树
                P = P->RightChild;  //p节点赋值
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    中序遍历

    image-20220729143735364
    void InOrderTraverse(PNodeBintree P)
    {
        if(P!=NULL)
        {
            PreTraverse(P->LeftChild);
            visit(P);
            PreTraverse(P->RightChild);
        }
    }
    //非递归版
    void InOrderTraverse2(PNodeBintree P){
        if(!P)
            return;
        LinkStack s;
        InitStack(&s);
        while(P||!IsEmptyStack(s)){
            if(P){
                Push(&s, P);
                P = P->LeftChild;
            }
            else{
                Pop(&s, &P);
                Visit(P);
                P = P->RightChild;
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    后序遍历

    image-20220729143814590
    void PostOrderTraverse(PNodeBintree P)
    {
        if(P!=NULL)
        {
            PreTraverse(P->LeftChild);
            PreTraverse(P->RightChild);
            visit(P);
        }
    }
    //非递归版
    void PostOrderTraverse2(PNodeBintree P){
        if(!P)
            return;
        LinkStack s;
        NodeBIntree *r = NULL;          //用来记录最后一访问的结点
        InitStack(&s);
        while(P||!IsEmptyStack(s)){
            if(P){                      //走到最左边
                Push(&s, P);
                P = P->LeftChild;
            }
            // else{ 错误
            //     Pop(&s, &P);
            //     if(!(P->RightChild))
            //         Visit(P);
            //     else{
            //         P = P->RightChild;
            //     }
            // }
            else{
                GetTop(s, &P);                          //获取栈顶元素
                if(P->RightChild&&P->RightChild!=r){   //有右子树,且并为被访问,故转向访问栈顶元素的右子树
                    P = P->RightChild;
                }
                else{                                 //弹出栈顶元素并访问
                    Pop(&s, &P);
                    Visit(P);
                    r = P;                           //记录最近访问的结点
                    P = NULL;                       //因为后续遍历根节点是最后一个访问的,当访问完根节点之后,
                                                    //则说明以该节点为根的树已经被访问完了
                }
            }
        }
    }
    void PostOrderTraverse3(PNodeBintree P){
        Stack2 st[11331];
        int top = 0;
        while(P||top!=0){
            while(P){
                st[++top].data = P;
                st[top].tag = 0;              //标记该节点左子树被访问
                P = P->LeftChild;               //一路向走,并不停入栈结点
            }
            while(top!=0&&st[top].tag==1){      //tag=1表示该节点的右子树已经被访问完了
                // st[top].tag = 0;                //将该栈节点标记恢复至初 0,因为遍历顺序先左子树
                //不需要重新给出栈的栈节点赋tag值,因为top始终指向的是形成一条路劲的结点,
                // 不会被已经出栈的栈节点tag干扰,如果新入了节点,则会在上面的语句中用0将其覆盖
                Visit(st[top--].data);          //此时左右子树均被访问完,该推出栈顶根节点并访问
            }
            if(top!=0&&st[top].tag==0){         //当前栈顶元素只被访问了左子树,此时需要访问右子树
                P = st[top].data->RightChild;   //获取栈顶结点的右子树
                st[top].tag = 1;                //标记访问了右子树
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65

    练习

    image-20220729143926361
    小节回顾
    image-20220729144036220

    5-3.2 二叉树的层次遍历

    image-20220729154154803

    image-20220729154726190

    5-3.3 遍历序列构造二叉树

    image-20220729161014357
    前序+中序确定二叉树
    image-20220729161935240

    image-20220729161514106

    后序+中序确定二叉树
    image-20220729161957997

    image-20220729162637073

    层序+中序确定二叉树
    image-20220729162838948

    image-20220729164726914

    可按照先序+中序方法来做

    小节回顾
    image-20220729164932161
    小试牛刀

    设计一个由先序序列与后序序列确定一棵二叉树的算法

    //由先序序列和中序序列确定一颗二叉树
    #include
    #include
    #include
     struct TreeNode {
        int val;
        struct TreeNode *left;
        struct TreeNode *right;
     };
    
     struct TreeNode *PreInCreat(int *, int *, int, int, int, int);
     struct TreeNode *buildTree(int *, int, int *, int);
     void PreTraverse(struct TreeNode *);
     void InOrderTraverse(struct TreeNode *);
    
     int main(void){
         int preorder[5] = {3, 9, 20, 15, 7};
         int inorder[5]= {9, 3, 15, 20, 7};
         struct TreeNode *root = buildTree(preorder, 5, inorder, 5);
         printf("<-------先序遍历------->");
         PreTraverse(root);
         printf("<-------中序遍历------->");
         InOrderTraverse(root);
         return 0;
     }
    
     struct TreeNode *buildTree(int *preorder, int preorderSize, int *inorder, int inorderSize)
     {
         return PreInCreat(preorder, inorder, 0, preorderSize - 1, 0, inorderSize - 1);
     }
    
    /**
     * @param preorder 先序遍历序列数组
     * @param inorder  中序遍历序列数组
     * @param preorder_left 先序遍历序列左边界
     * @param preorder_right 先序遍历序列右边界
     * @param inorder_left 中序遍历序列左边界
     * @param inorder_right 中序遍历序列右边界
     */
    struct TreeNode *PreInCreat(int *preorder, int *inorder, int preorder_left, int preorder_right, int inorder_left, int inorder_right){
        if (preorder_left > preorder_right) {
            return NULL;
        }
        //先序遍历的第一个节点就是根节点
        int preorder_root = preorder_left;
        //在中序遍历序列中查找根节点「preorder_root」的下标序号
        int inorder_root;
        for (inorder_root = inorder_left; inorder[inorder_root] != preorder[preorder_root]; inorder_root++);
        //创建根节点
        struct TreeNode *root = (struct TreeNode *)malloc(sizeof(struct TreeNode));
        root->val = preorder[preorder_root];
        // 得到左子树中的节点数目
        int size_left_subtree = inorder_root - inorder_left;
        // 递归地构造左子树,并连接到根节点
        // 先序遍历中「从 左边界+1 开始的 size_left_subtree」个元素就对应了中序遍历中「从 左边界 开始到 根节点定位-1」的元素
        root->left = PreInCreat(preorder, inorder, preorder_left + 1, preorder_left + size_left_subtree, inorder_left, inorder_root - 1);
        // 递归地构造右子树,并连接到根节点
        // 先序遍历中「从 左边界+1+左子树节点数目 开始到 右边界」的元素就对应了中序遍历中「从 根节点定位+1 到 右边界」的元素
        root->right = PreInCreat(preorder, inorder, preorder_left + 1 + size_left_subtree, preorder_right, inorder_root + 1, inorder_right);
        return root;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61

    5-3.4线索二叉树

    中序线索化二叉树

    image-20220730161147334

    image-20220731155141264

    先序线索化二叉树

    image-20220731151717885

    后序线索化二叉树

    image-20220731151752622

    中序线索二叉树中找中序后继

    image-20220731155026633

    image-20220731163121344
    中序线索二叉树找中序前驱

    image-20220731163327361

    image-20220731164440244

    先序线索二叉树找后继

    image-20220731171335676

    先序线索二叉树找前继

    达咩

    image-20220731171834481

    image-20220731174142309

    后续线索二叉树找前驱

    image-20220731174431262

    后续线索二叉树找后继

    达咩

    image-20220731174556075

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dabYs0V1-1663136701592)(https://raw.githubusercontent.com/qyhc/image/master/Blog/202207311746480.png)]

    小节回顾

    image-20220731174949255

    5-4.1树的存储结构

    树的逻辑结构
    image-20220810163308460
    树的存储–双亲表示法
    image-20220810164027122
    image-20220810164235357
    image-20220810164751615
    特性
    image-20220810164920849
    树的存储–孩子表示法

    image-20220810170824742

    树的存储–孩子兄弟表示法
    image-20220810172113347
    森林和二叉树之间的转换
    image-20220810172232852

    转换关系:”左孩子右兄弟”–最后转换成的二叉树中,父子关系在对应的森林关系中可能是兄弟关系或是原本就是父子关系

    小节回顾
    image-20220810172309499

    5-4.3树和森林的遍历

    树的先根遍历
    image-20220810172739668
    树的后根遍历
    image-20220810172802783
    树的层次遍历
    image-20220810173236791
    森林的先序遍历
    image-20220810174118861
    森林的中序遍历
    image-20220810174235525
    总结–对应关系
    image-20220810174715576

    5-5.1哈弗曼树

    定义
    image-20220812153536374
    构建
    image-20220812154613857
    哈夫曼编码
    image-20220812160044390 image-20220812160333320 image-20220813154240016
    小节回顾
    image-20220812160423033

    5-5.2并查集

    image-20220812171009400

    存储结构

    本质上为:树的双亲表示法

    image-20220812171112773
    基本操作–初始化
    image-20220812171333495
    并+查
    image-20220812171543805 image-20220812171615157
    优化[并]操作

    思路:

    image-20220812172226873 image-20220812173221251 image-20220812173303110
    再优化
    image-20220812173804085 image-20220812173822440 image-20220812174243326
    小节回顾
    image-20220812173337362 image-20220812173458102

    ch6图

    6-1.1基本概念

    image-20220814144356552 image-20220814144418595 image-20220814144448154 image-20220814144509932 image-20220814144724363
    连通图、强连通图
    image-20220814150448560

    image-20220818151049198

    子图、生成图
    image-20220814150603213 image-20220814150616050
    连通分量、强连通分量
    image-20220814151711303 image-20220814151816230
    生成树
    image-20220814151933883
    生成森林
    image-20220814153042941
    特殊形态的图
    image-20220814153420708 image-20220814153610737 image-20220814153735020
    小节回顾
    image-20220814153818101

    6-2.1邻接矩阵

    image-20220815150358077 image-20220815150443762
    性质
    image-20220815151617377 image-20220815150626452

    6-2.2邻接表

    image-20220815151409250
    复杂度
    image-20220815152232970
    性质与邻接矩阵的对比
    image-20220815152319485 image-20220815152344313

    6-2.3十字链表

    image-20220815155713167 image-20220815162847384
    删除
    状态/操作
    原始image-20220815163111640
    删除A-B边image-20220815163232806
    删除E及关联边image-20220815163411349

    小节回顾

    image-20220815163800852

    6-2.4图的操作

    操作无向图有向图
    Adjacent(G,x,y):判断图G是否存在边或(x,y)image-20220817154818820image-20220817154836631
    Neighbors(G,x):列出图G中与节点x邻接的边image-20220817154949838image-20220817155007158
    InsertVertex(G,x):在途中插入顶点ximage-20220817155041804类似无向图
    DeletedVertex(G,x):从图G中删除顶点ximage-20220817155120386image-20220817155134096
    AddEdge(G,x,y):若无向边(x,y)或有向边不存在,则向图G中添加该边image-20220817155225314类似无向图
    RemoveEdge(G,x,y):若无向边(x,y)或有向边存在,则从图中删除该边image-20220817155359728类似无向图
    FirstNeighbor(G,x):求图G中顶点x的第一个邻接点,若有则返回顶点号;若没有则返回-1image-20220817155432485image-20220817155445073
    NextNeighbor(G,x,y):假设图G中顶点y是顶点x的一个邻接点,返回除y之外顶点x的下一个邻接点的顶点号,若y是x的最后一个邻接点,则返回-1image-20220817155517155

    6-3.1图的遍历-BFS

    代码
    image-20220816165157879
    广度优先遍历序列
    image-20220816165307502 image-20220816165437985
    复杂度分析
    image-20220816170630123
    广度优先生成树\森林
    image-20220816171449492 image-20220816170850149
    小节回顾
    image-20220816170926993

    6-3.2图的遍历-DFS

    代码
    image-20220816174419132
    深度优先遍历序列
    image-20220816175556514 image-20220816174910593
    复杂度分析
    image-20220816175525811
    深度优先生成树\森林
    image-20220816175747343 image-20220816175840047
    图的遍历与连通性
    image-20220817144651200 image-20220817145319861
    小节回顾
    image-20220817150103585

    6-4.1最小生成树

    image-20220818152130685
    Prim算法
    image-20220818153206701
    image-20220818153304727
    image-20220818153315534
    image-20220818153332608
    image-20220818153344889
    image-20220818153356090
    image-20220818153426497
    实现思想

    见ppt

    image-20220818161341296
    Krushal算法
    image-20220818153513734
    顺序:1-56-11
    image-20220818153759809image-20220818153915778
    image-20220818153812381image-20220818153933247
    image-20220818153826978image-20220818153947136
    image-20220818153839089image-20220818153958919
    image-20220818153850053image-20220818154011327
    image-20220818154111133
    实现思想

    见ppt

    image-20220818161400588
    算法比较
    image-20220818161859259

    6-4.2最短路径

    image-20220818164225779
    用BFS求最短路劲
    代码实现
    image-20220818164431557

    执行过程见ppt

    image-20220818165702375
    用Dijkstra算法求最短路径

    执行过程见ppt

    过程与复杂度
    image-20220818172924793
    比较Prim算法
    image-20220818173116740
    不适用于负权值路劲
    image-20220818173829571
    Floyd算法求最短路劲
    image-20220818174158913

    过程见ppt

    小节回顾
    image-20220819144955416

    6-4.5有向无环图

    有向无环图(DAG)—有向图+无环
    转换表达式–建树:

    注意:顶点不可能出现重复操作数

    image-20220819154157246 image-20220819154218021 image-20220819154404297 image-20220819154439109 image-20220819154511099 image-20220819154524648 image-20220819154540417 image-20220819154719219 image-20220819154852307
    转换表达式–2:化简
    image-20220819155003045 image-20220819155014660 image-20220819155034207 image-20220819155046362 image-20220819155210082

    6-4.6_1拓扑排序

    AOV网
    image-20220819202138645 image-20220819155906589 image-20220819160000403 image-20220819175155370
    利用拓扑排序判断有环
    image-20220819175533294
    代码实现
    image-20220819194303431
    复杂度
    image-20220819194602403

    6-4.6_2逆拓扑排序

    用栈实现
    image-20220819194827016
    用DFS实现
    image-20220819195702718

    6-4.7关键路径

    AOE网
    image-20220819202033529

    性质

    image-20220821145140189

    关键路径

    image-20220821145229435
    关键时间
    image-20220821145426741 image-20220821145707586 image-20220821150019442
    方法

    image-20220819202221434

    image-20220821144447721

    image-20220821150434341 image-20220821150530308 image-20220821150705778 image-20220821150825656 image-20220821150935076
    小节回顾
    image-20220819202306681

    ch7查找

    7.1基本概念

    image-20220824153454315 image-20220824153709071

    image-20220824153744676

    7.2_1顺序查找

    代码实现
    image-20220824153946176
    时间复杂度
    image-20220824154032484 image-20220824154107219
    小节回顾

    image-20220824154143651

    7.2_2折半查找

    代码实现
    image-20220824154415473
    复杂度

    image-20220824154529023

    判定树的构造
    image-20220824154910653 image-20220824155123052
    小节回顾
    image-20220824155303932

    7.2_3分块查找

    小节回顾

    image-20220824155525956

    7.3_1二叉排序树

    查找
    image-20220824160249150
    效率分析

    image-20220824160630598

    image-20220824160801904

    小节回顾
    image-20220824160455414

    7.3_2平衡二叉树

    插入调整
    image-20220825141448000 image-20220825141558975 image-20220825141624052 image-20220825141644238 image-20220825141829194
    效率
    image-20220825143734011
    删除调整
    image-20220825145056207

    image-20220825145120659

    image-20220825145142633

    image-20220825145233149

    image-20220825145344024

    image-20220825145422059
    删除不平衡向上传导

    image-20220825150807240

    小节回顾
    image-20220825143818484

    image-20220825151151982

    7.3_4红黑树

    image-20220825151401153
    定义
    image-20220825160015020
    黑高
    image-20220825151613356 image-20220825160049142
    红黑树插入
    image-20220825153035786
    例子
    • 黑叔 LL型

    image-20220825153310552

    • 红叔

    image-20220825153349107

    • 黑叔 RR型

    image-20220825153424614

    • 黑叔 LR型

    image-20220825153903185

    image-20220825153927095

    image-20220825154015252

    image-20220825154055781

    • 黑叔 RL型

    image-20220825154311084

    image-20220825154332797

    image-20220825154410420

    image-20220825154455553

    小节回顾

    image-20220825155902382

    红黑树删除

    7.4_1B树

    定义
    image-20220826152926905 image-20220826153159696
    核心特性
    image-20220826153227309
    树高

    image-20220826153630859

    image-20220826153714626

    7.4_2B树插入与删除

    插入

    image-20220826154403831

    image-20220826154543461
    image-20220826154623747
    image-20220826154709168
    image-20220826154756387
    image-20220826154836574
    image-20220826155011770
    image-20220826155349831
    image-20220826155456302
    image-20220826160929395
    image-20220826160948969
    image-20220826161026459
    删除

    情况:

    1. 处于非终端节点

      • 用直接前驱代替

        image-20220827153109729

        image-20220827153139977

      • 用直接后继代替

        image-20220827153334713

        image-20220827153416937

    2. 处于终端节点

      • 兄弟够借

        image-20220827153841170

        image-20220827153959718

      • 兄弟不够借

        image-20220827154211594

        image-20220827154245214

        image-20220827154259697

        image-20220827154319783

        合并了父节点之后关键字个数不满足B树要求<2个,故对父节点进行合并

        image-20220827154805245

        image-20220827155054043

        image-20220827155152272

    小节回顾

    image-20220827155247435

    7.4_3B+树

    特点

    image-20220827155345754

    image-20220827155409092

    小节回顾

    image-20220827155551744

    7_5.1散列查找

    常见散列函数

    除留余数法

    image-20220827160533381

    直接定址法

    image-20220827160613776

    数学分析法

    image-20220827160633444

    平方取中法

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RNt06wTW-1663136701764)(https://raw.githubusercontent.com/qyhc/image/master/Blog/202208271607313.png)]

    冲突处理

    image-20220827161033106

    拉链法

    image-20220827161110470

    开放定址法–线性

    插入时的冲突

    image-20220827161728999

    查找

    image-20220827162056330

    image-20220827162256559

    删除

    image-20220827162430459

    查找效率

    image-20220827162550930

    image-20220827163444062

    开放定址法–平方

    image-20220827163813968

    开放定址法–伪随机序列

    image-20220827163853437

    小节回顾

    image-20220827163936541

    chp8 排序

    8_1.0排序概念

    小节回顾

    image-20220827164151904

  • 相关阅读:
    【ELFK】之消息队列kafka
    安卓毕业设计源码基于Uniapp+SSM实现的移动端农副产品销售平台购物商城电商
    Linux的命令行
    算法总篇章
    想知道ppt怎么转成pdf格式?这些转换妙招可以轻松实现
    文件操作和IO
    fmp4打包H265视频流
    蓝桥杯2022年第十三届决赛真题-围栏(求凸多边形的面积)
    linux——多线程,线程控制
    JavaScript对象知识点总结
  • 原文地址:https://blog.csdn.net/weixin_45609535/article/details/126851844