线性表类型的实现——链式映象
单链表
- 用一组地址任意的存储单元存放线性表中的数据元素
- 元素(数据元素的映象)+ 指针(指示后继元素存储位置的)= 结点(表示数据元素)
以结点的序列表示线性表——称作链表
以线性表的第一个数据元素 a 1 a_1 a1的存储地址作为线性表的地址,称作线性表的头指针
头节点:数据元素为空,指向第一个数据元素。
(头指针指向头节点,若为空表,则头节点的指针域为空)
结点和链表的C语言描述
Typedef struct LNode{
ElemType data; //数据域
struct Lnode *next; //指针域
}LNode,*LinkList
整个链表可以用指向头节点的指针来表示。
单链表操作的实现
- 线性表的操作
GetElem(L,i,&e)在链表中的实现
:
基本操作:使指针p始终指向线性表中的第j个元素
Status GetElem_L(LinkList L,int pos, ElemType &e){
p = L->next; j = 1; //初始化,p指向第一个结点,j为计数器
while(p && j<pos){p = p->next; ++j} //顺指针向后查找,知道p指向第pos个元素或p为空
if(!p||j>pos)
return ERROR; //第pos个元素不存在
e = p->data; //取第pos个元素
return OK;
}//GetElem_L
算法的时间复杂度为:O(ListLength(L))
- 线性表的操作
ListInsert(&L,i,e)
的实现:
基本操作为:找到线性表中第 i − 1 i-1 i−1的结点,修改其指向后继的指针
有序对 < a i − 1 , a i > 改 变 为 < a i − 1 , c > 和 < c , a i > <a_{i-1},a_i>改变为<a_{i-1},c>和<c,a_i> <ai−1,ai>改变为<ai−1,c>和<c,ai>
Status ListInsert_L(LinkList L,int pos, ElemType e){
p = L; j = 0; //有可能插入位置是第一个元素,所以j=0
while(p&&j<pos-1)
{p = p_next; ++j;} //寻找第pos-1个节点
if(!p || j>pos-1)
return ERROR; //pos小于1或者大于表长
s = (LinkList)molloc(sizeof(LNode)); //生成新结点
s->data = e; s->next = p->next; //插入L中
p->next = s;
return OK;
}//LinkInsert_L
算法的时间复杂度:O(ListLength(L))
- 线性表的操作
ListDelete(&L,i,&e)
在链表中的实现:
基本操作为:找到线性表中第 i − 1 i-1 i−1个结点,修改其后继指针
有序对 < a i − 1 , a i > <a_{i-1},a_i> <ai−1,ai>和 < a i , a i + 1 > <a_i,a_{i+1}> <ai,ai+1>改变为 < a i − 1 , a i + 1 > <a_{i-1},a_{i+1}> <ai−1,ai+1>
Status ListDelete_L(LinkList L, int pos, ElemType &e){
p = L; j = 0;
while(p->next&&j<pos-1)
{p = p->next; ++j;} //寻找第pos个结点,并令p指向其前驱
if(!(p->next) || j>pos-1)
return ERROR; //删除位置不合理
q = p->next; p->next = q->next; //删除并释放节点
e = q->data; free(q);
return OK;
}//ListDelete_L
算法的时间复杂度为O(ListLength(L))
链表的创建
- 策略一:每次插入到表头
- 策略二:每次插入到表尾
void CreateList_L(LinkList &L, int n){
//策略一
L = (LinkList)malloc(sizeof(LNode));
L->next = NULL; //先建立一个带头结点的单链表
for(i=n; i>0; --i){
p = (LinkList)malloc(sizeof(LNode));
scanf(&p->data); //输入元素值
p->next = L->next; L->next = p; //插入到表头
}
}//CreateList_L
算法的时间复杂度为O(ListLength(L))
链表改进
用上述定义的单链表实现线性表的操作时,存在的问题:
- 单链表的表长是一个隐含的值
- 在单链表的最后一个元素最后插入元素时,需遍历整个链表;
- 在链表中,元素的“位序”概念淡化,结点的“位置”概念强化
改进链表的设置:
- 增加“表长”,“表尾指针”,“当前位置指针”三个数据域
- 将基本操作中的位序改为指针
一个带头节点的线性链表类型
Typedef struct LNode{ //结点类型
ElemType data; //数据域
struct Lnode *next; //指针域
}*Link,*Position
Status MakeNode(Link &p, ElemType e);
//分配由p指向的值为e的结点,并返回OK;
//若分配失败,则返回ERROR
void FreeNode(Link &p);
//释放p所指结点
typedef struct{ //链表类型
Link head,tail; //指向头节点和最后一个结点
int len; //指示链表长度
Link current;
//指向当前访问的结点的指针
//初始位置指向头节点
}LinkList
表长变为显值
链表的基本操作
- 结构初始化和销毁结构
Status InitList(LinkList &L);
//构造一个空的线性表L
//头指针,尾指针和当前指针均指向头节点,表长为0
Status DestroyList(LinkList &L);
//销毁线性链表L,L不在存在
-
引用型操作
Status ListEmpty(LinkList L) //判表空;
int ListLength(LinkList L); //求表长
(时间复杂度为常量)
Status Prior(LinkList L); //改变当前指针指向其前驱
(时间复杂度和表长成正比)
Status Next(LinkList L); //改变当前指针指向其后继
(时间复杂度为常量)
ElemType GetCurElem(LinkList L); //返回当前指针所指数据元素
(时间复杂度为常量)
Status LocatePos(LinkList L, int i);
//改变当前指针指向第i个结点 (时间复杂度和表长成正比)
Status LocateElem(LinkList L,ElemType e, Status(*compare)(ElemType,ElemType));
//若存在与e满足函数compare()判定关系的元素,则移动当前指针指向第一个满足条件的元素,并返回OK,否则返回ERROR (时间复杂度和表长成正比)
Status ListTraverse(LinkList L, Status(*visit()));
//依次对L的每个元素调用visit() (时间复杂度和表长成正比) -
加工型操作
Status ClearList(LinkList &L); //重置为空表
Status SetCurElem(LinkList &L,ElemType e); //更新当前指针所指数据元素
Status Append(LinkList &L,Link s); //一串结点链接在最后一个节点之后,时间复杂度为常量
Status InsAfter(LinkList &L,ElemType e); //将元素插入在当前指针之后,时间复杂度为常量
Status DelAfter(LinkList &L,ElemType *e); //删除当前指针之后的结点 ,时间复杂度为常量
InsAfter(LinkList &L,ElemType e);
的实现:
Status InsAfter(LinkList &L,ElemType e){
//若当前指针在链表中,则将数据元素e插入在线性链表L中
//当前指针所指结点之后,并返回OK,否则返回ERROR
if(!L.current) return ERROR;
if(!MakeNode(s,e)) return ERROR;
s->next = L.current->next;
L.current->next = s;
return OK;
}//InsAfter
DelAfter(LinkList &L,ElemType *e);
的实现:
Status DelAfter(LinkList &L, ElemType &e){
//若当前指针及其后继在链表中,则删除线性链表L中
//当前指针所指结点之后的结点,并返回OK,否则返回ERROR
if(!(L.current&&L.current->next))
return ERROR;
q = L.current->next;
L.current->next = q->next;
e = q->data; FreeNode(q);
return OK;
}DelAfter
利用线性表的基本操作完成其他操作
- 例1
在带头结点的单链表线性表L的第i个元素之前插入元素e
Status ListInsert_L(LinkList, int i,ElemType e){
//在带头结点的单链表线性表L的第i个元素之前插入元素e
if(!LocatePos(L,i-1)) return ERROR; //i值不合法
if(InsAfter(L,e)) return OK; //插入在第i-1个结点之后
else return ERROR;
}//ListInsert_L
- 例2
归并两个“其数据元素按值非递减有序排列(关键字递增序排列,但并非是单调递增)的”线性表LA和LB,求得线性表LC也具有同样特性
void MergeList_L(LinkList &La,LinkList &Lb, LinkList &Lc,
int(*compare)(ElemType,ElemType)){
//时间复杂度与两个表长之和成正比
if(!InitList(Lc)) return ERROR; //存储空间分配失败
LocatePos(La,0); LocatePos(Lb.0); //当前指针指向头节点
if(DelAfter(La,e)) a = e;
else a = MAXC; //MAXC为常量最大值
if(DelAfter(Lb,e)) b = e;
else b = MAXC;
while(!(a=MAXC && b = MAXC)){ //La或Lb非空
if((*compare)(a,b)<=0){ //a<=b
InsAfter(Lc,a);
if(DelAfter(La,el)) a = el;
else a = MAXC;
}
else{ //a>b
InsAfter(Lc,b);
if(DelAfter(Lb,el)) b = el;
else b = MAXC;
}
}
DestroyList(La); DestroyList(Lb); //销毁链表La和Lb
return OK;
}//MergeList_L
其他形式链表
双向链表
- 定义:
//线性表的双向链表存储结构
typedef struct DulNode{
ElemType data; //数据域
struct DulNode *prior; //指向前驱的指针域
struct DulNode *next; //指向后继的指针域
}DulNode, *DuLinkList;
- 特点:
头指针的前驱和后继为空
对每个当前指针指向的元素来说,其后继的前驱和前驱的后继是当前节点
循环链表
最后一个元素的指针域指向头节点