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

1 线性表

1.5 线性表的应用

1.5.1 线性表的合并

【算法步骤】

  1. 分别获取 LA 表长 mLB 表长 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 的末尾
        }
    }
}

ListLengthGetElemLocateELemListInsert 可以参考之前顺序表章节的实现。

链表实现:链表的实现方式和顺序表几乎一致,就是把链表 LALB 的类型修改为 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 的时间复杂度和 LALB 的表长mn成正比 ;
  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. 指针 papb 初始化,分别指向 LALB 的第一个元素。
  4. 当指针 papb 均未到达相应表尾时,则依次比较 papb 所指向的元素值,从 LALB 中"摘取"元素值较小的结点插人到 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. 指针 papb 初始化,分别指向 LALB 的第一个结点。
  2. LC 的结点取值为 LA 的头结点。
  3. 指针 pc 初始化,指向 LC 的头结点。
  4. 当指针 papb 均未到达相应表尾时,则依次比较 papb 所指向的元素值,从 LALB 中"摘取"元素值较小的结点插入到 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)

相关推荐
不许哈哈哈18 小时前
Python数据结构
数据结构·算法·排序算法
sin_hielo21 小时前
leetcode 2872
数据结构·算法·leetcode
AI科技星21 小时前
为什么宇宙无限大?
开发语言·数据结构·经验分享·线性代数·算法
合方圆~小文1 天前
AI摄像头精准识别技术依赖于深度算法
数据结构·数据库·数码相机·模块测试
松涛和鸣1 天前
16、C 语言高级指针与结构体
linux·c语言·开发语言·数据结构·git·算法
小欣加油1 天前
leetcode 1018 可被5整除的二进制前缀
数据结构·c++·算法·leetcode·职场和发展
无敌最俊朗@1 天前
链表-力扣hot100-随机链表的复制138
数据结构·leetcode·链表
玖剹1 天前
递归练习题(四)
c语言·数据结构·c++·算法·leetcode·深度优先·深度优先遍历
Mz12211 天前
day04 小美的区间删除
数据结构·算法
希望有朝一日能如愿以偿1 天前
力扣每日一题:能被k整除的最小整数
数据结构·算法·leetcode