数据结构 第二章:线性表

2.1 线性表的定义和特点

线性表定义:

线性表是具有相同特性的数据元素的一个有限序列,是由n(n≥0)个数据元素(结点) a 1 , a 2 , … a n a_{1},a_{2},…a_{n} a1,a2,an组成的有限序列

e g : ( a 1 , a 2 , … … a i − 1 , a i , a i + 1 , … … , a n ) eg:(a_{1},a_{2},……a_{i-1},a_{i},a_{i+1},……,a_{n}) eg:(a1,a2,……ai1,ai,ai+1,……,an)

  • 第一个数据元素称为线性起点或起始节点
  • 中间的任意一个元素称为数据元素或节点
  • 最后一个元素称为线性终点或终端结点
  • 其中 a i - 1 为 a i 的直接前趋, a i + 1 为 a i 的直接后继 a_{i-1}为a_{i}的直接前趋,a_{i+1}为a_{i}的直接后继 ai1ai的直接前趋,ai+1ai的直接后继
  • 将非空的线性表(n>0)记作 ( a 1 , a 2 , … a n ) (a_{1},a_{2},…a_{n}) a1,a2,an
  • 这里的数据元素 a i a_{i} ai(1≤i=n)只是一个抽象的符号,其具体含义在不同的情况下可以不同。

下标:是元素的序号,表示元素在表中的位置

n:为元素总个数,即表长;n=0时,称为空表

线性表逻辑特征:

  • 在非空的线性表,有且仅有一个开始结点 a 1 a_{1} a1,它没有直接前趋,而仅有一个直接后继 a 2 a_{2} a2
  • 有且仅有一个终端结点 a n a_{n} an,它没有直接后继,而仅有一个直接前趋 a n − 1 a_{n-1} an1
  • 其余的内部结点 a i ( 2 ≤ i ≤ n − 1 ) a_{i}(2≤i≤n-1) ai(2in1)都有且仅有一个直接前趋 a i − 1 a_{i-1} ai1和一个直接后继$a_{i+1}

线性表是一种典型的线性结构。

2.2 案例引入

案例2.2.1:一元多项式的运算:

实现两个多项式加、减、乘运算

P n ( x ) = p 0 + p 1 x + p 2 x 2 + … + p n x n P_{n}(x)=p_{0}+p_{1}x+p_{2}x^{2}+…+p_{n}x^{n} Pn(x)=p0+p1x+p2x2++pnxn

线性表 P = ( p 0 , p 1 , p 2 … p n ) P=(p_{0},p_{1},p_{2}…p_{n}) P=(p0,p1,p2pn) (每一项的指数 i i i 隐含在其系数 p i p_{i} pi 的序号中)

数据结构 第二章:线性表_第1张图片

设: P = ( p 0 , p 1 , p 2 … p n ) P=(p_{0},p_{1},p_{2}…p_{n}) P=(p0,p1,p2pn) Q = ( q 0 , q 1 , q 2 … q m ) Q=(q_{0},q_{1},q_{2}…q_{m}) Q=(q0,q1,q2qm)
则: P + Q = ( p 0 + q 0 , p 1 + q 1 , p 2 + q 2 , … p m + q m , p m + 1 , … p n ) P+Q=(p_{0}+q_{0},p_{1}+q_{1},p_{2}+q_{2},…p_{m}+q_{m},p_{m+1},…p_{n}) P+Q=(p0+q0,p1+q1,p2+q2,pm+qm,pm+1,pn)

案例2.2.2:稀疏多项式的运算:

例如: S ( x ) = 1 + 3 x 10000 + 2 x 20000 S(x)=1+3x^{10000}+2x^{20000} S(x)=1+3x10000+2x20000的多项式,如果按照案例2.2.1的存储方式,将会造成很大的空间浪费。由于线性表的元素可以包含多个数据项,由此可改变元素的设定,对多项式的每一项,可采用(系数,指数)唯一确定。即
数据结构 第二章:线性表_第2张图片
数据结构 第二章:线性表_第3张图片
数据结构 第二章:线性表_第4张图片
数据结构 第二章:线性表_第5张图片数据结构 第二章:线性表_第6张图片

案例2.2.3:图书信息管理系统:

数据结构 第二章:线性表_第7张图片

  • 图书表抽象为线性表
  • 表中每本图书抽象为线性表中的数据元素
    数据结构 第二章:线性表_第8张图片
    数据结构 第二章:线性表_第9张图片

总结: 线性表中数据元素的类型可以为简单类型,也可以为复杂类型
 许多实际应用问题所涉的基本操作有很大相似性,不应为每个具体应用单独编写一个程序。
 从具体应用中抽象出共性的逻辑结构基本操作(抽象数据类型),然后实现其存储结构和基本操作

2.3 线性的类型定义

抽象数据类型线性表的定义如下:基本操作

ADT List{                    // List为线性表的表名
	数据对象:D={a_{i}|a_{i}属于Elemset,(i=1,2,……,n,n≥0)}
	数据对象:R={<a_{i-1},a_{i}>|a_{i-1},a_{i}属于D_{i}(i=2,3,……,n)}
	基本操作:
		InitList(&L);           DestroyList(&L);
		ListInsert(&L,i,e);     ListDelete(&L,i,&e);
		…………等等
}ADT List

2.4 线性表的顺序表示和实现

2.4.1 线性表的顺序存储定义

线性表的顺序表示指的是用一组地址连续的存储单元一次存储线性表的数据元素,这种表示也称作线性表的顺序存储结构顺序映像

定义:

把逻辑上相邻的数据元素存储在物理上相邻的存储单元中的存储结构
 要求依次存储,地址连续——中间没有空出的存储单元数据结构 第二章:线性表_第10张图片数据结构 第二章:线性表_第11张图片
顺序表特点:

  • 以物理位置相邻表示逻辑关系
  • 任一元素均可随机存取。(优点)

顺序表(元素)特点:地址连续、依次存放、随机存取、类型相同;

与数组(元素)特点相同,故可用一维数组表示顺序表,但线性表长度可变,数组长度不可动态定义

顺序表定义模板

注意:C语言数组的下标是从0开始的,而顺序表的位置序号是从1开始的。

// 一维数组的定义方式
// 类型说明符  数组名[常量表达式]
int a[10];
// 解决办法:用一变量表示顺序表的长度属性

/*模板如下*/
#define LIST_INIT_SIZE 100                  // 线性表存储空间的初始分配量
// typedef char ElemType;          可以将ElemType定义为char   以便后续使用
// typedef int ElemType;           可以将ElemType定义为int   以便后续使用
typedef struct{
	ElemType elem[LIST_INIT_SIZE];         // ElemType数据类型是为了描述统一而自定的,在实际应用中,用户可自定义数据类型;如int float struct(结构体型)等
	// 上一句也可改为定义存储空间的基地址
	// ElemType *elem;
	int length;         //当前长度
}

