循环链表
循环链表是一种头尾相接的链表,表中的最后一个结点的指针域指向头结点,整个链表形成一个环。
示意图:
这样的有点是可以从表中任一结点出发均可找到其他结点。而循环链表由于没有NULL指针,所以涉及遍历操作时,其终止条件就不再像非循环链表那样判断p或p->next是否为空,而是判断他们是否是头指针。
就像这样:
为了操作方便,一般需要一个尾指针,指向最后一个结点。(表的操作通常是在首位位置上进行)
来看一下有尾指针的例子,两个循环链表的合并:
代码:
//合并链表
//几乎与单链表相同
LinkList ConnectList(Linklist Ta,LinkList Tb){
LinkList p = Ta->next;//p存表(Ta)头结点
Ta->next = Tb->next->next;//Tb的表头连接Ta表尾
//delete Tb->next;
free(Tb->next);//释放Tb表的头结点
Tb->next = p;//修改指针,Tb的表尾结点指向p(Ta的首元)
return Tb;
}
双向链表
单链表中的结点只包含指向后继结点的指针,从而无法快速访问前驱结点(他就在你前面你却要绕一个圈才能找到他,甚至重新从头结点出发寻找)。对于较长的链表或者频繁进行删除插入等操作的链表来说,处理的速度很慢。为了比卖你这个问题,我们重新定义来年表,使链表中的每个结点有两个指针域,一个指向前驱(prior),一个指向后继(next)。这种链表被称为双向链表。
同样的,你也可以设置为双向循环链表:
- 让头结点的前驱指针指向链表的最后一个结点
- 让最后一个结点的后继指针指向头指针
双向链表的实现
双向的结构:
typedef struct DuNode {
Elemtype data;//
struct DuNode *prior,*next;//两个指针域
}DuNode,*DuLinkList;
关于双向链表的成员函数,比如:ListLength,GetElem等,因仅仅涉及一个方向的指针,故他们算法与单链表相同。但在插入删除时,需要修改两个方向的指针,两者操作的时间复杂度均为O(n).
插入:
//在带头结点的双向循环链表L中的第i个位置之前插入元素e
void DuListInsert(DuLinkList &L,int i,ElemType e) {
DuLinkList p;//第i个结点
//if(!(p = DuGetElem(L,i))) return false;//可以直接使用查找函数查找第i个元素的位置,也可以这样
p = L;//头结点
int cnt = 0;
while(p && cnt < i){
p = p->next;
cnt++;
}
if(!p || cnt > i)
return false;
DuLinkList s = (DuLinkList)malloc(DuNode);
//DuLinkList s = new DuNode
s->data = e;//输入数据域
s->prior= p->prior;//将新结点的前驱指向i-1位置结点
p->prior->next = s;//将i-1位置上的结点后继指针指向s
s->next = p;//将新结点的后继指针指向第i个结点
p->prior = s;//将第i个结点的前驱结点指向新结点
return true;
}
其他位置的插入与此大同小异 删除
void DuLinkList(DuLinkList &L, int i,ElemType &e) {
DuLinkList p;//第i个结点
//if(!(p = DuGetElem(L,i))) return false;//同样的可以使用查找函数找到第i个位置
p = L;//头结点
int cnt = 0;
while(p && cnt < i){
p = p->next;
cnt++;
}
if(!p || cnt > i)
return false;
e = p->data;
p->prior->next = p->next;//将第i-1位置的后继指针指向第i+1个结点
p->next->prior = p->next;//将第i+1个位置的前驱结点指向第i-1个结点
free(p);
return true;
}
比较
具体的选用看具体需求。当然表的使用还需多做题啊。