C语言数据结构-稀疏多项式运算

简介: C语言数据结构-稀疏多项式运算

title: C语言数据结构-稀疏多项式运算
date: 2021-05-08 10:28:41.0
updated: 2021-05-08 14:20:58.0

categories:

  • 数据结构

tags:

  • 数据结构
  • C语言
  • C语言数据结构

稀疏多项式运算

问题背景

稀疏多项式可以抽象成一个线性表,数据域存储指数和系数,指针域链接下一项,直到结束,操纵链表即可实现对多项式的运算!本文记录整个实现的过程,便于复查

开始

数据域和链表节点的定义

// file: SingleList.h
/**
 * 定义数据域结构体
 */
typedef struct {
    int coe; // 系数
    int exp; // 指数
} ElemType;

/**
 * 定义链表节点结构体
 */
typedef struct LNode{
    ElemType data; // 数据域
    struct LNode *next; // 指针域
}LNode, *LinkList;

/**
 * 初始化链表
 * @param 节点的头指针
 */
void InitList(LinkList * L) {
    (* L) = (LNode *) malloc(sizeof (LNode)); // 分配一个头节点内存空间
    (* L)->next = NULL; // 定义头点指针域为空
}
LinkList 与 LNode 本质上等价 ,习惯用 LinkList 定义单链表,强调定义的是某个单链表的头指针,LNode 定义指向单链表中任意结点的指针变量

初始化过程

// file main.c
ElemType data_A[] = {
    {1, 0},
    {2, 1},
    {-1, 3},
    {-3, 0},
    {-3, 2},
    {-2, 1},
    {3, 0},
    {3, 2}
};

ElemType data_B[] = {
    {-3,1},
    {4, 3},
    {1, 0},
    {3, 2},
    {5, 4},
    {-3, 2},
};
LinkList List_A, List_B; // 定义A,B两个链表头指针
// 初始化两个链表
InitList(&List_A);
InitList(&List_B);
// 计算数据域长度
int data_A_length = (sizeof(data_A))/(sizeof(data_A[0]));
int data_B_length = (sizeof(data_B))/(sizeof(data_B[0]));

创建链表

// file: SingleList.h
/**
 * 头插法创建链表
 * @param L
 * @param e
 */
void CreateList_H(LinkList *L, ElemType e[], int length) {
    for (int i = 0; i < length; i++) {
        LNode *node = (LNode *) malloc(sizeof(LNode)); // 新建一个节点
        node->data = e[i]; // 数据域赋值
        node->next = (*L)->next; // 将头节点的指针域赋值给子节点node的指针域
        (*L)->next = node; // 将node节点的指针赋值给头节点的指针域
    }
}

// file : main.c
// 头插法创建链表
CreateList_H(&List_A, data_A, data_A_length);
CreateList_H(&List_B, data_B, data_B_length);

至此,我们已经创建好了两个“多项式”,接下来需要考虑如何操作。

注:在《数据结构:C 语言版》-严蔚敏著书中,创建链表时是输入一个创建一个,这个可以考虑到当前“多项式”有相同项的情况,本文直接使用数组创建,就要重新考虑去重、排序等问题

我们得到两个数据,用于生成链表,首当其冲应该考虑到重复项,如果使用数组去重,由于数组是静态的,无法对其长度增或减。本文采用先创建链表,再对链表去重

// file: main.c
/**
 * 合并同类项
 * @param List
 * @return
 */
LinkList merge(LinkList List) {
    LinkList p = List->next; // p指向首元节点
    LinkList res; // 新建链表
    InitList(&res); // 初始化链表
    LinkList q = p->next; // q指向p的下一个节点
    int flag = 0; // 是否存在相同指数元素标志
    while (q){
        if(p->data.exp == q->data.exp){
            flag = 1; // 存在设为1
            p->data.coe += q->data.coe; // 系数求和
            if(p->data.coe != 0){
                Insert(&res, q->data); // 不为0保存下来
            }
        } else {
            Insert(&res, q->data); // 指数不相等保存下来
        }
        q = q->next; // 指针后移
    }
    free(q);
    if(flag == 1){
        return merge(res);
    }
    return List;
}

