循环链表
循环链表是另一种形式的链式存贮结构。它的特点是表中最后一个结点的指针域指向头结点,整个链表形成一个环。
判断空链表的条件
head == head->next;
rear == rear->next;
应用
在链表上实现将两个线性表(a1,a2,…,an)和(b1,b2,…,bm)连接成一个线性表(a1,…,an,b1,…bm)的运算。
分析:若在单链表或头指针表示的单循环表上做这种链接操作,都需要遍历第一个链表,找到结点an,然后将结点b1链到an的后面,其执行时间是O(n)。若在尾指针表示的单循环链表上实现,则只需修改指针,无须遍历,其执行时间是O(1)。
双向链表
前面提到的链式存储结构的结点中只有一个指示直接后继的指针域,因此,从某个结点出发只能顺指针往后寻查其他结点。如需查找节点的直接前驱,则需从表头指针出发。换言之,在单链表中,NextElem的执行时间为O(1),而PriorElem执行时间为O(n)。为克服这种单向性缺点,可以利用双向链表。
双向链表也叫双链表,是链表的一种,它的每个数据结点中都有两个指针,分别指向直接后继和直接前驱。所以,从双向链表中的任意一个结点开始,都可以很方便地访问它的前驱结点和后继结点。
存储结构
// 线性表的双向链表存储结构
typedef struct DuLNode
{
ElemType data;
struct DuLNode* prior;
struct DuLNode* next;
}DuLNode, * DuLinkList;
相关算法
像ListLength,GetElem,LocateElem等仅需涉及一个方向的指针,他们的算法与线性链表的操作相同。但是在插入和删除时有很大的不同,即需要同时修改两个方向上的指针,两者算法时间复杂度均为O(n)。
获取元素
DuLinkList GetElemP_DuL(DuLinkList L, int i)
{
DuLinkList p = L->next; // 初始化,p指向第一个结点
int j = 1; // 计数器
while (p != L && j < i) {
p = p->next;
++j;
}
if (p == L || j > i) // 第i个元素不存在
return NULL;
return p;
}
插入
Status ListInsert_DuL(DuLinkList L, int i, ElemType e)
{
// 在带头结点的双链循环线性表L中第i个位置之前插入元素e
// i合法值1<=i<=表长+1
DuLinkList p = NULL, s = NULL;
if (!(p = GetElemP_DuL(L, i))) // 在L中确定插入位置
return ERROR; // p=NULL,即插入位置不合法
if (!(s = (DuLinkList)malloc(sizeof(DuLNode))))
return ERROR;
s->data = e;
s->prior = p->prior;
p->prior->next = s;
s->next = p;
p->prior = s;
return OK;
}
删除
Status ListDelete_DuL(DuLinkList L, int i, ElemType* e)
{
// 删除带头结点的双链循环线性表L中第i个元素
DuLinkList p = NULL;
if (!(p = GetElemP_DuL(L, i))) // 在L中确定第i个元素的位置指针p
return ERROR; // p=NULL,即第i个元素不存在
*e = p->data;
p->prior->next = p->next;
p->next->prior = p->prior;
free(p);
return OK;
}