“ 忠诚、笃学、严谨、守纪 ”

点击任意处进入

数据结构实验一

  • 实验序号(一)

  • 实验名称:线性表的应用

  • 实验目的:巩固顺序表和链表知识及其编程实现能力,训练解决实际问题的能力。

  • 实验内容:一元稀疏多项式的表示和基本运算

  • 实验类型:综合类

// ============================================================================a
// 20252328 数据结构实验一
// ============================================================================l
#include <stdio.h>
#include <stdlib.h>
#include <math.h>

// ============================================================================o
// 第一部分:数据结构设计
// ============================================================================n
typedef struct {
    float coef;   // 系数:使用 float 支持小数运算
    int expn;     // 指数:整数,代表 x 的幂次
} ElemType;

typedef struct LNode {
    ElemType data;
    struct LNode *next;
} LNode, *LinkList;

// 为提升代码可读性,将 LinkList 重命名为 polynomial
typedef LinkList polynomial;

// ============================================================================e
// 第二部分:核心基础操作
// ============================================================================p


//初始化带头结点的空链表

polynomial InitList() {
    polynomial L = (polynomial)malloc(sizeof(LNode));
    if (!L) { printf("内存分配失败\n"); exit(1); }
    L->next = NULL; // 头结点 next 置空,表示多项式当前为 0
    return L;
}

/**
 * 有序插入 + 自动合并同类项 + 零系数过滤
 * 时间复杂度:O(N),N 为当前链表长度。遍历找到插入位置。
 * 算法思想:维护链表按指数升序排列。插入时只需比较指数:
 *   1. 若指数已存在 → 系数累加。累加后若 |coef| < 1e-6,说明相互抵消,直接删除节点。
 *   2. 若指数不存在 → 申请新节点,插入到 pre 和 pre->next 之间。
 * 浮点数比较:计算机中 float 存在精度误差,用 fabs() < EPSILON。
 */
int InsertPolyn(polynomial P, ElemType e) {
    if (fabs(e.coef) < 1e-6) return 0; // 零系数项无意义,直接过滤
    
    polynomial pre = P;
    // 寻找插入位置:跳过所有指数小于 e.expn 的节点
    while (pre->next && pre->next->data.expn < e.expn) {
        pre = pre->next;
    }

    // 情况A:指数已存在,合并同类项
    if (pre->next && pre->next->data.expn == e.expn) {
        pre->next->data.coef += e.coef;
        // 合并后系数趋近于0,说明正负抵消,释放该节点保持稀疏性
        if (fabs(pre->next->data.coef) < 1e-6) {
            polynomial q = pre->next;
            pre->next = q->next;
            free(q);
        }
    } 
    // 情况B:指数不存在,有序插入
    else {
        polynomial q = (polynomial)malloc(sizeof(LNode));
        q->data = e;
        q->next = pre->next; // 新节点指向后继
        pre->next = q;       // 前驱指向新节点
    }
    return 1;
}


/**
 * 多项式创建
 * 调用 InitList 创建空表,循环读入 m 个项
 */
polynomial CreatPolyn(int m) {
    polynomial P = InitList();
    ElemType e;
    printf("请输入%d个项(系数 指数):\n", m);
    for (int i = 0; i < m; i++) {
        scanf("%f %d", &e.coef, &e.expn);
        InsertPolyn(P, e); // 每读入一项,立即触发有序插入与合并
    }
    return P;
}


/**
 *   多项式备份
 *   备份了 Pa/Pb 原始数据
 *   实现:遍历原链表, 复用核心插入逻辑,InsertPolyn
 */
polynomial CopyPolyn(polynomial P) {
    polynomial C = InitList();
    for (polynomial p = P->next; p; p = p->next) {
        InsertPolyn(C, p->data);
    }
    return C;
}


/**
 * 链表销毁
 * 标准单链表遍历释放,从头结点开始。
 */
