上一篇中,我们了解了单链表与双链表,本次将单链表中终端结点的指针端由空指针改为指向头结点,就使整个单链表形成一个环,这种头尾相接的单链表称为单循环链表,简称循环链表(circular linked list)。

1.1 循环链表节点结构

数据结构基础温故-1.线性表(下)

  循环链表和单链表的主要差异就在于循环的判断条件上,原来是判断p.next是否为空,现在则是p.next不等于头结点,则循环未结束。

1.2 循环链表的O(1)访问时间

  在单链表中,有了头结点,我们可以在O(1)时间访问到第一个节点,但如果要访问最后一个节点却需要O(n)的时间,因为我们需要对整个链表进行一次遍历。在循环链表中,我们可以借助尾节点来实现,即不用头指针,而是用指向终端结点的尾指针来表示循环链表,这时候无论是查找第一个节点还是最后一个节点都很方便,可以控制在O(1)的时间内,如下图所示。

数据结构基础温故-1.线性表(下)

  从上图中可以看到,终端结点用尾指针(tail)指示,则查找终端结点是O(1),而开始结点,其实就是tail.Next,其时间复杂也为O(1)。由此也可以联想到,在合并两个循环链表时,只需要修改两个链表的尾指针即可快速地进行合并。

二、循环链表实现

2.1 循环链表节点的定义实现

    public class CirNode<T>
    {
        public T Item { get; set; }
        public CirNode<T> Next { get; set; }

        public CirNode()
        {
        }

        public CirNode(T item)
        {
            this.Item = item;
        }
    }

  这里跟单链表的节点定义实现并无区别。

2.2 循环链表新节点的插入实现

        public void Add(T value)
        {
            CirNode<T> newNode = new CirNode<T>(value);
            if (this.tail == null)
            {
                // 如果链表当前为空则新元素既是尾头结点也是头结点
                this.tail = newNode;
                this.tail.Next = newNode;
                this.currentPrev = newNode;
            }
            else
            {
                // 插入到链表末尾处
                newNode.Next = this.tail.Next;
                this.tail.Next = newNode;
                // 改变当前节点
                if (this.currentPrev == this.tail)
                {
                    this.currentPrev = newNode;
                }
                // 重新指向新的尾节点
                this.tail = newNode;
            }

            this.count++;
        }

  首先,这里的currentPrev字段是使用了前驱节点来标识当前节点,如要获取当前节点的值可以通过currentPrev.Next.Item来获得。其次,在最后将尾节点指针指向新插入的节点。

2.2 循环链表当前节点的移除实现

        public void Remove()
        {
            if (this.tail == null)
            {
                throw new NullReferenceException("链表中没有任何元素");
            }
            else if (this.count == 1)
            {
                // 只有一个元素时将两个指针置为空
                this.tail = null;
                this.currentPrev = null;
            }
            else
            {
                if (this.currentPrev.Next == this.tail)
                {
                    this.tail = this.currentPrev;
                }
                // 移除当前节点
                this.currentPrev.Next = this.currentPrev.Next.Next;
            }

            this.count--;
        }

  这里考虑到删除节点时必须寻找其前驱节点会导致链表进行遍历,故使用了当前节点的前驱节点来标识这个当前节点。移除当前节点只需要currentPrev.Next = currentPrev.Next.Next即可。

  以下是单循环链表的完整模拟实现代码,需要注意的是该CircularLinkedList主要是为下面的约瑟夫问题而设计,故只实现了一些很简单的功能:

    /// <summary>
    /// 单向循环链表的模拟实现
    /// </summary>
    public class MyCircularLinkedList<T>
    {
        private int count; // 字段:记录数据元素个数
        private CirNode<T> tail; // 字段:记录尾节点的指针
        private CirNode<T> currentPrev; // 字段:使用前驱节点标识当前节点

        // 属性:指示链表中元素的个数
        public int Count
        {
            get
            {
                return this.count;
            }
        }

        // 属性:指示当前节点中的元素值
        public T CurrentItem
        {
            get
            {
                return this.currentPrev.Next.Item;
            }
        }

        public MyCircularLinkedList()
        {
            this.count = 0;
            this.tail = null;
        }

        public bool IsEmpty()
        {
            return this.tail == null;
        }

        // Method01:根据索引获取节点
        private CirNode<T> GetNodeByIndex(int index)
        {
            if (index < 0 || index >= this.count)
            {
                throw new ArgumentOutOfRangeException("index", "索引超出范围");
            }

            CirNode<T> tempNode = this.tail.Next;
            for (int i = 0; i < index; i++)
            {
                tempNode = tempNode.Next;
            }

            return tempNode;
        }

        // Method02:在尾节点后插入新节点
        public void Add(T value)
        {
            CirNode<T> newNode = new CirNode<T>(value);
            if (this.tail == null)
            {
                // 如果链表当前为空则新元素既是尾头结点也是头结点
                this.tail = newNode;
                this.tail.Next = newNode;
                this.currentPrev = newNode;
            }
            else
            {
                // 插入到链表末尾处
                newNode.Next = this.tail.Next;
                this.tail.Next = newNode;
                // 改变当前节点
                if (this.currentPrev == this.tail)
                {
                    this.currentPrev = newNode;
                }
                // 重新指向新的尾节点
                this.tail = newNode;
            }

            this.count++;
        }

        // Method03:移除当前所在节点
        public void Remove()
        {
            if (this.tail == null)
            {
                throw new NullReferenceException("链表中没有任何元素");
            }
            else if (this.count == 1)
            {
                // 只有一个元素时将两个指针置为空
                this.tail = null;
                this.currentPrev = null;
            }
            else
            {
                if (this.currentPrev.Next == this.tail)
                {
                    // 当删除的是尾指针所指向的节点时
                    this.tail = this.currentPrev;
                }
                // 移除当前节点
                this.currentPrev.Next = this.currentPrev.Next.Next;
            }

            this.count--;
        }

        // Method04:获取所有节点信息
        public string GetAllNodes()
        {
            if (this.count == 0)
            {
                throw new NullReferenceException("链表中没有任何元素");
            }
            else
            {
                CirNode<T> tempNode = this.tail.Next;
                string result = string.Empty;
                for (int i = 0; i < this.count; i++)
                {
                    result += tempNode.Item + " ";
                    tempNode = tempNode.Next;
                }

                return result;
            }
        }
    }
View Code

相关文章:

  • 2021-11-27
  • 2021-09-11
  • 2021-12-06
  • 2021-05-09
  • 2021-10-26
  • 2022-03-01
  • 2022-12-23
  • 2021-07-28
猜你喜欢
  • 2021-09-30
  • 2021-08-16
  • 2022-01-17
  • 2021-05-31
  • 2021-12-21
  • 2021-11-23
  • 2021-07-18
相关资源
相似解决方案