数据结构实践——“求两集合交集”的一个错解分析

本文涉及的产品
传统型负载均衡 CLB,每月750个小时 15LCU
应用型负载均衡 ALB,每月750个小时 15LCU
网络型负载均衡 NLB,每月750个小时 15LCU
简介:   本文点评一位学生对基于线性表存储集合,然后对集合进行求并运算的错解,供学习者参考。【项目 - 求集合并集】   假设有两个集合 A 和 B 分别用两个线性表 LA 和 LB 表示,即线性表中的数据元素即为集合中的成员。设计算法,用函数unionList(List LA, List LB, List &LC )函数实现该算法,求一个新的集合C=A∪B,即将两

  本文点评一位学生对基于线性表存储集合,然后对集合进行求并运算的错解,供学习者参考。

【项目 - 求集合并集】
  假设有两个集合 A 和 B 分别用两个线性表 LA 和 LB 表示,即线性表中的数据元素即为集合中的成员。设计算法,用函数unionList(List LA, List LB, List &LC )函数实现该算法,求一个新的集合C=A∪B,即将两个集合的并集放在线性表LC中。

提示:
(1)除了实现unnionList函数外,还需要在main函数中设计代码,调用unionList进行测试和演示;
(2)可以充分利用前面建好的算法库[点击…],在程序头部直接加 #include<list.h>即可(工程中最普遍的方法,建议采纳);
(3)也可以将实现算法中需要的线性表的基本运算对应的函数,与自己设计的所有程序放在同一个文件中。

点这儿…】可以看课程中提供参考解答。

【错解】

#include <stdio.h>
#include "list.h"

void unionList(SqList *LA, SqList *LB, SqList *&LC)
{
    int e;
    int lena=LA->length;
    LC = LA;
    for (int i = 0; i <LA->length; i++)
    {
        if (LA->data[i] != LB->data[i])
        {
            ListInsert(LC, lena++, LB->data[i]);
        }
    }
    DispList(LC);
}

int main()
{
    SqList *la, *lb, *lc;
    ElemType x[2] = {1,2};
    ElemType y[2] = {1,4,3}; //原文中只有{1,4},为更好地反映问题,我增加1个元素3
    ElemType z[4];
    CreateList(la, x, 2);
    CreateList(lb, y, 3);
    CreateList(lc, z, 4);
    unionList(la, lb, lc);
    return 0;
}

【我的点评】
  阅读代码知道,第8行LC=LA,意即从此LC指向的也就是LA指向的线性表了。对照题目要求,合并后的LC应该是一个新的线性表,此处处理不合要求。
  若不考虑这一要求,LC=LA后,合并的结果就保存在LA(也是LC)中了。在内存访问的机制中,这是合法的。(这儿和内存管理中的什么堆区、栈区之类的没有关系。内存管理机制对于计算机类的学生很重要,但一般入门级阶段并不讲。)合法仅是在合乎语法要求的层面,事实上,LC原先指向的空间从此没有由任何变量指向,也没有被释放,成了“游离”的垃圾。  
  接下来的讨论,我们就以合并后的结果保存到LA中为起点。
  第9-15行的处理,可以看出学生在算法设计时没有理清头绪。LA(LC)中已经是并集中的第一部分元素了,接下来,应该是“将LB中有,但LA没有的元素,加到LC中”(严格讲,“LB中的元素”指LB指针指向的线性表代表的集合中的元素,LA、LC同),代码没有体现出这层意思。为了完成这一任务,要考察LB中的每个元素,最外层的循环,应该针对的是LB,而不是LA。
  故算法框架应该是:

    for (i = 0; i <LB->length; i++)
    {
        //若LB集合中的第i个元素不在原LA集合中,则将LB中的第i个元素加入到LC中
    }

  如何知道“LB集合中的第i个元素不在原LA集合中”?这需要和LA集合中的元素逐个比较的!于是这里需要针对“原LA集合”构造一个循环,以便逐个比较。显然,11-14行的一个分支结构,仅完成“LA和LB相同序号的元素是否相等”,是不足以考察LA中的每一个元素的。于是上面是算法框架拓展为:

    for (i = 0; i <LB->length; i++)
    {
        for (j = 0; j <lena; j++)  
            //若LB->data[i] == LA->data[j]退出循环
        //循环中未出现相等的情形,则说明LB->data[i]未在LA中出现过,要将LB->data[i]加入

    }

  于是,尽可能在原错误程序基础上修改,且合并后的结果LC实际就是LA的情况下,得到的完整代码为:

#include <stdio.h>
#include "list.h"

void unionList(SqList *LA, SqList *LB, SqList *&LC)
{
    int i,j;
    int lena,lenc;
    lena=lenc=LA->length; //lena是原LA长度,lenc代表合并后的长度
    LC = LA;  //LC和LA将指同一个集合
    for (i = 0; i < LB->length; i++)
    {
        for (j = 0; j <lena; j++)
            if(LB->data[i] == LA->data[j])
                break;
        if(j>=lena)  //退出前面的循环是因为全找过了找不着,即在原LA中不存在
        {
            ListInsert(LC, ++lenc, LB->data[i]);
        }
    }
}

