[データ構造] リンクリストの循環リンクリスト

目次

基本的な概念と操作:

実装方法と原則:

適用シナリオと使用上の注意事項:

アルゴリズムと複雑さの分析:

他のデータ構造との比較:

PS: 間違いや脱落があれば、修正してください


基本的な概念と操作:

循環リンク リストは特殊なリンク リスト構造で、最後のノードが最初のノードを指し、ループを形成するため、リスト内の任意のノードからトリガーすると、リスト内の他のノードを見つけることができます。循環リンク リストは、一方向循環リンク リストと双方向循環リンク リストの 2 つのタイプに分類できます。以下は、先頭ノードを持つ循環単一リンク リストの概略図です。

ヘッド ポインターを備えた循環単一リンク リストの概略図

循環リンク リストの場合、ポインタをテール ポインタに変更すると操作が容易になる場合があります。次の図は、テール ポインタを使用した循環単一リンク リストの模式図です。

テールポインタを備えた循環単一リンクリストの概略図

上の概略図からわかるように、線形テーブルを表すために循環テーブルを使用する論理関係は、単一リンク リストの論理関係と同じです。違いは、最後の要素の次の値を null にすることはできないことです。保存されたリンク リストの最初の要素のアドレス。

以下は、循環リンク リストに対する一般的な操作です。

  1. ヘッド ノード: 循環リンク リストの最初のノードの前のノード。通常はデータを格納せず、その主な機能は循環リンク リストの操作を容易にすることです。

  2. 末尾ノード: 循環リンク リストの最後のノード。その次のポインターは先頭ノードを指します。

  3. 要素の挿入: 指定した位置に新しい要素を挿入するには、前のノードの次のポインタと新しいノードの次のポインタを変更する必要があります。

  4. 要素の削除: 指定した位置にある要素を削除するには、前のノードの次のポインターを変更する必要があります。

  5. 要素の検索: ターゲット要素が見つかるまで、またはリンク リスト全体が走査されるまで、ヘッド ノードから循環リンク リストを走査します。

  6. 要素の走査: ヘッド ノードからの循環リンク リストのすべての要素の走査は、while ループまたは for ループを使用して実装できます。

  7. リンク リストの反転: 循環リンク リスト内のすべてのノードの順序を反転するには、3 つのポインターを使用する必要があります。

