数据结构与算法-线性表-线性表的应用

1 线性表

1.5 线性表的应用

1.5.1 线性表的合并

【算法步骤】

  1. 分别获取 LA 表长 m 和 LB 表长 n 。
  2. 从 LB 中第 1 个数据元素开始,循环 n 次执行以下操作:
    1. 从 LB 中查找第 i 个数据元素赋给 e ;
    2. 在 LA 中查找元素 e ,如果不存在,则将 e 插在表 LA 的最后。

【代码实现】

顺序表实现:

c 复制代码
// 合并两个线性表:顺序表实现。
// 将所有在线性表 LB 中但不在 LA 中的数据元素插入到 LA 中。
void MergeList_Sq(SqList *LA, SqList *LB)
{
    int m = ListLength(LA);
    int n = ListLength(LB);
    for (int i = 1; i <= n; i++)
    {
        ElemType e;
        GetElem(LB, i, &e);      // 获取 LB 中的第 i 个元素
        if (!LocateELem(LA, &e)) // 如果 LA 中没有该元素
        {
            ListInsert(LA, ++m, e); // 插入到 LA 的末尾
        }
    }
}

ListLength、GetElem、LocateELem、ListInsert 可以参考之前顺序表章节的实现。

链表实现:链表的实现方式和顺序表几乎一致,就是把链表 LA 和 LB 的类型修改为 LinkList 即可。

c 复制代码
// 合并两个线性表:链表实现。
// 将所有在线性表 LB 中但不在 LA 中的数据元素插入到 LA 中。
void MergeList(LinkList *LA, LinkList *LB)
{
    int m = ListLength(LA);
    int n = ListLength(LB);
    for (int i = 1; i <= n; i++)
    {
        ElemType e;
        GetElem(LB, i, &e);      // 获取 LB 中的第 i 个元素
        if (!LocateELem(LA, &e)) // 如果 LA 中没有该元素
        {
            ListInsert(LA, ++m, e); // 插入到 LA 的末尾
        }
    }
}

【算法分析】

顺序表实现分析:

  1. ListLength 的时间复杂度是 O(1) ;
  2. LB 顺序表要遍历一遍,这里和表长 n 成正比,而后在循环体内:
    1. 从 LB 顺序表中获取元素 GetElem 的时间复杂度是 O(1) ;
    2. 从 LA 顺序表中查找是否有相关元素 LocateELem,和表长 m 成正比;
    3. 插入到 LA 顺序表 ListInsert,因为是插入末尾,所以时间复杂度是 O(1) ;

因此时间复杂度是:O(m*n) 。

链表实现分析:

  1. ListLength 的时间复杂度和 LA 和 LB 的表长m、n成正比 ;
  2. LB 顺序表要遍历一遍,这里和表长 n 成正比,而后在循环体内:
    1. 从 LB 链表中获取元素 GetElem 的和表长 n 成正比;
    2. 从 LA 链表中查找是否有相关元素 LocateELem,和表长 m 成正比;
    3. 插入到 LA 链表表 ListInsert,链表的插入时间复杂度是 O(1) ;

因此时间复杂度是:O(m) + O(n) + O(n*(m+n)) + O(1),取最高阶,忽略低阶,再根据书中假设 m > n,所以最终时间复杂度就是:O(m*n) 。

1.5.2 有序表的合并

顺序表实现

【算法步骤】

  1. 创建一个表长为 m+n 的空表 LC。
  2. 指针 pc 初始化,指向 LC 的第一个元素。
  3. 指针 pa 和 pb 初始化,分别指向 LA 和 LB 的第一个元素。
  4. 当指针 pa 和 pb 均未到达相应表尾时,则依次比较 pa 和 pb 所指向的元素值,从 LA 或 LB 中"摘取"元素值较小的结点插人到 LC 的最后。
  5. 如果 pb 已到达 LB 的表尾,依次将 LA 的剩余元素插人 LC 的最后。
  6. 如果 pa 已到达 LA 的表尾,依次将 LB 的剩余元素插人 LC 的最后。

【代码实现】

c 复制代码
// 合并两个有序表:顺序表实现。
Status MergeList(SqList *LA, SqList *LB, SqList *LC)
{
    LC->maxsize = LC->length = LA->length + LB->length;           // 合并后的最大长度
    LC->elem = (ElemType *)malloc(LC->length * sizeof(ElemType)); // 分配初始空间
    if (LC->elem == NULL)
    {
        return OVERFLOW;
    }

    ElemType *pc = LC->elem; // pc 指向合并后的顺序表的第一个元素

    ElemType *pa = LA->elem; // pa 指向第一个顺序表
    ElemType *pb = LB->elem; // pb 指向第二个顺序表

    ElemType *pa_last = pa + LA->length - 1; // pa 指向第一个顺序表的最后一个元素
    ElemType *pb_last = pb + LB->length - 1; // pb 指向第二个顺序表的最后一个元素

    while (pa <= pa_last && pb <= pb_last) // 只要两个顺序表都没有遍历完
    {
        if (pa->x < pb->x) // 如果第一个顺序表的元素小于第二个顺序表的元素
            *pc++ = *pa++; // 将第一个顺序表的元素放入合并后的顺序表
        else
            *pc++ = *pb++; // 将第二个顺序表的元素放入合并后的顺序表
    }

    while (pa <= pa_last) // 如果第一个顺序表还有元素
        *pc++ = *pa++;    // 将第一个顺序表的元素放入合并后的顺序表
    while (pb <= pb_last) // 如果第二个顺序表还有元素
        *pc++ = *pb++;    // 将第二个顺序表的元素放入合并后的顺序表

    return OK;
}