void DestroyPolyn(polynomial P) {
    polynomial p = P, q;
    while (p) { q = p->next; free(p); p = q; }
}


/**
 * 加法
 * 核心逻辑:
 * 使用三指针 p1(被加数), p2(加数), p3(结果尾指针)。
 *   1. p1.expn < p2.expn:p1 指数小,直接链入结果,p1 后移。
 *   2. p1.expn > p2.expn:p2 指数小,直接链入结果,p2 后移。
 *   3. 指数相等:系数相加。若和不为0,更新 p1 系数后链入,释放 p2 节点(节省内存);
 *      若和为0,释放 p1、p2 两个节点(抵消消失)。
 * 内存管理:本函数为会改变 Pa/Pb 的数据节点,并释放它们的头结点。
 * 所以外部调用前保留原多项式,先 CopyPolyn。
 */
polynomial AddPolyn(polynomial Pa, polynomial Pb) {
    polynomial Pc = InitList();
    polynomial p1 = Pa->next, p2 = Pb->next, p3 = Pc;

    while (p1 && p2) {
        if (p1->data.expn < p2->data.expn) {
            p3->next = p1; p3 = p1; p1 = p1->next;
        } else if (p1->data.expn > p2->data.expn) {
            p3->next = p2; p3 = p2; p2 = p2->next;
        } else { // 指数相同,合并系数
            float sum = p1->data.coef + p2->data.coef;
            if (fabs(sum) > 1e-6) { // 未完全抵消
                p1->data.coef = sum;
                p3->next = p1; p3 = p1; p1 = p1->next;
                polynomial tmp = p2; p2 = p2->next; free(tmp); // 释放加数节点
            } else { // 完全抵消
                polynomial tmp1 = p1, tmp2 = p2;
                p1 = p1->next; p2 = p2->next;
                free(tmp1); free(tmp2); // 两项均消失
            }
        }
    }
    // 将剩余未比较完的链表直接拼接
    p3->next = p1 ? p1 : p2; 

    free(Pa); free(Pb); // 释放原头结点,防止内存泄漏
    return Pc;
}


/**
 *  减法
 *  Pa - Pb = Pa + (-Pb)
 * - 算法步骤:
 *   1. 将 Pa 所有项插入结果链表 Pc。
 *   2. 遍历 Pb,将每一项系数取反(e.coef = -e.coef),再插入 Pc。
 *   3. 用InsertPolyn 完成指数对齐、系数相加(实为相减)、同类项合并。
 *   时间复杂度 O(m+n)。
 */
polynomial SubPolyn(polynomial Pa, polynomial Pb) {
    polynomial Pc = InitList();
    for (polynomial p = Pa->next; p; p = p->next) InsertPolyn(Pc, p->data);
    for (polynomial p = Pb->next; p; p = p->next) {
        ElemType e = p->data; e.coef = -e.coef; // 系数取反
        InsertPolyn(Pc, e);
    }
    return Pc;
}

/**
 * 多项式乘法
 *   (a1*x^i + a2*x^j) * (b1*x^k + b2*x^l) 
 *   = a1*b1*x^(i+k) + a1*b2*x^(i+l) + a2*b1*x^(j+k) + a2*b2*x^(j+l)
 * - 算法步骤:双重循环遍历 Pa 和 Pb。新系数 = c1*c2,新指数 = e1+e2。
 *   将新项调用 InsertPolyn 插入结果链表。
 * - 自动处理:InsertPolyn 会自动按指数排序、合并同类项。
 * - 复杂度:O(m*n*logK),K 为结果项数(受插入排序影响)。
 */
polynomial MulPolyn(polynomial Pa, polynomial Pb) {
    polynomial Pc = InitList();
    for (polynomial p1 = Pa->next; p1; p1 = p1->next)
        for (polynomial p2 = Pb->next; p2; p2 = p2->next) {
            ElemType e;
            e.coef = p1->data.coef * p2->data.coef;
            e.expn = p1->data.expn + p2->data.expn;
            InsertPolyn(Pc, e); // 插入时自动排序与合并
        }
    return Pc;
}

