440 likes | 570 Views
数据结构. 第 2 章 线性表. 授课教师:杨树英. 第 2 章 线性表. 本章内容概要 线性表 是一种最简单、最基本、也是最常用的数据结构。线性表的概念在操作系统和数据库系统中有重要应用。本章主要介绍线性表的逻辑结构及两种存储结构:顺序存储和链式存储结构,以及线性表涉及的主要基本操作:插入、删除和查找。. 2-1 线性表的定义与运算 2-1-1 线性表的定义 2-1-2 线性表的基本操作 2-2 线性表的顺序存储 2-2-1 顺序表 2-2-2 顺序表上基本运算的实现 2-3 线性表的链式存储 2-3-1 线性链表
E N D
数据结构 第2章 线性表 授课教师:杨树英
第2章 线性表 本章内容概要 线性表是一种最简单、最基本、也是最常用的数据结构。线性表的概念在操作系统和数据库系统中有重要应用。本章主要介绍线性表的逻辑结构及两种存储结构:顺序存储和链式存储结构,以及线性表涉及的主要基本操作:插入、删除和查找。
2-1 线性表的定义与运算 2-1-1 线性表的定义 2-1-2 线性表的基本操作 2-2 线性表的顺序存储 2-2-1 顺序表 2-2-2 顺序表上基本运算的实现 2-3 线性表的链式存储 2-3-1 线性链表 2-3-2 线性链表上基本运算的实现 2-3-3 循环链表 2-3-4 双向链表 2-3-5 顺序表和链表的比较 2-4 线性表的应用举例 第2章 目录
2-1线性表的定义与运算 2-1-1 线性表的定义 1.线性表的定义 线性表是具有相同数据类型的n(n>=0)个数据元素的有限序列,通常记为: (a1,a2,… ai-1,ai,ai+1,…an) 线性表可以用一个标示符来命名,如果用L来表示线性表,则 L=(a1,a2,… ai-1,ai,ai+1,…an) 其中L表示该线性表;n为表长,n=0时称为空表;下标i表示数据元素的位序。 在线性表中,元素之间存在线性的逻辑关系:表中有且仅有一个开始结点,或称首元结点a1;有且仅有一个终端结点,或称表尾结点an;除开始结点外,表中每个结点ai(1<i≤n)均只有一个前驱结点;除终端结点外,表中每个结点ai(1≤i<n)均只有一个后继结点。元素之间为一对一的关系。 线性表是一种非常典型的线性结构,用二元组可以表示成: S=(D,R) D={a1,a2,…,ai,…,an} R={<a1,a2>,<a2,a3>,…,<ai,ai+1 >,…,<an-1,an >} 对应的逻辑结构图如图2.1所示
图2.1 线性表逻辑结构示意图 线性表的特点: (1)有且仅有一个开始结点(a1),它没有直接前趋; (2)有且仅有一个终端结点(an),它没有直接后继; (3)除了开始结点和终端结点以外,其余的结点都有且仅有一个直接前驱和一个直接后继。 例:下面是一个学生信息表的例子,见表2—1。 在这个比较复杂的线性表中,一个数据元素是每个学生所对应的一行信息,包括学号、姓名、性别、年龄和成绩共五个数据项
2-1-2 线性表的基本操作 线性表上的基本操作有: (1) 初始化线性表:InitList(L) 初始条件:表L不存在。 操作结果:构造一个空的线性表 。 (2) 求线性表的长度:LengthList(L) 初始条件:表L存在。 操作结果:返回线性表L所含数据元素的个数。 (3) 读取线性表中的第i个数据元素:GetList(L,i) 初始条件:表L存在。 操作结果:返回线性表L中的第i个元素的值或地址。如果线性表为空,或者i超出了线性表的长度,则报错。
(4) 按值查找:SearchList(L,x), 初始条件:线性表L存在,x是给定的一个数据元素。 操作结果:在表L中查找值为x的数据元素,其结果返回在L中首次出现的值为x的那个元素的序号或地址,则查找成功; 否则,在L中未找到值为x的数据元素,返回一特殊值表示查找失败。 (5) 插入操作:InsertList(L,i,x) 初始条件:线性表L存在,i表示新元素将要插入的位置,插入位置正确 (1≤i≤n+1,n为插入前的表长)。 操作结果:在线性表L的第 i 个位置上插入一个值为 x的新元素,这样使原序号为 i , i+1, ... , n 的数据元素的序号变为 i+1,i+2, ... , n+1,插入后表长=原表长+1。 (6) 删除操作:DeleteList(L,i) 初始条件:线性表L存在,i表示需要删除的数据元素的位序(1≤i≤n,n为表长)。 操作结果:在线性表L中删除序号为i的数据元素,删除后使序号为 i+1, i+2,..., n 的元素变为序号为 i, i+1,...,n-1,新表长=原表长-1。
2-2线性表的顺序存储 2-2-1 顺序表 线性表的顺序存储结构是指在内存中用地址连续的一块存储空间顺序存放线性表的各元素,用这种存储形式存储的线性表称其为顺序表。因此,顺序表的逻辑顺序与其在内存空间中的物理顺序一致,由线性表中各数据元素在存储空间中的顺序可以知道这些数据元素的逻辑关系。 只要知道顺序表首地址和每个数据元素所占地址单元的个数就可求出第i个数据元素的地址来,这也是顺序表具有按数据元素的序号随机存取的特点。 图2.2 线性表顺序存储结构示意图
2-2-2 顺序表上基本运算的实现 1.插入运算 插入运算是指在具有n个元素的线性表的第i(1≤i≤n)个元素之前插入一个值为 x 的新元素,成为新的第i个元素,原来的第i个元素成为第i+1个元素,插入后使原表长为 n的表: (a1,a2,... ,ai-1,ai,ai+1,... ,an) 成为表长为 n+1的线性表: (a1,a2,...,ai-1,x,ai,ai+1,...,an) 。 此时,i 的合法取值范围为1≤i≤n+1 。顺序表上完成插入运算如图2.3所示。
算法如下: int insertsqlist(SqList *sql,int i,elementtype x) { int j; if((i<1)||(i>sql->len+1)) /*首先判断插入位置是否合法*/ { printf("插入位置%d不合法\n",i); return(0); } if(sql->len>=MAXLEN-1) { printf("表已满无法插入"); return(0); } for(j=sql->len;j>=i;j--) sql->s[j+1]=sql->s[j]; /*向后移动数据,腾出要插入的空位*/ sql->s[j+1]=x; /*修正插入位置为j+1,将新元素插入到s[j+1]位置*/ (sql->len)++; /*表长加1*/ return(1); /*插入成功,返回值为1*/ }
本算法中注意以下问题: (1) 顺序表中数据区域最多有MAXLEN个存储单元,所以在向顺序表 中做插入时先检查表空间是否满了,在表满的情况下不能再做插入,否则产生溢出错误。 (2) 要检验插入位置的有效性,这里i 的有效范围是:1≤i≤r+1,其中r为原表长(本程序中r为用户输入的长度)。 (3) 注意数据的移动方向。 如图2-3所示。
2.删除运算 删除运算是指在具有n个元素的线性表中,删除其中的第i(1≤i≤n)个元素,删除后使原表长为n的线性表: (a1,a2,... ,ai-1,ai,ai+1,... ,an) 成为表长为 n-1的线性表: (a1,a2,...,ai-1,ai+1,...,an) 。 此时,i 的取值范围为1≤i≤n 。 顺序表上完成删除运算如图2.4所示。 图2.4 顺序表删除示意图
算法如下: int DelList(SqList *sql,int i,elementtype *x) /*在顺序表sql中删除第i个数据元素,并用指针参数x返回其值。 i的合法取值为1≤i≤sql.len*/ { int k; if((i<1)||(i>sql->len)) { printf("删除位置不合法!"); return(0); } *x = sql->s[i]; /* 将删除的元素存放到x所指向的变量中*/ for(k=i+1; i<=sql->len; k++) sql->s[k-1] = sql->s[k]; /*将后面的元素依次前移*/ sql->len--; return(1); }
3.按值查找 线性表中的按值查找是指在线性表中查找与给定值x相等的数据元素。在顺序表中完成该运算最简单的方法是:从第一个元素 a1 起依次和x比较,直到找到一个与x 相等的数据元素,则返回它在顺序表中的存储下标或序号;或者查遍整个表都没有找到与x 相等的元素,返回-1。 【算法2.1】按值查找 int SearchList(SqList sql, elementtype x) { int i=1; /*初值为1,即从第一个元素开始比较,seq.s[0]闲置不用*/ while((i<= sql.len)&&( sql.s[i]!=x)) i++; /*顺序扫描表,直到找到值为x的元素, 或扫描到表尾而没找到*/ if (i<= sql.last) return(i); /*若找到值为x的元素,则返回其序号*/ else return(-1); /*若没找到,则返回空序号*/ } 本算法的主要运算时比较。显然比较的次数与x在表中的位置有关,也与表长有关。当a1=x时,比较一次成功。当a n=x时比较n次成功。平均比较次数为(n+1)/2,时间复杂度为O(n)。
2-3线性表的链式存储 (1)顺序存储的优点: 可以随机存取表中任意一个元素; 存储位置可以用公式:B+(i-1)*d 计算; 节约存储空间。 (2)顺序存储的缺点: 对顺序表作插入、删除时需要通过移动大量的数据元素,影响了运行效率。 线性表预先分配空间时,必须按最大空间分配,存储空间得不到充分的利用。 表的容量难以扩充(对有些高级语言而言)。
2-3-1 线性链表 1. 线性链式存储结构的特点 (1)用一组任意的存储单元存储线性表的数据元素。 (2)单链表的每个结点由一个数据域和一个指针域组成: 结点中存放数据元素信息的域称为数据域;存放其后继地址的域称为指针域。 (3)单链表的存取必须从头指针开始 图2-6 单链表示意图
链表是由一个个结点构成的,在C语言中可以用结构体类型定义链表的结点:链表是由一个个结点构成的,在C语言中可以用结构体类型定义链表的结点: typedef struct node { Datatype data; /*数据域,Datatype可以为任意类型数据*/ struct node *next; /*指针域*/ }NODE; 2. 关于头指针、头结点和首元结点: (1) 头指针——指向链表中第一个结点(头结点或无头结点时的首元结点)的指针。 (2) 头结点——在首元结点之前增设的一个结点,该结点不计入链表长度。 (3) 首元结点——在链表中,存储第一个数据元素(a1)的结点。3. 在C(或C++)中可以用“结构体指针”来描述
2-3-2 线性表上基本运算的实现 1.建立带头结点的线性链表 在链表的头部插入结点建立线性链表 在链表的头部插入结点建立链表的过程如下: (1) 申请存储单元,用C语言的动态分配库函数malloc(sizeof(NODE))得到。 (2) 读入新结点的数据,新结点的指针域初始化为空。 (3) 把新结点链接到链表的头部上去。 重复以上步骤,直到将所有结点都链接到链表上为止,如图2.8所示。
在链表的头部入结点建立线性表的算法: NODE *create()/*此函数采用头插法建立单链表,并返回一个指向链表表头的指针*/ { NODE *head,*q,*p; /*定义指针变量*/ char ch; int a; head=(NODE*)malloc(sizeof(NODE)); /*申请新的存储空间,建立头结点*/ head->next=NULL; q=head->next; ch='*'; printf("\n用头插法建立单链表,请输入链表数据,以?结束!\n"); while(ch!='?')/*"ch"为是否建立新结点的标志,若"ch"为"?"则输入结束*/ { scanf("%d",&a); /*输入新元素*/ p=(NODE*)malloc(sizeof(NODE)); /*对应图2.8中的①*/ p->data=a; /*对应图2.8中的②*/ head->next=p; /*对应图2.8中的③*/ p->next=q; /*对应图2.8中的④*/ q=p; /*对应图2.8中的⑤*/ ch=getchar(); /*读入输入与否的标志*/ } return(head); /*返回表头指针head*/ }
在线性链表的尾部插入结点建立线性链表 在链表的尾部插入结点建立链表的过程如下: (1) 申请存储单元,用C语言的动态分配库函数malloc(sizeof(NODE))得到。 (2) 读入新结点的数据,新结点的指针域为空。 (3) 把新结点链接到链表的尾部上去。 重复以上步骤,直到将所有结点都链接到链表上为止,如图2.9所示。
算法如下: NODE *create()/*此函数采用尾插入方式建立单链表, 并返回一个指向链表表头的指针*/ { NODE *head,*q,*p; /*定义指针变量*/ char ch; int a; head=(NODE*)malloc(sizeof(NODE)); /*申请新的存储空间,建立头结点*/ q=head; ch='*'; printf("\n用尾插法建立单链表,请输入链表数据,以?结束!\n"); while(ch!='?') /*"ch"为是否建立新结点的标志,若"ch"为"?"则输入结束*/ { scanf("%d",&a); /*输入新元素*/ p=(NODE*)malloc(sizeof(NODE)); /*对应图2.9中的①*/ p->data=a; /*对应图2.9中的②*/ q->next=p; /*对应图2.9中的③*/ q=p; /*对应图2.9中的④*/ ch=getchar(); /*读入输入与否的标志*/ } q->next=NULL; return(head); /*返回表头指针head*/ }
分析尾部插入结点(尾插法)建立线性链表的过程可见,在一般情况下,无论是带头结点还是不带头结点的单链表,插入一个元素对应的指针操作是:q->next=p;q=p。对于带头结点的单链表,当单链表为空链表时(此时q=head且head->next=NULL),插入第一个结点对应的指针操作为:head ->next=p;q=p,可见操作与一般情况是一致的;而对于不带头结点的单链表,当单链表为空链表时(此时head=NULL,q=NULL),尾插法插入第一个结点的指针操作为:head=p;q=p,可见对不带头结点单链表,若单链表为空链表而又是插入第一个元素时是一种比较特殊的情形,这也就是我们前面讲到的对单链表要增设一个头结点的重要原因之一。 增设一个类型相同的头结点,并将首元结点的存储地址存放在头结点的指针域中,有两个方面的优点: 首先,可使表中所有元素结点的地址均放在前驱结点中,算法对所有元素结点的处理可一致化; 其次,无论链表是否为空,头指针均指向头结点,给算法的处理带来方便。
2.单链表中结点的查找操作 (1)按序号查找 从链表的头指针出发,顺着next域(指针域)往后搜索,判断当前结点是否为第i个,若是,则返回该结点的指针,否则继续下一个结点,直到链表结束为止都没有第i个结点时返回查找失败信息。 算法如下: NODE *find(NODE *head,int i)/*在已知链表中查找给定的位置i*/ { int j=1; NODE *p; p=head->next; while((p!=NULL)&&(j<i)) /*未到表尾且未找到给定数据*/ { p=p->next; /*指向下一个元素*/ j++; } return(p); }
(2)按值查找 从链表的头指针出发,顺着next域(指针域)往后搜索,在单链表中查找是否存在数据域的值为给定的值(如整数x)的结点,若存在,返回该结点的位置,否则返回NULL.。 算法如下: NODE *locate(NODE *head,int x)/*在已知链表中查找给定的值x*/ { NODE *p; p=head->next; while((p!=NULL)&&(p->data!=x)) /*未到表尾且未找到给定数据*/ p=p->next; /*指向下一个元素*/ return(p) }
3.单链表上的插入操作 假设要在线性表的两个元素a和b之间插入一个数据元素x,p为指向结点a的指针。为了插入数据元素x,首先要生成一个数据域为x的新结点,q为指向新增结点的指针。其过程如下: (1)新建一个新结点q,将x值赋给q->data. (2)修改有关结点的指针域。 void insert(NODE *p,int x) /*在链表的p结点位置后插入给定元素x*/ { NODE *q; q=(NODE*)malloc(sizeof(NODE)); /*对应图2.10中的①*/ q->data=x; /*对应图2.10中的②*/ q->next=p->next; /*对应图2.10中的③*/ p->next=q; /*对应图2.10中的④*/ }
4.单链表上的删除操作 如图2.11所示,要删除数据元素a和b中间的数据元素x,并由系统收回其占用的存储空间,仅需要修改数据元素a所在结点的指针域。其过程如下: (1) 设定两个指针p和q,p指针指向被删除结点,q为跟踪指针,指向被删除结点的直接前驱结点。 (2) p从头指针head指向的第一个结点开始依次向后搜索。当p—>data等于x时,被删除结点找到。 (3) 修改p的前驱结点q的指针域。使p结点被删除,然后释放存储空间。
void delete(NODE *head,int x) /*删除链表中的给定元素x*/ • { • NODE *p,*q; • q=head; • p=q->next; • while((p!=NULL)&&(p->data!=x)) /*查找要删除的元素*/ • { • q=p; • p=p->next; • } • if(p==NULL) • printf("%d 不存在.\n",x); /*x结点未找到*/ • else • { • q->next=p->next; /*链接x直接后继结点*/ • free(p); /*删除x结点,释放x结点空间*/ • } • }
2-3-3 循环链表 1.循环链表的特点 循环链表(Circular Linked List)是单链表的另一种形式,它是一个首尾相接的链表。其特点是将单链表最后一个结点的指针域由NULL改为指向头结点或线性表中的首元结点,就得到了单链形式的循环链表,并称为循环单链表。在有些应用问题中,用循环单链表可使操作更加方便灵活。在循环单链表中也可设置一个头结点。这样,空循环链表仅由一个自成循环的头结点组成。带头结点的循环链表如图2.12所示。
2.在循环链表上的操作 循环单链表中从任一结点出发均可找到表中其他所有结点。在许多实际问题中,链表的插入和删除等操作主要发生在表的首尾两端,此时采用图2.12表示的循环单链表显得不够方便,主要是不利于查找尾结点。 如果改用尾指针rear来表示循环单链表,如图2.13所示,则查找开始结点a1和终端结点an都很方便,它们的存储地址分别由rear和rear->netxt->next指向。因此通常采用尾指针rear来表示循环单链表。循环单链表的操作和带头结点的单链表的操作实现算法基本一致,差别仅在于算法中的循环条件p!=NULL或p->next!=NULL改为p->next!=head。
在循环单链表中,可以从表中任一结点p出发找到它的直接前驱,而不必从头指针head出发,其算法如下:在循环单链表中,可以从表中任一结点p出发找到它的直接前驱,而不必从头指针head出发,其算法如下: 【算法2.2】在循环单链表中,从任一结点p出发找它直接前驱的算法 NODE *prior(NODE *p) { NODE *q; q=p->next; while(q->next!=p) q=q->next; return(q); }
2-3-4 双向链表 1.单向链表的缺点 单向链表只能顺指针往后寻找其它结点。若要寻找结点的前驱,则需要从表头指针出发。克服上述缺点可以采用双向链表。如图2.14(b)所示。 2.双向链表 一个用来存储数据元素的数据域data,一个用来存储前驱结点的位置的指针域prior,还有一个用来存储后继结点的位置的指针域next,如图2.14(a)所示。
和单链表类似,双向链表也是由头指针head唯一确定的,增加头结点也能使双向链表上的某些操作变得方便,将头结点和尾结点接起来也能构成循环链表,并称之为双向循环链表,如图2.15所示。和单链表类似,双向链表也是由头指针head唯一确定的,增加头结点也能使双向链表上的某些操作变得方便,将头结点和尾结点接起来也能构成循环链表,并称之为双向循环链表,如图2.15所示。 双向链表用的结点结构用 C语言中描述如下: typedef int elementtype; /*根据需要,elementtype也可以定义为其它任何类型*/ typedef struct DNode { elementtype data; //结点数据,数据类型为 struct DNode *prior,*next; //指向前驱结点和后继结点的指针 }DNode,*DoubleList; 由于在双向链表中既有前向链又有后向链,因此寻找任一个结点的直接前驱结点与直接后继结点变得非常方便。设指针p指向双链表中某一结点,则有下式成立: p->prior->next = p = p->next->prior 下面主要介绍在双向链表中实现插入操作和删除操作的算法。
2.双链表的操作 • (1) 插入结点 • 在双向链表第i个结点之前插入一个新结点的基本思路如下: • 在双向链表中搜索到第i个结点,并用p表示,之后做如下操作。 • ① 生成一个新结点q,将x赋给q->data。 • ② 将新结点q的prior指针指向p结点的前驱结点,即q->prior=p->prior。 • ③ 将新结点q的next指针指向p结点,即q->next=p。 • ④ 将p结点的前驱结点的next指针指向新结点q,即p->prior->next=q。 • ⑤ 将p结点的prior指针指向新结点q,即p->prior=q。 • 在双向链表中的p结点之前插入数据元素x的指针变化情况如图2.16所示。
在双向链表Dlist中的第i个结点之前插入数据元素x的算法用C语言描述如下:在双向链表Dlist中的第i个结点之前插入数据元素x的算法用C语言描述如下: 【算法2.3】 int DLinkInnode (DoubleList Dlist,int i,elementtype x) { DNode *q, *p; ... /* 先检查待插入的位置i是否合法(实现方法类似单链表的头插操法作)*/ ... /* 若位置i合法, 则让指针p指向它 */ q=(DNode*)malloc(sizeof(DNode)); q->data=x; q->prior=p->prior; //对应图2.16中的① q->next=p; //对应图2.16中的② p->prior->next=q; //对应图2.16中的③ p->prior=q; //对应图2.16中的④ }
(2) 删除结点 • 删除双向链表中的第i个结点的基本思路如下: • 在双向链表中搜索到被删除的第i个结点,并用p表示,做如下操作。 • ① 将p结点的前驱结点的next指针指向p结点的后继结点, • 即p->prior->next = p->next; • ② 将p结点的后继结点的prior指针指向p结点的前驱结点, • 即p->next->prior = p->prior。 • 在双向链表中删除结点p,指针变化情况如图2.17所示。
在双向链表Dlist中删除第i个结点的算法用C语言描述如下:在双向链表Dlist中删除第i个结点的算法用C语言描述如下: 【算法2.4】 int DLinkDelnode(DoubleList Dlist,int i, elementtype *x) { DNode *p; ... /* 首先检查待插入的位置i是否合法(实现方法类似单链表的删除操作) */... /* 若位置i合法, 则让指针p指向它 */ *x=p->data; p->prior->next=p->next; //对应图2.17中的① p->next->prior=p->prior; //对应图2.17中的② free(p); /*释放被删除结点的空间*/ }
2-3-5 顺序表和链表的比较 本章介绍了线性表的逻辑结构及它的两种存储结构:顺序表和链表。通过对它们的讨论可知它们各有优缺点,顺序存储有三个优点: (1) 方法简单,各种高级语言中都有数组,容易实现。 (2) 不用为表示结点间的逻辑关系而增加额外的存储开销。 (3) 顺序表具有按元素序号随机访问的特点。 但它也有两个缺点: 在顺序表中做插入删除操作时,平均移动大约表中一半的元素,因此对n较大的顺序表效率低。 需要预先分配足够大的存储空间,估计过大,可能会导致顺序表后部大量闲置,造成空间浪费;预先分配过小,又会造成溢出。 链表的优缺点恰好与顺序表相反。在实际中怎样选取存储结构呢?通常有以下几点考虑:
2-4 线性表的应用举例 例2.1有顺序表A和B,其元素均按从小到大的升序排列,编写算法将它们合并成一个顺序表C,要求C的元素也是从小到大的升序排列。 算法思路:依次扫描A和B的元素,比较当前的元素的值,将较小值的元素赋给C,如此直到一个线性表扫描完毕,然后将未完的那个顺序表中余下部分赋给C即可。C的容量要能够容纳A、B两个线性表相加的长度。 图2.18 单链表的逆置
例2.3 多项式相加问题。 (1) 存储结构的选取 任意一个一元多项式可表示为Pn(x)=P0+P1x+P2x2+...+Pnxn,显然,由其n+1个系数可惟一确定该多项式。故一元多项式可用一个仅存储其系数的线性表来表示,多项式指数i隐含于Pi的序号中。 P=( P0,P1,P2,...,Pn) 若采用顺序存储结构来存储这个线性表,那么多项式相加的算法实现十分容易,同位序元素相加即可。 但当多项式的次数很高而且变化很大时,采用这种顺序存储结构极不合理。例如,多项式S(x)= 1+ 3x+12x999需用一长度为1000的线性表来表示,而表中仅有三个非零元素,这样将大量浪费内存空间。此时可考虑另一种表示方法,如线性表S(x)可表示成S=((1,0),(3,1), (12,999)),其元素包含两个数据项:系数项和指数项。 这种表示方法在计算机内对应两种存储方式:当只对多项式进行访问、求值等不改变多项式指数(即表的长度不变化)的操作时,宜采用顺序存储结构;当要对多项式进行加法、减法、乘法等改变多项式指数的操作时,宜采用链式存储结构。 图2.18 单链表的逆置
(2)一元多项加法运算的实现 采用单链表结构来实现多项加法运算,就是前述单向链表基本运算的综合应用。其数据结构描述如下, typedef stuct Pnode { float coef;/*系数域*/ int exp; /*指数域*/ struct pnode *next; }Pnode,*Ploytp; 图2.19给出了多项式A(x) = 15+ 6x+ 9x7+3x18和B(x)= 4x+5x6+ 16x7的链式存储结构(设一元多项式均按升幂形式存储,首指针为-1)。 图2.19 一元多项式的存储
若上例A+B结果仍存于A中,根据一元多项式相加的运算规则,其实质是将B逐项按指数分情况合并于“和多项式”A中。设p, q分别指向A, B的第一个结点,如图2.20所示,其算法思路如下: (1) p->exp<q->exp, 应使指针后移p=p->next,如图2.20(a)所示。 (2) p->exp=q->exp,将两个结点系数相加,若系数和不为零,则修改p->ceof,并借助s释放当前q结点, 而使q指向多项式B的下一个结点,如图2.20(b)所示;若系数和为零,则应借助s释放p, q结点, 而使p, q分别指向多项式A,B的下一个结点。 (3) p->exp > q->exp,将q结点在p结点之前插入A中, 并使q指向多项式B的下一个结点,如图2.20(c)所示。 直到q=NULL为止或p=NULL,将B的剩余项链到A尾为止。最后释放B的头结点。 图2.20 多项式相加运算示例
例2.4 有两个线性表A和B,都是用尾指针表示循环链表存储结构,两个链表尾指针分别为reara和rearb,将B链表链接到A链表的后面,合并成一个新的循环单链表C。其尾指针为rearb。 算法思想:将A链表的尾指针reara与B链表的第一个结点(首元结点)链接起来,并修改B链表的尾指针rearb,使它指向链表A的头结点。如图2.21所示。
小 结 (1)线性表是一种最简单的数据结构,数据元素之间存在着一对一的关系。其存储方法通常采用顺序存储和链式存储。 (2)线性表的顺序存储可以采用结构体的形式,它含有两个域。一个整型的长度域,用以存放表中元素的个数;另一个数组域,用来存放元素,其类型可以根据需要而定。顺序存储的最大优点是可以随机存取,且存储空间比较节约,而缺点是表的扩充困难,插入、删除要做大量的元素移动。 (3)线性表的链式存储是通过结点之间的链接而得到的。根据链接方式又可以分为:单链表、双链表和循环链表等。
(4)单链表有一个数据域(data)和一个指针域(next)组成,数据域用来存放结点的信息;指针域指出表中下一个结点的地址。在单链表中,只能从某个结点出发找它的后继结点。单链表最大的优点是表的扩充容易、插入和删除操作方便,而缺点是存储空间比较浪费。(4)单链表有一个数据域(data)和一个指针域(next)组成,数据域用来存放结点的信息;指针域指出表中下一个结点的地址。在单链表中,只能从某个结点出发找它的后继结点。单链表最大的优点是表的扩充容易、插入和删除操作方便,而缺点是存储空间比较浪费。 (5)双链表有一个数据域(data)和两个指针域(prior和next)组成,它的优点是既能找到结点的前趋,又能找到结点的后继。 (6)循环链表使最后一个结点的指针指向头结点(或开始结点)的地址,形成一个首尾链接的环。利用循环链表将使某些运算比单链表更方便。