带头结点的单链表
类型定义
typedef struct LNode{
ElemType data;
struct LNode *next;
}LNode,*LinkList;
变量定义
LinkList L;
LNode *p,*s;
重要操作:
p=L;//P指向头结点
s=L->next;//S指向首元结点
P=P->next;//p指向下一个结点
算法:取值-----取单链表中第i个元素的内容
思考:顺序表里如何找到第i个元素?L->elem[i-1]
思考:链表怎么取出第3个元素和第15个元素?
从链表的头指针出发,顺着链域next逐个结点往下搜索,直到搜索到第i个结点为止。因此,链表不是随机存取结构。链表第一个指向1,因此没有0,-1;
算法步骤:
1.从第1个结点(L->next)顺链扫描,用指针p指向当前扫描到的结点,p初值p=L->next.
2.j做计数器,累计当前扫描过的节点数,j初值为1.
3.当p指向扫描到的下一结点时,计时器j加1.
4.当j==i时,p所指的结点就是要找的第i个结点。
status GetElem_L(LinkList L,int i,ElemType &e){
p=L->next;j=1;//初始化
while(p&&j<1){
p=p->next;++j;//向后扫描,直到p指向第i个元素或p为空
}
if(!p||j>i)return ERROR;//第i个元素不存在
e=p->data;//取第i个元素
return OK;
}//GetElem_L
单链表的查找
1.按值查找:根据指定数据获取该数据所在的位置(地址)
如:分别查找值为30和值为15的元素
查找地址 和值查找
Lnode *LocateELem_L(LinkList L,Elemtype e)
{
p=L->next;
while(p&&p->data!=e)
p=p->next;
return p;
}
int LocateElem_L(LinkList L,Elemtype e)
{
p=L->next;j=1;
while(p&&p->data!=e)
{
p=p->next;j++;
}
if(p)return j;
else return 0;
}
链式表的插入:在第i个结点前插入新结点
status ListInsert_L(LinkList &L,int i,ElemType e){
p=L;j=0;
while(p&&j<i-1){p=p->next;++j;}//寻找第i-1个结点,p指向i-1结点
if(!p||j>i-1)return ERROR;
s=new LNode;s->data=e;
s->next=p->next;
p->next=s;
return OK;
}//ListInsert_L
链表的删除:删除第i个结点
status ListDelete_L (LinkList &L,int i,ElemType &e){
p=L;j=0;
while(p->next&&j<i-1){p=p->next;++j;}
if(!p->next||j>i-1)return ERROR;
q=p->next;
p->next=q->next;
e=q->data;
delete q;
return OK;
}//ListDelete_L
单链表的查找,插入,删除算法时间效率分析
1.查找:线性链表只能顺序查找,从头指针开始,O(n)
2.插入和删除:链表不需要移动元素,只需要修改指针,一般情况下啊,时间复杂度为O(1)
但是,如果要在单链表中进行前插或者删除操作,由于要从头查找前驱结点,所消耗时间复杂度为O(n).
单链表的建立
1.头插法:元素插入在链表头部,也叫做前插法
从一个空表开始,重复读入数据;
生成新结点,将读入数据存放到新结点的数据域中。
从最后一个结点开始,依次将各结点插入到链表的前端。
例子:建立链表L(A,B,C,D,E)
1.E。2.E,D。3.E,D,C。4.E,D,C,B。5.E,D,C,B,A。
void CreateList_H(LinkList &L,int n){
L=new LNode;//建立一个带头结点的单链表
L->next=NULL;
for(i=n;i>0;--i){
p=new LNode;
cin>>p->data;
p->next=L->next;
L->next=p;
}
}//CreateList_H
2.尾插法:元素插入在链表尾部,也叫做后插法
从一个空表L开始,将新结点逐个插入到链表的尾部,尾指针R指向链表的尾结点。
初始时,R同L均指向头结点。每读入一个数据元素则申请一个新的结点,将新结点插入到尾结点后,R指向新结点。
算法描述:正位序输入n个元素的值,建立带表头结点的单链表L
void CreateList_R(LinkList &L,int n){
L=new LNode; L->next=NULL;
r=L;//尾指针R指向头结点
for(i=0;i<n;++i)
{
p=new LNode;
cin>>p->data;
p->next=NULL;
r->next=p;
r=p;
}
}//CreateList_R
循环链表:是一种头尾相接的链表(既:表中最后一个结点的指针域指向头结点,整个链表形成一个环)。
优点:从表中任意一个结点出发均可以找到表中其他结点。
注意:由于循环链表中没有NULL指针,故涉及到遍历操作时,其终止条件就不再像非循环链表那样判断p或者p->next是否为空,而是判断题目是否等于头指针。
头指针表示单循环链表(找到a1的时间复杂度:O(1)找到an的时间复杂度为O(n).)
注意:表的操作常常是在表的首尾位置上进行。
尾指针表示单循环链表(a1的存储位置是:R->next->next.an的存储位置是:R,时间复杂度为O(1)。)
如何将带尾指针循环链表进行合并(Tb合并在Ta之后)
1.p存放表头结点
2.Tb表头连接到Ta表尾
3.释放Tb表头结点
4.修改指针
算法描述:
LinkList Connect(LinkList Ta,LinkList Tb){
//假设Ta,Tb都是非空的单循环链表
p=Ta->next;
Ta->next=Tb->next->next;
delete Tb->next;
Tb->next=p;
return Tb;
}
双向链表
单链表的结点-》有指示后继的指针域-》找后继结点方便。
既:查找某一个结点的后继结点的执行时间为O(1);
无指示前驱的指针域-》找前驱结点难:从表头出发寻找。
既:查找某个结点的前驱结点的执行时间为O(n).
双向链表:在单链表的每个结点里面再增加一个指向其直接前驱的指针域prior,这样链表中就形成了有2个方向不同的链,故称为双向链表。
双向链表的结构可定义如下:
typedef struct DuLNode{
Elemtype data;
struct DuLNode *prior,*next;
}DuLNode,*DuLinkList;
双向循环链表和单链表类似,双向链表也可以有循环表
1.让头结点的前驱指针指向链表的最后一个结点。
2.让最后一个结点的后继指针指向头结点。
双向链表结构的对称性(设指针p指向某一结点):
p->prior->next=p=p->next->prior;
在双向链表中有些操作(如:ListLength,GetElem等)因为仅仅涉及一个方向的指针,故它们的算法与线性链表的相同。但在插入,删除时,则需要同时修改两个方向上的指针,两者的操作的时间复杂度均为O(n).
算法:双向链表的插入
void ListInsert_DuL(DuLinkList &L,int i;ElemType e){
//在带头结点的双向循环链表L中第i个位置之前插入元素e
if(!(p=GetElemP_DuL(L,i)))return ERROR;
s=new DuLNode;
s->data=e;
s->prior=p->prior;
p->prior->next=s;
s->next=p;
p->prior=s;
return OK;
}//ListInsert_DuL
算法:双向链表的删除
void listdetele_dul(dulink &L,int i,ElemType &e)
{
//删除带头结点的双向链表的第i个元素,并用e返回。
if(!(p=grtelemp_dul(L,i)))return EROR;
e=p->data;
p->prior->next=p->next;
p->next->prior=p->prior;
free(p);
return OK;
}