int main()
{
    SqList *la, *lb, *lc;
    ElemType x[2] = {1,2};
    ElemType y[3] = {1,4,3}; //原文中只有{1,4},为更好地反映问题,我增加1个元素3
    //ElemType z[4];
    CreateList(la, x, 2);
    CreateList(lb, y, 3);
    //CreateList(lc, z, 4);
    unionList(la, lb, lc);
    DispList(lc);
    return 0;
}

  需要强调的是,for (j = 0; j <lena; j++)中的lena是“原LA”的长度,不能用LA->length代替,因为在LA、LC混用的情况下,LA->length随着插入,是动态变化着的。
  在原参考解答中,“插入LB中每一个元素”只用了一重循环,但要知道,实现if (!LocateElem(LA,e))的内部,“藏”对LA指向的每一个元素的扫描,是内含一层循环的,到算法库[点击…]中考察基本操作的实现可以验证这一说法。这种写法看起来更简单,也道出了我们应该用基本运算为单位进行思考的必要性。这是在学习数据结构中,应该养成的习惯。这是工程中用到的思维,代码写得出,还要写得好。
  在上面的解答中,我将DispList(LC);放到main函数中了。unionList只管合并,不管别的任何事情。这是软件工程中“高内聚”的要求——一个模块尽可能只完成单一的工作。“显示结果”是“求并”以后做的工作,两者是“平级”的,不要将显示作为合并的一部分。
  还有,新代码中的27和30(在原代码中也有)没有必要,这样创建了线性表,却在合并时直接将LC和LA共用空间了,何必呢,反倒使一块空间彻底成了垃圾。
  在初学者的学习中,一定要争取自己写出来。可以参考一切可以用到的资料启发自己,给出自己的解答。写出这样的错解,也是好的成果,中间的思考、尝试过程是我们真正要的东西。这个过程价值连城。当自己已经经过一定的思考之后,再看一些相对规范的解法(例如本文中的参考解答),也是很必要的。观摩、阅读是学习方法。如果能在观摩中品到其味道,再去仿制一份,也便极好。