举例代码

数据结构 第二章:线性表_第12张图片
数据结构 第二章:线性表_第13张图片
在上述定义后,可通过变量定义语句 SqList L;

将L定义为SqList类型的变量,便可以利用L.elem[i-1]访问表中位置序号为i的图书记录了

2.4.2类C语言有关操作补充

/*数组静态分配*/
typedef struct{
	ElemType data[MaxSize];        // 创立一个数组date
	int length;
}SqList;   //顺序表类型
/*数组动态分配*/
typedef struct{
	ElemType *data;            //创立一个数组date
	int length;
}SqList;     //顺序表类型
SqList L;           //定义一个SqList类型的变量
L.data=(ElemType*)malloc(sizeof(ElemType)*MaxSize);

数据结构 第二章:线性表_第14张图片

1. C语言内存分配函数:

在这里插入图片描述
其中ElemType决定了malloc()函数获得的内存空间以什么类型(几个字节)划分;然后再通过(ElemType)强制类型转换为指向ElemType类型的指针*

eg:ElemType为char型,则将存储空间划分为1个字节一个空间
  ElemType为Int型,则将存储空间划分为4个字节一空间

2. C++的动态存储分配

数据结构 第二章:线性表_第15张图片

3. C++中的参数传递

  • 函数调用时传送给形参表的实参必须与形参有三个一致(类型、个数、顺序)
  • 参数传递有两种方式
    • 传值方式(参数为整形、实型、字符型等):形参变化不影响实参
       把实参的值传送给函数局部工作区相应的副本中,函数使用这个副本执行必要的功能。函数修改的是副本的值,实参的值不定。

      #include 
      **// 因为swap并未进行返回值,所以实参a,b的值没有发生变化**
      void swap(float m,float n){
      	float temp;
      	temp=m;
      	m=n;
      	n=temp;
      }
      void main(){
      	float a,b;
      	cin>>a>>b;
      	swap(a,b);
      	cout<<a<<endl<<b<<endl;
      }
      
    • 传地址:形参变化影响实参

      • 参数为指针变量
        数据结构 第二章:线性表_第16张图片
        数据结构 第二章:线性表_第17张图片

      • 参数为引用类型
        引用:它用来给一个对象提供一个替代的名字,实参和形参用的是同一个空间
        数据结构 第二章:线性表_第18张图片
        j是一个引用类型,代表i的一个替代名i值改变时,j值也跟着改变,所以会输出i=7,j=7数据结构 第二章:线性表_第19张图片
        引用类型作形参的三点说明

        • 传递引用给函数与传递指针的效果是一样的,形参变化实参也发生变化。
        • 引用类型作形参,在内存中并没有产生实参的副本,它直接对实参操作;而一般变量作参数,形参与实参就占用不同的存储单元,所以形参变量的值是实参变量的副本。因此,当参数传递的数据量较大时,用引用比用一般变量传递参数的时间和空间效率都好。
        • 指针参数虽然也能达到与使用引用的效果,但在被调函数中需要重复使用“*指针变量名”的形式进行运算,这很容易产生错误且程序的阅读性较差;另一方面,在主调函数的调用点处,必须用变量的地址作为实参。
      • 参数为数组名
        传递的时数组的首地址
        对形参数组所做的任何改变都将反映到实参数组中
        数据结构 第二章:线性表_第20张图片
        数据结构 第二章:线性表_第21张图片

2.4.3 顺序表中基本操作的实现

/*如果定义L为变量,则引用方式为L.xxx*/
SqList L;         
L.elem[x];
L.length;
/*如果定义L为指针变量,则引用方式为L->xxx*/
SqList *L;
L->elem[x];
L->length;

数据结构 第二章:线性表_第22张图片

预定义常量和类型

写在头部,否则会出现奇怪报错

// 函数结果状态代码
#define TRUE 1
#define FALSE 0
#define OK 1
#define ERROR 0
#define INFEASIBLE -1
#define OVERFLOW -2
// Staus 是函数的类型,其值是函数结果状态代码
typedef int Status;
typedef char ElemType;

算法2.1 线性表的初始化(参数为引用类型)

  1. 为顺序表L动态分配一个预定义大小的数组空间,使elem指向这段空间的基地址
  2. 将表的当前长度设为0;
Status InitList_Sq(SqList &L){      // 构建一个空的顺序表L,Status为返回状态
	L.elem=new ElemType[MAXSIZE];     // 为顺序表分配空间
	if(!L.elem) exit(OVERFLOW);       // 存储分配失败
	L.length=0;                      // 空表长度为0
	return OK;
}
// 销毁线性表L
void DestroyList(SqList &L){
	if(L.elem) delete L.elem;        // 释放存储空间
}
// 清空线性表L
void ClearList(SqList &L){
	L.length=0;                    // 将线性表的长度置为0
}
// 求线性表的长度
int GetLength(SqList L){
	return (L.length);
}
// 判断线性表L是否为空
int IsEmpty(SqList L){
	if(L.length==0) return 1;
	else return 0;
}

算法2.2 顺序表的取值

根据位置i获取相应位置数据元素的内容

  1. 判断指定的位置序号i值是否合理(1≤i≤L.length)。若不合理,则返回ERROR;
  2. 若i值合理,则将第i个数据元素L.elem[i-1]赋给参数e,通过e返回第i个数据元素的传值
int GetElem(SqList L,int i,ElemType &e){
	if(i<0||i>length) return ERROR;     // 判断i值是否合理,若不合理,返回ERROR
	e=L.elem[i-1];                     // 第i-1的单元存储着第i个数据
	return OK;
}

显然,顺序表取值算法的时间复杂度为O(1);

算法2.3 顺序表的查找

查找操作是根据指定的元素值e,查找顺序表中第1个与e相等的元素

  1. 从表的一端起,依次和e相比较,若找到与e相等的元素L.elem[i],则查找成功,返回该元素的序号i+1;
  2. 若查遍整个顺序表都没有找到,则查找失败,返回0;
// 在线性表L中查找值位e的数据元素,返回其序号(是第几个元素)
int LocateElem(SqList L,ElemType e){
	for(i=0;i<L.length;i++){
		if(L.elem[i]==e) return i+1;    //查找成功,返回序号
	return 0;   // 查找失败,返回0
	}
}

【算法分析】

因为查找算法的基本操作位:将记录的关键字同给定值进行比较

基本操作:L.elem[i]==e

