目录
今天走入数据结构的线性表一节。希望各位大神多多指点哦。
(一)线性表
线性表第一个元素没有前驱元素,称为头结点。最后一个元素没有后驱元素,称尾结点。中间的每一个元素都有前驱元素和后驱元素。
线性表按照物理存储结构,又可以分为顺序表和链表。线性表没有元素的时候称为空表。表起的位置叫做表头,表结束的位置叫做表尾。
常见的线性表:顺序表,链表,栈,队列,字符串。一个数据元素可以由若干项组成,把数据元素称为记录,含有大量记录线性表叫做文件。
线性表是一种逻辑上连续的物理结构:
数组(物理上和逻辑上都是线性)——浪费内存
链表 (逻辑上线性,物理上非线性)——按需申请内存
1.1抽象数据类型
类型名称: 线性表(List)
数据对象集:线性表是n个元素构成的有序序列(a1,a2,a3…an)
操作集: 线性表L∈list , i表示位置,元素X∈ElementTyp
初始化空表:List make() |
查找值为x的下标 int find(int x,List ptrl) |
在线性表的第i个位置插入一个值为x的元素(i从1开始)void insert(int X,int i,List Ptrl); |
删除指定位序为i的元素 void Delete(int i,Last Ptrl) |
List make()
{
List Ptrl;
Ptrl=(List)malloc(sizeof(struct LNode));
Ptrl->last=-1;
return Ptrl;
}
int find(int x,List Ptrl)
{
int i=0;
while(Ptrl->Date[i]!=X&&i<=Ptrl->last)
{
i++;
}
if(i>Ptrl->last) return -1;//没找到
else return i;
}
插入:
void insert(int X,int i,List Ptrl)
{
int j;
if(Ptrl->Last==MAXSIZE-1)//Maxsize是数组大小
printf("表满");//表已经满了,不能插入
if(i<1||i > Ptrl->Last+2)
{
printf("位置不合法");
return;
}//检查插入的合法性
for(j=Ptrl->Last;j>=i-1;j--)
{
//先移动a[n]
Ptrl->Date[j+1]=Ptrl->Date[j];//每次把j位置的元素移动到i+1
Ptrl->Date[i-1]=x;//新元素插入(数组从1开始下标)
Ptrl->Last++;//last仍然指向最后的元素,多了一个元素
}
return;
}
此时的插入的数组是从1开始计数的,实际上从数组下标i-1的时候开始插入。
也就是要把i-1下标之后的元素全部往后面移动一位,留出(i-1)的位置来插入。
删除:
删除表中第i个位置上的元素 (要删除下标为i-1的元素,要把下标为i的移动到i-1位置上)
void Delete(int i,Last Ptrl)
{
int j;
if( i<1|| i>Ptrl->last+1 )//检查空表以及删除位置的合法性
{
printf("不存在第%d个元素”,i);
return 0;
}
for(j=i;j<=Ptrl->Last;j++)
{
Ptrl->Date[j-1]=Ptrl->Date[j];//i到i-1 i+1到i
}
ptrl->last--;//Last元素仍然指向最后一个元素
return;
}
1.2 线性表的顺序存储
typedef struct LNode* List;//定义结构体指针名字为List
struct LNode{
int a[100];
int last;//last记录线性表最后一个元素存放的位置
};
struct LNode L;
List Ptrl;
访问下标为i的元素 :L.a[i]或者Ptrl->a[i]
线性表长度:L.last+1或者ptrl->last+1
1.3 线性表的链式存储
typedef struct LNode*List;
struct LNode{
int data;//数据域
List next;//地址域
};
struct LNode L;
List Ptrl;
求表长 int Length(List Ptrl) | |
按序号查找元素 List FindKth(int k,List Ptrl) | |
按值查找 List Find(int X,list Ptrl) | |
插入新结点 List insert(int X,int i,List Ptrl) 删除链表的第i个结点 List Delete(int i,List Ptrl) |
求表长(链表遍历)
int Length(List Ptrl)
{
List p=Ptrl;//p指向表的第一个结点
int count=0;
while(p){
p=p->Next;
count++;//当前p指向的是第j个结点
}
return count;
}
按序号查找
List FindKth(int k,List Ptrl)
{
list p=ptrl;
int i;
while(p!=NULL&&i<k)
{
p=p->next;
i++;
}
if(i==k) return p;//找到第k个,返回指针
else return NULL;//否则返回空
}
按值查找
List Find(int X,list Ptrl)
{
List p=Ptrl;
while(p!=NULL&&p->Date!=x)
{
p=p->Next;
}
return p;
}
插入(在第i-1个结点后插入一个值为x的新结点)
(1)先构造一个新结点,用s指向
(2) 再找到链表的第i-1个结点,用p指向
(3)修改指针,插入结点(p之后的新结点s)
List insert(int X,int i,List Ptrl)//在第i个位置插入x
{
List p,s;
if(i==1){
//新节点插入在表头
s=(List)malloc(sizeof(struct LNode));//申请,填充结点
s—>Date=X;
s->Next=Ptrl;
return s;//返回新表头指针
}
p=Findkth(i-1,Ptrl);//查找第i-1个结点
if(p==Null){
printf("参数错”);
return NUll;
}
else{
s=(List)malloc(sizeof(struct LNode));//申请填充结点
s->Date=X;
s->Next=p->Next;//新节点插入在第i-1个节点后面
p->Next=s;
return NUll;
}
删除(删除链表的第i个结点)
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);
if(p==NULL)
{
printf("这个结点不存在"); 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 NULL;
}
(二)广义表
(1)广义表是线性表的推广
(2) 对于线性表而言,n个元素的都是最基本的单元素。广义表中这些元素不仅可以是单元素,也可以是另一个广义表。
(3)广义表中可能会遇到指针域或者数据域,union可以把不同类型的数据组合在一起
typedef struct GNode *GList
struct GNode{
int Tag;//标志域:0表示结点是单元素,1表示结点是广义表
union{
int Data;//子表指针域sublist与单元素数据域Date复用,即共用存储空间
GList SubLidst;
}URegion;
GList Next;//指向后继节点
};
Tag | Data/SubList | Next |
(三)多重链表
多重链表:链表中的结点可能同时隶属于多个链
>多重链表的指针域会有多个
>但是包含俩个指针域的链表不一定是多重链表,比如在双向链表不是多重链表
>如果你的链表带有换头的操作可以带有返回值,否则就不用。
(四)实例分析
矩阵的表示:
分析:
1.用二维数组表示明显有俩个缺陷:
>一是数组的大小需要事先确定
>对于“稀疏矩阵”(0很多),但造成大量的空间浪费
2.采用一种典型的多重链表———十字链表来存储稀疏矩阵
(每个结点既属于每一行也属于每一列)
>只存储矩阵非0元素:结点的数据域:行坐标Row,列坐标Col,数值Vaule
>每个结点通过俩个指针域,把同行,同列串起来
行指针(向右指针)Right
列指针(向下指针)Down
>头结点的标识值为-1(head),矩阵非零元素结点的标识值为Term
>用一个Tag标识区分头结点和非0元素的结点.
多项式的表示
a(n)=a0+a1*x+a2*x^2+a3*x^3+…+an*x^n;
方法1: 顺序存储结构直接表示
利用数组的连续存储空间顺序
数组各分量对应多项式各项
则俩个多项式相加即俩个数组的对应分量相加
下标i | 0 | 1 | 2 | 3 | 4 | 5 |
a[i] | 1 | 0 | -3 | 0 | 0 | 4 |
typedef struct Lnode*List
Struct LNode{
int Date[20];
int last;
}
Struct Lnode l;
Last ptrl;
方法2:顺序存储结构表示非零项
每个非零项ai x^i涉及俩个信息 ai和x^i。可以把一个多项式看成一个(ai,i)二元组的集合
用结构数组表示:数组分量分别由ai,指数i组成的结构,对应一个非零项。
G(x)=9x^12+15x^8+3x^2
下标i | 0 | 1 | 2 |
系数ai | 3 | 15 | 9 |
指数i | 2 | 8 | 12 |
相加过程:从头开始,比较俩个多项式当前对应项的指数
P1 | (9,12) | (15,8) | (3,2) | |||
P2 | (26,19) | (-4,8) | (-13,6) | (82,0) | ||
P3 | (26,19) | (9,12) | (11,8) | (-13,6) | (3,2) | (82,0) |
方法3:链表结构存储非零项
链表中的每个结点存储多项式中的一个非零项,包括系数和指数俩个数据域以及一个指针域
Coef | Expon | link |
采用不带头结点的单向链表,按照指数递减的顺序排列各项
struct PolyNode{
int coef;//系数
int expon;//指数
struct PolyNode*link;//指向下一个结点的指针
};
typedef stuct PolyNode* Poly;
Poly p1,p2;
思路:
俩个指针p1和p2分别指向俩个多项式的第一个结点,不断循环。
>1:p1->expon==p2->expon系数相加,若结果不为0就作为多项式对应项的系数。同时,p1和p2指向下一项。
>2:p1->expon>p2->expon:将p1当前的项存入多项式中,并且使p1指向下一项
>3.p1->expon<p2->expon:将p2当前的项存入多项式中,并且使p2指向下一项
当某一个多项式处理完之后,再将另一个多项式的所有结点复制到结果多项式中。
void Attach(int c,int e,Poly* rear)//指针的指针
{
poly p;
p=(polyno)malloc(sizeof(struct polyno));
//对新结点复制
p->coef=c;
p->expon=e;
p->link=NULL;
(*rear)->link=p;
*rear=p;//更改rear的值
}
poly add(poly p1,poly p2)
{
poly front,rear,temp;
//front和rear分别指向结果多项式的头和尾
int sum;
rear=(poly)malloc(sizeof(struct polynode));
front=rear;
//构造一个临时空结点,作为多项式的表头
while(p1&&p2)
{
swithch(Compare(p1->expan,p2->expan))
case 1:
Attach(p1->coef,p1->expon,&rear);
//拷贝函数,把p1的相关信息接到rear的后面
p1=p1->link;//p1后移
break;
case -1:
Attach(p2->coef,p2->expon,&rear);
//拷贝函数,把p2的相关信息接到rear的后面
p2=p2->link;//p2后移
break;
case 0:
sum=p1->coef+p2->coef;
if(sum) Attach(sum,p1->expan,&rear);
p1=p1->next;
p2=p2->next;
break;
}
//将未处理完的另外一个多项式的所有结点依次复制到多项式中
for(;p1;p1=p1->link) Attach(p1->rear,p1->expan,&rear);
for(;p2;p2=p2->link) Attach(p2->rear,p2->expan,&rear);
rear->link=NULL;
temp=front;//释放临时结点
front=front->next;//让front指向多项式的第一个非零项
free(temp);
return front;
}
多项式的加法和乘法
(1)多项式的表示:只表示非零项(动态数组)
数组 | 链表 |
编程简单,容易调试 | 动态性强 |
需要事先确定数组大小 | 编程复杂,调试困难 |
typedef struct PolyNode*Polynomial;
struct PolyNode {
int ceof;
int expon;
Polynomial link;
}
int main()
{
Polynomial P1,P2,PP,PS;
P1=read();
P2=read();
PP=Mult(P1,P2);
print(PP);
Ps=Add(P1,P2);
print(PS);
}
//读入多项式
Polynomial ReadPoly()
{
int n,c,e;
Polynomial P,Rear,t;
scanf("%d",&n);
//n表示有几位多项式
P=(Polynomial)malloc(sizeof(struct PolyNode));//链表空结点
P->link=NULL;
Rear=P;
while(n--)
{
scanf("%d%d",&c,&e);
Attach(c,e,&rear);
//rear是结果表达式的尾指针
//并且要使得rear的值向后移动,在插入的结点后面
}
t=P;P=P->link;free(t);
return P;
}
rear的初值: 让rear指向一个空结点,以后的值全插在rear后面.
多项式相乘:逐项插入(把P1的当前项乘P2的当前项,加入到结果多项式中)
Polynomial mult(Polynomial p1,Polynomial p2)
{
Polynomial p,rear,t1,t2,temp;//t1,t2用来暂时替代p1,p2;
int c,e;
if(!p1||!p2) return NULL;//p1或者p2为空时,返回空
t1 = p1;
t2 = p2;
p = malloc(sizeof(struct PolyNode));
p->link = NULL;
rear = p;
while(t2) //先用p1的第一项乘以p2,得到p;
{
attach(t1->coef*t2->coef,t1->expon+t2->expon,&rear);
t2 = t2->link;
}
t1 = t1->link;
while(t1)
{
t2 = p2; //初始化,保证每次从t2头部开始
rear = p;
while(t2)
{
e = t1->expon+t2->expon;
c = t1->coef*t2->coef;
while(rear->link&&rear->link->expon > e)
//查找第一个小于或者等于当前指数的非空节点
rear = rear->link;
if(rear->link&&rear->link->expon == e)
//如果相等,非0时,系数相加,为0时,删除掉该节点
{
if(rear->link->coef+c)
rear->link->coef += c;
else
{
t = rear->link;
rear->link = t->link;
free(t);
}
}
else//如果小于,建立新节点,并且存储数据 。
{
t = (Polynomial)malloc(sizeof(struct PolyNode));
t->coef = c;
t->expon = e;
t->link = rear->link;
rear->link = t;
rear = rear->link;
}
t2 = t2->link;
}
t1=t1->link;
}
t2 = p;
p=p->link;
free(t2);
return p;
}
void Printpoly(Polynomial p)
{
int flag = 0;
if(!p) {
printf("0 0\n");
return ;
}//p为空
while(p)
{
if(!flag) flag = 1;
else printf(" ");//flag用来控制第一个数的空格,控制格式;
printf("%d %d",p->coef,p->expon);
p=p->link;
}
printf("\n");
}