2、线性结构

2.1 线性表及其实现

2.1.1 多项式的表示

[例] 一元多项式及其运算

一元多项式 :
主要运算:多项式相加、相减、相乘等
【分析】如何表示多项式?
多项式的关键数据: 多项式项数n 、各项系数ai 及指数 i

方法1、顺序存储(顺序存储结构直接表示 )
处理多项式诸如f(x)=4x5+3x2+1
(1)数组:数组a[i]表示多项式的系数,i表示指数。这种方法浪费,比如要计算f(x)=x^2000+x,就需要2001个数组元素。
在这里插入图片描述
两个多项式相加: 两个数组对应分量相加
问题:如何表示多项式 x+3x^2000 ?

方法2:数组结构:顺序存储结构表示非零项
在这里插入图片描述
在这里插入图片描述
方法3:链表结构存储非零项:coef系数,expon指数
在这里插入图片描述

2.1.2 什么是线性表

多项式表示问题的启示:

  • 同一个问题可以有不同的表示(存储)方法
  • 有一类共性问题:有序线性序列的组织和管理

线性表(Linear List)”:由同类型数据元素构成有序序列的线性结构
Ø 表中元素个数称为线性表的长度
Ø 线性表没有元素时,称为空表
Ø 表起始位置称表头,表结束位置称表尾

线性表的抽象数据类型描述

类型名称:线性表(List)
数据对象集:线性表是 n (≥0)个元素构成的有序序列( a1, a2, …,an ) 操作集:线性表L
属于 List,整数i表示位置,元素X 属于 ElementType, 线性表基本操作主要有:
1、List MakeEmpty():初始化一个空线性表L;
2、ElementType FindKth( int K, List L ):根据位序K,返回相应元素 ;
3、int Find( ElementType X, List L ):在线性表L中查找X的第一次出现位置;
4、void Insert( ElementType X, int i, List L):在位序i前插入一个新元素X;
5、void Delete( int i, List L ):删除指定位序i的元素;
6、int Length( List L ):返回线性表L的长度n。

2.1.3 线性表的顺序存储实现

利用数组的连续存储空间顺序存放线性表的各元素
在这里插入图片描述
主要操作的实现:

1. 初始化(建立空的顺序表)
List MakeEmpty( )      
{ List PtrL;
    PtrL = (List )malloc( sizeof(struct LNode) );
    PtrL->Last = -1;
    return PtrL;
}

2. 查找
int Find( ElementType X, List PtrL )      
{ int i = 0;
    while( i <= PtrL->Last && PtrL->Data[i]!= X )
        i++;
    if (i > PtrL->Last) return -1; /* 如果没找到,返回-1 */
    else return i; /* 找到后返回的是存储位置 */
}
查找成功的平均比较次数为(n +1)/2,平均时间性能为 O(n)
  1. 插入(第 i (1≤i≤n+1)个位置上插入一个值为X的新元素)
    在这里插入图片描述
插入操作实现:
void Insert( ElementType X, int i, List PtrL )      
{ int j;
    if ( PtrL->Last == MAXSIZE-1 ){ /* 表空间已满,不能插入*/
        printf("表满");
        return;
    }
    if ( i < 1 || i > PtrL->Last+2) { /*检查插入位置的合法性*/
        printf("位置不合法");
        return;
    }
    for ( j = PtrL->Last; j >= i-1; j-- )
        PtrL->Data[j+1] = PtrL->Data[j]; /*将 ai~ an倒序向后移动*/
    PtrL->Data[i-1] = X; /*新元素插入*/
    PtrL->Last++; /*Last仍指向最后元素*/
    return;
}
平均移动次数为 n /2,平均时间性能为 O(n)
  1. 删除(删除表的第 i (1≤i≤n)个位置上的元素)
    在这里插入图片描述
删除操作实现:
void Delete( int i, List PtrL )      
{ int j;
    if( i < 1 || i > PtrL->Last+1 ) { /*检查空表及删除位置的合法性*/
        printf (“不存在第%d个元素”, i );
        return ;
    }
    for ( j = i; j <= PtrL->Last; j++ )
        PtrL->Data[j-1] = PtrL->Data[j]; /*将 ai+1~ an顺序向前移动*/
    PtrL->Last--; /*Last仍指向最后元素*/
    return;
}
检查空表及删除位置的合法,平均时间性能为 O(n)

2.1.4 线性表的链式存储实现

