zoukankan      html  css  js  c++  java
  • 第3章 顺序表的链式存储

    第3章 顺序表的链式存储

    数据结构与算法_师大完整教程目录(更有python、go、pytorch、tensorflow、爬虫、人工智能教学等着你):https://www.cnblogs.com/nickchen121/p/13768298.html

    一、链式存储

    1. 解决问题:对于线性结构,使用顺序存储,需要足够大的连续存储区域
    2. 链式存储:结点除了存放信息,并且附设指针,用指针体现结点之间的逻辑关系
    3. 注:(c)语言的动态分配函数(malloc())(free())分别实现内存空间的动态分配和回收,所以不必知道某个结点的具体地址
    4. 注:链式存储中,必须有一个指针指向第一个结点的存储位置,一般为(head)标示
    5. 顺序存储和链式存储的区别:顺序存储更适合查询量大的程序设计;链式存储更适合需要频繁插入和删除的程序

    二、单链表

    2.1 单链表的基本概念及描述

    1. 单链表结点构造:两个域,一个存放数据信息的(info)域;另一个指向该结点的后继结点的(next)

    2.2 单链表的实现

    1. 单链表的常用操作:

      1. 建立一个空的单链表
      2. 输出单链表中各个结点的值
      3. 在单链表中查找第(i)个结点

    2.2.1 单链表的存储结构

    typedef int datatype;
    typedef struct link_node {
        datatype info;
        struct link_node *next;
    } node;
    

    2.2.2 单链表的插入操作(算法)

    1. 算法步骤(插入结点为(p),插入到结点(q)后面):

      1. 通过 find(head,i) 查找(q)结点,查不到打印报错信息
      2. 给插入结点(p)分配空间,并设置信息
      3. 如果在单链表的最前面插入新结点,让单链表的首指针指向新插入的结点

    p->next = head;
    head = p;
    6. 如果在单链表中间插入新结点:

    p->next = q->next;
    q->next=p;

    typedef int datatype;
    typedef struct link_node {
        datatype info;
        struct link_node *next;
    } node;
    
    node *insert(node *head, datatype x, int i) {
        node *p, *q;
        q = find(head, i); // 查找第i个结点
        if (!q && i != 0) {
            printf("
    找不到第%d个结点,不能插入%d!", i, x);
        } else {
            p = (node *) malloc(sizeof(node)); // 分配空间
            p->info = x; // 设置新结点
            if (i == 0) // 插入的结点作为单链表的第一个结点
            {
                p->next = head;
                head = p;
            } else {
                p->next = q->next; // 后插
                q->next = p;
            }
        }
        return head;
    }
    

    2.2.3 单链表的删除操作(算法)

    1. 算法步骤(被删除结点(q),被删除结点前一个结点(pre)

      1. 判断链表是否为空
      2. 循环查找被删除结点(q),并且设置一个结点(pre)标示被删除结点的前一个结点
      3. 如果删除结点为第一个结点

    head = head->next;
    free(p)
    6. 如果删除结点为其他结点

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

    typedef int datatype;
    typedef struct link_node {
        datatype info;
        struct link_node *next;
    } node;
    
    node *dele(node *head, datatype x) {
        node *pre = NULL, *p;
        if (!head) {
            printf("单链表是空的");
            return head;
        }
        p = head;
        while (p && p->info != x) // 寻找被删除结点p
        {
            pre = p; // pre指向p的前驱结点
            p = p->next;
        }
        if (p) {
            if (!pre) // 被删除结点没有上一个结点,则是要删除的是第一个结点
            {
                head = head->next;
            } else {
                pre->next = p->next;
            }
            free(p)
        }
        return head;
    }
    

    三、带头结点的单链表

    3.1 带头结点的单链表的基本概念及描述

    1. 头结点的作用:单链表的插入和删除需要对空的单链表进行特殊处理,因此可以设置 (head) 指针指向一个永远不会被删除的结点——头结点
    2. 注:(head) 指示的是所谓的头结点,它不是实际结点,第一个实际结点应该是 head->next 指示的

    3.2 带头结点的单链表的实现

    1. 带头结点的单链表的常用操作:

      1. 建立一个空的带头结点的单链表
      2. 输出带头结点的单链表中各个结点的值
      3. 在带头结点的单链表中查找第 (i) 个结点

    3.2.1 带头结点的单链表的存储结构

    typedef int datatype;
    typedef struct link_node {
        datatype info;
        struct link_node *next;
    } node;
    

    3.2.2 带头结点的单链表的插入(算法)

    1. 算法步骤( (p) 为插入结点,(q) 为插入前一个结点):

      1. 通过 find(head,i) 查找带头结点的单链表中的第 (i) 个结点( (i=0) 表示新结点插入在头结点之后)
      2. 如果没找到结点 (q),打印报错信息
      3. 如果在非空的带头结点的单链表最前面插入一个新结点

    p->next = q->next;
    q->next = p;
    6. 如果在非空的带头结点的单链表的内部插入一个新结点

    p->next = q->next;
    q->next = p;

    typedef int datatype;
    typedef struct link_node {
        datatype info;
        struct link_node *next;
    } node;
    
    node *insert(node *head, datatype x, int i) {
        node *p, *q;
    
        q = find(head, i); // 查找带头结点的单链表中的第 i 个结点,i=0 时表示新结点插入在头结点之后
    
        if (!q) // 没有找到
        {
            printf("
    带头结点的单链表中不存在第%d个结点!不能插入%d!", i, x);
            return head;
        }
    
        p = (node *) malloc(sizeof(node)); // 为准备插入的新结点分配空间
        p->info = x; // 为新结点设置值
        p->next = q->next;
        q->next = q; // i=0 时,本语句等价于 head->next=p
        return head;
    }
    

    3.2.3 带头结点的单链表的删除(算法)

    1. 算法步骤(被删除结点为 (q),被删除结点的前一个结点为 (pre)):

      1. 设置 (pre) 指向头结点
      2. (q) 从带头结点的单链表的第一个实际结点开始循环寻找值为 (x) 的结点
      3. 删除带头结点的单链表的第一个实际结点:

    pre->next = q->next;
    free(q)
    6. 删除带头结点的单链表的内部结点:

    pre->next = q->next;
    free(q)

    typedef int datatype;
    typedef struct link_node {
        datatype info;
        struct link_node *next;
    } node;
    
    node *dele(node *head, datatype x) {
        node *pre = head, *q; // pre 指向头结点
    
        q = head->next; // q 从带头结点的单链表的第一个实际结点开始找值为 x 的结点
        while (q && q->info != x) // 循环查找值为 x 的结点
        {
            pre = q; // pre 指向 q 的前驱
            q = q->next;
        }
    
        if (q) {
            pre->next = q->next; // 删除
            free(q); // 释放内存空间
        }
        return head;
    }
    

    四、循环单链表

    4.1 循环单链表的基本概念及描述

    1. 单链表存在的问题:从表中的某个结点开始,只能访问该结点后面的结点
    2. 循环单链表解决的问题:从表中的任意一个结点开始,使其都能访问到表中的所有的结点
    3. 循环单链表:在单链表的基础上,设置表中最后一个结点的指针域指向表中的第一个结点

    4.2 循环单链表的实现

    1. 循环单链表的常用操作:

      1. 建立一个空的循环单链表
      2. 获得循环单链表的最后一个结点的存储地址
      3. 输出循环单链表中各个结点的值
      4. 在循环单链表中查找一个值为 (x) 的结点
      5. 循环单链表的插入操作
      6. 循环单链表的删除操作
      7. 循环单链表的整体插入与删除操作

    4.2.1 循环单链表的存储结构

    typedef int datatype;
    typedef struct link_node {
        datatype info;
        struct link_node *next;
    } node;
    

    五、双链表

    5.1 双链表的基本概念及描述

    1. 双链表解决的问题:设置一个 (llink) 指针域,通过这个指针域直接找到每一个结点的前驱结点

    5.2 双链表的实现

    1. 双链表的常用操作:

      1. 建立一个空的双链表
      2. 输出双链表中各个结点的值
      3. 查找双链表中第 (i) 个结点
      4. 双链表的插入操作
      5. 双链表的删除操作

    5.2.1 双链表的存储结构

    typedef int datatype;
    typedef struct dlink_node {
        datatype info;
        struct dlink_node *llink, *rlink;
    } dnode;
    

    六、链式栈

    6.1 链式栈的基本概念及描述

    1. 链式栈:使用链式存储的栈
    2. 注:链式栈的栈顶指针一般用 (top) 表示

    6.2 链式栈的实现

    1. 链式栈的常用操作:

      1. 建立一个空的链式栈
      2. 判断链式栈是否为空
      3. 取得链式栈的栈顶结点值
      4. 输出链式栈中各个结点的值
      5. 向链式栈中插入一个值为 (x) 的结点
      6. 删除链式栈的栈顶节点

    6.2.1 链式栈的存储结构

    typedef int datatype;
    typedef struct link_node {
        datatype info;
        struct link_node *next;
    } node;
    

    七、链式队列

    7.1 链式队列的基本概念及描述

    1. 链式队列:使用链式存储的队列
    2. 注:队列必须有队首和队尾指针,因此增加一个结构类型,其中的两个指针域分别为队首和队尾指针

    7.2 链式队列的实现

    1. 链式队列的常用操作:

      1. 建立一个空的链式队列
      2. 判断链式队列是否为空
      3. 输出链式队列中各个结点的值
      4. 取得链式队列的队首结点值
      5. 向链式队列中插入一个值为 (x) 的结点
      6. 删除链式队列中的队首结点

    7.2.1 链式队列的存储结构

    typedef int datatype;
    typedef struct link_node {
        datatype info;
        struct link_node *next;
    } node;
    typedef struct {
        node *front, *rear; // 定义队首和队尾指针
    } queue;
    

    八、算法设计题

    8.1 求单链表中结点个数(算法)

    设计一个算法,求一个单链表中的结点个数

    typedef struct node {
        int data;
        struct node *next;
    } linknode;
    typedef linknode *linklist;
    
    int count(linklist head) {
        int c = 0;
        linklist p = head; // head为实际的第一个结点
    
        while (p) // 计数
        {
            c++;
            p = p->next;
        }
        return c;
    }
    

    8.2 求带头结点的单链表中的结点个数(算法)

    设计一个算法,求一个带头结点单链表中的结点个数

    typedef struct node {
        int data;
        struct node *next;
    } linknode;
    typedef linknode *linklist;
    
    int count(linlist head) {
        int c = 0;
        linklist = head->next; // head->next 为实际的第一个结点
    
        while (p) // 计数
        {
            c++;
            p = p->next;
        }
        return c;
    }
    

    8.3 在单链表中的某个结点前插一个新结点(算法)

    设计一个算法,在一个单链表中值为 y 的结点前面插入一个值为 x 的结点。即使值为 x 的新结点成为值为 y 的结点的前驱结点

    typedef struct node {
        int data;
        struct node *next;
    } linknode;
    typedef linknode *linklist;
    
    void insert(linklist head, int y, int c) {
        linklist pre, p, s; // 假设单链表带头结点
        pre = head;
        p = head->next;
    
        while (p && p->data != y) {
            pre = p;
            p = p->next;
        }
    
        if (p) // 找到了值为 y 的结点,即 p == y
        {
            s = (linklist) malloc(sizeof(linknode));
            s->data = x;
            s->next = p;
            pre->next = s;
        }
    }
    

    8.4 判断单链表的各个结点是否有序(算法)

    设计一个算法,判断一个单链表中各个结点值是否有序

    typedef struct node {
        int data;
        struct node *next;
    } linknode;
    typedef linknode *linklist;
    
    int issorted(linklist head, char c) // c='a' 时为升序,c='d' 时为降序
    {
        int flag = 1;
        linklist p = head->next;
    
        switch (c) {
            case 'a': // 判断带头结点的单链表 head 是否为升序
                while (p && p->next && flag) {
                    if (p->data <= p->next->data) p = p->next;
                    else flag = 0;
                }
                break;
            case 'd': // 判断带头结点的单链表 head 是否为降序
                while (p && p->next && flag) {
                    if (p->data >= p->next->data) p = p->next;
                    else flag = 0
                }
                break;
        }
        return flag;
    }
    

    8.5 逆转一个单链表(算法)

    设计一个算法,利用单链表原来的结点空间将一个单链表就地转置

    1. 核心思想:通过 head->next 保留上一个 (q) 的状态

    2. 算法步骤:

      1. (p) 指向实际的第一个结点
      2. 循环以下步骤:

    (p) 一直循环下去,直到走完整个链表,(p) 循环的时候,(q) 跟着 (p) 一起刷新
    (q)(next) 指针域始终指向 head->next;
    head->next; 始终指向上一个 (q)

    typedef struct node {
        int data;
        struct node *next;
    } linknode;
    typedef linknode *linklist;
    
    void verge(linklist head) {
        linlist p, q;
        p = head->next;
        head->next = NULL;
    
        while (p) {
            q = p;
            p = p->next;
            q->next = head->next; // 通过 head->next 保留上一个 q 的状态
            head->next = q;
        }
    }
    

    8.6 拆分结点值为自然数的单链表,原链表保留值为偶数的结点,新链表存放值为奇数的结点(算法)

    设计一个算法,将一个结点值自然数的单链表拆分为两个单链表,原表中保留值为偶数的结点,而值为奇数的结点按它们在原表中的相对次序组成一个新的单链表

    typedef struct node {
        int data;
        struct node *next;
    } linknode;
    typedef linknode *linklist;
    
    linklist sprit(linklist head) {
        linklist L, pre, p, r;
    
        L = r = (linklist) malloc(sizeof(linknode));
        r->next = NULL;
        pre = head;
        p = head->next;
    
        while (p) {
            if (p->data % 2 == 1) // 删除奇数值结点,并用 L 链表保存
            {
                pre->next = p->next;
                r->next = p;
                r = p; // 这样使得 r 变成了 r->next
                p = pre->next; // 这样使得 p 变成了 head->next->next
            } else // 保留偶数值结点
            {
                pre = p; // 书中的貌似多余操作
                p = p->next;
            }
        }
        r->next = NULL; // 置返回的奇数链表结束标记
        return L;
    }
    

    8.7 在有序单链表中删除值大于 x 而小于 y 的结点(算法)

    设计一个算法,对一个有序的单链表,删除所有值大于 x 而不大于 y 的结点

    typedef struct node {
        int data;
        struct node *next;
    } linknode;
    typedef linknode *linklist;
    
    void deletedata(linklist head, datatype x, datatype y) {
        linklist pre = head, p, q;
    
        p = head->next;
    
        // 找第 1 处大于 x 的结点位置
        while (p && p->data <= x) {
            pre = p;
            p = p->next;
        }
    
        // 找第 1 处小于 y 的位置
        while (p && p->data <= y) p = p->next;
    
        // 删除大于 x 而小于 y 的结点
        q = pre->next;
        pre->next = p; // 小于 x 的第一个结点指向大于 y 的第一个结点
        pre = q->next;
      	// 释放被删除结点所占用的空间
        while (pre != p) { // 此时 p 已经指向了大于 y 的第一个结点
            free(q);
            q = pre;
            pre = pre->next;
        }
    }
    

    九、错题集

    1. 在头结点的单链表中查找 (x) 应选择的程序体是:node *p = head; while (p && p->info != x) p = p->next; return p;

      1. 注:未找到时需要返回头结点 (head),而不是返回一个 (NULL)
    2. 用不带头结点的单链表存储队列时,其队头指针指向队头结点,其队尾指针指向队尾结点,则在进行删除操作时队头队尾指针都可能要修改

      1. 注:链式队列中只有一个结点是会出现该情况,插入时同理
    3. 若从键盘输入 (n) 个元素,则建立一个有序单向链表的时间复杂度为 (O(n^2))

      1. 注:第 (1) 个数:(0) 次查找;第 (2) 个数:(1) 次查找 (,cdots,)(n) 个数,(n-1) 次查找,总共 (n(n-1)/2)
  • 相关阅读:
    一只特立独行的猪
    每天写出好代码的5个建议
    电脑修好了
    VB编程修改AutoCAD长度型尺寸标注
    前天我放学回家
    VB编程修改AutoCAD坐标型尺寸标注
    VB编程操作AutoCAD尺寸标注样式
    这是一个错误的程序
    2011年6月27日星期二
    2011年6月20日星期一
  • 原文地址:https://www.cnblogs.com/nickchen121/p/13766832.html
Copyright © 2011-2022 走看看