【算法分析】

第一个 while 循环执行的次数是 m + n - LA或LB表剩余未插入到LC表的元素个数 次,主要是取决于顺序表中的数字情况,不管怎么样,这个循环最终执行完毕后,一定有一个顺序表的元素全部插入到 LC 表中。而后面的两个循环就是处理另外一个顺序表,将该顺序表的剩余元素插入到 LC 表中,所以执行次数就是 m + n 次,时间复杂度 O(m+n),因为多用了一个 m + n 的空间,所以空间复杂度 O(m+n)。

链表实现

【算法步骤】

  1. 指针 pa 和 pb 初始化,分别指向 LA 和 LB 的第一个结点。
  2. LC 的结点取值为 LA 的头结点。
  3. 指针 pc 初始化,指向 LC 的头结点。
  4. 当指针 pa 和 pb 均未到达相应表尾时,则依次比较 pa 和 pb 所指向的元素值,从 LA 或 LB 中"摘取"元素值较小的结点插入到 LC 的最后。
  5. 将非空表的剩余段插入到 pc 所指结点之后。
  6. 释放 LB 的头结点。

【代码实现】

c 复制代码
// 合并两个有序表:链表实现。
Status MergeList(LinkList *LA, LinkList *LB, LinkList *LC)
{
    LNode *pa = (*LA)->next; // 指向链表LA的第一个结点
    LNode *pb = (*LB)->next; // 指向链表LB的第一个结点
    LC = LA;                 // 将链表LA的头结点赋值给LC
    LNode *pc = *LC;         // 指向合并后的链表的头结点

    while (pa != NULL && pb != NULL) // 遍历到链表LA或LB的末尾
    {
        if (pa->data.x <= pb->data.x) // 如果链表LA的当前结点小于等于链表LB的当前结点
        {
            pc->next = pa; // 将链表LA的当前结点添加到合并后的链表中
            pc = pa;       // 移动到下一个结点
            pa = pa->next; // 移动到下一个结点
        }
        else
        {
            pc->next = pb; // 将链表LB的当前结点添加到合并后的链表中
            pc = pb;       // 移动到下一个结点
            pb = pb->next; // 移动到下一个结点
        }
    }

    pc->next = pa != NULL ? pa : pb; // 将剩余的结点添加到合并后的链表中

    free(*LB);    // 释放链表LB头结点的内存
    (*LB) = NULL; // 将链表LB的头结点指针置为NULL

    return OK;
}

【算法分析】

假设 LA 链表的长度为 m,LB 链表的长度为 n,m < n。分析其中的代码,执行主体在 while 循环:

  • 最好的情况,就是小的数字全部在 LA 中,这样循环只要执行 m 次即可。
  • 最差的情况 LA 中第一个元素是最小值,最后一个元素是最大值, 这样 LA 和 LB 中的元素都要遍历一遍,理论就是 m + n 次。

所以平均的时间复杂度就是 O(m+n) 。因为只需将原来两个链表中结点之间的关系解除, 重新按元素值非递减的关系将所有结点链接成一个链表即可,所以空间复杂度为 O(1) 。

相关推荐
50万马克的面包6 小时前
顺序表和链表_精华知识点_CSDN博客
数据结构·链表
All for pursuit.6 小时前
【回溯-7】494.目标和
数据结构·c++·算法·leetcode
50万马克的面包6 小时前
数据结构01-数据结构概念与复杂度-博客稿
数据结构
Logic1016 小时前
C语言/数据结构贪心算法题解:字符串趋同——逐位统计最高频率求最小修改代价
c语言·数据结构·贪心算法·字符串·时间复杂度·频率统计·算法题
红豆诗人8 小时前
C++二叉搜索树:查找、插入和删除为什么这样写
数据结构·c++·算法
H.莓飛8 小时前
【数据结构】堆_OJ题
linux·数据结构·算法
7yewh17 小时前
SLAM 三维空间刚体运动(2)
数据结构·人工智能·机器人·嵌入式·slam
leikooo17 小时前
用餐厅和盘子理解栈和堆
java·数据结构·链表·rust
lvwangshu20 小时前
登录日志 题解
数据结构·线段树·题解·技巧·性质题·思维好题·细节题
CQU_JIAKE21 小时前
10.2【A】
数据结构