不要求逻辑上相邻的两个元素物理上也相邻;通过?链”建立起数据元素之间的逻辑关系。 插入、删除不需要移动数据元素,只需要修改“链”。
在这里插入图片描述
访问序号为 i 的元素?求线性表的长度?

//链表结构
typedef struct LNode *List;
struct LNode{
    ElementType Data;
    List Next;
};
struct Lnode L;
List PtrL;

主要操作的实现:

1.求表长
int Length ( List PtrL )      
{ List p = PtrL; 	/* p指向表的第一个结点*/
    int j = 0;
    while ( p ) {
        p = p->Next;
        j++; 		/* 当前p指向的是第 j 个结点*/
    }
    return j;
}
时间性能为 O(n)2. 查找
(1)按序号查找: FindKth;
List FindKth( int K, List      PtrL )      
{ List p = PtrL;
    int i = 1;
    while (p !=NULL && i < K ){
        p = p->Next;
        i++;
    }
    if ( i == K ) return p;    	/* 找到第K个,返回指针 */
    else return NULL;        	/* 否则返回空 */
}2)按值查找: Find
List Find( ElementType X, List      PtrL )      
{
    List p = PtrL;
    while ( p!=NULL && p->Data != X      )
        p = p->Next;
    return p;
}
平均时间性能为 O(n)
  1. 插入(在第 i-1(1≤i≤n+1)个结点后插入一个值为X的新结点)
    (1)先构造一个新结点,用s指向;
    (2)再找到链表的第 i-1个结点,用p指向;
    (3)然后修改指针,插入结点 ( p之后插入新结点是 s)
    在这里插入图片描述
    插入操作实现:
List Insert( ElementType X, int i, List PtrL )      
{ List p, s;
    if ( i == 1 ) { 			/* 新结点插入在表头 */
        s = (List)malloc(sizeof(struct LNode)); /*申请、填装结点*/
        s->Data = X;
        s->Next = PtrL;
        return s; 				/*返回新表头指针*/
    }
    p = FindKth( i-1, PtrL ); 	/* 查找第i-1个结点 */
    if ( p == NULL ) { 			/* 第i-1个不存在,不能插入 */
        printf("参数i错");
        return NULL;
    }else {
        s = (List)malloc(sizeof(struct LNode)); /*申请、填装结点*/
        s->Data = X;
        s->Next = p->Next; 		/*新结点插入在第i-1个结点的后面*/
        p->Next = s;
        return PtrL;
    }
}
平均查找次数为 n /2,平均时间性能为 O(n)
  1. 删除(删除链表的第 i (1≤i≤n)个位置上的结点)
    (1)先找到链表的第 i-1个结点,用p指向;
    (2)再用指针s指向要被删除的结点(p的下一个结点);
    (3)然后修改指针,删除s所指结点;
    (4)最后释放s所指结点的空间。
    在这里插入图片描述
    删除操作实现:
List Delete( int i, List PtrL )      
{ List p, s;
    if ( i == 1 ) { 	/* 若要删除的是表的第一个结点 */
        s = PtrL; 		/*s指向第1个结点*/
        if (PtrL!=NULL) PtrL = PtrL->Next; 	/*从链表中删除*/
        else return NULL;
        free(s); 							/*释放被删除结点 */
        return PtrL;
    }
    p = FindKth( i-1, PtrL ); 				/*查找第i-1个结点*/
    if ( p == NULL ) {
        printf(“第%d个结点不存在”, i-1); return NULL;
    } else if ( p->Next == NULL ){
        printf(“第%d个结点不存在”, i); return NULL;
    } else {
        s = p->Next; 			/*s指向第i个结点*/
        p->Next = s->Next; 		/*从链表中删除*/
        free(s); 				/*释放被删除结点 */
        return PtrL;
    }
}
平均查找次数为 n /2,平均时间性能为 O(n)

2.1.5 广义表

我们知道了一元多项式的表示,那么二元多项式又该如何表示?比如,给定二元多项式:
在这里插入图片描述
【分析】 可以将上述二元多项式看成关于x 的一元多项式
在这里插入图片描述
所以,上述二元多项式可以用?复杂”链表表示为:
在这里插入图片描述
广义表(Generalized List)
Ø 广义表是线性表的推广
Ø 对于线性表而言, n个元素都是基本的单元素;
Ø 广义表中,这些元素不仅可以是单元素也可以是另一个广义表。
在这里插入图片描述

