《数据结构》C语言版(严蔚敏)——线性表(一)
作者:互联网
目录
一、线性表定义与特点
1.定义
由n个数据特性相同的元素构成的有限序列称为线性表
n为线性表的的长度,n=0时为空表
2.特点
(非空线性表或线性结构)
- 唯一“第一个”数据元素
- 唯一“最后一个”数据元素
- 除第一个外,每个数据元素都只有一个前驱
- 除最后一个,都只有一个后继
3.类型定义
(见具体程序练习)
二、线性表的顺序表示和实现
顺序表:用一组地址连续的存储单元依次存储线性表的数据元素;
特点:逻辑上相邻,物理次序上相邻
第i个元素的存储位置:LOC(ai )=LOC(a1)+(i-1)*l
(设每个元素占用l个存储单位)
优点:
- 存储密度大(结点本身所占存储量/结点结构所占存储量)
- 可以随机存取表中任一元素
缺点: - 在插入、删除某一元素时,需要移动大量元素
- 浪费存储空间
- 属于静态存储形式,数据元素的个数不能自由扩充
顺序表的基本操作实现
初始化
Status InitList(SqList &L)
{//构造一个空的顺序表L
L.elem=new ElemType[MAXSIZE]; //为顺序表分配一个大小为MAXSIZE的数组空间
if(!L.elem)exit(OVERFLOW); //存储分配失败退出
L.length=0; //空表长度为0
return OK;
}
取值
根据指定的位置序号i,获取顺序表中第i个元素的值。
随即存储的特点,直接通过数组下标定位得到,elem[i-1]单元存储第i个元素
Status GetElem(SqList L,int i,ElemType &e)
{
if(i<1||i>L.length) return ERROR; //判断i值是否合理,不合理返回error
e=L.elem[i-1]; //elem[i-1]单元存储第i个数据元素
return OK;
}
顺序表取值算法的时间复杂度为:O(1)
查找
根据指定的元素值e查找顺序表中第一个与e相等的元素
int LocateElem(SqList L,ElemType e)
{//在顺序表L中查找值为e的数据元素,返回其序号
for(i-0;i<L.length;i++)
if(L.elem[i]==e ) return i+1; //查找成功,返回序号i+1
return 0;//查找失败返回0
}
平均查找长度 ASL=(n+1)/2
平均时间复杂度:O(n)
插入
在表的第i个位置插入一个新的数据元素e,使长度为n的线性表变成长度为n+1的线性表,一般需从最后一个元素即第n个元素开始,依次向后移动一个位置,直至第i个(共n-i+1个)
Status ListInsert(SqList &L,ElemType e)
{//在顺序表L中第i个位置插入新的元素e,i值的合法范围是1<=i<=L.length+1
if(i<1)||(i>L.length+1) return ERROR;//i值不合法
if(L.length==MAXSIZE) retur 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;
}
平均次数Eins=n/2
平均时间复杂度: O(n)
删除
将表的第i个元删除,使长度为n的线性表变成长度为n-1的线性表,一般需将第i+1个至第n个(共n-i个)依次向前移动一个位置(i=n无需移动)
Status ListDelete(SqList &L,int i)
{//在顺序表L中删除第i元素,i值的合法范围是1<=i<=L.length
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;//表长减1
return OK;
}
平均次数Edel=(n-1)/2
平均时间复杂度: O(n)
三、线性表的链式表示和实现
1.单链表的定义和表示
单链表由表头指针唯一确定
typedef struct LNode
{
ElemType data; //结点的数据域
struct LNode *next;//结点的指针域
}LNode,*LinkList;//LinkList为指向结构体LNode的指针类型
LinkList与LNode为同一结构体指针类型,本质等价
习惯用LinkList定义单链表,LNode定义指向单链表中任意结点的指针变量
LinkList L/LNode *p 称该链表为表L,L为头指针命名
- 首元结点:链表中存储的第一个数据元素a1的结点
- 头结点:首元结点之前附设的一个结点,指针域指向首元结点
- 头指针:指向链表中第一个结点的指针
增加头结点作用 - 便于首元结点的处理 首元结点的地址保存在头结点的指针域中,在链表的第一个位数据元素的操作和其它相同,无须进行特殊处理;
- 便于空表和非空表的统一处理 不设头结点时当单链表的长度为零的空表,头指针为空(L==NULL)
无论链表是否为空,头指针都是指向头结点的非空指针。
若p->data=ai,则p->next->data=ai+1.单链表是非随机存取的存储结构,必须从头指针出发顺链进行寻找,称为顺序存取的存取结构。
2.单链表基本操作的实现
初始化
构造一个空表
Status InitList(LinkList &L)
{//构造一个空的单链表L
L=new LNode;//生成新结点作为头结点,用头指针L指向头结点
L->next=NULL;//头结点的指针域置空
return ok;
}
取值
不同于顺序表,逻辑相邻的结点没有储存在物理相邻的单元中,不能像顺序表一样随机访问,只能从链表首元结点出发顺着链域next逐个结点向下访问
Status GetElem(LinkList L,int i;ElemType &e)
{//在头结点的单链表L中更具序号i获取元素的值,用e返回L中第i个数据元素的值
p=L->next;j=1;//初始化,p指向首元结点,计数器j初值赋值1
while(p&&j<i)//顺链域向后扫描,直到p为空或p指向第i个元素
{
p=p->next;//p指向下一个结点
++j;//计数器j相应加1
}
if(!p||j>1)return ERROR;//i值不合法i>n或i<=0
e=p->data;//取第i个结点的数据域
return ok;
}
ASL=(n-1)/2
平均时间复杂度: O(n)
查找
与顺序表类似,从首元结点出发以此比较
LNode *LocateElem(LinkList L,ElemType e)
{//在带头结点的单链表L中查找值为e的元素
p=L->next;//初始化,p指向首元结点
while(p&&p->data!=e)//顺链域向后扫描,直到P为空或p所指结点的数据域等于e
p=p->next;//p指向下一个结点
return p;//查找成功返回值为e的结点地址p,查找失败p为NULL
}
与顺序表类似
平均时间复杂度: O(n)
插入
s->next=p->next;p->next=s;
Status ListInsert(LinkList &L,int i,ElemType e)
{//在带头结点的单链表L中第i个位置插入值为e的新结点
p=L;j+0;
while(p&&(j<i-1))
{p=p->next;++j;}//查找第i-1个结点,p指向该结点
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的指针域指向结点ai
p->next=s;//将结点*p的指针域指向结点*s
return OK;
}
不需要像顺序表一样移动元素,但在第i个结点前插入元素,需先找到第i-1个结点
平均时间复杂度: O(n)
删除
p->next=p->next->next;在修改指针前,应引入另一指针q,临时保存b的地址以备释放
Status ListDelete(LinkList &L,int i)
{//在带头结点的单链表L中删除第i个元素
p=L;j=0;
while((p=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;//改变被删结点前驱结点的指针域
delete q;//释放删除结点的空间
return OK;
}
平均时间复杂度: O(n)
创建单链表
- 前插法
void CreateList_H(LinkList &L,int n)
{//逆位序输入n个元素的值,建立带头结点的单链表L
L=new LNode;
L->next=NULL;//先建立一个带头结点的空链表
for(i=0;i<n;++i)
{
p=new LNode;//生成新结点*p
cin>>p->next;//输入元素值赋给新结点*p的数据域
p->next=L->next;L->next=p;//将新结点*p插入到头结点之后
}
}
平均时间复杂度: O(n)
- 后插法
void CreateList_R(LinkList &L,int n)
{//正位序输入n个元素的值,建立带头结点的单链表L
L=new LNode;
L->next=NULL;//先建立一个带头结点的空链表
r=L;//尾指针r指向头结点
for(i=0;i<n;++i)
{
p=new LNode;//生成新结点*p
cin>>p->next;//输入元素值赋给新结点*p的数据域
p->next=NULL;r->next=p;//将新结点*p插入到尾结点*r之后
r=p;//r指向新的尾结点*p
}
}
平均时间复杂度: O(n)
3.循环链表
特点:表中最后一个结点指针域指向头结点,整个链表形成一个环;
与单链表的差别:单链表判别条件:p!=NULL或p->next!=NULL,循环单链表为p!=L或p->next!=L
两线性表合并为一个
p=B->next->next;
B->next=A->next;
A->next=p;
时间复杂度: O(1)
4.双向链表
克服单链表的单向性
两个指针域
typedef stryct DuLNode
{
ElemType data;//数据域
struct DuLNode *prior;//指向直接前驱
struct DuLNode *next;//指向直接后继
} DuLNode,*DuLinkList;
插入结点时需修改4个指针,删除结点时需修改2个指针
插入
Status ListInsert_DuL(DuLinkList &L,int i,ElemType e)
{//在带头结点的双向链表L中第i个位置之前插入元素e
if(!(p=GetElem_DuL(L,i)))//在L中确定第i个元素的位置指针p
return ERROR;//p为NULL时,第i个元素不存在
s=new DuLNode;//生成新结点*s
s->data=e;//将新结点*s数据域置为s
s->prior=p->prior;//将新结点*s插入L中,对应图2.20.1
p->prior->next=s;//对应2.20.2
s->next=p;//对应2.20.3
p->prior=s;//对应2.20.4
return OK;
}
删除
Status ListDelete_DuL(DuLinkList &L,int i)
{//删除带头结点的双向链表L中第i个元素
if(!(p=GetElem_DuL(L,i)))//在L中确定第i个元素的位置指针p
return ERROR;//p为NULL时,第i个元素不存在
p->prior->next=p->next;//修改被删除结点的前驱结点的后继指针,对应2.21.1
p->next->prior=p->prior;//修改被删结点的后继结点的前驱指针,对应2.21.2
delete p;//释放被删结点的空间
return OK;
}
二者时间复杂度均为O(n)
四、顺序表和链表的比较
五、线性表的应用
1.线性表的合并
时间复杂度: O(m*n)
2.有序表的合并
- 顺序有序表:
时间复杂度: O(m+n)
空间复杂度: O(m+n) - 链式有序表:
时间复杂度: O(m+n)
空间复杂度: O(1)
标签:结点,return,线性表,元素,next,严蔚敏,C语言,指针 来源: https://blog.csdn.net/Jennifer_XL/article/details/121605552