さまざまな操作を実行するときは、循環リンク リストの連続性と順序、つまり各ノードの次のポインタが次のノードを指し、最後のノードの次のポインタが次のノードを指すことを保証する必要があることに注意してください。ヘッドノード。実装の詳細については、以下の C# コードを参照してください。

  /// <summary>
    /// 循环单链表数据结构实现接口具体步骤
    /// </summary>
    /// <typeparam name="T"></typeparam>
     class CLinkedList<T>:ILinarList<T>
    {
        public SNode<T> tail;
        int length;//循环链表长度

        public CLinkedList()
        {
            this.tail = null;
        }


        /// <summary>
        /// 在链表的末尾追加数据元素 data
        /// </summary>
        /// <param name="data">数据元素</param>
        public void InsertNode(T data)
        {
            SNode<T> node = new SNode<T>(data);
            if (IsEmpty())
            {
                tail = node;
                tail.Next = tail;
            }
            else
            {
                T firstData = tail.Data;
                SNode<T> current = tail;
                while (current.Next != null && !current.Next.Data.Equals(firstData))
                {
                    current = current.Next;
                }
                current.Next = new SNode<T>(data);
                current.Next.Next = tail;
             
            }
            length++;
            return;
        }

        /// <summary>
        /// 在链表的第i个数据元素的位置前插入一个数据元素data
        /// </summary>
        /// <param name="data"></param>
        /// <param name="i"></param>
        public void InsertNode(T data, int i)
        {
            if (i < 1 || i > (length+1))
            {
                Console.WriteLine("Position is error!");
                return;
            }
            SNode<T> current;
            SNode<T> newNode = new SNode<T>(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<T> 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;
        }


        /// <summary>
        /// 删除链表的第i个数据元素
        /// </summary>
        /// <param name="i"></param>
        public void DeleteNode(int i)
        {

            if (IsEmpty() || i < 1)
            {
                Console.WriteLine("Link is empty or Position is error");
            }
            SNode<T> current = tail;
            SNode<T> 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!");
        }

        /// <summary>
        /// 获取链表的第i个数据元素
        /// </summary>
        /// <param name="i"></param>
        /// <returns></returns>
        public T SearchNode(int i)
        {
            if (IsEmpty())
            {
                Console.WriteLine("List is empty");
                return default(T);
            }
            SNode<T> 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);
        }

        /// <summary>
        /// 在链表中查找值为data的数据元素
        /// </summary>
        /// <param name="data"></param>
        /// <returns></returns>
        public T SearchNode(T data)
        {
            if (IsEmpty())
            {
                Console.WriteLine("List is empty");
                return default(T);
            }
            SNode<T> 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);
        }

        /// <summary>
        /// 获取链表的长度
        /// </summary>
        /// <returns></returns>
        public int GetLength()
        {
            return length;
        }


        /// <summary>
        /// 清空链表
        /// </summary>
        public void Clear()
        {
            tail = null;
            length = 0;
        }



        public bool IsEmpty()
        {
            return length == 0;
        }

        /// <summary>
        /// 该函数将链表头节点反转后,重新作为链表的头节点。算法使用迭代方式实现,遍历链表并改变指针指向
        /// 例如链表头结点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:....
        ///                      ]
        ///                ]
        ///          ]
        ///     ] 
        /// </summary>
        // 反转循环链表
        public void ReverseList()
        {

            if (length == 1 || this.tail == null)
            {
                return;
            }
            //定义 previous next 两个指针
            SNode<T> previous = null;
            SNode<T> next;
            SNode<T> 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;

        }
    }

つまり、循環リンク リストは特殊なリンク リスト構造であり、実用的に広く使用されています。なお、各種操作を行う際には、データの不整合などの問題を避けるため、循環リンクリストの連続性や順序性を確保する必要がある。

実装方法と原則:

循環リンク リストは特殊な一方向または双方向のリンク リスト構造であり、最後のノードの次のポインタが最初のノードを指し、循環を形成します。一方向および双方向の循環リンク リストの実装方法と原理については、他の記事で紹介しました。

つまり、循環リンク リストは特殊なリンク リスト構造であり、実装中にリンク リストの連続性と順序を確保するように注意する必要があります。

適用シナリオと使用上の注意事項:

循環リンク リストは特別なリンク リスト構造であり、ノードを反復処理する必要があるシナリオに適しています。以下に、循環リンク リストの一般的なアプリケーション シナリオをいくつか示します。

  1. ジョセフ問題: ジョセフ問題は、循環リンク リストを使用して実装できる古典的な数学の問題です。具体的には、円形リンク リストを使用して、円を形成する人々のグループをシミュレートし、M 人目の人を順番に殺害し、最後に残った人が勝者となります。

  2. キャッシュ削除アルゴリズム: キャッシュ削除アルゴリズムでは、循環リンク リストを使用して LRU (最も最近使用されていない) アルゴリズムを実装できます。具体的には、キャッシュ内のデータをアクセス時間順に循環リンクリスト化し、キャッシュがいっぱいになった場合、最も長時間アクセスされていないデータを削除することができる。

  3. マーキー効果: グラフィカル インターフェイスの開発では、テキストが循環スクロールで表示される場合でも、循環リンク リストを使用してマーキー効果を実現できます。

循環リンク リストを使用する場合は、次の点に注意する必要があります。

  1. 循環リンク リストの操作は通常のリンク リストの操作と似ており、挿入、削除、検索が比較的簡単です。ただし、無限ループやデータの不一致などの問題を避けるために、循環リンク リストの連続性と順序に注意を払う必要があります。

  2. 循環リンク リストのノードは追加のポインターを格納する必要があるため、通常のリンク リストよりも多くのスペースを占有します。

  3. 双方向循環リンクリストを使用する場合、演算ミスによるリンクリストの破損を避けるために、先頭ノードと末尾ノードの処理に注意する必要があります。