/**
 *  ai实现 : 多项式除法(长除法求商)
 * - 类比整数长除法。每次用【当前余式的首项】除以【除式的首项】,得到商的一项。
 *   商项 × 除式 → 从余式中减去 → 更新余式 → 循环直到余式最高次 < 除式最高次。
 * - 算法步骤:
 *   1. 检查除数是否为零多项式。
 *   2. 拷贝 Pa 作为当前余式 R。
 *   3. while(R最高指数 >= Pb最高指数):
 *      - 计算商项:coef = R.lead.coef / Pb.lead.coef,expn = R.lead.expn - Pb.lead.expn
 *      - 将商项加入结果 Pc。
 *      - 计算修正项:修正项 = -(商项 × Pb),调用 InsertPolyn(R, 修正项) 实现 R = R - 商项*Pb。
 *   4. 循环结束,Pc 即为商。若 R 非空,说明有余式(本程序仅返回商)。
 * - 注意:多项式除法通常有余式,本实现遵循工程习惯,打印提示并忽略余式。
 */
polynomial DivPolyn(polynomial Pa, polynomial Pb) {
    if (!Pb->next) { printf("⚠️ 错误:除数不能为零多项式!\n"); return InitList(); }
    polynomial Pc = InitList();
    polynomial R = CopyPolyn(Pa); // 创建余式副本,保护原数据

    // 长除法核心循环:只要余式最高次 >= 除式最高次,就能继续除
    while (R->next && R->next->data.expn >= Pb->next->data.expn) {
        ElemType term;
        // 商项指数 = 余式首项指数 - 除式首项指数
        term.expn = R->next->data.expn - Pb->next->data.expn;
        // 商项系数 = 余式首项系数 / 除式首项系数
        term.coef = R->next->data.coef / Pb->next->data.coef;
        if (fabs(term.coef) < 1e-6) break; // 系数趋近0,停止除法

        InsertPolyn(Pc, term); // 商项加入结果
        
        // 关键步骤:R = R - term * Pb
        // 实现技巧:计算 -(term * Pb) 的每一项,插入 R。InsertPolyn 会自动完成减法与合并
        for (polynomial p_div = Pb->next; p_div; p_div = p_div->next) {
            ElemType sub;
            sub.coef = -(term.coef * p_div->data.coef); // 负号实现减法
            sub.expn = term.expn + p_div->data.expn;
            InsertPolyn(R, sub);
        }
    }
    if (R->next) printf("(注:存在余式,已自动忽略)\n");
    DestroyPolyn(R); // 释放余式临时链表
    return Pc;
}




/**
 *  多项式打印(格式化输出)
 * 书写习惯:首项正号不显示,负号直接带出;后续项显式显示 + 或 -。
 * 指数处理:expn=0 不输出 x;expn=1 输出 x;expn>1 输出 x^n。
 * 系数处理:使用 fabs() 输出绝对值,符号由前面的 +/- 控制。
 */
void PrintPolyn(polynomial P) {
    if (!P->next) { printf("0\n"); return; } // 零多项式特判
    polynomial p = P->next;
    int first = 1; // 标记是否为首项
    while (p) {
        float c = p->data.coef;
        int e = p->data.expn;
        if (first) { if (c < 0) printf("-"); } // 首项负号处理
        else { printf(c > 0 ? " + " : " - "); } // 后续项符号处理
        
        printf("%.2f", fabs(c)); // 输出系数绝对值
        if (e > 0) printf("x");  // 指数>0 输出变量 x
        if (e > 1) printf("^%d", e); // 指数>1 输出幂次
        
        first = 0; p = p->next;
    }
    printf("\n");
}





// ============================================================================
//  第三部分:交互菜单与main主函数
// ============================================================================