则平均时间复杂度位:(1+2+3+4+5+6+7)/7=4
数据结构 第二章:线性表_第23张图片

平均查找长度ASL(Average Search Length)

为确定记录在表中的位置,需要与给定值进行比较的关键字的个数的期望值叫做查找算法的平均查找长度
 对含有n个记录的表,查找成功时:
  A S L = ∑ i = 1 n P I C I ASL=∑^{n}_{i=1}P_{I}C_{I} ASL=i=1nPICI
  其中 P i 为第 i 个记录被查找的概率 ; C i 为找到第 i 个记录需比较的次数 其中P_{i}为第i个记录被查找的概率;C_{i}为找到第i个记录需比较的次数 其中Pi为第i个记录被查找的概率;Ci为找到第i个记录需比较的次数

举例: ( 1 + 2 + 3 + … + 7 ) / 7 = 1 / 7 ∗ 1 + 1 / 7 ∗ 2 … + 1 / 7 ∗ 7 (1+2+3+…+7)/7=1/7*1+1/7*2…+1/7*7 (1+2+3++7)/7=1/71+1/72+1/77
故此例中的 故此例中的 故此例中的P_{i} 为 1 / 7 为1/7 1/7;  C 1 = 1   C 2 = 2 C_{1}=1 C_{2}=2 C1=1 2=2

顺序查找的平均查找长度:
  A S L = P 1 + 2 P 2 + … + ( n − 1 ) P n − 1 + n P n ASL=P_{1}+2P_{2}+…+(n-1)P_{n-1}+nP_{n} ASL=P1+2P2++(n1)Pn1+nPn
 假设每个记录的查找概率相等: P i = 1 / n P_{i}=1/n Pi=1/n
 则: A S L s s = ∑ i = 1 n P i C i = 1 / n ∑ i = 1 n i = 1 / n ( 1 + 2 + … + n ) = ( n + 1 ) / 2 ASL_{ss}=∑^{n}_{i=1}P_{i}C_{i}=1/n∑^{n}_{i=1}i=1/n(1+2+…+n)=(n+1)/2 ASLss=i=1nPiCi=1/ni=1ni=1/n(1+2++n)=(n+1)/2
 数据结构 第二章:线性表_第24张图片

算法2.4 顺序表的插入

线性表的插入操作是指在表中的第i个位置插入一个新的数据元素e,使长度为n的线性表变成长度为n+1的线性表

算法步骤

  1. 判断插入位置i是否合法(i值的合法范围是1≤i≤n+1),若不合法则返回ERROR
  2. 判断顺序表的存储空间是否已满,若满(即溢出)则返回ERROR
  3. 将第n个(最后一个元素)至第i个位置的元素依次向后移动一个位置,空出第i个位置(i=n+1时无需移动)
  4. 将要插入的新元素e放入第i个位置
  5. 表长加一
// 在顺序表L中第i个位置插入新的元素e,i的值合法范围是1≤i≤L.length+1
Status ListInsert_Sq(SqList &L,int i,ElemType e){
	if(i<1 || L.length+1){
		return ERROR;       // i值不合法
	}
	if(L.length==MAXSIZE){
		return ERROR;     // 当前存储空间已满
	}
	for(j=L.length-1;j>=i-1;j--){
		L.elem[j+1]=L.elem[j];        // 插入位置及之后的元素后移
	}
	L.elem[i-1]=e;           //将新元素e放入第i个位置
	L.length++;        // 表长增1
	return OK;   
}

【算法分析】

算法时间主要耗费在移动元素的操作上

  • 若插入在尾结点之后,则根本无需移动(特别快)
  • 若插入在首结点之前,则表中元素全部后移(特别慢)
  • 若要考虑在各种位置插入(共n+1种可能)的平均移动次数,该如何计算?
    数据结构 第二章:线性表_第25张图片
  • 顺序表插入算法的平均时间复杂度为O(n)

算法2.5 顺序表的删除

线性表的删除是指将表的第i个元素删去,将长度为n的线性表变成长度为n-1的线性表。

算法步骤

  1. 判断删除位置i是否合法(i值的合法范围是1≤i≤n),若不合法则返回ERROR
  2. 将第i+1个至第n个位置的元素依次向前移动一个位置(i=n时无需移动)
  3. 表长减一
// 在顺序表L中删除第i个元素,i值的合法范围是1≤i≤L.length
Status ListDelete_Sq(SqList &L,int i){
	if(i<1||(i>L.length)){
		return ERROR;           // i值不合法
	}
	for(j=i;j<=L.length-1;j++){
		L.elem[j-1]=L.elem[j];      // 被删除元素后面的元素前移
	}
	L.length--;          // 表长减一
return OK;
}

【算法分析】

算法时间主要耗费在移动元素的操作上

  • 若删除尾结点,则根据无需移动(特别快)

  • 若删除首结点,则表中n-1个元素全部前移(特别慢)

  • 若要考虑在各种位置删除(共n种可能)的平均移动次数,该如何计算?
    在这里插入图片描述

  • 顺序表删除算法的平均时间复杂度为O(n)

2.4.4 顺序表小结

  1. 利用数据元素的存储位置表示线性表中相邻数据元素之间的前后关系,即线性表的逻辑结构与存储结构一致
  2. 在访问线性表时,可以快速地计算出任何一个数据元素的存储地址。因此可以粗鲁地认为,访问每个元素所花时间相等
  • 这种存取元素的方法被称为随机存取法

时间复杂度:
  查找、插入、删除算法的平均时间复杂度为O(n)
空间复杂度:
  显然,顺序表操作算法的空间复杂度S(n)=O(1)
  (没有占用辅助空间)

  • 优点
    • 存储密度大(结点本身所占存储量/结点结构所占存储量)
    • 可以随机存取表中任一元素
  • 缺点
    • 在插入、删除某一元素时,需要移动大量元素
    • 浪费存储空间
    • 属于静态存储形式,数据元素的个数不能自由扩充

2.5 线性表的链式表现和实现

链表是顺序存取

单链表是由表头指针唯一确定的,因此单链表可以用头指针的名字来命名

2.5.1 单链表的定义和表示

  • 链式存储结构
    • 结点在存储器中的位置是任意的,即逻辑上相邻的数据元素在物理上不一定相邻
  • 线性表的链式表示又称为非顺序映像链式映像

定义:
 用一组物理位置任意的存储单元来存放线性表的数据元素。
 这组存储单元即可以是连续的,也可以是不联系的,甚至是零散分布在内存中的任意位置上的
 链表中元素的逻辑次序和物理次序不一定相同