つまり、循環リンク リストは特別なリンク リスト構造であり、ノードへの反復アクセスが必要なシナリオに適しています。実際のアプリケーションでは、特定のニーズやシナリオに応じて適切なデータ構造を選択し、その特性や使用上の注意点に注意する必要があります。

アルゴリズムと複雑さの分析:

循環リンク リストの基本的なアルゴリズムには次のものがあります。

  1. 指定した位置に要素を挿入する: まず、挿入位置で前のノードを見つけてから、新しいノードの次のポインタを挿入位置のノードにポイントし、次に前のノードの次のポインタを挿入位置にポイントする必要があります。新しいノード。時間計算量は O(n) です。

  2. 指定した位置にある要素を削除する: 削除位置にある前のノードを見つけて、その次のポインターが削除位置にある次のノードを指すようにする必要があります。時間計算量は O(n) です。

  3. 要素の検索: ターゲット要素が見つかるまで、またはリンク リスト全体が横断されるまで、ヘッド ノードから循環リンク リストを横断する必要があります。時間計算量は O(n) です。

  4. 要素の走査: ヘッド ノードからの循環リンク リストのすべての要素の走査は、while ループまたは for ループを使用して実装できます。時間計算量は O(n) です。

  5. リンク リストの反転: 循環リンク リスト内のすべてのノードの順序を反転するには、3 つのポインターを使用する必要があります。時間計算量は O(n) です。

循環リンクリストでは、挿入および削除操作では、最後のノードの next ポインタと最初のノードの prev ポインタの変更をさらに考慮する必要があることに注意してください。同時に、循環リンクリストの特殊な性質により、要素を走査して検索する際には、ループの終了条件が満たされているかどうかを判断する必要があります。

つまり、循環リンクリストは特殊なリンクリスト構造であり、さまざまな操作を実行する際には、その連続性や順序に注意し、特殊な状況に応じて対処する必要があります。実際のアプリケーションでは、特定のニーズやシナリオに応じて適切なアルゴリズムとデータ構造を選択し、トレードオフや妥協を行う必要があります。

他のデータ構造との比較:

循環リンク リストは特殊なリンク リスト構造であり、他のデータ構造と比較して次のような利点と欠点があります。

  1. 循環リンクリストは、通常のリンクリストに比べて運用が柔軟であり、反復アクセスやトラバーサルなどの機能を実現できます。ただし、挿入、削除、検索などの操作では、時間計算量は通常のリンク リストと同じです。

  2. 配列と比較して、循環リンク リストは動的に拡張でき、効率的な挿入および削除操作をサポートします。ただし、要素にランダムにアクセスする場合、時間計算量は高く、配列ほど効率的ではありません。

  3. スタックやキューと比較して、循環リンク リストはより多くの要素を保存でき、より柔軟なアクセス方法をサポートできます。ただし、要素の挿入と削除の処理時間は比較的高く、この種の頻繁な操作には適していません。

  4. ツリーやグラフなどの複雑なデータ構造と比較して、循環リンク リストは実装が簡単で、理解と保守が簡単です。ただし、大量のデータを検索および走査する場合、時間計算量は高く、このようなアプリケーション シナリオには適していません。

  5. ハッシュ テーブルと比較すると、循環リンク リストの検索効率は低く、高速な挿入および削除操作はサポートされていません。ただし、実装は簡単で、ハッシュの衝突などの問題に対処する必要はありません。

つまり、循環リンク リストは特殊なリンク リスト構造であり、実際のアプリケーションでは特定のシナリオと要件に従って選択する必要があります。なお、異なるデータ構造にはトレードオフや妥協点があり、利用する場合にはそれぞれのメリット・デメリットを総合的に考慮し、適切なデータ構造やアルゴリズムを選択する必要があります。

PS: 間違いや脱落があれば、修正してください

おすすめ

転載: blog.csdn.net/beenles/article/details/131432802