相关实践学习
小试牛刀,一键部署电商商城
SAE 仅需一键,极速部署一个微服务电商商城,体验 Serverless 带给您的全托管体验,一起来部署吧!
负载均衡入门与产品使用指南
负载均衡(Server Load Balancer)是对多台云服务器进行流量分发的负载均衡服务,可以通过流量分发扩展应用系统对外的服务能力,通过消除单点故障提升应用系统的可用性。 本课程主要介绍负载均衡的相关技术以及阿里云负载均衡产品的使用方法。
目录
相关文章
|
2月前
|
存储 C语言 C++
【C++数据结构——栈与队列】顺序栈的基本运算(头歌实践教学平台习题)【合集】
本关任务:编写一个程序实现顺序栈的基本运算。开始你的任务吧,祝你成功!​ 相关知识 初始化栈 销毁栈 判断栈是否为空 进栈 出栈 取栈顶元素 1.初始化栈 概念:初始化栈是为栈的使用做准备,包括分配内存空间(如果是动态分配)和设置栈的初始状态。栈有顺序栈和链式栈两种常见形式。对于顺序栈,通常需要定义一个数组来存储栈元素,并设置一个变量来记录栈顶位置;对于链式栈,需要定义节点结构,包含数据域和指针域,同时初始化栈顶指针。 示例(顺序栈): 以下是一个简单的顺序栈初始化示例,假设用C语言实现,栈中存储
160 77
|
2月前
|
C++
【C++数据结构——树】二叉树的性质(头歌实践教学平台习题)【合集】
本文档介绍了如何根据二叉树的括号表示串创建二叉树,并计算其结点个数、叶子结点个数、某结点的层次和二叉树的宽度。主要内容包括: 1. **定义二叉树节点结构体**:定义了包含节点值、左子节点指针和右子节点指针的结构体。 2. **实现构建二叉树的函数**:通过解析括号表示串,递归地构建二叉树的各个节点及其子树。 3. **使用示例**:展示了如何调用 `buildTree` 函数构建二叉树并进行简单验证。 4. **计算二叉树属性**: - 计算二叉树节点个数。 - 计算二叉树叶子节点个数。 - 计算某节点的层次。 - 计算二叉树的宽度。 最后,提供了测试说明及通关代
60 10
|
2月前
|
存储 C++
【C++数据结构——树】哈夫曼树(头歌实践教学平台习题) 【合集】
【数据结构——树】哈夫曼树(头歌实践教学平台习题)【合集】目录 任务描述 相关知识 测试说明 我的通关代码: 测试结果:任务描述 本关任务:编写一个程序构建哈夫曼树和生成哈夫曼编码。 相关知识 为了完成本关任务,你需要掌握: 1.如何构建哈夫曼树, 2.如何生成哈夫曼编码。 测试说明 平台会对你编写的代码进行测试: 测试输入: 1192677541518462450242195190181174157138124123 (用户分别输入所列单词的频度) 预
72 14
【C++数据结构——树】哈夫曼树(头歌实践教学平台习题) 【合集】
|
2月前
|
Java C++
【C++数据结构——树】二叉树的基本运算(头歌实践教学平台习题)【合集】
本关任务:编写一个程序实现二叉树的基本运算。​ 相关知识 创建二叉树 销毁二叉树 查找结点 求二叉树的高度 输出二叉树 //二叉树节点结构体定义 structTreeNode{ intval; TreeNode*left; TreeNode*right; TreeNode(intx):val(x),left(NULL),right(NULL){} }; 创建二叉树 //创建二叉树函数(简单示例,手动构建) TreeNode*create
61 12
|
2月前
|
算法 C++
【C++数据结构——图】最小生成树(头歌实践教学平台习题) 【合集】
【数据结构——图】最小生成树(头歌实践教学平台习题)目录 任务描述 相关知识 测试说明 我的通关代码: 测试结果:【合集】任务描述 本关任务:编写一个程序求图的最小生成树。相关知识 为了完成本关任务,你需要掌握:1.建立邻接矩阵,2.Prim算法。建立邻接矩阵 上述带权无向图对应的二维数组,根据它建立邻接矩阵,如图1建立下列邻接矩阵。注意:INF表示无穷大,表示整数:32767 intA[MAXV][MAXV];Prim算法 普里姆(Prim)算法是一种构造性算法,从候选边中挑
48 10
|
2月前
|
存储 算法 C++
【C++数据结构——图】图的邻接矩阵和邻接表的存储(头歌实践教学平台习题)【合集】
本任务要求编写程序实现图的邻接矩阵和邻接表的存储。需掌握带权有向图、图的邻接矩阵及邻接表的概念。邻接矩阵用于表示顶点间的连接关系,邻接表则通过链表结构存储图信息。测试输入为图的顶点数、边数及邻接矩阵,预期输出为Prim算法求解结果。通关代码提供了完整的C++实现,包括输入、构建和打印邻接矩阵与邻接表的功能。
59 10
|
2月前
|
C++
【C++数据结构——栈和队列】括号配对(头歌实践教学平台习题)【合集】
【数据结构——栈和队列】括号配对(头歌实践教学平台习题)【合集】(1)遇到左括号:进栈Push()(2)遇到右括号:若栈顶元素为左括号,则出栈Pop();否则返回false。(3)当遍历表达式结束,且栈为空时,则返回true,否则返回false。本关任务:编写一个程序利用栈判断左、右圆括号是否配对。为了完成本关任务,你需要掌握:栈对括号的处理。(1)遇到左括号:进栈Push()开始你的任务吧,祝你成功!测试输入:(()))
48 7
|
11天前
|
算法 调度 C++
STL——栈和队列和优先队列
通过以上对栈、队列和优先队列的详细解释和示例,希望能帮助读者更好地理解和应用这些重要的数据结构。
26 11
|
24天前
|
DataX
☀☀☀☀☀☀☀有关栈和队列应用的oj题讲解☼☼☼☼☼☼☼
### 简介 本文介绍了三种数据结构的实现方法:用两个队列实现栈、用两个栈实现队列以及设计循环队列。具体思路如下: 1. **用两个队列实现栈**: - 插入元素时,选择非空队列进行插入。 - 移除栈顶元素时,将非空队列中的元素依次转移到另一个队列,直到只剩下一个元素,然后弹出该元素。 - 判空条件为两个队列均为空。 2. **用两个栈实现队列**: - 插入元素时,选择非空栈进行插入。 - 移除队首元素时,将非空栈中的元素依次转移到另一个栈,再将这些元素重新放回原栈以保持顺序。 - 判空条件为两个栈均为空。
|
2月前
|
存储 C++ 索引
【C++数据结构——栈与队列】环形队列的基本运算(头歌实践教学平台习题)【合集】
【数据结构——栈与队列】环形队列的基本运算(头歌实践教学平台习题)【合集】初始化队列、销毁队列、判断队列是否为空、进队列、出队列等。本关任务:编写一个程序实现环形队列的基本运算。(6)出队列序列:yzopq2*(5)依次进队列元素:opq2*(6)出队列序列:bcdef。(2)依次进队列元素:abc。(5)依次进队列元素:def。(2)依次进队列元素:xyz。开始你的任务吧,祝你成功!(4)出队一个元素a。(4)出队一个元素x。
53 13
【C++数据结构——栈与队列】环形队列的基本运算(头歌实践教学平台习题)【合集】