数据结构 第二章:线性表_第26张图片
数据结构 第二章:线性表_第27张图片在这里插入图片描述
各结点由两个域组成:

数据域:存储元素数值数据

指针域:存储直接后继结点的存储位置

1. 与链式存取有关的术语

  1. 结点:数据元素的存储映像。由数据域和指针域两部分组成
  2. 链表:n个结点由指针链组成一个链表。它是线性表的链式存储映像,称为线性表的链式存储结构
  3. 头指针:是指向链表中第一个结点的指针
  4. 首元结点:是指链表中存储第一个数据元素 a 1 a_{1} a1的结点
  5. 头节点:是在链表的首元结点之前附设的一个结点;
    数据结构 第二章:线性表_第28张图片
    数据结构 第二章:线性表_第29张图片
    数据结构 第二章:线性表_第30张图片

2. 单链表、双链表、循环链表

  • 结点只有一个指针域的链表,称为单链表或线性链表
  • 结点由两个指针域的链表,称为双链表
  • 首尾相接的链表称为循环链表
    数据结构 第二章:线性表_第31张图片

3.讨论

  • 讨论1:如何表示空表?
    数据结构 第二章:线性表_第32张图片
  • 讨论2:在链表中设置头节点有什么好处
    1. 便于首元结点的处理
      首元结点的地址保存在头节点的指针欲中,所以在链表的第一个位置上的操作和其他位置一致,无须进行特殊处理
    2. 便于空表和非空表的统一处理
      无论链表是否为空,头指针都是指向头节点的非空指针,因此空表和非空表的处理也就统一了。
  • 讨论3:头节点的数据域内装的是什么?
    头节点的数据域可以为空,也可存放线性表长度等附加信息,但此结点不能计入链表长度值