typedef struct GNode *GList;
struct GNode{
    int Tag; /*标志域:0表示结点是单元素,1表示结点是广义表 */
    union {  /*子表指针域Sublist与单元素数据域Data复用,即共用存储空间*/
        ElementType Data;
        GList SubList;
    } URegion;
    GList Next; /* 指向后继结点 */
};

2.1.6 多重链表

多重链表:链表中的节点可能同时隶属于多个链
Ø 多重链表中结点的指针域会有多个,如前面例子包含了Next和SubList两个指针域;
Ø 但包含两个指针域的链表并不一定是多重链表,比如在双向链表不是多重链表。

多重链表有广泛的用途:基本上如树、图这样相对复杂的数据结构都可以采用多重链表方式实现存储。
[例] 矩阵可以用二维数组表示,但二维数组表示有两个缺陷:
Ø 一是数组的大小需要事先确定,
Ø 对于“稀疏矩阵 ”,将造成大量的存储空间浪费。
在这里插入图片描述
【分析】 采用一种典型的多重链表——十字链表来存储稀疏矩阵
(1)只存储矩阵非0元素项
结点的数据域:行坐标Row、列坐标Col、数值Value
(2)每个结点通过两个指针域,把同行、同列串起来;
Ø 行指针(或称为向右指针)Right
Ø 列指针(或称为向下指针)Down

矩阵A的多重链表图:
在这里插入图片描述
(1)用一个标识域Tag来区分头结点和非0元素结点:
(2)头节点的标识值为“Head”,矩阵非0元素结点的标识值为“Term”。
在这里插入图片描述

2.2 堆栈

什么是堆栈?计算机如何进行表达式求值?
[例] 算术表达式5+6/2-34。正确理解: 5+6/2-34 = 5+3-34 = 8-34 = 8-12 = -4
(1)由两类对象构成的:
Ø运算数,如2、3、4
Ø运算符号,如+、-、*、/
(2)不同运算符号优先级不一样

2.2.1 后缀表达式

Ø 中缀表达式:运算符号位于两个运算数之间。如 ,a + b * c - d / e
Ø 后缀表达式:运算符号位于两个运算数之后。如, a b c * + d e / -

〖例〗 6 2 / 3 - 4 2 * + = ?

后缀表达式求值策略:从左向右"扫描”,逐个处理运算数和运算符号
遇到运算数怎么办?如何“记住”目前还不未参与运算的数?
遇到运算符号怎么办?对应的运算数是什么?
启示:需要有种存储方法,能顺序存储运算数,并在需要时“倒序”输出!

〖例〗 6 2 / 3 - 4 2 * + = ? 8

在这里插入图片描述

2.2.2 堆栈的抽象数据类型描述

堆栈(Stack):具有一定操作约束的线性表 Ø只在一端(栈顶,Top)做 插入、删除
Ø 插入数据:入栈(Push)
Ø 删除数据:出栈(Pop)
Ø 后入先出:Last In First Out(LIFO)

