第二讲 线性结构
2.1 线性表及其实现
【例】一元多项式及其运算 一元多项式:$f(x)=a_0+a_1x+…+a_{n-1}x^{n-1}+a_ns^x$ 主要运算:多项式相加、相减、相乘等
方法1:顺序存储结构直接表示
数组各分量对应多项式各项: $a[i]:项x^i的系数a_i$
例如:$f(x)=4x^5-3x^2+1$ 表示成: 问题:表示$x+x^{2000}$浪费很多内存
方法2:顺序结构表示非零项
每个非零项$a_ix^i$涉及两个信息:系数$a_i和指数i$,可以将一个多项式看成一个$(a_i,i)$二元组的集合。 用结构数组表示:数组分量是由系数$a^i$、指数$i$组成的结构,对应一个非零项
例如:$P(x)=9x^{12}+15x^8+3x^2$ $P(x)=26x^{19}-4x^8-13x^6+82$
|下标i | 0 | 1 | 2 | …… | |下标i | 0 | 1 | 2 | 3 | …… | |——–|—|—|—|—| |——–|—|—|—|—| | 系数$a^i$ | 9 | 15 | 3 | —— | | 系数$a^i$ | 26 | -4 | -13 | 82 | —— | | 指数$i$ | 12 | 8 | 2 | —— | | 指数$i$ | 19 | 8 | 6 | 0 | —— | 注意:按指数大小有序存储 相加过程:从头开始,比较两个多项式当前对应项的指数。
方法3:链表结构存储非零项
链表中每个结点存储多项式中的一个非零项,包括系数和指数两个数据域以及一个指针域。
| coef | expon | link | |——–|——–| | | |
typedef struct PolyNode *Polynomial;
struct PolyNode{
int coef;
int expon;
Polynomial link;
}
例如: $P(x)=9x^12+15x^8+3x^2$
$P(x)=26x^19-4x^8-13x^6+82$ 链表存储形式为:
什么是线性表
线性表:由同类型数据元素构成有序序列的线性结构
- 表中元素个数称为线性表的长度
- 线性表没有元素时,称为空表
- 表起始位置称表头,表结束位置称表尾
线性表的抽象数据类型描述
类型名称:线性表(List) 数据对象集:线性表是$n(>=0)个元素构成的有序序列(a_1,a_2,…,a_n)$ 操作集:线性表$L\in List,整数i表示位置,元素X\in ElementType$
-
List MakeEmpty():初始化一个空线性表L;
-
ElementType FindKth(int K,List L):根据位序K,返回相应元素;
-
int Find(ElementType X,List L):在线性表中查找X的第一次出现位置;
-
void Insert(ElementType X,int i,List L):在位序i前插入一个新元素X;
-
void Delete(int i,List L):删除指定位序i的元素;
-
int Length(List L):返回线性表L的长度n.
线性表的顺序存储实现
typedef struct LNode *List;
struct LNode{
ElementType Data[MAXSIZE];
int Last;
};
struct LNode L;
List PtrL;
访问下标为i的元素:L.Data[i]或PtrL->Data[i] 线性表的长度:L.Last+1或PtrL->Last+1
主要操作的实现
- 初始化(建立空的顺序表)
List MakeEmpty(){
List PtrL;
PtrL=(List)malloc(sizeof(struct LNode));
PtrL->Last=-1;
return PtrL;
}
malloc函数(memory allocation 动态内存分配) 原型:extern void *malloc(unsigned int num_bytes); 头文件:#include
或 #include (注意:alloc.h 与 malloc.h 的内容是完全一致的。) 功能:分配长度为num_bytes字节的内存块 说明:如果分配成功则返回指向被分配内存的指针,否则返回空指针NULL。 当内存不再使用时,应使用free()函数将内存块释放。
- 查找
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)
- 插入(第$i(1<=i<=n+1)个位置上插入一个值为X的新元素$)
先移动,后插入
void Insert(ElementType X,int i,List PtrL){
int j;
if(PtrL->Last==MAXSIZE-1){ //表空间已满,不能插入
print("表满");
return;
}
if(i<1||i>PtrL->Last+2){ //检查插入位置合法性
print("位置不合法");
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)
- 删除(删除表的第$i(1<=i<=n)$个位置上的元素)
后面的元素依次前移
void Delete(int i,,List PtrL){
int j;
if(i<1||i>PtrL->Last+1){ //检查空表及删除位置的合法性
return;
}
for(j=ij>=PtrL->Last;j++)
PtrL->Data[j-1]=PtrL->Data[j];
PtrL->Last--;
return;
}
平均移动次数:(n-1)/2 平均时间性能:O(n)
线性表的链式存储实现
typedef struct LNode *List;
struct LNode{
ElementType Data;
List Next;
};
struct LNode L;
List PtrL;
主要操作的实现
- 求表长
int Length(List Ptrl){
List p=Ptrl; //p指向表的第一个结点
int j=0;
while(p){
p=p->Next;
j++; //当前p指向的是第i个结点
}
return j;
}
- 查找
- 按序号查找: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; //否则返回空 }
- 按值查找:Find
List FindKth(ElementType X,,List PtrL){ List p=Ptrl; int i=1; while(p!=NULL&&p->Data!=X) P=P->Next; return p; }
- 插入(在第$i-1(1<=i<=n+1)个结点后插入一个值为X的新元素$)
- 先构造一个新节点,用s指向;
- 再找到链表的第i-1个结点,用p指向;
- 然后修改指针,插入结点(p之后插入新节点是s)
void 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;
}
}
平均时间复杂度:1/2n
- 删除(删除表的第$i(1<=i<=n)$个位置上的元素)
- 先找到链表的第i-1个结点,用p指向
- 再用指针s指向要被删除的结点(p的下一节点);
- 然后修改指针,删除s所指结点;
- 最后释放s所指结点空间。
void Delete(int i,,List PtrL){
List p,s;
if(i==1){
s=PtrL;
if(PtrL!=NULL) PtrL=PtrL->Next;
else return NULL;
free(s);
return PtrL;
}
p=FindKth(i-1,PtrL);
if(p==NULL){
printf("第%d个结点不存在",i-1); return NULL;
return NULL;
}else if(p->Next==NULL){
printf("第%d个结点不存在",i); return NULL;
}else{
s=p->Next;
p->Next=s->Next;
free(s);
return PtrL;
}
}
平均时间复杂度:1/2n
广义表
【例】二元多项式:$P(x,y)=9x^{12}y^2+4x^12+15x^8y^3-x^8y+3x^2$ 【分析】将二院多项式看成关于x的一元多项式 $P(x,y)=(9y^2+4)x^{12}+(15y^3-y)x^8+3x^2$ ####广义表
- 广义表是线性表的推广
- 对于线性表而言,n个元素都是基本的单元素
- 广义表中,这些元素不仅可以是单元素也可以是另一个广义表。
typedef struct GNode GList;
struct GNode{
int Tag; //标志域:0表示结点是单元素,1表示结点是广义表
union{ //子表指针域SubList与单元素数据域data复用,即公用存储空间
ElementType Data;
GList SubList;
}URegion;
Glist Next; //指向后继结点
};
多重链表
多重链表:链表中的结点可能同时隶属于多个链
- 多重链表中结点的指针域会有多个
- 但包含两个指针域的链表并不一定是多重链表,比如双向链表
多重链表的用途:基本如树、图这样相对复杂的数据结构都可以采用多重链表方式实现存储。 【例】矩阵可以用二维数组表示,但有缺陷:
- 一是数组的大小需要事先确定
- 对于“稀疏矩阵”,将造成大量存储空间浪费
【分析】采用一种经典的多重链表——十字链表来存储稀疏矩阵
- 只存储军阵非0元素项 结点的数据域:行坐标Row 列坐标Col 数值Value
- 每个结点通过两个指针域,把同行、同列串起来;
- 行指针(或称为向右指针)Right
- 列指针(或称为向下指针)Down
矩阵A的多重链表图
- 用一个标志域Tag来区分结点和非0元素结点
- 头结点的标识值为“Head”,矩阵非0元素结点的标识值为“Term”