4. 链表(链式存储结构)的特点

  1. 结点在存储器中的位置是任意的,即逻辑上相邻的数据元素在物理上不一定相邻
  2. 访问时只能通过头指针进入链表,并通过每个结点的指针域依次向后顺序扫描其余结点,所以寻找第一个结点和最后一个结点所花费的时间不等。(这种存取元素的方法被称为顺序存取法

5. 带头节点的单链表

  • 单链表是由表头唯一确定,因此单链表可以用头指针的名字来命名。若头指针名为L,则把链表称为表L
    数据结构 第二章:线性表_第33张图片
  • 单链表的存储结构
    数据结构 第二章:线性表_第34张图片
    typedef **struct Lnode**{    // 声明结点的类型和指向结点的指针类型
    	ElemType data;          // 结点的数据域
    	**struct Lnode** *next;     // 结点的指针欲
    }Lnode,*LinkList;        // LinkList为指向结构体Lnode的指针类型,两个都可以代表这个结构体
    // 二者本质上是等价的
    // 定义一个Lnode的结构体变量,两个方法都可以
    Lnode *L;        // 通常用Lnode *定义指向单链表中任意结点的指针变量
    LinkList L;      // 通常习惯上用LinkList定义单链表,强调定义的是某个单链表的头指针
    
    数据结构 第二章:线性表_第35张图片
    • 举例

      例如,存储学生学号、姓名、成绩的单链表结点类型定义如下:

      typedef Struct student{
      	char num[8];      //数据域
      	char name[8];     // 数据域
      	int score;       // 数据域
      	struct student *next;    //指针域
      }Lnode,*LinkList;
      **//为了统一链表的操作,通常像下面这样定义**
      typedef Struct{
      	char num[8];      //数据域
      	char name[8];     // 数据域
      	int score;       // 数据域
      }ElemType;
      typedef struct Lnode{
      	ElemType data;   //数据域
      	struct Lnode *next;   //指针域
      }Lnode,*LinkList;
      

      数据结构 第二章:线性表_第36张图片

2.5.2 单链表基本操作的实现

算法2.6 单链表的初始化

即构造一个如图的空表
在这里插入图片描述
算法步骤

  1. 生成新结点作头节点,用头指针L指向头节点
  2. 将头节点的指针域置空

算法描述

typedef struct Lnode{
	ElemType data;   //数据域
	struct Lnode *next;   //指针域
}LNode,*LinkList;
**// 初始化**
Status InitList_L(LinkList &L){    //采用引用传递;构造一个空的单链表L
// 生成新结点作为头节点,用头指针L指向头节点
	L=new LNode;   // 或用C的语法:L=(LinkList)malloc(sizeof(LNode));
// 头节点的指针域为空
	L->next=NULL;
	return OK;
}

补充算法

1. 判断链表是否为空

空表:链表中无元素,成为空链表(头指针和头节点仍然在)

算法思路】——判断头节点指针域是否为

typedef struct Lnode{
	ElemType data;   //数据域
	struct Lnode *next;   //指针域
}LNode,*LinkList;
**// 判断链表是否为空**
int ListEmpty(LinkList L){    //若L为空表,则返回1,否则返回0
	if(L->next)   //非空
		return 0;
	else
		return 1;
}

2. 单链表的销毁

链表销毁后不存在

算法思路】——从头指针开始,依次释放所有结点
数据结构 第二章:线性表_第37张图片
C++语法为delete p;
C语言语法为free§,但对应的创建p时也要使用C语言的方法,否则会报错;

typedef struct Lnode{
	ElemType data;   //数据域
	struct Lnode *next;   //指针域
}Lnode,*LinkList;
**// 销毁单链表**
Status DestroyList_L(LinkList &L){   // 销毁单链表L
	Lnode *p;   // 或LinkList p;
	while(L){
		p=L;
		L=L->next;
		delete p;
	}
	return OK;
}

3. 清空链表

链表仍存在,但链表中无元素,成为空链表(头指针和头结点仍然在)

【算法思路】——依次释放所有结点,并将头结点指针域设置为空
数据结构 第二章:线性表_第38张图片

Status ClearList(LinkList &L){     // 将L重置为空表
	Lnode *p,*q;   // 或LinkList p,q;
	p=L->next;
	while(p){     //没到表尾
		q=p->next;
		delete p;
		p=q;
	}
	L->next=NULL;     //头结点指针域为空
	return OK;
}

4. 求单链表的表长

【算法思路】——从首元结点开始,依次计数所有结点
数据结构 第二章:线性表_第39张图片

int ListLength_L(LinkList L){  //返回L中数据元素个数
	LinkList p;
	p=L->next          // p指向第一个结点
	i=0;
	while(p){      // 遍历单链表,统计结点数
		i++;
		p=p->next;
	}
}

知识回顾

数据结构 第二章:线性表_第40张图片

算法2.7 单链表的取值

取单链表中的i个元素的内容

从链表的头指针出发,顺着链域next逐个结点往下搜索,直至搜索到第i个结点为止。因此,链表不是随机存取结构


  • 数据结构 第二章:线性表_第41张图片

【算法步骤】

  1. 用指针p指向首元结点,用j做计数器初值赋为1
  2. 从首元结点开始依次顺着链域next向下访问,只要指向当前结点的指针p不为空(NULL),并且没有到达序号为i的结点,则循环执行以下操作:
    • p指向下一结点(p=p→next)
    • 计数器j相应加一
  3. 退出循环,如果指针p为空,或者计数器j大于i,说明指定的序号i值不合法(i大于表长n或i小于等于0),取值失败返回ERROR;否则取值成功,此时j=i时,p所指的结点就是要找的第i个结点,用参数e保存当前结点的数据域,返回OK

【算法描述】

// 在带头节点的单链表L中根据序号i获取元素的值,用e返回L中第i个数据元素的值
Status GetElem_L(LinkList L,int i,ElemType &e){
  // 初始化,p指向首元结点,计数器j初值赋为1
	p=L->next;
	j=1;   
	**// 当p不为空,并且i值合法时,**向后扫描,直到p指向第i个元素或p为空  **** 
	while(p&&j<i){
		p=p->next;    // p指向下一个结点
		++j;          // 计数器j相应加1
	}
	**if(!p || j>i)     // i的值不合法i>n或i≤0**
		return ERROR;
	e=p->data;       // 取第i个结点的数据域
	return OK;
}//GetElem_L

算法2.8 单链表的按值查找

1. 根据指定数据获取该数据所在的位置(地址)

从链表的首元结点出发,依次将结点值和给定值e进行比较,返回查找结果

【算法步骤】

  1. 从第一个结点起,依次和e相比较
  2. 如果找到一个其值与e相等的数据元素,则返回其在链表中的“位置”或地址;
  3. 如果查遍整个链表都没有找到其值和e相等的元素,则返回0或“NULL”.

【算法描述】

// 在带头节点的单链表L中查找值为e的元素;找到,则返回L中值为e的数据元素的地址,查找失败返回NULL
Lnode *LocateEem_L(LinkList L,Elemtype e){
	p=L->next;       // 初始化,p指向首元结点
	while(p && p->data!=e)       // 顺链域向后扫描,直到p为空或p所指结点的数据域等于e
		p=p->next;      // p指向下一个结点
	return p;       // 查找成功返回值为e的结点地址p,查找失败p为NULL
}

2. 根据指定数据获取该数据位置序号

【算法描述】

//在带头节点的单链表L中查找值为e的数据元素的位置序号
int LocateElem_L(LinkList L,Elemtype e){
// 返回L中值为ejiang

时间复杂度

因为线性链表只能顺序存取,即在查找时要从头指针找起,查找的时间复杂度为O(n)

算法2.9 单链表的插入

将值为e的新结点插入到表的第i个结点的位置上,即插入到结点 a i − 1 与 a i 之间 a_{i-1}与a_{i}之间 ai1ai之间

【算法步骤】

  1. 查找结点 a i − 1 a_{i-1} ai1并由指针p指向该结点
  2. 生成一个新结点 ∗ s ^{*}s s
  3. 将新结点 ∗ s ^{*}s s的数据域置为e
  4. 将新结点 ∗ s ^{*}s s的指针域指向结点 a i a_{i} ai
  5. 将结点 ∗ p ^{*}p p的指针域指向新结点 ∗ s ^{*}s s
    数据结构 第二章:线性表_第42张图片
    【算法描述】
// 在带头节点的单链表L中第i个位置插入值为e的新结点
Status ListInsert_L(LinkList &L,int i,ElemType e){
	p=L;
	j=0;
	while(p && j<i-1){       // 查找第i-1个结点,p指向i-1结点
		p=p->next;
		++j;
	}
	if(!p || j>i-1) return ERROR;      // i大于表长n+1或i<1,插入位置非法
	s=new LNode;      // 生成新节点s
	s->data=e;        // 将结点s的数据域置为e
	s->next=p->next;    // 将结点s的指针域指向结点a_{i}
	p->next=s;         // 将结点p的指针域指向结点s
	return OK;
} //ListInsert_L

时间复杂度

  • 因线性表不需要移动元素,只要修改指针;一般情况下时间复杂度为O(1)
  • 但是,如果要在单链表中进行前插或删除操作,由于要从头查找前驱结点,所耗时间复杂度为O(n)

算法2.10 单链表的删除

删除单链表的第i个结点 a i a_{i} ai

  1. 查找结点 a i − 1 a_{i-1} ai1并由指针p指向该结点
  2. 临时保存待删除结点 a i a_{i} ai的地址在q中,以备释放
  3. 将结点 ∗ p ^{*}p p的指针域指向 a i a_{i} ai的后继结点
  4. 释放结点 a i a_{i} ai的空间
    数据结构 第二章:线性表_第43张图片

【算法描述】

// 在带头节点的单链表L中,删除第i个元素
Status ListDelete_L(LinkList &L,int i,ElemType &e){
	p=L;j=0;
	while(p->next && j<i-1){    // 查找第i-1个结点,并令p指向该结点
		p=p->next;
		++j;
	}
	if(!(p->next) || j>i-1) return ERROR;    //当i>n或i<1时,删除位置不合理
	q=p->next;      // 临时保存被删结点的地址以备释放
	p->next=q->next;     // 改变删除结点前驱结点的指针域
	e=q->data;      // 保存删除结点的数据域,如果不需要则可不写
	delete q;       // 释放删除结点的空间
	return OK;
}// ListDelete_L

时间复杂度

  • 因线性表不需要移动元素,只要修改指针;一般情况下时间复杂度为O(1)
  • 但是,如果要在单链表中进行前插或删除操作,由于要从头查找前驱结点,所耗时间复杂度为O(n)

算法2.11 头插法建立单链表

头插法——元素插入在链表头部,也叫前插法

即通过将新结点逐个插入链表的头部(头结点之后)来创建链表,每次申请一个新结点,读入相应的数据元素值,然后新结点插入到头结点之后。

【算法步骤】

  1. 创建一个只有头结点的空链表

    // 用C++来写:
    L=new LNode;  
    // 或用C语言来写:
     L=(LinkList)malloc(sizeof(LNode));
    // 然后将L的指针域置为NULL
    L->next=null;
    
  2. 从一个空表开始,重复读入数据;

  3. 生成新结点,将读入数据存储到新结点的数据域中

    p=new LNode;
    p->data=a_{n};
    
  4. 从最后一个结点开始,依次将各结点插入到链表的前端

    p->next=L->next;
    L->next=p;
    

数据结构 第二章:线性表_第44张图片
数据结构 第二章:线性表_第45张图片

【算法描述】

// 逆位序输入n个元素的值,建立带头结点的单链表L
void CreateList_H(LinkList &L,int n){
	L=new LNode;
	L->next=NULL;      // 先建立一个带头结点的单链表
	for(i=n;i>0;--i){
		p=new LNode;    // 生成新结点p;p=(LNode*)malloc(sizeof(LNode));
		cin>>p->data;    // 输入元素值赋给新结点*p的数据域;scanf(&p->data);
		p->next=L->next;   // 将新结点p插入到头结点
		L->next=p;
	}
}// CreateList_H

显然,该算法的时间复杂度为O(n)。

算法2.12 尾插法建立单链表

尾插法——元素插入在链表尾部,也叫后插法

尾插法是通过将新结点逐个插入到链表的尾部来创建链表。需要增加一个尾指针r指向链表的尾结点。

【算法步骤】

  1. 创建一个只有头结点的空链表
  2. 尾指针r初始化,指向头结点
  3. 根据创建链表包括的元素个数n,循环n次执行以下操作:
    1. 生成一个新结点*p;
    2. 输入元素值赋给新结点*p的数据域
    3. 将新结点p插入到尾结点r之后
    4. 尾指针r指向新的尾结点*p
      数据结构 第二章:线性表_第46张图片

【算法描述】

// 正位序输入n个元素的值,建立带头结点的单链表L
void CreateList_R(LinkList &L,int n){
	L=new Lnode;
	L->next=NULL;      // 先建立一个带头结点的空链表
	r=L;              // 尾指针r指向头结点
	for(i=0;i<n;++i){
		p=new LNode;
		cin>>p->data;      // 生成新节点,输入元素值
		r->next=p;        // 将新结点*p插入到尾结点*r之后
		r=p;      // r指向新的尾结点
	}
}//CreateList_R

显然,该算法的时间复杂度亦为O(n)。

2.5.3 循环链表

循环链表:是一种头尾相接的链表(即:表中最后一个结点的指针域指向头结点,整个链表形成一个环)。
数据结构 第二章:线性表_第47张图片
优点:从表中任一结点出发均可以找到表中其他结点

注意:
 由于循环链表中没有NULL指针,故涉及遍历操作时,其终止条件就不再像非循环链表那样判断p或p→next是否为空,而是判断它们是否等于头指针。
数据结构 第二章:线性表_第48张图片

循环链表中使用尾指针更多一些:
数据结构 第二章:线性表_第49张图片

循环链表合并

带尾指针循环链表的合并
数据结构 第二章:线性表_第50张图片
数据结构 第二章:线性表_第51张图片
【算法描述】

LinkList Connect(LinkList Ta,LinkList Tb){
	//假设Ta、Tb都是非空的单循环链表
	p=Ta->next;      //p存表头结点
	Ta->next=Tb->next->next;       // Tb表头连结Ta表尾
	delete Tb->next;            // 释放Tb表头结点
	Tb->next=p;             // 修改指针
	return Tb;
}

时间复杂度为O(1)

2.5.4 双向链表

双向链表的结点中有两个指针域,一个指向直接后继,另一个指向直接前驱

为什么要讨论双向链表:

单链表中,查找某结点的直接后继结点的执行时间为O(1),而查找直接前驱结点的执行时间为O(n)

双向链表:在单链表的每个结点里再增加一个指向其直接前趋的指针域prior,这样链表中就形成了有两个方向不同的链。
//-----------双向链表的存储结构-------------
typedef struct DuLNode{    //DuL为double的意思
	Elemtype data;        // 数据域
	struct DuLNode *prior;    // 指向直接前驱
	struct DuLNode *next;     // 指向直接后继
}

数据结构 第二章:线性表_第52张图片

双向循环链表

和单链的循环表类似,双向链表也可以有循环表

  • 让头结点的前驱指针(prior)指向链表的最后一个结点
  • 让最后一个结点的后继指针(next)指向头结点
    数据结构 第二章:线性表_第53张图片双向链结构的对称性(设指针p指向某一结点):

p − > p r i o r − > n e x t = p = p − > n e x t − > p r i o r p->prior->next=p=p->next->prior p>prior>next=p=p>next>prior

在双向链表中有些操作(如:ListLength、GetElem等),因仅涉及一个方向的指针,故它们的算法与线性链表的相同。但在插入、删除时,则需同时修改两个方向上的指针,两者的操作时间复杂度均为O(n)

算法2.13 双向链表的插入

数据结构 第二章:线性表_第54张图片
【算法描述】

void ListInsert_DuL(DuLinkList &L,int i,ElemType e){
	//带头结点的双向链表L中第i个位置之前插入元素e
	if(!(p=GetElemP_DuL(L,i)))   // 在L中确定第i个元素的位置指针p
		return ERROR;        // 在L中确定第i个元素的位置指针p// 在L中确定第i个元素的位置指针p
	s=new DuLNode;    //生成新结点*s
	s->data=e;       // 将结点*s的数据域置为e
	s->prior=p->prior;     // 将结点*s插入L中
	p->prior->next=s;     
	s->next=p;
	p->prior=s;
	return OK;
}// ListInsert_Dul

算法2.14 双向链表的删除

数据结构 第二章:线性表_第55张图片

【算法描述】

void ListDelete_Dul(DuLink &L,int i,ElemType &e){
	//删除带头结点的双向链表L中的第i个元素
	if(!(p=GetElemP_DuL(L,i)))              // 在L中确定第i个元素的位置指针p
		return ERRORL          // 在L中确定第i个元素的位置指针p
	e=p->data;
	p->prior->next=p->next;      // 修改被删结点的前驱结点的后继指针
	p->next->prior=p->prior;     // 修改被删结点的后继结点的前驱指针
	free(p);    //用C语言的方法释放被删结点的空间;如果用C++则为delete p;
	return OK;
}// ListDelete_DuL

2.5.5 时间效率比较(单链表、循环链表和双向链表)

数据结构 第二章:线性表_第56张图片

2.6 顺序表和链表的比较

链式存储结构的优点:

  • 结点空间可以动态申请和释放
  • 数据元素的逻辑次序靠结点的指针来指示,插入和删除时不需要移动数据元素。

链式存储结构的缺点:

  • 存储密度小,每个结点的指针域需额外占用存储空间。当每个结点的数据域所占字节不多时,指针域所占存储空间的比重显得很大。
  • 链式存取结构时非随机存取结构。对任一结点的操作都要从头指针依指针链查找到该结点,这增加了算法的复杂度
  • 链式存储是以空间换时间

存储密度

存储密度时指数据本身所占的存储量和整个结点结构中所占的存储量之比,即

存储密度 = 结点数据本身所占用的空间 结点占用的空间总量 存储密度=\tfrac{结点数据本身所占用的空间}{结点占用的空间总量} 存储密度=结点占用的空间总量结点数据本身所占用的空间
在这里插入图片描述

数据结构 第二章:线性表_第57张图片

2.7 线性表的应用

算法2.15 线性表的合并

【问题描述】

假设利用两个线性表La和Lb分别表示两个集合A和B,现要求一个新的集合A=A∪B

L a = ( 7 , 5 , 3 , 11 )    L b = ( 2 , 6 , 3 )  ——>  L a = ( 7 , 5 , 3 , 11 , 2 , 6 ) La=(7,5,3,11)   Lb=(2,6,3) ——> La=(7,5,3,11,2,6) La=(7,5,3,11)  Lb=(2,6,3) ——> La=(7,5,3,11,2,6)

【问题分析】

将存在Lb中而不存在La中的数据元素插入到La中去,只要从Lb中依次取得每个数据元素,并依值在La中进行查访,若不存在,则插入之。

【算法步骤】

  1. 分别获取La表长m和Lb表长n
  2. 依次取出Lb中的每0元素,执行以下操作
    1. 从Lb中查找第(1≤i≤n)个数据元素赋给e
    2. 在La中查找元素e,如果不存在,则将e插在表La的最后

【算法描述】

// 将所有在线性表Lb中但不在La中的数据元素插入到La中
void union(List &La,List Lb){
	La_len=ListLength(La);     
	Lb_len=ListLength(Lb);    // 求线性表的长度
	for(i=1;i<=Lb_len;i++){
		GetElem(Lb,i,e);          // 取Lb中第i个数据元素赋给e
		if(!LocateElem(La,e))         // 如果La中不存在和e相同的数据元素
			ListInsert(&La,++La_len,e);     // 则将e插入在La的最后
	}
}

无论是顺序还随链式,时间复杂度均为O(mn);即La的长度Lb的长度

有序表的合并

有序表:若线性表中的数据元素相互之间可以比较,并且数据元素在线性表中依值非递减或非递增**有序排列,**则称该线性表为有序表。

有序集合:是指集合中的元素有序排列。

【问题描述】

已知线性表La和Lb中的数据元素按值非递减有序排列,现要求将La和Lb归并为一个新的线性表Lc,且Lc中的数据元素仍按值非递减有序排列。

若La的表长为m,Lb的表长为n,则Lc的表长应为m+n; c = { a 当  a ≤ b 时 b 当  a > b 时 c = \begin{dcases} a &\text{当 } a\le b时 \\ b &\text{当 } a\gt b时 \end{dcases} c={ab ab a>b

L a = ( 1 , 7 , 8 )    L b = ( 2 , 4 , 6 , 8 , 10 , 11 )  ——>  L c = ( 1 , 2 , 4 , 6 , 7 , 8 , 8 , 10 , 11 ) La=(1,7,8)   Lb=(2,4,6,8,10,11) ——> Lc=(1,2,4,6,7,8,8,10,11) La=(1,7,8)  Lb=(2,4,6,8,10,11) ——> Lc=(1,2,4,6,7,8,8,10,11)

算法2.16 顺序有序表的合并

【算法步骤】

  1. 创建一个空表Lc,表长为m+n
  2. 指针pc初始化,执行Lc的第一个元素
  3. 指针pa和pb初始化,分别指向La和Lb的第一个元素
  4. 依次从La或Lb中“摘取”元素值较小的结点插入到Lc表的最后,直至其中一个表变空为止
  5. 继续将La或Lb其中一个表的剩余结点插入在Lc表的最后

【算法描述】

// 已知顺序有序表LA和LB的元素按值非递减排列
void MergeList_Sq(SqList LA,SqList LB,SqList &LC){
	// 归并LA和LB得到的新的顺序有序表LC,LC的元素也按值非递减排列
	pa=LA.elem;      // 指针pa的初值指向LA表的第一个元素
	pb=LB.elem;      // 指针pb的初值指向LB表的第一个元素
	LC.length=LA.length+LB.length;    // 新表的长度为待合并两表的长度之和
	LC.elem=new ElemType[LC.length];    // 为合并后的新表分配一个数组空间
	pc=LC.elem;      // 指针pc指向新表的第一个元素
	pa_last=LA.elem+LA.length-1;       // 指针pa_last指向LA表的最后一个元素
	pb_last=LB.elem+LB.length-1;       // 指针pb_last指向LB表的最后一个元素
	while(pa<=pa_last && pb<=pb_last){   // 两个表都非空
		if(*pa<=*pb)         // 依次“摘取”两表中值较小的结点插入到LC表的最后
			*pc++=*pa++;
		else
			*pc++=*pb++;
	}
	while(pa<=pa_last) 
		*pc++=*pa++;     // LB表已到达结尾,将LA中剩余元素加入LC
	while(pb<=pb_last)
		*pc++=*pb++;       // LA表已到达结尾,将LB中剩余元素加入LC
}// MergeList_Sq

时间复杂度为O(m+n);即La的长度+Lb的长度

空间复杂度为O(m+n);即La的长度+Lb的长度

算法2.17 链式有序表的合并

【算法步骤】

  1. 指针pa和pb初始化,分别指向LA和LB的第一个结点
  2. LC的结点取值为LA(或LB)的头结点
  3. 指针pc初始化,指向LC的头结点
  4. 当指针pa和pb均未到达相应表尾时,则依次比较pa和pb所指向的元素值,从LA或LB中“摘取”元素值较小的结点插入到LC的最后
  5. 将非空表的剩余段插入到pc所指结点之后。
  6. 释放LB(或LA)的头结点

【算法描述】

// 已知单链表LA和LB的元素按值非递减排列
void MergeList_L(LinkList &La,LinkList &Lb,LinkList &Lc){
// 归并LA和LB得到的新的单链表LC,LC的元素也按值非递减排列
	pa=La->next;
	pb=Lb->next;     // pa和pb的初值分别指向两个表的第一个结点
	pc=Lc=La;       // 用La的头结点作为Lc的头结点
	while(pa && pb){
	// LA和LB均为到达表尾,依次“摘取”两表中值较小的结点插入到Lc的最后
		if(pa->data <= pb->data){      // “摘取”pa所指结点
			pc->next=pa;            // 将pa所指结点链接到pc所指结点之后
			pc=pa;                 // pc指向pa
			pa=pa->next;           // pa指向下一结点
		} 
		else{                 // "摘取"pb所指结点
			pc->next=pb;        // 将pb所指结点链接到pc所指结点之后
			pc=pb;              // pc指向pb
			pb=pb->next;        // pb指向下一结点
		}
	}
	pc->next=pa?pa:pb;     // 将非空表的剩余段差人到pc所指结点之后
	delete Lb;            // 释放Lb的头结点
}

时间复杂度为O(m+n);即La的长度+Lb的长度

2.8 案例分析与实现

案例2.1 一元多项式的运算

数据结构 第二章:线性表_第58张图片
数据结构 第二章:线性表_第59张图片

案例2.2 稀疏多项式的运算

对于两个多项的相加运算,类似于两个线性表的归并运算
数据结构 第二章:线性表_第60张图片
数据结构 第二章:线性表_第61张图片

但是数组c不确定定义多大合适,故更适合用链表

【案例实现】

用链表表示多项式时,每个链表结点存储多项式中的一个非零项,包括系数(coef)和指数(expn)两个数据域以及一个指针域(next)。

// 创建多项式的结构体
typedef struct PNode{
	float coef;     // 系数
	int expn;       // 指数
	struct PNode *next; // 指针域
}PNode,*Polynomial;

算法2.18 多项式链表的创建

多项式链表是一个有序表,每项的位置都要经过比较才能确定。
首先初始化一个空链表用来表示多项式,然后逐个输入各项,通过比较,找到第一个大于该输入项指数的项,并将输入项插入到此项的前面。

【算法步骤】

  1. 创建一个只有头结点的空链表
  2. 根据多项式的项的个数n,循环n次执行以下操作:
    • 生成一个新节点*s
    • 输入多项式当前项的系数和指数赋给新节点*s的数据域
    • 设置一前驱指针pre,用于指向待找到的第一个大于输入项指数的结点的前驱。pre初值指向头结点
    • 指针p初始化,指向首元结点;
    • 循链向下逐个比较链表中当前结点与输入项指数,找到第一个大于输入项指数的结点*p
    • 将输入项结点s插入到结点p之前。

【算法描述】

// 输入n项的系数和指数,建立表示多项式的有序链表P
void CreatePolyn(Polynomial &P,int n){
	P=new PNode;  
	P->next=NULL;         //先建立一个带头结点的单链表
	for(i=1;i<=n;++i){         // 依次输入n个非零项
		s=new PNode;           // 生成新结点
		cin>>s->coef>>s->expn;     //输入系数和指数
		pre=P;                  // pre用于保存q的前驱,初值为头结点
		// 假设输入项的指数为4,则要找到第一个指数比4大的项,然后将输入项(即指数为4的项)插入到它前面
		while(q && q->expn< s->expn){     // 找到,第一个,大于,输入项指数,的项*q  
 			pre=q;
			q=q->next;
		}
		s->next=q;          // 将输入项s插入到q和其前驱结点pre之间
		pre->next=s;
	} 
}

【算法分析】
每次循环都需要从前向后比较输入项与各项的指数。最坏情况下,第n次循环需要作n次比较,因此,时间复杂度为O( n 2 n^{2} n2

算法2.19 多项式的相加

逐一比较两个结点中的指数项,对于指数相同的项,对应系数相加,若其和不为0,则将插入到“和多项式”链表中去;对于指数不相同的项,则通过比较将指数在较小的项插入到“和多项式”链表中去
数据结构 第二章:线性表_第62张图片

【算法步骤】

  1. 指针p1和p2初始化,分别指向Pa和Pb的首元结点
  2. p3指向和多项式的当前结点,初值为Pa的头结点
  3. 当指针p1和p2均未到达相应表尾时,则循环比较p1和p2所指结点对应的指数值(p1→expn与p2→expn)时,有下列3种情况:
    • 当p1→expn=p2→expn时,则将两个结点中的系数相加
      • 和不为零,则修改p1所指结点的系数值,同时删除p2所指结点
      • 和为零,则删除p1和p2所指结点
    • 当p1→expn
    • 当p1→expn>p2→expn时,则应摘取p2所指结点插入到“和多项式”链表中去。
  4. 将非空多项式的剩余段插入到p3所指结点之后
  5. 释放Pb的头结点

【算法描述】

// 多项式加法:Pa=Pa+Pb,利用两个多项式的结点构成“和多项式”
void AddPolyn(Polynomial &Pa,Polynomial &Pb){
	p1=Pa->next;
	p2=Pb->next;            // p1和p2初值分别指向Pa和Pb的首元结点
	p3=Pa;                  // p3指向和多项式的当前结点,初值为Pa
	while(p1&&p2){        //p1和p2均非空
		if(p1->expn==p2->expn){     // 指数相等
			sum=p1->coef+p2->coef;     //sum保存两项的系数和
			if(sum!=0){      // 系数和不为0
				p1->coef=sum;       //修改Pa当前结点的系数值为两项系数的和
				p3->next=p1;
				p3=p1;             // 将修改后的Pa当前结点链在p3之后,p3指向P1
				p1=p1->next;       // p1指向后一项
				r=p2;              //删除Pb当前结点,p2指向下一项
				p2=p2->next;
				delete r;     
			}
			else{              //系数和为0
				r=p1;             //删除Pa当前结点,p1指向下一项
				p1=p1->next;
				delete r;
				r=p2;             //删除Pb当前结点,p1指向下一项
				p2=p2->next;
				delete r;
			}
		}
		else if(p1->expn < p2->expn){      //Pa当前结点的指数值小
			p3->next=p1;          // 将p1链在p3之后
			p3=p1;              // p3指向p1
			p1=p1->next;       //p1指向后一项
		}
		else{                    //Pb当前结点的指数值小
			p3->next=p2;          // 将p2链在p3之后
			p3=p2;              // p3指向p2
			p2=p2->next;       //p2指向后一项
		]
	}
	p3->next=p1?p1:p2;        // 插入非空多项式的剩余段
	delete Pb;               // 释放Pb的头结点
}

时间复杂度位O(m+n),空间复杂度O(1)

案例2.3 图书信息管理系统

【案例分析】

把图书表抽象成一个线性表,每本图书(包括ISBN、书名、定价)作为线性表中的一个元素。把图书信息管理系统中要求实现查找、插入、删除、修改、排序和计数总计6个功能。
数据结构 第二章:线性表_第63张图片

struct Book{
	char id[20];      //ISBN
	char name[50];     // 书名
	int price;         // 定价
};
typedef struct{   //顺序表
	Book *elem;
	int length;
}SqList;
typedef struct LNode{   // 链表
	Book data;
	struct LNode *next;
}LNode,*LinkList;

你可能感兴趣的:(数据结构(C语言版),数据结构)