类型名称: 堆栈(Stack)
数据对象集:一个有0个或多个元素的有穷线性表。
操作集:长度为MaxSize的堆栈S 属于 Stack,堆栈元素item 属于 ElementType
1、Stack CreateStack( int MaxSize ):生成空堆栈,其最大长度为MaxSize;
2、int IsFull( Stack S, int MaxSize ):判断堆栈S是否已满;
3、void Push( Stack S, ElementType item ):将元素item压入堆栈;
4、int IsEmpty ( Stack S ):判断堆栈S是否为空;
5、ElementType Pop( Stack S ):删除并返回栈顶元素;
在这里插入图片描述
Push 和 Pop 可以穿插交替进行;
按照操作系列
(1)Push(S,A), Push(S,B),Push((S,C),Pop(S),Pop(S),Pop(S) 堆栈输出是?CBA
(2) 而Push(S,A), Pop(S),Push(S,B),Push((S,C),Pop(S),Pop(S) 堆栈输出是? ACB

[例] 如果三个字符按ABC顺序压入堆栈
•ABC的所有排列都可能是出栈的序列吗?No
•可以产生CAB这样的序列吗?No

2.2.3 栈的顺序存储实现

栈的顺序存储结构通常由一个一维数组和一个记录栈顶元素位置的变量组成。

#define MaxSize <储存数据元素的最大个数>
typedef struct SNode *Stack;
struct SNode{
        ElementType Data[MaxSize];
        int Top;
};

(1)入栈

在这里插入图片描述

void Push( Stack PtrS, ElementType      item )      
{
    if ( PtrS->Top == MaxSize-1 ) {
        printf(“堆栈满”); return;
    }else {
        PtrS->Data[++(PtrS->Top)] = item;
        return;
    }
}

(2)出栈

在这里插入图片描述

ElementType Pop( Stack PtrS )      
{
    if ( PtrS->Top == -1 ) {
        printf(“堆栈空”);
        return ERROR; /* ERROR是ElementType的特殊值,标志错误 */
    } else
        return ( PtrS->Data[(PtrS->Top)--] );
}

例题:用一个数组实现两个堆栈

[例] 请用一个数组实现两个堆栈,要求最大地利用数组空间,使数组只要有空间入栈操作就可以成功。

【分析】 一种比较聪明的方法是使这两个栈分别从数组的两头开始向中间生长;当两个栈的栈顶指针相遇时,表示两个栈都满了。
#define MaxSize <存储数据元素的最大个数>
struct DStack {
        ElementType Data[MaxSize];
        int Top1; /* 堆栈1的栈顶指针 */
        int Top2; /* 堆栈2的栈顶指针 */
} S;
S.Top1 = -1;
S.Top2 = MaxSize;

void Push( struct DStack *PtrS, ElementType item, int Tag )      
{ /* Tag作为区分两个堆栈的标志,取值为1和2 */
    if ( PtrS->Top2 – PtrS->Top1 == 1) { /*堆栈满*/
        printf(“堆栈满”); return ;
    }
    if ( Tag == 1 ) /* 对第一个堆栈操作 */
        PtrS->Data[++(PtrS->Top1)] = item;
    else /* 对第二个堆栈操作 */
        PtrS->Data[--(PtrS->Top2)] = item;
}

ElementType Pop( struct DStack *PtrS, int Tag )      
{ /* Tag作为区分两个堆栈的标志,取值为1和2 */
    if ( Tag == 1 ) { /* 对第一个堆栈操作 */
        if ( PtrS->Top1 == -1 ) { /*堆栈1空 */
            printf(“堆栈1空”); return NULL;
        } else return PtrS->Data[(PtrS->Top1)--];
    } else { /* 对第二个堆栈操作 */
        if ( PtrS->Top2 == MaxSize ) { /*堆栈2空 */
            printf(“堆栈2空”); return NULL;
        } else return PtrS->Data[(PtrS->Top2)++];
    }
}

2.2.4 堆栈的链式存储实现

栈的链式存储结构实际上就是一个单链表,叫做链栈。插入和删除操作只能在链栈的栈顶进行。栈顶指针Top应该在链表的哪一头?

typedef struct SNode *Stack;
        struct SNode{
        ElementType Data;
        struct SNode *Next;
} ;

(1) 堆栈初始化(建立空栈)
(2) 判断堆栈S是否为空
在这里插入图片描述

Stack CreateStack()      
{ /* 构建一个堆栈的头结点,返回指针 */
    Stack S;
    S =(Stack)malloc(sizeof(struct SNode));
    S->Next = NULL;
    return S;
}

int IsEmpty(Stack S)
{ /*判断堆栈S是否为空,若为空函数返回整数1,否则返回0 */
    return ( S->Next == NULL );
}

void Push( ElementType item, Stack S)      
{ /* 将元素item压入堆栈S */
    struct SNode *TmpCell;
    TmpCell=(struct SNode *)malloc(sizeof(struct SNode));
    TmpCell->Element = item;
    TmpCell->Next = S->Next;
    S->Next = TmpCell;
}

ElementType Pop(Stack S)      
{ /* 删除并返回堆栈S的栈顶元素 */
    struct SNode *FirstCell;
    ElementType TopElem;
    if( IsEmpty( S ) ) {
        printf(“堆栈空”); return NULL;
    } else {
        FirstCell = S->Next;
        S->Next = FirstCell->Next;
        TopElem = FirstCell ->Element;
        free(FirstCell);
        return TopElem;
    }
}

2.2.5 堆栈应用:表达式求值

回忆:应用堆栈实现后缀表达式求值的基本过程:从左到右读入后缀表达式的各项(运算符或运算数);
1.运算数:入栈;
2.运算符:从堆栈中弹出适当数量的运算数,计算并结果入栈;
3.最后,堆栈顶上的元素就是表达式的结果值。
在这里插入图片描述
中缀表达式求值
基本策略:将中缀表达式转换为后缀表达式,然后求值。如何将中缀表达式转换为后缀?
观察一个简单例子: 2+9/3-5 -> 2 9 3 / + 5 -
1.运算数相对顺序不变
2.运算符号顺序发生改变
Ø需要存储“等待中”的运算符号 (堆栈! )
Ø要将当前运算符号与“等待中”的最后一个运算符号比较

有括号怎么办?

〖例〗 a * ( b + c ) / d = ? a b c + * d /
在这里插入图片描述
中缀表达式求值
在这里插入图片描述
中缀表达式如何转换为后缀表达式
Ø 从头到尾读取中缀表达式的每个对象,对不同对象按不同的情况处理。
① 运算数:直接输出;
② 左括号:压入堆栈;
③ 右括号:将栈顶的运算符弹出并输出,直到遇到左括号(出栈,不输出);
④ 运算符:
•若优先级大于栈顶运算符时,则把它压栈;
•若优先级小于等于栈顶运算符时,将栈顶运算符弹出并输出;再比较新的栈顶运算符,直到该运算符大于栈顶运算符优先级为止,然后将该运算符压栈;
⑤ 若各对象处理完毕,则把堆栈中存留的运算符一并输出。
在这里插入图片描述

堆栈的其他应用

Ø 函数调用及递归实现
Ø 深度优先搜索
Ø 回溯算法
Ø。。。

2.3 队列

2.3.1 定义

队列(Queue):具有一定操作约束的线性表。插入和删除操作:只能在一端插入,而在另一端删除。
特点:
Ø数据插入:入队列(AddQ)
Ø 数据删除:出队列(DeleteQ)
Ø先来先服务
Ø先进先出:FIFO
队尾rear++插入,队头front++删除。
链表队列头删除,链表队列尾插入。front做删除操作,rear做插入操作,front应在链表头

2.3.2 队列的抽象数据类型描述

类型名称:队列(Queue) 数据对象集:一个有0个或多个元素的有穷线性表。 操作集:长度为MaxSize的队列Q 属于 Queue,队列元素item 属于 ElementType
1、Queue CreatQueue( int MaxSize ):生成长度为MaxSize的空队列;
2、int IsFullQ( Queue Q, int MaxSize ):判断队列Q是否已满;
3、void AddQ( Queue Q, ElementType item ): 将数据元素item插入队列Q中;
4、int IsEmptyQ( Queue Q ): 判断队列Q是否为空;
5、ElementType DeleteQ( Queue Q ):将队头数据元素从队列中删除并返回。

2.3.3 队列的顺序存储实现

队列的顺序存储结构通常由一个一维数组和一个记录队列头元素位置的变量front以及一个记录队列尾元素位置的变量rear组成。

#define MaxSize <储存数据元素的最大个数>
struct QNode {
    ElementType Data[ MaxSize ];
    int rear;
    int front;
};
typedef struct QNode *Queue;

环形队列

rear==front 即可表示队空也可能表示队满,原因是rear与front之间“差距”n种情况,而队列装载元素情况有n+1种情况(队空、1、2、…、n);
如何判断队满?解决方案有两种:

  • 1、使用额外标记,Size或Tag。 Size记录装载元素的个数,添加+1,删除-1,若Size等于n,且rear ==front则队满,Size=0,且rear=front则队空
    Tag插入记1,删除记0,只需要看最后一次操作,若是1则队满;若是0则队空。
  • 2、仅使用n-1个数组空间。

(1)入队列

void AddQ( Queue PtrQ, ElementType item)      
{
    //Front和rear指针的移动采用“加1取余”法,体现了顺序存储的“循环使用”。
    if ( (PtrQ->rear+1) % MaxSize == PtrQ->front ) {
        printf(“队列满”);
        return;
    }
    PtrQ->rear = (PtrQ->rear+1)% MaxSize;
    PtrQ->Data[PtrQ->rear] = item;
}
ElementType DeleteQ ( Queue PtrQ )      
{
    if ( PtrQ->front == PtrQ->rear ) {
        printf(“队列空”);
        return ERROR;
    } else {
        PtrQ->front = (PtrQ->front+1)% MaxSize;
        return PtrQ->Data[PtrQ->front];
    }
}

2.3.4 队列的链式存储实现

队列的链式存储结构也可以用一个单链表实现。插入和删除操作分别在链表的两头进行;队列指针front和rear应该分别指向链表的哪一头?

struct Node{
    ElementType Data;
    struct Node *Next;
};
struct QNode{ /* 链队列结构 */
    struct Node *rear; /* 指向队尾结点 */
    struct Node *front; /* 指向队头结点 */
};
typedef struct QNode *Queue;
Queue PtrQ;

在这里插入图片描述

不带头结点的链式队列出队操作的一个示例:
ElementType DeleteQ ( Queue PtrQ )      
{ struct Node *FrontCell;
    ElementType FrontElem;
    if ( PtrQ->front == NULL) {
        printf(“队列空”); return ERROR;
    }
    FrontCell = PtrQ->front;
    if ( PtrQ->front == PtrQ->rear) /* 若队列只有一个元素 */
        PtrQ->front = PtrQ->rear = NULL; /* 删除后队列置为空 */
    else
        PtrQ->front = PtrQ->front->Next;
    FrontElem = FrontCell->Data;
    free( FrontCell ); /* 释放被删除结点空间 */
    return FrontElem;
}

2.3.5 队列应用: 多项式加法运算

这里是引用
主要思路:相同指数的项系数相加,其余部分进行拷贝。
多项式加法运算,采用不带头结点的单向链表,按照指数递减的顺序排列各项
在这里插入图片描述

struct PolyNode {
    int coef; // 系数
    int expon; // 指数
    struct PolyNode *link; // 指向下一个节点的指针
};
typedef struct PolyNode *Polynomial;
Polynomial P1, P2;

算法思路:两个指针P1和P2分别指向这两个多项式第一个结点,不断循环:

  • P1->expon==P2->expon: 系数相加,若结果不为0,则作为结果多项式对应项的系数。同时,P1和P2都分别指向下一项;
  • P1->expon>P2->expon: 将P1的当前项存入结果多项式,并使P1指向下一项;
  • P1->exponexpon: 将P2的当前项存入结果多项式,并使P2指向下一项;
  • 当某一多项式处理完时,将另一个多项式的所有结点依次复制到结果多项式中去。

在这里插入图片描述

//代码实现
Polynomial PolyAdd (Polynomial P1, Polynomial P2)
{
    Polynomial front, rear, temp;
    int sum;
    //为方便表头插入,先产生一个临时空结点作为结果多项式链表头
    rear = (Polynomial) malloc(sizeof(struct PolyNode));
    front = rear; /* 由front 记录结果多项式链表头结点 */
    while ( P1 && P2 ) /* 当两个多项式都有非零项待处理时 */
        switch ( Compare(P1->expon, P2->expon) ) {
            //P1中的数据项指数较大
            case 1:
                Attach( P1->coef, P1->expon, &rear);
                P1 = P1->link;
                break;
            //P2中的数据项指数较大
            case -1:
                Attach(P2->coef, P2->expon, &rear);
                P2 = P2->link;
                break;
            //两数据项指数相等
            case 0:
                sum = P1->coef + P2->coef;
                //注意判断系数和是否为0
                if ( sum ) Attach(sum, P1->expon, &rear);
                    P1 = P1->link;
                    P2 = P2->link;
                break;
    }
    /* 将未处理完的另一个多项式的所有节点依次复制到结果多项式中去 */
    for ( ; P1; P1 = P1->link ) Attach(P1->coef, P1->expon, &rear);
    for ( ; P2; P2 = P2->link ) Attach(P2->coef, P2->expon, &rear);
    rear->link = NULL;
    temp = front;
    front = front->link; /*令front指向结果多项式第一个非零项 */
    free(temp); /* 释放临时空表头结点 */
    return front;

void Attach( int c, int e, Polynomial *pRear )
{ /* 由于在本函数中需要改变当前结果表达式尾项指针的值, */
    /* 所以函数传递进来的是结点指针的地址,*pRear指向尾项*/
    Polynomial P;
    P =(Polynomial)malloc(sizeof(struct PolyNode)); /* 申请新结点  */
    P->coef = c; /* 对新结点赋值 */
    P->expon = e;
    P->link=NULL;
    /* 将P指向的新结点插入到当前结果表达式尾项的后面 */
    (*pRear)->link = P;
    *pRear = P; /* 修改pRear值 */
}

在这里插入图片描述

2.3.6 队列应用:多项式乘法与加法运算

题目:设计函数分别求两个一元多项式的乘积与和, 已知两个多项式:
(1) 3x^4 - 5x^2 + 6x - 2
(2) 5x^20 -7x^4 + 3x
多项式和: 5x^20 - 4x^4 - 5x^2 + 9x - 2

题目:设计函数分别求两个一元多项式的乘积与和, 已知两个多项式: (1) 3x^4 - 5x^2 + 6x - 2 (2) 5x^20 -
7x^4 + 3x

多项式的乘积:
(a+b)(c+d) = ac+ad+bc+bd

多项式乘积:
15x24-25x22+30x21-10x20-21x8+35x6-33x5+14x4-15x3+18x2-6x

设计函数分别求两个一元多项式的乘积与和

 输入样例:
4 3 4 -5 2 6 1 -2 0                4:项数 3x^4-5x^2+6x-2
3 5 20 -7 4 3 1                    3:项数 5x^20-7x^4+3x
输出样例:
相乘结果:15 24 -25 22 30 21 -10 20 -21 8  35 6 -33 5 14 4 -15 3 18 2 -6 1
相加结果:5 20 -4 4 -5 2 9 1 -2 0

15x^24-25x^22+30x^21-10x^20-21x^8+35x^6-33x^5+14x^4-15x^3+18x^2-6x
5x20-4x4-5x2+9x-2

求解思路

1.多项式表示
2. 程序框架
3. 读多项式
4. 加法实现
5. 乘法实现
6. 多项式输出

(1)多项式的表示

仅表示非零项(关键信息)
数组:
优点: 编程简单、调试容易
缺点: 需要事先确定数组大小
链表:
优点:动态性强
缺点:编程略为复杂、调试比较困难
一种比较好的实现方法是: 动态数组

(2)下面介绍链表表示

这里是引用

数据结构设计
typedef struct PolyNode *Polynomial;
struct PolyNode {
	int coef;
	int expon;
	Polynomial link;
};
程序框架搭建
int main()
{
	读入多项式1
	读入多项式2
	乘法运算并输出
	加法运算并输出
	return 0;
}

需要设计的函数:
Ø读一个多项式
Ø两多项式相乘
Ø两多项式相加
Ø多项式输出

int main()
{
	Polynomial P1, P2, PP, PS;
	P1 = ReadPoly();
	P2 = ReadPoly();
	PP = Mult( P1, P2 );
	PrintPoly( PP );
	PS = Add( P1, P2 );
	PrintPoly( PS );
	return 0;
}
如何读入多项式

这里是引用

//4 3 4 -5 2 6 1 -2 0
Polynomial ReadPoly()
{
	……
	scanf("%d", &N);
	……
	while ( N-- ) {
		scanf("%d %d", &c, &e);
		Attach(c, e, &Rear);
	}..
	return P;
}

Rear初值是多少?
两种处理方法:

1.Rear初值为NULL 在Attach函数中根据Rear是否为NULL做不同处理
这里是引用
2.Rear指向一个空结点
在这里插入图片描述

方法2实现Attach函数:

这里是引用

//方法2
void Attach( int c, int e, Polynomial *pRear )
{ Polynomial P;
	P = (Polynomial)malloc(sizeof(struct PolyNode));
	P->coef = c; /* 对新结点赋值 */
	P->expon = e;
	P->link = NULL;
	(*pRear)->link = P;
	*pRear = P; /* 修改pRear值 */
}

读入表达式函数:

这里是引用

Polynomial ReadPoly()
{
	Polynomial P, Rear, t;
	int c, e, N;
	scanf("%d", &N);
	P = (Polynomial)malloc(sizeof(struct PolyNode)); /* 链表头空结点  */
	P->link = NULL;
	Rear = P;
	while ( N-- ) {
		scanf("%d %d", &c, &e);
		Attach(c, e, &Rear); /* 将当前项插入多项式尾部 */
	}
	t = P; P = P->link; free(t); /* 删除临时生成的头结点 */
	return P;
}
如何将两个多项式相加
Polynomial Add( Polynomial P1, Polynomial P2 )
{ ……
	t1 = P1; t2 = P2;
	P = (Polynomial)malloc(sizeof(struct PolyNode)); P->link = NULL;
	Rear = P;
	while (t1 && t2) {
		if (t1->expon == t2->expon) {..
		}
		else if (t1->expon > t2->expon) {
			……
		}
		else {
			……
		}
	}
	while (t1) {
		……
	}
	while (t2) {..
	}
	……..
	return P;
}
如何将两个多项式相乘

方法:

1.将乘法运算转换为加法运算 将P1当前项(ci,ei)乘P2多项式,再加到结果多项式里

 t1 = P1; t2 = P2; 
 P = (Polynomial)malloc(sizeof(struct PolyNode));  
 P->link = NULL; 
 Rear = P; 
 while (t2) { 
 	Attach(t1->coef*t2->coef,  t1->expon+t2->expon, &Rear); 
 	t2 = t2->link;
 } 

2.逐项插入 将P1当前项(c1i,e1i)乘P2当前项(c2i,e2i),并插入到结果多项式中。关键是要找到插入位置 初始结果多项式可由P1第一项乘P2获得(如上)

以下是“逐项插入”的实现方法:

//核心部分:两层while循环,用 多项式1的每一项 乘以 多项式2,指数expon相加,系数coef相乘
Polynomial Mult( Polynomial P1, Polynomial P2 )
{ 	…….
	t1 = P1; t2 = P2;
	…….
	while (t2) { /* 先用P1的第1项乘以P2,得到P */
		……..
	}
	t1 = t1->link;
	while (t1) {
		t2 = P2; Rear = P;
		while (t2) {
			e = t1->expon + t2->expon;
			c = t1->coef * t2->coef;
			………..
			t2 = t2->link;
		}
		t1 = t1->link;
	}
	…….
}
Polynomial Mult( Polynomial P1, Polynomial P2 )
{ 
	Polynomial P, Rear, t1, t2, t;
	int c, e;
	
	//开始部分:获得初始结果多项式P,用P1的第一项乘以P2的每一项
	if (!P1 || !P2) return NULL;
	t1 = P1; t2 = P2;
	P = (Polynomial)malloc(sizeof(struct PolyNode)); P->link = NULL;
	Rear = P;
	while (t2) 
	{ /* 先用P1的第1项乘以P2,得到P */
		Attach(t1->coef*t2->coef, t1->expon+t2->expon, &Rear);
		t2 = t2->link;
	}
	
	t1 = t1->link;
	while (t1) 
	{
		t2 = P2; Rear = P;
		while (t2) 
		{
			………..
			t2 = t2->link;
		}
		t1 = t1->link;
	}
	…….
}

中间核心部分:多项式P1每一项乘以多项式P2每一项部分的实现

这里是引用

Polynomial Mult( Polynomial P1, Polynomial P2 )
{ 	…….
	while (t1) 
	{
		t2 = P2; Rear = P;
		while (t2) 
		{
			e = t1->expon + t2->expon;
			c = t1->coef * t2->coef;
			//将相乘的结果指数从大到小插入到对应位置,结果多项式P的Rear先移动到合适位置
			while (Rear->link && Rear->link->expon > e)
				Rear = Rear->link;
			//如果指数相等,合并同类项
			if (Rear->link && Rear->link->expon == e) 
			{
				……..
			}
			//不相等则进行插入
			else 
			{
				……..
			}
			t2 = t2->link;
		}
	t1 = t1->link;
	}
	…….
}

中间核心部分:合并同类项或者插入

这里是引用

Polynomial Mult( Polynomial P1, Polynomial P2 )
{ 	…….
	while (Rear->link && Rear->link->expon > e)
		Rear = Rear->link;
	//指数相同,合并同类项
	if (Rear->link && Rear->link->expon == e) 
	{
		//合并之后,若系数不为0
		if (Rear->link->coef + c)
			Rear->link->coef += c;
		//若系数为0,则删除掉该项
		else 
		{
			t = Rear->link;
			Rear->link = t->link;
			free(t);
		}
	}
	//指数不同,插入该项
	else 
	{
		t = (Polynomial)malloc(sizeof(struct PolyNode));
		t->coef = c; t->expon = e;
		t->link = Rear->link;
		Rear->link = t; Rear = Rear->link;
	}
	…….
}

最后部分:删掉开头的空指针,因创建结果多项式P的时候,第一项指向的是一个空节点

这里是引用

Polynomial Mult( Polynomial P1, Polynomial P2 )
{ 	…….
	t1 = P1; t2 = P2;
	…….
	while (t2) 
	{ /* 先用P1的第1项乘以P2,得到P */
		……..
	}
	t1 = t1->link;
	while (t1) 
	{
		t2 = P2; Rear = P;
		while (t2) 
		{
			e = t1->expon + t2->expon;
			c = t1->coef * t2->coef;
			………..
			t2 = t2->link;
		}
		t1 = t1->link;
	}
	
	//删除头部空结点
	t2 = P; P = P->link; free(t2);
	return P;
}
如何将多项式输出
void PrintPoly( Polynomial P )
{ /* 输出多项式 */
	int flag = 0; /* 辅助调整输出格式用 */
	if (!P) {printf("0 0\n"); return;}
	while ( P ) 
	{
		if (!flag)
			flag = 1;
		else
			printf(" ");
		printf("%d %d", P->coef, P->expon);
		P = P->link;
	}
	printf("\n");
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值