线性表是具有相同特性的数据元素的一个有限序列,是由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,……ai−1,ai,ai+1,……,an)
下标:是元素的序号,表示元素在表中的位置
n:为元素总个数,即表长;n=0时,称为空表
线性表是一种典型的线性结构。
实现两个多项式加、减、乘运算
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,p2…pn) (每一项的指数 i i i 隐含在其系数 p i p_{i} pi 的序号中)
设: P = ( p 0 , p 1 , p 2 … p n ) P=(p_{0},p_{1},p_{2}…p_{n}) P=(p0,p1,p2…pn), Q = ( q 0 , q 1 , q 2 … q m ) Q=(q_{0},q_{1},q_{2}…q_{m}) Q=(q0,q1,q2…qm)
则: 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)
例如: S ( x ) = 1 + 3 x 10000 + 2 x 20000 S(x)=1+3x^{10000}+2x^{20000} S(x)=1+3x10000+2x20000的多项式,如果按照案例2.2.1的存储方式,将会造成很大的空间浪费。由于线性表的元素可以包含多个数据项,由此可改变元素的设定,对多项式的每一项,可采用(系数,指数)唯一确定。即
总结: 线性表中数据元素的类型可以为简单类型,也可以为复杂类型
许多实际应用问题所涉的基本操作有很大相似性,不应为每个具体应用单独编写一个程序。
从具体应用中抽象出共性的逻辑结构和基本操作(抽象数据类型),然后实现其存储结构和基本操作。
抽象数据类型线性表的定义如下:基本操作
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
线性表的顺序表示指的是用一组地址连续的存储单元一次存储线性表的数据元素,这种表示也称作线性表的顺序存储结构或顺序映像。
把逻辑上相邻的数据元素存储在物理上相邻的存储单元中的存储结构
要求依次存储,地址连续——中间没有空出的存储单元
顺序表特点:
顺序表(元素)特点:地址连续、依次存放、随机存取、类型相同;
与数组(元素)特点相同,故可用一维数组表示顺序表,但线性表长度可变,数组长度不可动态定义
注意: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; //当前长度
}
将L定义为SqList类型的变量,便可以利用L.elem[i-1]访问表中位置序号为i的图书记录了
/*数组静态分配*/
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);
其中ElemType决定了malloc()函数获得的内存空间以什么类型(几个字节)划分;然后再通过(ElemType)强制类型转换为指向ElemType类型的指针*
eg:ElemType为char型,则将存储空间划分为1个字节一个空间
ElemType为Int型,则将存储空间划分为4个字节一空间
传值方式(参数为整形、实型、字符型等):形参变化不影响实参
把实参的值传送给函数局部工作区相应的副本中,函数使用这个副本执行必要的功能。函数修改的是副本的值,实参的值不定。
#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;
}
传地址:形参变化影响实参
参数为引用类型
引用:它用来给一个对象提供一个替代的名字,实参和形参用的是同一个空间
j是一个引用类型,代表i的一个替代名i值改变时,j值也跟着改变,所以会输出i=7,j=7
引用类型作形参的三点说明
/*如果定义L为变量,则引用方式为L.xxx*/
SqList L;
L.elem[x];
L.length;
/*如果定义L为指针变量,则引用方式为L->xxx*/
SqList *L;
L->elem[x];
L->length;
写在头部,否则会出现奇怪报错
// 函数结果状态代码
#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;
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;
}
根据位置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);
查找操作是根据指定的元素值e,查找顺序表中第1个与e相等的元素
// 在线性表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
为确定记录在表中的位置,需要与给定值进行比较的关键字的个数的期望值叫做查找算法的平均查找长度
对含有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/7∗1+1/7∗2…+1/7∗7
故此例中的 故此例中的 故此例中的P_{i} 为 1 / 7 为1/7 为1/7; C 1 = 1 C 2 = 2 C_{1}=1 C_{2}=2 C1=1 C2=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+…+(n−1)Pn−1+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/n∑i=1ni=1/n(1+2+…+n)=(n+1)/2
线性表的插入操作是指在表中的第i个位置插入一个新的数据元素e,使长度为n的线性表变成长度为n+1的线性表
【算法步骤】
// 在顺序表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;
}
【算法分析】
算法时间主要耗费在移动元素的操作上
线性表的删除是指将表的第i个元素删去,将长度为n的线性表变成长度为n-1的线性表。
【算法步骤】
// 在顺序表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;
}
【算法分析】
算法时间主要耗费在移动元素的操作上
时间复杂度:
查找、插入、删除算法的平均时间复杂度为O(n)
空间复杂度:
显然,顺序表操作算法的空间复杂度S(n)=O(1)
(没有占用辅助空间)
链表是顺序存取的
单链表是由表头指针唯一确定的,因此单链表可以用头指针的名字来命名
定义:
用一组物理位置任意的存储单元来存放线性表的数据元素。
这组存储单元即可以是连续的,也可以是不联系的,甚至是零散分布在内存中的任意位置上的
链表中元素的逻辑次序和物理次序不一定相同
各结点由两个域组成:
数据域:存储元素数值数据
指针域:存储直接后继结点的存储位置
typedef **struct Lnode**{ // 声明结点的类型和指向结点的指针类型
ElemType data; // 结点的数据域
**struct Lnode** *next; // 结点的指针欲
}Lnode,*LinkList; // LinkList为指向结构体Lnode的指针类型,两个都可以代表这个结构体
// 二者本质上是等价的
// 定义一个Lnode的结构体变量,两个方法都可以
Lnode *L; // 通常用Lnode *定义指向单链表中任意结点的指针变量
LinkList L; // 通常习惯上用LinkList定义单链表,强调定义的是某个单链表的头指针
举例
例如,存储学生学号、姓名、成绩的单链表结点类型定义如下:
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;
即构造一个如图的空表
【算法步骤】
【算法描述】
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;
}
空表:链表中无元素,成为空链表(头指针和头节点仍然在)
【算法思路】——判断头节点指针域是否为
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;
}
链表销毁后不存在
【算法思路】——从头指针开始,依次释放所有结点
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;
}
链表仍存在,但链表中无元素,成为空链表(头指针和头结点仍然在)
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;
}
int ListLength_L(LinkList L){ //返回L中数据元素个数
LinkList p;
p=L->next // p指向第一个结点
i=0;
while(p){ // 遍历单链表,统计结点数
i++;
p=p->next;
}
}
取单链表中的i个元素的内容
从链表的头指针出发,顺着链域next逐个结点往下搜索,直至搜索到第i个结点为止。因此,链表不是随机存取结构
【算法步骤】
【算法描述】
// 在带头节点的单链表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
从链表的首元结点出发,依次将结点值和给定值e进行比较,返回查找结果
【算法步骤】
【算法描述】
// 在带头节点的单链表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
}
【算法描述】
//在带头节点的单链表L中查找值为e的数据元素的位置序号
int LocateElem_L(LinkList L,Elemtype e){
// 返回L中值为ejiang
时间复杂度
因为线性链表只能顺序存取,即在查找时要从头指针找起,查找的时间复杂度为O(n)
将值为e的新结点插入到表的第i个结点的位置上,即插入到结点 a i − 1 与 a i 之间 a_{i-1}与a_{i}之间 ai−1与ai之间。
【算法步骤】
// 在带头节点的单链表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
时间复杂度
删除单链表的第i个结点 a i a_{i} ai
【算法描述】
// 在带头节点的单链表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
时间复杂度
头插法——元素插入在链表头部,也叫前插法
即通过将新结点逐个插入链表的头部(头结点之后)来创建链表,每次申请一个新结点,读入相应的数据元素值,然后新结点插入到头结点之后。
【算法步骤】
创建一个只有头结点的空链表
// 用C++来写:
L=new LNode;
// 或用C语言来写:
L=(LinkList)malloc(sizeof(LNode));
// 然后将L的指针域置为NULL
L->next=null;
从一个空表开始,重复读入数据;
生成新结点,将读入数据存储到新结点的数据域中
p=new LNode;
p->data=a_{n};
从最后一个结点开始,依次将各结点插入到链表的前端
p->next=L->next;
L->next=p;
【算法描述】
// 逆位序输入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)。
尾插法——元素插入在链表尾部,也叫后插法
尾插法是通过将新结点逐个插入到链表的尾部来创建链表。需要增加一个尾指针r指向链表的尾结点。
【算法步骤】
【算法描述】
// 正位序输入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)。
循环链表:是一种头尾相接的链表(即:表中最后一个结点的指针域指向头结点,整个链表形成一个环)。
优点:从表中任一结点出发均可以找到表中其他结点
注意:
由于循环链表中没有NULL指针,故涉及遍历操作时,其终止条件就不再像非循环链表那样判断p或p→next是否为空,而是判断它们是否等于头指针。
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)
双向链表的结点中有两个指针域,一个指向直接后继,另一个指向直接前驱
为什么要讨论双向链表:
单链表中,查找某结点的直接后继结点的执行时间为O(1),而查找直接前驱结点的执行时间为O(n)
双向链表:在单链表的每个结点里再增加一个指向其直接前趋的指针域prior,这样链表中就形成了有两个方向不同的链。
//-----------双向链表的存储结构-------------
typedef struct DuLNode{ //DuL为double的意思
Elemtype data; // 数据域
struct DuLNode *prior; // 指向直接前驱
struct DuLNode *next; // 指向直接后继
}
和单链的循环表类似,双向链表也可以有循环表
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)
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
【算法描述】
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
存储密度时指数据本身所占的存储量和整个结点结构中所占的存储量之比,即
存储密度 = 结点数据本身所占用的空间 结点占用的空间总量 存储密度=\tfrac{结点数据本身所占用的空间}{结点占用的空间总量} 存储密度=结点占用的空间总量结点数据本身所占用的空间
【问题描述】
假设利用两个线性表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中进行查访,若不存在,则插入之。
【算法步骤】
【算法描述】
// 将所有在线性表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当 a≤b时当 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)
【算法步骤】
【算法描述】
// 已知顺序有序表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的长度
【算法步骤】
【算法描述】
// 已知单链表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的长度
但是数组c不确定定义多大合适,故更适合用链表
【案例实现】
用链表表示多项式时,每个链表结点存储多项式中的一个非零项,包括系数(coef)和指数(expn)两个数据域以及一个指针域(next)。
// 创建多项式的结构体
typedef struct PNode{
float coef; // 系数
int expn; // 指数
struct PNode *next; // 指针域
}PNode,*Polynomial;
多项式链表是一个有序表,每项的位置都要经过比较才能确定。
首先初始化一个空链表用来表示多项式,然后逐个输入各项,通过比较,找到第一个大于该输入项指数的项,并将输入项插入到此项的前面。
【算法步骤】
【算法描述】
// 输入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)
逐一比较两个结点中的指数项,对于指数相同的项,对应系数相加,若其和不为0,则将插入到“和多项式”链表中去;对于指数不相同的项,则通过比较将指数在较小的项插入到“和多项式”链表中去
【算法步骤】
【算法描述】
// 多项式加法: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)
【案例分析】
把图书表抽象成一个线性表,每本图书(包括ISBN、书名、定价)作为线性表中的一个元素。把图书信息管理系统中要求实现查找、插入、删除、修改、排序和计数总计6个功能。
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;