目录
基本概念和操作:
实现方法和原理:
应用场景和使用注意事项:
算法和复杂度分析:
与其他数据结构的比较:
PS:如有错漏之处,敬请指正
循环链表是一种特殊的链表结构,其最后一个节点指向第一个节点,形成了一个循环,这样从表中的任一节点触发都可以找到表中其他节点。循环链表可以分为单向循环链表和双向循环链表两种类型。以下为带头节点的循环单链表示意图。
带头指针的循环单链表示意图对循环链表来说,有的时候指针改为尾指针会使操作更简单,下图是带尾指针的循环单链表示意图。
带尾指针的循环单链表示意图从上图的示意图可以看出,用循环表表示线性表的逻辑关系与单链表的表示方法一样,不同的是最后一个元素的next的值不能为null,而是存储的链表中的第一个元素的地址。
以下是循环链表的常用操作:
头节点:循环链表第一个节点之前的节点,通常没有存储数据,主要作用是方便对循环链表的操作。
尾节点:循环链表最后一个节点,其next指针指向头节点。
插入元素:在指定位置插入新元素,需要修改前一个节点的next指针和新节点的next指针。
删除元素:删除指定位置的元素,需要修改前一个节点的next指针。
查找元素:从头节点开始遍历循环链表,直到找到目标元素或遍历完整个链表。
遍历元素:从头节点开始遍历循环链表的所有元素,可以使用while循环或for循环实现。
反转链表:将循环链表中的所有节点反转顺序,需要使用三个指针来实现。
需要注意的是,在进行各种操作时,需要保证循环链表的连续性和有序性,即每个节点的next指针都指向下一个节点,并且最后一个节点的next指针指向头节点。实现细节参见下面的 C#代码:
///
/// 循环单链表数据结构实现接口具体步骤
///
///
class CLinkedList:ILinarList
{
public SNode tail;
int length;//循环链表长度
public CLinkedList()
{
this.tail = null;
}
///
/// 在链表的末尾追加数据元素 data
///
/// 数据元素
public void InsertNode(T data)
{
SNode node = new SNode(data);
if (IsEmpty())
{
tail = node;
tail.Next = tail;
}
else
{
T firstData = tail.Data;
SNode current = tail;
while (current.Next != null && !current.Next.Data.Equals(firstData))
{
current = current.Next;
}
current.Next = new SNode(data);
current.Next.Next = tail;
}
length++;
return;
}
///
/// 在链表的第i个数据元素的位置前插入一个数据元素data
///
///
///
public void InsertNode(T data, int i)
{
if (i < 1 || i > (length+1))
{
Console.WriteLine("Position is error!");
return;
}
SNode current;
SNode newNode = new SNode(data);
if (i == 1)
{
newNode.Next = tail;
tail = newNode;
length++;
current = tail;
for (int j = 0; j < length; j++)
{
if (j== (length - 1))
{
current.Next = tail;
break;
}
current = current.Next;
}
return;
}
//两个元素中间插入一个元素
current = tail;
SNode previous = null;
int index = 1;
while (current != null && index < i)
{
previous = current;
current = current.Next;
index++;
}
if (index == i)
{
previous.Next = newNode;
newNode.Next = current;
length++;
}
return;
}
///
/// 删除链表的第i个数据元素
///
///
public void DeleteNode(int i)
{
if (IsEmpty() || i < 1)
{
Console.WriteLine("Link is empty or Position is error");
}
SNode current = tail;
SNode previus = null;
if (i == 1)
{
tail.Data = current.Next.Data;
tail.Next = current.Next.Next;
length--;
return;
}
if (i > length)
{
return;
}
int j = 1;
while (current.Next != null && j < i)
{
previus = current;
current = current.Next;
j++;
}
if (j == i)
{
previus.Next = current.Next;
current = current.Next;
length--;
return;
}
//第i个节点不存在
Console.WriteLine("the ith node is not exist!");
}
///
/// 获取链表的第i个数据元素
///
///
///
public T SearchNode(int i)
{
if (IsEmpty())
{
Console.WriteLine("List is empty");
return default(T);
}
SNode current = tail;
int j = 1;
while (current.Next != null && j < i && j<=length)
{
current = current.Next;
j++;
}
if (j == i)
{
return current.Data;
}
//第i个节点不存在
Console.WriteLine("the ith node is not exist!");
return default(T);
}
///
/// 在链表中查找值为data的数据元素
///
///
///
public T SearchNode(T data)
{
if (IsEmpty())
{
Console.WriteLine("List is empty");
return default(T);
}
SNode current = tail;
int i = 1;
bool isFound = false;
while (current != null && i<=length)
{
if (current.Data.ToString().Contains(data.ToString()))
{
isFound = true;
break;
}
current = current.Next;
i++;
}
if (isFound)
{
return current.Data;
}
return default(T);
}
///
/// 获取链表的长度
///
///
public int GetLength()
{
return length;
}
///
/// 清空链表
///
public void Clear()
{
tail = null;
length = 0;
}
public bool IsEmpty()
{
return length == 0;
}
///
/// 该函数将链表头节点反转后,重新作为链表的头节点。算法使用迭代方式实现,遍历链表并改变指针指向
/// 例如链表头结点start:由原来的
/// data:a,
/// next:[
/// data:b,
/// next:[
/// data:c,
/// next:[
/// data:a,
/// next:[
/// data:b,
/// next:...
/// ]
/// ]
/// ]
/// ]
///翻转后的结果为:
/// data:c,
/// next:[
/// data:b,
/// next:[
/// data:a,
/// next:[
/// data:c,
/// next:[
/// data:b,
/// next:....
/// ]
/// ]
/// ]
/// ]
///
// 反转循环链表
public void ReverseList()
{
if (length == 1 || this.tail == null)
{
return;
}
//定义 previous next 两个指针
SNode previous = null;
SNode next;
SNode current = this.tail;
//循环操作
while (current != null)
{
//定义next为Head后面的数,定义previous为Head前面的数
next = current.Next;
current.Next = previous;//这一部分可以理解为previous是Head前面的那个数。
//然后再把previous和Head都提前一位
previous = current;
current = next;
}
this.tail = previous.Next;
//循环结束后,返回新的表头,即原来表头的最后一个数。
return;
}
}
总之,循环链表是一种特殊的链表结构,在实际应用中具有广泛的使用。需要注意的是,在进行各种操作时,需要保证循环链表的连续性和有序性,以避免数据不一致等问题的发生。
循环链表是一种特殊的单向或双向链表结构,其最后一个节点的next指针指向第一个节点,形成了一个循环。本人已经在其他文章介绍了单向和双向循环链表的实现方法和原理。
总之,循环链表是一种特殊的链表结构,在实现时需要注意保证链表的连续性和有序性。
循环链表是一种特殊的链表结构,适用于需要循环访问节点的场景。以下是一些常见的循环链表应用场景:
约瑟夫问题:约瑟夫问题是一个经典的数学问题,可以通过循环链表来实现。具体来说,可以使用循环链表模拟一组人围成一个圆圈,然后依次杀掉每个第M个人,最后剩下的人就是胜利者。
缓存淘汰算法:在缓存淘汰算法中,可以使用循环链表来实现LRU(Least Recently Used)算法。具体来说,可以将缓存中的数据按照访问时间顺序形成一个循环链表,当缓存满时,删除最久未被访问的数据。
跑马灯效果:在图形界面开发中,可以使用循环链表来实现跑马灯效果,即使一段文本以循环滚动的方式展示。
使用循环链表时,需要注意以下几点:
循环链表的操作与普通链表类似,在插入、删除和查找等方面相对容易。但是,需要注意循环链表的连续性和有序性,避免出现死循环或数据不一致等问题。
循环链表的节点需要额外存储一个指针,因此比普通链表占用更多的空间。
在使用双向循环链表时,需要注意头节点和尾节点的处理,避免操作失误导致链表出现断裂。
总之,循环链表是一种特殊的链表结构,适用于需要循环访问节点的场景。在实际应用中,需要根据具体的需求和场景选择合适的数据结构,并注意其特点和使用注意事项。
循环链表的一些基本算法包括:
在指定位置插入元素:需要先找到插入位置的前一个节点,然后将新节点的next指针指向插入位置的节点,再将前一个节点的next指针指向新节点。时间复杂度为O(n)。
在指定位置删除元素:需要先找到删除位置的前一个节点,然后将其next指针指向删除位置的下一个节点即可。时间复杂度为O(n)。
查找元素:需要从头节点开始遍历循环链表,直到找到目标元素或者遍历完整个链表为止。时间复杂度为O(n)。
遍历元素:从头节点开始遍历循环链表的所有元素,可以使用while循环或for循环实现。时间复杂度为O(n)。
反转链表:将循环链表中的所有节点反转顺序,需要使用三个指针来实现。时间复杂度为O(n)。
需要注意的是,在循环链表中,插入和删除操作需要额外考虑最后一个节点的next指针和第一个节点的prev指针的修改。同时,由于循环链表的特殊性质,遍历和查找元素时需要判断是否已经满足循环结束条件。
总之,循环链表是一种特殊的链表结构,在实现各种操作时需要注意其连续性和有序性,并针对特殊情况进行相应的处理。在实际应用中,需要根据具体的需求和场景选择合适的算法和数据结构,并进行权衡和折衷。
循环链表是一种特殊的链表结构,与其他数据结构相比具有以下优缺点:
与普通链表相比,循环链表在操作上更灵活,可以实现循环访问和遍历等功能。但是,在插入、删除和查找等操作中,其时间复杂度与普通链表相同。
与数组相比,循环链表可以动态扩容,并且支持高效的插入和删除操作。但是,在随机访问元素时,其时间复杂度较高,不如数组效率高。
与栈和队列相比,循环链表可以存储更多的元素,并支持更灵活的访问方式。但是,在插入和删除元素时,其时间复杂度相对较高,不适合频繁进行这类操作。
与树和图等复杂数据结构相比,循环链表的实现简单,易于理解和维护。但是,在对大量数据进行搜索和遍历时,其时间复杂度较高,不适合用于这类应用场景。
与哈希表相比,循环链表的查找效率较低,同时不支持快速插入和删除操作。但是,其实现简单,无需处理哈希冲突等问题。
总之,循环链表是一种特殊的链表结构,在实际应用中需要根据具体场景和需求进行选择。需要注意的是,不同数据结构之间存在权衡和折衷的问题,在使用时需要综合考虑其优点和缺点,并选择合适的数据结构和算法。