// file: SingleList.h
/**
 * 链表追加元素
 * @param L
 * @param e
 */
void Insert(LinkList *L, ElemType e){
    LNode *node = (LinkList) malloc(sizeof(LNode)); // 创建待插入的节点
    node->data = e;
    LinkList p = *L; //头节点
    if(p->next){
        LinkList q = p->next;
        LinkList r;
        while (q){
            r = q;
            q = q->next;
        }
        node->next = r->next;
        r->next = node;
    }else{
        node->next = p->next;
        p->next = node;
    }
}

我的思路如下:对一个链表进行去重,毫无疑问需要遍历他,我们首先保存他的首元结点(第一个“有用的”结点),因为链表是靠指针域连接的,拿到了其中某一个,就拿到了他后面所有的结点,这样就可以用第一个与他后面的每一个比较,指数相同则系数相加判断,但这里又出来一个问题,在自身操作会修改自身的值,这样一来第一趟遍历会多出指数不相等的,但后续不可能再回来了,就会导致相同节点越来越多。

于是我创建了一个新链表,用于保存指数不同,或者指数相同,但系数之和不为 0 的结点,这样第一趟下来,我们就得到了第一个结点去重后的结果,但处理完第一趟循环后,很明显后续结点都需要此操作,即需要递归处理,于是就需要找到递归出口,拿示例数据演示

// 原数据
ElemType data_A[] = {
    {1, 0},
    {2, 1},
    {-1, 3},
    {-3, 0},
    {-3, 2},
    {-2, 1},
    {3, 0},
    {3, 2}
};
// 第一趟比较之后
res[] = {{3, 0},{-2, 1},{-3, 0},{-1, 3},{2, 1},{1, 0}}

此时 p 代表第一个结点 {3, 2}q代表 {3, 0},{-2, 1},{-3, 2},{-3, 0},{-1, 3},{2, 1},{1, 0}{-3, 2}这个结点指数与待合并的{3, 2}指数相同,会将flag值修改,从本质上,我们无法判断下一次比较是否还有与待合并结点指数相同的结点,因为每一次比较都以本次为条件去判断,但只要本次比较中有,我们就先认为下一趟也有,设置flag标志为 1,进行递归,并且递归之后flag在循环之前会被重新初始化,意味着我们只需要多牺牲一次无意义的循环即可准确判断递归出口,并且这次无意义的循环一定是最后一个结点,循环次数也不长

以上便可完成对多项式的合并,接下来进行排序。排序的意义在于,合并两多项式时,是选其中一个多项式作为结果,主要以指数大小关系来进行合并,指数杂乱无章,就会遗漏该合并的项,排序是为更简单的合并做铺垫。

排序参照网上的快速排序方法,代码实现如下:

// file: main.c
/**
 * 找出最后一个节点
 * @param List
 * @return
 */
LNode *FindEnd(LinkList *List){
    LNode *End = *List;
    while (End->next) {
        End = End->next;
    }
    return End;
}

/**
 * 交换元素
 * @param a
 * @param b
 */
void swap(ElemType *a, ElemType *b)
{
    ElemType temp = *a;
    *a = *b;
    *b = temp;
}

/**
 * 快速排序操作
 * @param pBegin
 * @param pEnd
 */
void Partition(LNode * pBegin, LNode * pEnd){
    if(pBegin == NULL || pEnd == NULL || pBegin == pEnd){
        return;
    }else{
        //定义两个指针
        LNode* p1 = pBegin;
        LNode* p2 = pBegin->next;
        int pivot = pBegin->data.exp;

        while(p2 != NULL && p2 != pEnd->next){
            if(p2->data.exp < pivot){
                p1 = p1->next;
                if(p1 != p2){
                    swap(&p1->data, &p2->data);
                }
            }
            p2 = p2->next;
        }
        swap(&p1->data, &pBegin->data);
        //此时p1是中值节点

        if(p1->data.exp > pBegin->data.exp){
            Partition(pBegin, p1);
        }
        if(p1->data.exp < pEnd->data.exp){
            Partition(p1->next, pEnd);
        }
    }
}