void PrintMenu() {
    printf("\n╔════════════════════════════════════╗\n");
    printf("║      一元稀疏多项式四则运算系统      \n");
    printf("╠════════════════════════════════════╣\n");
    printf("║  1. 创建/重置多项式 Pa & Pb        ║\n");
    printf("║  2. 计算加法 Pa + Pb               ║\n");
    printf("║  3. 计算减法 Pa - Pb               ║\n");
    printf("║  4. 计算乘法 Pa * Pb               ║\n");
    printf("║  5. 计算除法 Pa / Pb (求商)        \n");
    printf("║  0. 退出程序                       ║\n");
    printf("╚════════════════════════════════════╝\n");
    printf("请输入操作编号: ");
}


int main() {
    // 编码切换:若直接双击 .exe 出现中文乱码,需要取消下一行注释
    // system("chcp 65001 > nul"); 

    polynomial Pa = InitList();
    polynomial Pb = InitList();
    polynomial Pr = NULL; // 用于接收运算结果
    int m1 = 0, m2 = 0;
    int choice;

    // 初始化空多项式,防止空指针访问
    Pa = CreatPolyn(0); Pb = CreatPolyn(0);

    // 
    while (1) {
        PrintMenu();
        // 安全输入处理:检查 scanf 返回值,防止用户输入字母导致死循环
        if (scanf("%d", &choice) != 1) {
            while (getchar() != '\n'); // 清空输入缓冲区中的非法字符
            printf("❌ 输入无效,请重新输入数字!\n");
            continue;
        }

        switch (choice) {
            case 1: // 重建多项式
                DestroyPolyn(Pa); DestroyPolyn(Pb); // 先释放旧内存
                printf("=== 重新创建多项式 ===\n");
                printf("Pa 项数: "); scanf("%d", &m1);
                Pa = CreatPolyn(m1);
                printf("Pb 项数: "); scanf("%d", &m2);
                Pb = CreatPolyn(m2);
                printf("✅ 当前 Pa = "); PrintPolyn(Pa);
                printf("   当前 Pb = "); PrintPolyn(Pb);
                break;

            case 2: // 加法
                if (!Pa->next && !Pb->next) { printf("⚠️ 请先创建多项式!\n"); break; }
                // 必须拷贝!因为 AddPolyn 会销毁传入的链表节点
                polynomial Pa_c1 = CopyPolyn(Pa);
                polynomial Pb_c1 = CopyPolyn(Pb);
                Pr = AddPolyn(Pa_c1, Pb_c1); 
                printf("📊 计算结果: Pa + Pb = "); PrintPolyn(Pr);
                DestroyPolyn(Pr); Pr = NULL;
                break;

            case 3: // 减法
                if (!Pa->next && !Pb->next) { printf("⚠️ 请先创建多项式!\n"); break; }
                Pr = SubPolyn(Pa, Pb);
                printf(" 计算结果: Pa - Pb = "); PrintPolyn(Pr);
                DestroyPolyn(Pr); Pr = NULL;
                break;

            case 4: // 乘法
                if (!Pa->next && !Pb->next) { printf("️ 请先创建多项式!\n"); break; }
                Pr = MulPolyn(Pa, Pb);
                printf("📊 计算结果: Pa * Pb = "); PrintPolyn(Pr);
                DestroyPolyn(Pr); Pr = NULL;
                break;

            case 5: // 除法
                if (!Pa->next && !Pb->next) { printf("⚠️ 请先创建多项式!\n"); break; }
                Pr = DivPolyn(Pa, Pb);
                printf("📊 计算结果: Pa / Pb = "); PrintPolyn(Pr);
                DestroyPolyn(Pr); Pr = NULL;
                break;

            case 0: // 退出
                printf("👋 感谢使用,程序已退出。\n");
                DestroyPolyn(Pa); DestroyPolyn(Pb); // 善后清理
                return 0;

            default:
                printf("❌ 无效选项,请重新选择!\n");
        }
    }
}

posted @ 2026-04-27 23:02  alonep  阅读(12)  评论(0)    收藏  举报