循环链表

从单链表升级到双链表之后,很多操作一下子就变得简单容易了。
但有一个很麻烦的问题还是没有解决:如何从任意结点出发通过遍历访问所有结点。
下面的循环链表就是这个问题的解决方案。

循环单链表

在这里插入图片描述
将表中尾结点的指针由 NULL 改为指向头结点,整个链表形成一个环。从表中任一结点出发均可找到链表中其他结点。

所以,循环单链表与非循环单链表的差异就是:

  • 循环单链表没有 NULL 指针
  • p 所指节点为尾节点的条件:p->next == L

例 1

某线性表最常用的操作是:在尾元素之后插入一个元素和删除第一个元素,采用下列哪种存储方式最节省运算时间

A. 单链表

B. 仅有头结点指针的循环单链表

C. 双链表

D. 仅有尾结点指针的循环单链表

四种结构删除第一个元素的操作的时间复杂度都是 O(1),现在仅需考虑在尾结点后插入新元素的操作。要插入元素首先要找到尾结点

  • 单链表遍历整个链表找到尾结点的时间复杂度为 O(n)
  • 仅有头结点的指针的循环单链表,尽管首尾相连,但只有一个指向头结点的指针,仍要通过遍历链表来找到尾结点,插入的时间复杂度为 O(n)
  • 双链表,尽管可以反向遍历,但首尾没有相连,仍需要遍历链表来找到尾结点,插入的时间复杂度也为 O(n)
  • 仅有尾结点指针的循环链表找到尾结点的时间复杂度为 O(1)
void
listInsert( node **l, int e )
{
	node *s;
	s = ( node* )malloc( sizeof( node ) );
	s->data = e;
	s->next = (*l)->next;
	(*l)->next = s;
	*l = s;
}
void
listDelete( node *l )
{
	node *t;
	t = l->next;
	l->next = l->next->next;
	free( t );
}
插入删除
单链表O(n)O(1)
仅有头结点指针的循环单链表O(n)O(1)
双链表O(n)O(1)
仅有尾结点指针的循环单链表O(1)O(1)

循环双链表

在这里插入图片描述
循环双链表:形成两个环。

  • 没有 NULL 指针域
  • p 所指结点为尾结点的条件:p->next == L
  • 由L可以直接找到尾结点:L->prior

例 2

如果含有 n( n > 1 )个元素的线性表的运算只有 4 种

  1. 删除第一个元素
  2. 删除尾元素
  3. 在第一个元素前面插入新元素
  4. 在尾结点的后面插入新元素

最好使用下列哪个存储方式

A. 只有尾结点指针没有头结点的循环单链表

B. 只有尾结点指针没有头结点的非循环双链表

C. 只有首结点指针没有尾结点指针的循环双链表

D. 既有头指针又有尾指针的循环单链表

和上面的例子类似,抓住这些操作的本质,分析它们在对应的存储结构下工作的时间复杂度

1234
AO(1)O(n)O(1)O(1)
BO(n)O(1)O(n)O(1)
CO(1)O(1)O(1)O(1)
DO(1)O(n)O(1)O(1)
void
headDelete( node **l )
{
	node *t = *l;
	(*l)->prior->next = (*l)->next;
	(*l)->next->prior = (*l)->prior;
	*l = (*l)->next;
	free( t );
}
void
tailDelete( node *l )
{
	node *t = l->prior;
	l->prior->prior->next = l;
	l->prior = l->prior->prior;
	free( t );
}
void
listInsert( node *l, int e )
{
	node *s;
	s = ( node* )malloc( sizeof( node ) );
	s->data = e;
	s->next = l;
	l->prior->next = s;
	s->prior = l->prior;
	l->prior = s;
}
posted @ 2020-07-15 00:29  LanceHansen  阅读(137)  评论(0编辑  收藏  举报