定义:零个或多个数据元素的有限序列。
一个有顺序的序列。第一个元素无前驱,最后一个元素无后继。其他每个元素都只有一个前驱和一个后继。
线性表的元素个数n定义为线性表的长度n,当n=0的时候为空表。在非空的数据表中每个元素的位置都是固定的。
ADT | 线性表 抽象数据类型 |
---|
DATA:线性表的数据对象的集合是a1,a2,…,an,每个数据的数据类型是DataType.
Operation
InitList(*L): 初始化操作,建立一个空的线性表。
ListEmpty(L): 若线性表为空返回true否则就返回false
ClearList(*L):将线性表清空
GetElem(L,i,*e):把数据表中第i个元素返回给e
LocateElem(L,e):在线性表中查找与给定的e相等的元素,如果查找成功返回该元素在表中的序列号;否则返回0表示失败
ListInsert(*L,i,e):在线性表中的第i个位置插入新元素e
ListDelete(*L,i,e):删除线性表中的第i个位置元素,并用e返回其值
ListLength(L):返回线性表L的元素个数
下面举一个简单的例子:
把两个线性表进行合并把仅存在集合B的元素插入到集合A:
void unionL(List *La,List Lb)
{
int La_len,Lb_len,i;
ElemType e;
La_len=ListLength(*La);
Lb_len=ListLength(Lb);
for(i=1;<Lb_len;i++)
{
GetElem(Lb,i,&e);//依次取出元素存入e
if(!LocateElem(*La,e));//如果有与e相同的元素
ListInsert(La,++La_len,e);//在La的末尾插入此相同元素
}
}
线性表有两种物理存储结构,一种是顺序存储,一种是链式存储结构;
顺序存储结构就是用一串地址连续的存储单元依次存放线性表的数据元素。
顺序结构的存储方式就是在内存中找了一块区域把一串相同数据类型的数据元素存在这块空地里面。
顺序表存储结构代码:
#define MAXSIZE 20
typedef int ElemType ;
typedef struct{
ElemType data[MAXSIZE];
int length;
}SqList;
这存储空间有三个属性
数组的长度和线性表的长度,其中数组的长度在一开始就被定义了,是一个不变的量,而线性表的长度随着数据的插入和删除会相应的增加和减小。一般来说线性表的长度要小于等于数组的长度。
线性表的顺序是从一开始的,但是我们定义的数组是从0开始的下标。就是说现线性表种的第i个元素对应的是数组下标为i-1的。在存储器中每个存储单元都有自己的编号,这个编号就叫做地址。这举个例子来说明如何算当前的地址:
假设每个数据元素占c个字节那么线性表中第i个数据元素的地址和第i+1个数据元素地址之间有如下关系:(LOC表示获取当前存储位置的函数)
LOC(ai+1)=LOC(ai)+c
那么第i个元素的位置就可以由第一个公式推算出:
LOC(ai)=LOC(a1)+(i-1)*c
由最后这个公式你可以很快的算出任何一个元素的地址,不管是第一个还是最后一个,都是相同的时间,所以对线性表的每个元素经行读取和删除操作的时候,时间是一样的。在算法中的概念就是算法的时间复杂度是O[1]这一存储结构由被称为随机存储结构。
对于线性表的顺序存储结构我们进行GetElem操作:把顺序表中的第i个元素返回。在c语言中就是把数组下标为i-1的元素返回:
#define OK 1
#define ERROR 0
#define TRUE 1
#define FALSE 0
typedef int Status;
Status GetElem(SqList L,int i,ElemType *e)
{
if((L.length==0)||i<1||i>L.length)
return ERROR;
*e =L.data[i-1];
reuturn OK;
}
实现向线性表中插入一个元素,就是ListInsert(*L,i,e),向线性表的第i个位置插入元素e。
插入的思路:
Status ListInsert(Sqlist *L,int i, ElemType e)
{
int k;
if(L->length==MAXSIZE)
return ERROR;
if (i<1||I>L->length-1)
return ERROR;
if(i<=L->length)
{
for(k=L=>length-1;k>=i-1;k--)
{
L->data[k+1]=L->data[k];
}
L->data[i-1]=e;
L->length++;
return OK;
}
}
删除的算法思路:
具体代码如下:
Status ListDelete(Sqlist *L,int i,ElemType *e)
{
int k;
if(L->length==0)
rreturn ERROR;c
if(i<1||i>L->length)
return ERROR;
*e=L->data[i-1];//取出被删除的元素
if(i<L->length)
{
for(k=i;k<L->length;k++)
L->data[k-1]=L->data[k];//向前挪动一位
}
L->length--;
return OK;
}
线性表的顺序存储结构的优点与缺点:
优点
可以快速的存取表种任意位置的元素
无需位表示表中元素之间的存储关系增加额外的空间
缺点
插入和删除操纵要移动大量的元素
当线性表长度发生变化时难以确定存储空间的容量
容易造成存储空间的碎片化
定义:用一组任意的存储空间来存储线性表的数据元素,存储单元可以是线性的,也可以是不线性的。(这些数据元素可以存在任意未被分配的内存空间中)
为了表示每个元素与其后继元素之间的关系除了存储数据本身的信息外,还需要一个存储一个指示其直接后继元素的信息。
存储元素信息的域成为数据域
存储直接后继位置的域称为指针域
指针域中存储的信息叫做指针或者链
这两部分的信息组成数据元素ai的存储映像,叫做结点
n个这样的结点链接成链表,成为线性表的链式存储结构,由于每个结点的指针域中只包含一个指针信息,所以称为单链表。
对线性表来说肯定有头和尾
头:我们把链表中第一个结点的存储地址当作头指针
尾:我们规定线性表的最后一个结点的指针位NULL空指针。
为了表示方便,我们常常在第一个结点前加上一个头结点,头结点不包含任何的数据,指针域包含一个头指针指向第一个结点
单链表,在c语言中用结构体就能进行很好的构建:
typedef struct Node
{
ElemType data;
struct Node *next;
}Node;
typedef struct Node *LinkList;//定义LinkList
可以清楚的看到,结点由存放数据元素的数据与存放下一个结点地址的指针与构成。
假设p是指向线性表的第i个元素的指针,则该节点的的数据可以用p->data来表示
而下一个节点的数据表示为:p->next->data;
在线性表的顺序存储结构中,由于元素是按顺序存在数组里面的所以很容易知道任意一个元素在线性表中位置。但是当线性表是链式存储结构,要想访问第i个元素就必须从一开始查找。算法思路:
//用e返回L中第i个元素的值
Status GetElem (LinkList L,int e,ElemType *e)
{
int j;
LinkList p;
p=L->next;
j=1;
while(p&&j<i)//指针不为空且j始终小于i
{
p=p-next;
j++;
}
if(!p||j>1)
return ERROR;
*e =p->data;
return OK;
}
此算法的时间复杂度取决于i的大小,最坏情况时间复杂度为O[n],核心为工作指针后移
单链表的插入,假设要,存储单元e的结点为s,要实现结点p,p->next和s之间的逻辑关系变化,只需要把s结点插入到p和p->next中,不许需要改变其他结点。
s->next=p->next;p->next=s;
让p的后继结点改成s的后继结点再把结点s变成p的后继结点。
Status ListInsert(ListType *L,int i,ElemType e)
{
int j;
LinkList p,s;
p=*L;
j=1;
while(p&&j<i)
{
p=p->next;
++j;
}
if(!p||j>i)
{
s=(LinkList)malloc(sizeof(Node))//为新生成的结点申请内存空间
s->data=e;
s->next=p->next;
p->next=s;
return OK;
}
}
这里要做的就是把p->next=p->next->next现在用q代表p->next;
q=p->next;p->next=q->next;
就是把p的后继结点变成p的后继结点的后继结点;
Status ListDelete()
{
int j;
LinkList p,q;
p=*L;
j=1;
while(p->next&&j<i)
{
p=p->next;
j++;
}
if(!(p->next)||j>i)
return ERROR;
q=p->next;
p->next=q->next;
*e =q->data;
free(q);
return OK;
}
由此可以看出,如果对插入删除操作比较多的时候,用链表结构会比较有又是,如果只是查找某个元素的话,线性表的顺序存储结构会方便很多。
回顾之前的可得:线性表的顺序存储结构就是数组的创建,为线性表的链式存储结构就是一种动态结构,对于每个链表来说所占据的空间大小和位置是不需要预先划分的,根据需要即使生成。单链表创立的整体思路如下:
void CreateListHead(LinkList *L,int n)
{
LinkList p;
int i;
srand(time(0));
*L=(LinkList)malloc(sizeof(Node));
(*L)->next =NULL;
for(i-0;idata =rand()%100+1;//随机生成数字
p->next =(*L)->next;
(*L)->next=p;
}
}