首先先了解如何判断单链表中是否有环
环的定义:链表中的尾节点指向了链表中的某个节点。
方法一:使用p、q两个指针,p一直往前走,q每次从头往前走,当p等于q但是p、q移动的步数不相等的时候,存在环。
方法二:使用p、q两个指针,p每次往前走一步,q每次往前走两步,当存在p==q的时候,存在环。
循环链表,只要将单链表的最后一个指针域(空指针)指向链表中第一个结点即可(这里之所以说第一个结点而不说是头结点是因为,如果循环单链表是带头结点的则最后一个结点的指针域要指向头结点;如果循环单链表不带头结点,则最后一个指针域要指向开始结点)。
带头结点的循环单链表当head等于head->next时链表为空;
不带头结点的循环单链表当head等于null时链表为空。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace _001_线性表
{
/// <summary>
/// 单链循环链表
/// </summary>
/// <typeparam name="T"></typeparam>
class LoopLinkList<T> : IListDS<T>
{
private Node<T> head;
public Node<T> Head
{
set
{
head = value;
head.Next = head;
}
get { return head; }
}
public T this[int index] => GetEle(index);
/// <summary>
/// 在末端添加一个新节点
/// </summary>
/// <param name="item"></param>
public void Add(T item)
{
Node<T> newNode = new Node<T>(item);
if (IsEmpty()) //判断是否为空
{
this.Head = newNode;
return;
}
else
{
Node<T> temp = head;
while (temp.Next != head) //循环 判断是否为头部,是就跳出循环
{
temp = temp.Next;
}
temp.Next = newNode;
newNode.Next = head;
}
}
/// <summary>
/// 清空表元素
/// </summary>
public void Clear()
{
head = null;
}
/// <summary>
/// 删除链表指定位置的元素
/// </summary>
/// <param name="index"></param>
/// <returns></returns>
public T Delete(int index)
{
T data = default(T);
if (IsEmpty())
throw new Exception("链表为空,没有可清除的项");
if (index < 0 || index > this.GetLength() - 1)
throw new Exception("给定索引超出链表长度");
Node<T> preNode = head;
if (index == 0)
{
while (preNode.Next!=head)
{
preNode = preNode.Next;
}
this.head = head.Next;
data=preNode.Next.Data;
preNode.Next = this.head;
}
else
{
for (int i = 1; i < index-1; i++)
{
preNode = preNode.Next;
}
preNode.Next = preNode.Next.Next;
}
return data;
}
/// <summary>
/// 找到链表指定位置的元素
/// </summary>
/// <param name="index"></param>
/// <returns></returns>
public T GetEle(int index)
{
if (index < 0 || index > this.GetLength() - 1)
throw new Exception("索引超出链表长度");
Node<T> node = head;
for (int i = 0; i < index; i++)
{
node = node.Next;
}
return node.Data;
}
/// <summary>
/// 获取链表长度
/// </summary>
/// <returns></returns>
public int GetLength()
{
if (IsEmpty())
{
return 0;
}
else
{
int length = 1;
Node<T> temp = head;
while (temp.Next!=head)
{
temp = temp.Next;
length++;
}
return length;
}
}
/// <summary>
/// 在链表指定的位置插入一个新节点
/// </summary>
/// <param name="item"></param>
/// <param name="index"></param>
public void Insert(T item, int index)
{
if (IsEmpty())
throw new Exception("数据链表为空");
if (index < 0 || index > this.GetLength())
throw new Exception("给定索引超出链表长度");
Node<T> newNode = new Node<T>(item);
Node<T> preNode = head;
if (index == 0) //等于零 先找到 链表中 head的前一个节点 这个节点连接 head
{
while (preNode.Next!=head)
{
preNode = preNode.Next;
}
preNode.Next = newNode;
newNode.Next = this.head;
return;
}
else
{
for (int i = 1; i < index-1; i++)
{
preNode = preNode.Next;
}
Node<T> atfNode= preNode.Next;
preNode.Next = newNode;
newNode.Next = atfNode;
}
}
/// <summary>
/// 链表是否为空
/// </summary>
/// <returns></returns>
public bool IsEmpty()
{
return head == null;
}
/// <summary>
/// 根据给定的值查找链表中哪个元素为这个值,如果链表中存在两个元素值相同,则取排在链表前面的元素
/// </summary>
/// <param name="value"></param>
/// <returns></returns>
public int Locate(T value)
{
if (IsEmpty())
throw new Exception("链表为空");
Node<T> preNode = head;
int index = 0;
while (true)
{
if (!preNode.Data.Equals(value))
{
if (preNode.Next != head)
{
index++;
preNode = preNode.Next;
}
else
{
break;
}
}
else
{
return index;
}
}
return -1;
}
}
}
循环链表的目的是只要知道表中任一一个节点的地址,就能遍历表中其他任一节点。
相关题目
问题一:魔术师发牌问题
问题描述:魔术师利用一副牌中的13张黑牌,预先将他们排好后叠放在一起,牌面朝下。对观众说:“我不看牌,只数数就可以猜到每张牌是什么,我大声数数,你们听,不信?现场演示。”魔术师将最上面的那张牌数为1,把他翻过来正好是黑桃A,将黑桃A放在桌子上,第二次数1,2,将第一张牌放在这些牌的下面,将第二张牌翻过来,正好是黑桃2,也将他放在桌子上,这样一次进行,将13张牌全部翻出,准确无误。
class Solution1
{
/// <summary>
/// 为扑克牌排序,使其能够在一定规则中从A到K的输出
/// </summary>
private void SortPoker(ref Poker pHead)
{
Poker pNode = pHead;
int cur1 = 1; //用于统计扑克牌中已放好位置的牌数
int count = 2; //数多少张牌放入牌堆下面
while (true)
{
//往后数n张牌,然后翻出牌值为n的牌
for (int i = 0; i < count;)
{
pNode = pNode.next;
if (pNode.data == 0)
{
i++;
}
}
pNode.data = ++cur1;
//令下一张牌的牌值加1
count++;
//牌数为13张,从A~K,排完所有的牌则跳出循环
if (cur1 == 13)
break;
}
//打印排好序的牌
PrintPoker(pHead);
}
/// <summary>
/// 创建扑克牌并为其赋值
/// </summary>
/// <returns>扑克牌堆最上面的牌值</returns>
public Poker CreateCircleLinkList()
{
Poker pHead = new Poker(1);
Poker pNode = pHead;
//创建需要排序的13张牌并初始化,每张牌的牌值为0
for(int i=1;i<13;i++)
{
pNode.next = new Poker(0)
{
next = pHead
};
pNode = pNode.next;
}
//给13张牌排序
SortPoker(ref pHead);
return pHead;
}
/// <summary>
/// 打印输出扑克的牌值
/// </summary>
/// <param name="pHead">牌堆的第一张牌</param>
private void PrintPoker(Poker pHead)
{
Poker pNode = pHead;
while (true)
{
Console.Write(pNode.data + "\t");
pNode = pNode.next;
if (pNode == pHead)
break;
}
Console.WriteLine();
}
}
问题二:拉丁方阵问题
拉丁方阵是一种m×n的方阵,方阵中恰有n中不同的元素,每种元素恰有n个,并且每种元素在一行和一列中切好出现一次。著名数学家和物理学家欧拉使用拉丁字母来作为拉丁方阵里元素的符号,拉丁方阵因此而得名。
下图为一个3×3的拉丁方阵:
1 | 2 | 3 |
---|---|---|
2 | 3 | 1 |
3 | 1 | 2 |
请写出一个算法,当输入一个n的时候,可以输出一个n✖n的拉丁方阵,拉丁方阵的值从1到n
class Solution2
{
public List<LaDingLinkNode> CreateLaDingMatrix(int n)
{
if (n <= 0)
return null;
//方阵
List<LaDingLinkNode> list = new List<LaDingLinkNode>();
//方阵第一列的头节点
LaDingLinkNode pHead = new LaDingLinkNode(1);
//方阵1️已创建的行和列
int rows = 1, cols = 1;
LaDingLinkNode pNode = pHead;
//先创建方阵的第一行
for (; cols < n; cols++)
{
pNode.next = new LaDingLinkNode(cols + 1)
{
next = pHead
};
pNode = pNode.next;
}
//把第一列添加进方阵中
list.Add(pHead);
//方便把每一列添加进方阵中,只需每次把当前节点的下一个节点加入方阵即可,因为是循环链表,所以每一列的节点都会按照拉丁方阵分配
pNode = pHead;
while (rows < n)
{
rows++;
list.Add(pNode.next);
pNode = pNode.next;
}
return list;
}
public void PrintLaDingMatrix(List<LaDingLinkNode> list)
{
foreach(LaDingLinkNode item in list)
{
LaDingLinkNode pNode = item;
while (pNode.next != item)
{
Console.Write(pNode.data + "\t");
pNode = pNode.next;
}
Console.WriteLine(pNode.data);
}
Console.WriteLine();
}
}
下面为测试的代码:
“`
class Program
{
static void Main(string[] args)
{
Solution1 s1 = new Solution1();
s1.CreateCircleLinkList();
Solution2 s2 = new Solution2();
s2.PrintLaDingMatrix(s2.CreateLaDingMatrix(int.Parse(Console.ReadLine())));
}
}
下列为运行结果: