2.1线性表及其实现
2.1.1引子:多项式表示
例:一元多项式及其运算
一元多项式: f(x) = a0 + a1x + a2x^2 + ……+ a(n-1) x ^ (n-1) + an x^n
主要运算:多项式相加、相减、相乘等
如何表示多项式?
多项式的关键数据:多项式项数n,各项系数ai及指数i
方法1:顺序存储结构直接表示
数组各分量对应多项式各项
例如:f(x) = 4x^5 - 3x^2 +1 表示成:
a[0] = 1,a[1] = 0,a[2] = -3,a[3] = 0,a[4] = 0 ,a[5] = 4
a[i]每个分量表示系数,i表示x的指数。
即用数组的6个分量来表示多项式的3个项。
两个多项式相加:两个数组对应分量相加
问题:如何表示多项式x+3*x^2000?
则至少需要大小为2001的数组,并且只有两项是非零的,造成空间的巨大浪费。遍历的话也是很麻烦的。
因此:有没有办法只表示非零项呢?
方法2:顺序存储结构表示非零项
每个非零项涉及两个信息:系数ai,指数i,可以把多项式看成是一个(ai,i)二元组的集合
用结构数组表示:数组分量是由系数ai,指数i组成的结构,对应一个非零项。按指数大小有序储存
两个多项式相加过程:
把指数大的排在前面,指数小的排在后面,从头开始,比较两个多项式当前对应项的指数,指数大的那项则直接输出,指数相同则对应系数相加后输出。
相加过程实例:
但是我们不一定非要存储在数组里,因此:
方法3:链表结构存储非零项
链表中每个结点存储多项式中的一个非零项,包括系数和指数两个数据域以及一个指针域。
typedef struct PolyNode* Polynomial;
struct PolyNode{
int coef; //系数
int expon; //指数
Polynomial link;
};
同样需要将指数项从大到小排列,相加过程跟数组一样。
2.1.2线性表及顺序存储
多项式表示问题的启示:
1.同一个问题可以有不同的表示(存储)方法;
2.有一类共性问题:有序线性序列的组织和管理
什么是线性表?
线性表(Linear List):由同类型数据元素构成有序序列的数据结构。表中元素个数称为线性表的长度;线性表没有元素时称为空表;表起始位置称表头,表结束位置称表尾。
线性表的顺序存储实现
利用数组的连续存储空间顺序存放线性表的各元素
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
2.1.3 线性表主要操作的实现
线性表的数组实现:
1.初始化(建立空的顺序表)
List makeEmpty()
{
List Ptrl;
Ptrl = (List) malloc(sizeof(struct LNode));
Ptrl->last = -1;
return Ptrl;
}
2.查找
给定一个线性表List Ptrl和要查找的元素ElementType x:
int find(ElementType x,List Ptrl)
{
int i = 0;
while(i <= Ptrl->last && Ptrl->Data[i] != x)
i++; //循环遍历每个元素,看是否有与x相等的
if(i > Ptrl->last)
return -1; //如果没找到,返回-1
else
return i; //找到后返回的是存储位置
}
查找成功的平均次数为(n+1)/2(运气好,第1次就找到了,运气不好,最后一次n次才找到,平均一下就是(n+1)/2),平均实现性能为O(n)。
3.顺序存储的插入
void insert(ElementType x,int i,List Ptrl)
{
int j;
if(Ptrl->last == MAXSIZE - 1) //表空间已满,不能插入
{
printf("表满\n");
return;
}
if(i < 1 || i > Ptrl->last + 2) //检查插入位置的合法性
{
printf("位置不合法\n");
return;
}
for(j = Ptrl->last;j >= i - 1; j--)
Ptrl->Data[j+1] = Ptrl->Data[j]; //将ai~an倒序向后移动
//从最后一个元素开始,每次都将Data[j]移动到Data[j+1]
Ptrl->Data[j] = x; //将新元素插入
Ptrl->last++; //Last仍指向最后元素,因为多了一个元素,所以+1
return;
}
平均移动次数为n/2,平均时间性能为O(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顺序向前移动,把Data[j-1]每次向前移动一个得到Data[j]
Ptrl->last--; //last仍指向最后元素,因为少了一个元素,所以-1
return;
}
2.1.4 线性表的链式存储实现
不要求逻辑上相邻的两个元素物理上也相邻;通过“链”建立起数据元素之间的逻辑关系
插入、删除不需要移动数据元素,只需要修改“链”
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)。
3.插入
(在第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
4.删除
(删除链表的第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指向第一个结点
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
2.1.6广义表与多重链表
广义表(Generalized List)是线性表的推广,对于线性表而言,n个元素都是基本的单元素,广义表中,这些元素不仅可以是单元素也可以是另一个广义表。
那么怎么区分结点是一个单元素还是一个广义表呢?
可以用一个tag标志域来实现。
typedef struct GNode* GList;
struct GNode{
int Tag; //标志域,0表示结点是单元素,1表示结点是广义表
union{ //子表指针域Sublist与单元素数据域Data复用,即共用存储空间
ElementType Data;
GList SubList;
}URegion;
GList Next; //指向后继结点
};
【emmm 这一部分看的不是很懂】
多重链表:链表中的结点可能同时隶属于多个链
多重链表中结点的指针域会有多个,如前面例子包含了Next和Sublist两个指针域,但包含两个指针域的链表不一定是多重链表,比如双向链表不是多重链表。
多重链表有广泛的用途:基本上如树、图这样相对复杂的数据结构都可以采用多重链表方式实现存储。
例:矩阵可以用二维数组表示,但二维数组表示有两个缺陷:
一是数组的大小需要事先确定
二是对于“稀疏数组”(零项很多,非零项很少),将造成大量的存储空间浪费
分析:采用一种典型的多重链表——十字链表来存储稀疏矩阵
只存储矩阵非零元素
结点的数据域:行坐标Row,列坐标Col,数值Value
每个结点通过两个指针域,把同行、同列串起来
行指针(或称为向右指针)Right
列指针(或称为向下指针)Down
term:属于某一行也属于某一列,形成一个十字形,因此叫做十字链表。(代表稀疏矩阵中的非零项)
head:作为行这个链表的头结点,也作为列这个链表的头结点
最头上的这个term表示:这个矩阵总共有4行5列,非零项有7项。
是整个矩阵的入口结点
用一个标识符Tag来区分头结点和非零元素结点:头结点的标志值为Head,矩阵非零元素结点的标志值为Term。
本博客参考:https://blog.csdn.net/wtrnash/article/details/48339021