/**
 * 排序方法
 * @param List
 */
void sortList(LinkList *List) {
    LNode *End = FindEnd(List);
    Partition(*List, End);
}

最终效果:
datastructure_poly_1.webp

datastructure_poly_2.webp

总结

创建、操作链表,链表去重和排序,完整实现代码:Gitee,没梯子了先上传到 Gitee 吧!

目录
相关文章
|
11月前
|
算法 数据处理 C语言
C语言中的位运算技巧,涵盖基本概念、应用场景、实用技巧及示例代码,并讨论了位运算的性能优势及其与其他数据结构和算法的结合
本文深入解析了C语言中的位运算技巧,涵盖基本概念、应用场景、实用技巧及示例代码,并讨论了位运算的性能优势及其与其他数据结构和算法的结合,旨在帮助读者掌握这一高效的数据处理方法。
409 1
|
9月前
|
存储 C语言 C++
【C++数据结构——栈与队列】顺序栈的基本运算(头歌实践教学平台习题)【合集】
本关任务:编写一个程序实现顺序栈的基本运算。开始你的任务吧,祝你成功!​ 相关知识 初始化栈 销毁栈 判断栈是否为空 进栈 出栈 取栈顶元素 1.初始化栈 概念:初始化栈是为栈的使用做准备,包括分配内存空间(如果是动态分配)和设置栈的初始状态。栈有顺序栈和链式栈两种常见形式。对于顺序栈,通常需要定义一个数组来存储栈元素,并设置一个变量来记录栈顶位置;对于链式栈,需要定义节点结构,包含数据域和指针域,同时初始化栈顶指针。 示例(顺序栈): 以下是一个简单的顺序栈初始化示例,假设用C语言实现,栈中存储
334 77
|
8月前
|
定位技术 C语言
c语言及数据结构实现简单贪吃蛇小游戏
c语言及数据结构实现简单贪吃蛇小游戏
|
9月前
|
存储 C++ 索引
【C++数据结构——栈与队列】环形队列的基本运算(头歌实践教学平台习题)【合集】
【数据结构——栈与队列】环形队列的基本运算(头歌实践教学平台习题)【合集】初始化队列、销毁队列、判断队列是否为空、进队列、出队列等。本关任务:编写一个程序实现环形队列的基本运算。(6)出队列序列:yzopq2*(5)依次进队列元素:opq2*(6)出队列序列:bcdef。(2)依次进队列元素:abc。(5)依次进队列元素:def。(2)依次进队列元素:xyz。开始你的任务吧,祝你成功!(4)出队一个元素a。(4)出队一个元素x。
244 13
【C++数据结构——栈与队列】环形队列的基本运算(头歌实践教学平台习题)【合集】
|
9月前
|
搜索推荐 C语言
数据结构(C语言)之对归并排序的介绍与理解
归并排序是一种基于分治策略的排序算法,通过递归将数组不断分割为子数组,直到每个子数组仅剩一个元素,再逐步合并这些有序的子数组以得到最终的有序数组。递归版本中,每次分割区间为[left, mid]和[mid+1, right],确保每两个区间内数据有序后进行合并。非递归版本则通过逐步增加gap值(初始为1),先对单个元素排序,再逐步扩大到更大的区间进行合并,直至整个数组有序。归并排序的时间复杂度为O(n*logn),空间复杂度为O(n),且具有稳定性,适用于普通排序及大文件排序场景。
|
9月前
|
Java C++
【C++数据结构——树】二叉树的基本运算(头歌实践教学平台习题)【合集】
本关任务:编写一个程序实现二叉树的基本运算。​ 相关知识 创建二叉树 销毁二叉树 查找结点 求二叉树的高度 输出二叉树 //二叉树节点结构体定义 structTreeNode{ intval; TreeNode*left; TreeNode*right; TreeNode(intx):val(x),left(NULL),right(NULL){} }; 创建二叉树 //创建二叉树函数(简单示例,手动构建) TreeNode*create
185 12
|
9月前
|
存储 C语言 C++
【C++数据结构——栈与队列】链栈的基本运算(头歌实践教学平台习题)【合集】
本关任务:编写一个程序实现链栈的基本运算。开始你的任务吧,祝你成功!​ 相关知识 初始化栈 销毁栈 判断栈是否为空 进栈 出栈 取栈顶元素 初始化栈 概念:初始化栈是为栈的使用做准备,包括分配内存空间(如果是动态分配)和设置栈的初始状态。栈有顺序栈和链式栈两种常见形式。对于顺序栈,通常需要定义一个数组来存储栈元素,并设置一个变量来记录栈顶位置;对于链式栈,需要定义节点结构,包含数据域和指针域,同时初始化栈顶指针。 示例(顺序栈): 以下是一个简单的顺序栈初始化示例,假设用C语言实现,栈中存储整数,最大
141 9
|
9月前
|
存储 算法 测试技术
【C++数据结构——线性表】求集合的并、交和差运算(头歌实践教学平台习题)【合集】
本任务要求编写程序求两个集合的并集、交集和差集。主要内容包括: 1. **单链表表示集合**:使用单链表存储集合元素,确保元素唯一且无序。 2. **求并集**:遍历两个集合,将所有不同元素加入新链表。 3. **求交集**:遍历集合A,检查元素是否在集合B中存在,若存在则加入结果链表。 4. **求差集**:遍历集合A,检查元素是否不在集合B中,若满足条件则加入结果链表。 通过C++代码实现上述操作,并提供测试用例验证结果。测试输入为两个集合的元素,输出为有序集合A、B,以及它们的并集、交集和差集。 示例测试输入: ``` a c e f a b d e h i ``` 预期输出:
223 7
|
9月前
|
机器学习/深度学习 存储 C++
【C++数据结构——线性表】单链表的基本运算(头歌实践教学平台习题)【合集】
本内容介绍了单链表的基本运算任务,涵盖线性表的基本概念、初始化、销毁、判定是否为空表、求长度、输出、求元素值、按元素值查找、插入和删除数据元素等操作。通过C++代码示例详细解释了顺序表和链表的实现方法,并提供了测试说明、通 - **任务描述**:实现单链表的基本运算。 - **相关知识**:包括线性表的概念、初始化、销毁、判断空表、求长度、输出、求元素值、查找、插入和删除等操作。 - **测试说明**:平台会对你编写的代码进行测试,提供测试输入和预期输出。 - **通关代码**:给出了完整的C++代码实现。 - **测试结果**:展示了测试通过后的预期输出结果。 开始你的任务吧,祝你成功!
337 5
|
9月前
|
机器学习/深度学习 存储 C++
【C++数据结构——线性表】顺序表的基本运算(头歌实践教学平台习题)【合集】
本文档介绍了线性表的基本运算任务,涵盖顺序表和链表的初始化、销毁、判定是否为空、求长度、输出、查找元素、插入和删除元素等内容。通过C++代码示例详细展示了每一步骤的具体实现方法,并提供了测试说明和通关代码。 主要内容包括: - **任务描述**:实现顺序表的基本运算。 - **相关知识**:介绍线性表的基本概念及操作,如初始化、销毁、判定是否为空表等。 - **具体操作**:详述顺序表和链表的初始化、求长度、输出、查找、插入和删除元素的方法,并附有代码示例。 - **测试说明**:提供测试输入和预期输出,确保代码正确性。 - **通关代码**:给出完整的C++代码实现,帮助完成任务。 文档
192 5