線形リスト、二重リンクリスト、静的リンクリスト、循環リンクリスト(ジョセフリング)

目次

リニアテーブル(リニアストレージ構造)とは

シーケンシャルストレージ構造とチェーンストレージ構造

先代と後継者

シーケンステーブル(逐次記憶構造)と初期化の詳細

シーケンステーブルの初期化

二重リンクリストとその作成(C言語)詳細解説

二重リンクリストの作成

静的リンクリストとその作成(C言語実装)

静的リンクリスト内のノード

スタンバイリスト

静的リンクリストを作成する

循環リンクリスト(ジョセフリング)の確立とC言語での実装

循環リンクリストはジョセフリングを実装します

要約する


リニアテーブル(リニアストレージ構造)とは

これまでの研究により、「1 対 1」の論理関係でデータを保存する最良の方法は、線形テーブルを使用することであることがわかりました。では、線形テーブルとは何でしょうか?

線形テーブル、正式名は線形ストレージ構造です線形テーブルを使ったデータの格納方法は、 「すべてのデータを線で繋いで物理空間に格納する」というように理解できます

 

「1対1」の論理関係データ


図1 「1対1」の論理関係のデータ


図 1 に示すように、これは「1 対 1」の関係を持つデータのセットであり、線形テーブルを使用して物理空間に格納します。

まず、図 2 に示すように、「スレッド」を使用してそれらを順番に「文字列」にします。

 


図2 データの「線形」構造


図 2 では、左側が「文字列」データ、右側が物理的な空き領域です。このデータの「文字列」を物理空間に配置するには、図 3 に示すように、次の 2 つの方法を選択できます。

 

2つのリニアストレージ構造


図 3 2 つの直線状のストレージ構造


図 3a) はほとんどの人が考える保存方法ですが、図 3b) はほとんど考えられません。データ ストレージの成功は、データを元の形式に完全に復元できるかどうかにかかっています。図 3a) と図 3b) の線の一方の端を引き上げると、データの位置が変わっていないことがわかります (図 1 と同じ)。したがって、どちらの保存方法も正しいと結論付けることができます。「1対1」の関係にあるデータは物理空間上に「線形」に格納されており、この格納構造を線形記憶構造(線形テーブルと呼ぶ)

と呼ぶ線形テーブルに格納されるデータは、配列に格納されるデータと同じデータ型である必要があります。つまり、線形テーブルに格納されるデータは、すべて整数かすべて文字列のいずれかです。半分が整数、もう半分が文字列であるデータのセットは、線形テーブルに格納できません。

シーケンシャルストレージ構造チェーンストレージ構造

図 3 から、線形テーブルのストレージ データは次の 2 つのタイプに細分できることがわかります。

  1. 図 3a) に示すように、データは物理空間の連続したブロックに順番に格納され、この格納構造はシーケンシャル ストレージ構造(略してシーケンス テーブル)と呼ばれます。
  2. 図 3b) に示すように、データは物理空間に点在して格納され、データ間の論理的な関係は線で保存され、この格納構造は連鎖格納構造 (リンク リストと呼ばれます) と呼ばます


すなわち、線形テーブル記憶構造は、順次記憶構造とリンク記憶構造とに細分することができる。

先代と後継者

データ構造では、一連のデータ内の個々の要素を「データ要素」(または略して要素」)と呼びます。たとえば、図 1 に示すデータ セットでは、1、2、3、4、および 5 はすべてデータ セットの要素です。

また、「1 対 1」の論理関係を持つデータについて、「要素の左側 (前) または右側 (後)」など、専門的ではない言葉を使用してきましたが、実際には、より正確な表現があります。線形表の項:

  • 要素の左に隣接する要素は「直接先行要素」と呼ばれ、この要素の左側にある総称して「先行要素」と呼ばれます。
  • 要素の右に隣接する要素は「直接後続要素」と呼ばれ、この要素の右側にあるすべての要素は総称して「後続要素」と呼ばれます。


図 1 のデータの要素 3 を例にとると、その直接の先行要素は 2 であり、この要素の 2 つの前駆要素、つまり 1 と 2 が存在します。同様に、この要素の直接の後続要素は 4 であり、また 2 もあります。後続要素、それぞれ 4 と 5。図 4 に示すように:

 

先代と後継者


図 4 先行者と後継者

シーケンステーブル(逐次記憶構造)と初期化の詳細

シーケンス テーブル (フルネーム シーケンシャル ストレージ構造) は、線形テーブルの一種です。「線形テーブルとは」セクションの学習を通じて、線形テーブルは「1 対 1」の論理関係でデータを格納するために使用され、順序テーブルも例外ではないことがわかりました。

それだけでなく、シーケンス テーブルにはデータの物理的なストレージ構造に関する要件もあります。シーケンシャルテーブルは、データを格納する際に、十分なサイズの物理空間をあらかじめ割り当ててから、データ要素間に隙間ができないように順番にデータを格納します。

たとえば、シーケンシャル テーブルを使用してコレクションを保存すると {1,2,3,4,5}、データの最終的な保存状態が図 1 に示されます。


 


図1 シーケンシャルストレージ構造の模式図


このことから、「物理空間全体に『1対1』の論理関係を持つデータを順番に格納する」ストレージ構造はシーケンシャルストレージ構造であると結論付けることができます。

図 1 のデータの格納状態を観察すると、シーケンシャル テーブルに格納されているデータが配列に非常に近いことがわかります。実際、シーケンス テーブルは配列を使用してデータを保存します。

シーケンステーブルの初期化

シーケンス テーブルを使用してデータを保存する前に、十分なサイズの物理スペースを適用することに加えて、テーブル内のデータを後で使用しやすくするために、シーケンス テーブルには次の 2 つのデータ項目も実際に記録する必要があります。時間:

  1. シーケンス テーブルによって要求されるストレージ容量。
  2. シーケンス テーブルの長さ、つまりテーブルに格納されているデータ要素の数。

ヒント: 通常の状態では、シーケンス テーブルによって要求されるストレージ容量はシーケンス テーブルの長さよりも大きくなります。

したがって、シーケンス テーブルをカスタマイズする必要があり、C 言語の実装コードは次のとおりです。

 
 
  1. typedef 構造体テーブル{
  2. int * head;//「動的配列」とも呼ばれる、head という名前の不定長の配列を宣言します。
  3. int length;//現在のシーケンステーブルの長さを記録します
  4. int size;//レコードシーケンステーブルによって割り当てられるストレージ容量
  5. }テーブル;

head は宣言した初期化されていない動的配列であり、通常のポインタとして扱うだけではないことに注意してください。

次に、シーケンス テーブルの初期化、つまりシーケンス テーブルを最初に確立する方法の学習を開始します。シーケンス テーブルを作成するには、次の操作を行う必要があります。

  • ヘッドの動的データ用に十分な物理スペースを適用します。
  • サイズと長さに初期値を割り当てます。


したがって、C 言語の実装コードは次のようになります。

 
 
  1. #define Size 5 //シーケンステーブルのアプリケーション空間のサイズを示すSizeのマクロ定義を作成します。
  2. テーブル initTable(){
  3. タブレット;
  4. t.head=(int*)malloc(Size*sizeof(int));//空のシーケンステーブルを構築し、ストレージスペースを動的に適用します
  5. if (!t.head) //アプリケーションが失敗した場合は、プロンプトを表示してプログラムを直接終了します
  6. {
  7. printf("初期化に失敗しました");
  8. 終了(0);
  9. }
  10. t.length=0;//空のテーブルの長さは0に初期化されます
  11. t.size=Size;//空のテーブルの初期ストレージスペースは Size です
  12. t を返します。
  13. }

シーケンス テーブルの初期化プロセス全体が関数にカプセル化されており、この関数の戻り値が初期化されたシーケンス テーブルであることがわかります。これの利点は、コードの使いやすさが向上し、より美しくなることです。同時に、シーケンステーブルの初期化処理では、物理空間のアプリケーションの判断やアプリケーションの失敗への対応にも注意する必要があります。ここでは、「出力プロンプト」の動作のみを説明します。情報と強制終了」が実行され、必要に応じて変更できます。コード内の if ステートメントが改善されました。

main 関数で initTable ステートメントを呼び出すと、空のシーケンス テーブルが正常に作成されます。同時に、シーケンス テーブルに要素を追加することもできます。C 言語の実装コードは次のとおりです。

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #define サイズ 5
  4. typedef 構造体テーブル{
  5. int * ヘッド;
  6. 整数の長さ;
  7. 整数サイズ;
  8. }テーブル;
  9. テーブル initTable(){
  10. タブレット;
  11. t.head=(int*)malloc(Size*sizeof(int));
  12. if (!t.head)
  13. {
  14. printf("初期化に失敗しました");
  15. 終了(0);
  16. }
  17. t.length=0;
  18. t.size=サイズ;
  19. t を返します。
  20. }
  21. //配列表の要素を出力する関数
  22. void displayTable(テーブル t){
  23. for (int i=0;i<t.length;i++) {
  24. printf("%d ",t.head[i]);
  25. }
  26. printf("\n");
  27. }
  28. int main(){
  29. テーブル t=initTable();
  30. //リストに要素を追加します
  31. for (int i=1; i<=Size; i++) {
  32. t.head[i-1]=i;
  33. t.length++;
  34. }
  35. printf("シーケンス テーブルに格納されている要素は次のとおりです:\n");
  36. ディスプレイテーブル(t);
  37. 0を返します。
  38. }

プログラムを実行した結果は次のようになります。

シーケンス テーブルに格納される要素は次のとおりです:
1 2 3 4 5

ご覧のとおり、シーケンス テーブルは正常に初期化されています。

 

二重リンクリストとその作成(C言語)詳細解説

これまでに学習したリンク リストは、動的リンク リストであっても静的リンク リストであっても、テーブル内の各ノードにはポインター (カーソル) が 1 つだけ含まれており、それらはすべて一律に直接の後続ノードを指します。 list は通常、一方向連結リスト (または単一連結リスト) と呼ばれます。

単一リンク リストを使用すると、「1 対 1」のデータ論理関係のストレージの問題を 100% 解決できますが、いくつかの特別な問題を解決する場合、単一リンク リストは最も効率的なストレージ構造ではありません。たとえば、アルゴリズムが指定されたノードの多数の先行ノードを検索する必要がある場合、単一リンク リストの使用は間違いなく悲惨です。なぜなら、単一リンク リストは「前から後ろへ」の検索と「後ろから」検索に適しているからです。 「前へ」検索とそれが得意ではありません。

同様の問題を効率的に解決するために、このセクションでは二重連結リスト (二重連結リストと呼ばれます) について学習します。

二重リンク リストは名前から理解できます。つまり、図 1 に示すように、リンク リストは「双方向」です。


 

二重リンクリスト構造の模式図


図1 二重リンクリストの構造の模式図

双方向とは、ノード間の論理関係が双方向であることを意味しますが、実際の状況で必要な場合を除き、通常はヘッド ポインタが 1 つだけ設定されます

図 1 からわかるように、二重リンク リストの各ノードには、次の 3 つの部分の情報が含まれています (図 2 を参照)。

  1. ポインタフィールド:現在のノードの直接の先行ノードを指すために使用されます。
  2. データ フィールド: データ要素を格納するために使用されます。
  3. ポインタフィールド:現在のノードの直接の後続ノードを指すために使用されます。


 

二重リンクリストのノード構成


図2 二重リンクリストのノード構成


したがって、二重リンクリストのノード構造は C 言語で次のように実装されます。

  1. typedef 構造体行{
  2. struct line * priority; // 直前の行を指す
  3. int データ;
  4. struct line * next; // 直接の後続オブジェクトを指す
  5. }ライン;

二重リンクリストの作成

シングルリンク リストと比較すると、ダブルリンク リストには、各ノードが直接の先行ノードを指すポインター フィールドが 1 つだけ増えています。したがって、単一リンクリストに基づいて二重リンクリストを簡単に作成できます。

単一リンク リストとは異なり、二重リンク リストの作成プロセスでは、新しいノードが作成されるたびに、その先行ノードとの 2 つの接続を確立する必要があることに注意してください。

  • 新しいノードの前のポインタが直接の先行ノードを指すようにします。
  • 直接の先行ノードの次のポインタが新しいノードを指すようにします。


二重リンクリストを作成するための C 言語実装コードは次のとおりです。

  1. line* initLine(line * head){
  2. head=(line*)malloc(sizeof(line));//リンクリストの最初のノード(ヘッドノード)を作成
  3. head->prior=NULL;
  4. head->next=NULL;
  5. ヘッド->データ=1;
  6. line * list=head;
  7. for (int i=2; i<=3; i++) {
  8. // 新しいノードを作成して初期化する
  9. line * body=(line*)malloc(sizeof(line));
  10. 本文->前=NULL;
  11. 本文->次=NULL;
  12. 本体->データ=i;
  13. list->next=body;//直接の先行ノードの次のポインタは新しいノードを指します
  14. body->prior=list;//新しいノードは直接の先行ノードを指します
  15. リスト=リスト->次;
  16. }
  17. 頭を戻します。
  18. }


作成した二重リンクリストを main 関数で出力してみます。C 言語コードは次のとおりです。

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. //ノード構造
  4. typedef 構造体行{
  5. 構造体行 * 前;
  6. int データ;
  7. 構造体行 * 次;
  8. }ライン;
  9. //二重リンクリストの関数を作成
  10. line* initLine(line * head);
  11. //二重連結リストを出力する関数
  12. void 表示(行 * 先頭);
  13. int main() {
  14. //ヘッドポインタを作成する
  15. 行 * ヘッド = NULL;
  16. //リンクリスト作成関数を呼び出す
  17. head=initLine(head);
  18. //作成したリンクリストを出力する
  19. ディスプレイ(ヘッド);
  20. //二重リンクリストの利点を表示
  21. printf("リンク リストの 4 番目のノードの直前のノードは: %d", head->next->next->next->prior->data);
  22. 0を返します。
  23. }
  24. line* initLine(line * head){
  25. // ヘッド ノードを作成します。リンク リストのヘッド ポインタは head です。
  26. head=(line*)malloc(sizeof(line));
  27. //ノードを初期化する
  28. head->prior=NULL;
  29. head->next=NULL;
  30. ヘッド->データ=1;
  31. // 後でリンク リストに新しく作成したノードを追加しやすくするために、ヘッド ノードへのポインタを宣言します。
  32. line * list=head;
  33. for (int i=2; i<=5; i++) {
  34. // 新しいノードを作成して初期化します
  35. line * body=(line*)malloc(sizeof(line));
  36. 本文->前=NULL;
  37. 本文->次=NULL;
  38. 本体->データ=i;
  39. //新しいノードは、リンクされたリストの最後のノードとの関係を確立します
  40. リスト->次=本文;
  41. 本体->前=リスト;
  42. //リストは常にリンクされたリストの最後のノードを指します
  43. リスト=リスト->次;
  44. }
  45. //新しく作成したリンクリストを返す
  46. 頭を戻します。
  47. }
  48. void display(line * head){
  49. 行 * temp=head;
  50. while (温度) {
  51. //このノードに後続ノードがない場合、このノードがリンク リストの最後のノードであることを意味します
  52. if (temp->next==NULL) {
  53. printf("%d\n",temp->data);
  54. }それ以外{
  55. printf("%d <-> ",temp->data);
  56. }
  57. temp=temp->next;
  58. }
  59. }

プログラム実行結果:

1 <-> 2 <-> 3 <-> 4 <-> 5
リンク リストの 4 番目のノードの直前のノードは次のとおりです: 3

 

静的リンクリストとその作成(C言語実装)

シーケンスリストとリンクドリストのメリットとデメリット」では、2つの記憶構造のそれぞれの特徴について学びましたが、ではシーケンスリストリンクドリストのそれぞれの利点を兼ね備えた記憶構造はあるのか、これにより、要素にすばやくアクセスでき、データ要素をすばやく追加または削除できます

静的リンク リストも線形ストレージ構造の一種で、シーケンシャル リストとリンク リストの両方の利点を考慮しており、シーケンシャル リストとリンク リストのアップグレード版とみなすことができます。

データを保存するには静的リンク リストを使用します。すべてのデータは配列 (シーケンシャル テーブルと同じ) に保存されますが、保存場所はランダムであり、データ間の「1 対 1」の論理関係は整数変数を介して渡されます。 (「カーソル」と呼ばれる、ポインタと同様の機能)を保持します(リンクリストと同様)。

たとえば、静的リンク リスト ストレージを使用するプロセスは {1,2,3} 次のとおりです。

図 1 に示すように、サイズが 6 であると仮定して、十分に大きな配列を作成します。


 

空の配列


図 1 空の配列


次に、データを配列に格納するときに、図 2 に示すように、各データ要素には整数変数が装備されます。この変数は、各要素の直接の後続要素が配置される配列内の位置の添字を示すために使用されます。


 

静的リンク リストにはデータが保存されます


図 2 静的リンク リストにデータが格納される

通常、静的リンク リストは、最初のデータ要素を配列添字 1 (a[1]) の位置に配置します。

図 2 では、

a[1] に格納されているデータ要素 1 から開始して、格納されているカーソル変数 3 を介して、要素 1 の直接の後続要素 2 を a[3] で見つけることができます。

要素a[3]に格納されているカーソル変数5により、要素2の直接の後続要素3がa[5]に見つかり、要素のカーソル変数が0になるまでループ処理が終了します( [0] はデフォルトではデータ要素を保存しません)

図 2 と同様に、「配列 + カーソル」のような線形関係でデータを格納する記憶構造は、静的リンク リストです。

静的リンクリスト内のノード

上記の調査を通じて、データ要素を静的リンク リストに保存するにはカスタム データ型も必要であることがわかりました。カスタム データ型には、少なくとも次の 2 つの部分の情報が含まれている必要があります。

  • データフィールド: データ要素の値を保存するために使用されます。
  • カーソル: 実際、これは配列の添字であり、配列内で直接の後続要素が配置される位置を示します。


したがって、静的リンク リスト内のノードの構成は、C 言語で次のように実装されます。

 
 
  1. typedef 構造体 {
  2. int データ;//データフィールド
  3. int cur;//カーソル
  4. }成分;

スタンバイリスト

図 2 に示す静的リンク リストは完全ではなく、静的リンク リストでは、カーソルを介してデータ自体によって形成されるリンク リストに加えて、各自由位置を接続するリンク リストが必要です。スタンバイリンクリスト。

スタンバイ リンク リストの役割は、アレイ内の未使用または以前に使用された (現在未使用の) 記憶域スペースを後で使用できるように再利用することです。つまり、静的リンク リストの配列によって適用される物理空間には 2 つのリンク リストがあり、1 つはデータをリンクし、もう 1 つは配列内の未使用スペースをリンクします。

通常、スタンバイ リンク リストの先頭は配列添字 0 (a[0]) の位置にあり、データ リンク リストの先頭は配列添字 1 (a[1]) の位置にあります。 。

静的リンク リストにスタンバイ リンク リストを設定する利点は、配列に空き位置があるかどうかを明確に把握できるため、新しいデータを追加するときにデータ リンク リストを使用できることです。例えば、静的リンクリストにおいて配列の添字が0の位置にデータが格納されていれば、配列が満杯であることがわかります。

たとえば、静的リンク リスト ストレージを使用し {1,2,3}、長さ 6 の配列 a が使用されると仮定すると、ストレージの状態は図 3 のようになります。


 

スタンバイリンクリストとデータリンクリスト


図3 スタンバイリンクリストとデータリンクリスト


図3では、スタンバイリンクリストではa[0]、a[2]、a[4]が順に接続され、スタンバイリンクリストではa[1]、a[3]、a[5]が順に接続されています。データリンクリスト。

静的リンクリストを作成する

静的リンク リスト (配列長 6) がストレージに使用されると仮定すると {1,2,3}、次の段階が必要です。

  1. データ リンク リストが初期化される前は、配列内のすべての位置が空いているため、図 4 に示すように、スタンバイ リンク リストにリンクする必要があります。


     

    データを保存する前の静的リンク リストの状態


    図4 データ保存前の静的リンクリストの状態


    静的リンクリストにデータを追加する場合、新たなデータを使用するために、事前にスタンバイリンクリストからノードを削除する必要があります。

    スタンバイ リンク リストからノードを削除する最も簡単な方法は、a[0] の直接後継ノードを削除することです。同様に、スタンバイ リンク リストにアイドル ノードを追加するには、a[0] の新しい直接後継ノードを追加します。a[0] はバックアップ リストの最初のノードであるため、その位置がわかっており、バックアップ リストを横断せずにその直接の後続ノードを操作するのは比較的簡単で、時間計算量です O(1)

  2. 図 4 に基づいて、要素 1 を静的リンク リストに追加するプロセスを図 5 に示します。


     

    要素 1 を静的リンク リストに追加します。


    図 5 静的リンク リストへの要素 1 の追加

  3. 図 5 に基づいて、要素 2 を追加するプロセスを図 6 に示します。


     

    静的リンク リスト 2 への要素の追加を続けます。


    図 6 静的リンク リスト 2 に要素を追加し続ける

  4. 図 6 に基づいて要素 3 を追加し続けると、そのプロセスが図 7 に示されます。


     

    静的リンク リスト 3 に要素を追加し続けます。


    図 7 静的リンク リスト 3 に要素を追加し続ける

このようにして、静的リンクリストが作成される。

静的リンク リストを作成するための C 言語実装コードを以下に示します。

  1. #include <stdio.h>
  2. #maxSize 6 を定義します
  3. typedef 構造体 {
  4. int データ;
  5. int cur;
  6. }成分;
  7. //構造体配列内のすべてのコンポーネントをスタンバイ リストにリンクします
  8. voidreserveArr(component *array);
  9. //静的リンクリストを初期化する
  10. int initArr(コンポーネント *配列);
  11. // 出力関数
  12. void displayArr(component * array,int body);
  13. //空きノードをスタンバイリストから削除する関数
  14. int mallocArr(コンポーネント * 配列);
  15. int main() {
  16. コンポーネント配列[maxSize];
  17. int body=initArr(配列);
  18. printf("静的リンク リストは:\n");
  19. displayArr(配列, 本体);
  20. 0を返します。
  21. }
  22. //バックアップリストを作成する
  23. voidreserveArr(component *array){
  24. for (int i=0; i<maxSize; i++) {
  25. array[i].cur=i+1;//各配列コンポーネントをリンクする
  26. 配列[i].data=-1;
  27. }
  28. array[maxSize-1].cur=0;//リンクリストの最後のノードのカーソル値は0です
  29. }
  30. // 割り当てられたスペースを抽出します
  31. int mallocArr(コンポーネント * 配列){
  32. //スタンバイリンクリストが空でない場合は、割り当てられたノードの添字を返し、それ以外の場合は0を返します(最後のノードが割り当てられている場合、ノードのカーソル値は0です)
  33. int i=array[0].cur;
  34. if (配列[0].cur) {
  35. 配列[0].cur=配列[i].cur;
  36. }
  37. 私を返します。
  38. }
  39. //静的リンクリストを初期化する
  40. int initArr(コンポーネント *配列){
  41. 予約Arr(配列);
  42. int body=mallocArr(配列);
  43. //変数を宣言し、それをポインタとして使用し、リンク リストの最後のノードを指します。リンク リストは空なので、先頭ノードと一致します。
  44. int tempBody=本体;
  45. for (int i=1; i<4; i++) {
  46. int j=mallocArr(array);//スタンバイリストから空きコンポーネントを取り出す
  47. array[tempBody].cur=j;//アプリケーションの無料コンポーネントをリンク リストの最後のノードの後ろにリンクします
  48. array[j].data=i;//新しく適用されたコンポーネントのデータフィールドを初期化します
  49. tempBody=j;//リンクされたリストの最後のノードにポインタを後方に移動します
  50. }
  51. array[tempBody].cur=0;//新しいリンクリストの最後のノードのポインタは0に設定されます
  52. 本体を返します。
  53. }
  54. void displayArr(component * array,int body){
  55. int tempBody=body;//tempBody はトラバーサルの準備ができています
  56. while (array[tempBody].cur) {
  57. printf("%d,%d ",array[tempBody].data,array[tempBody].cur);
  58. tempBody=array[tempBody].cur;
  59. }
  60. printf("%d,%d\n",array[tempBody].data,array[tempBody].cur);
  61. }

コード出力は次のとおりです。

静的リンク リストは次のとおりです:
-1,2 1,3 2,4 3,0

ヒント: このコードはヘッド ノードを含む静的リンク リストを作成するため、最初の出力「-1,2」はヘッド ノード (-1 はデータがここに保存されていないことを意味します) とそのヘッド ノード (ストレージ エレメント 1 ノード) を表します。配列 array[2]。

循環リンクリスト(ジョセフリング)の確立とC言語での実装

静的リンク リストであっても動的リンク リストであっても、特定の問題を解決する際には、その構造を若干調整する必要がある場合があります。たとえば、リンク リストの両端を接続して、通常は循環リンク リストと呼ばれる循環リンク リストにすることができます。

その名前が示すように、リストの最後のノードのポインターをヘッド ノードにポイントするだけで、図 1 に示すように、リンクされたリストがリングを形成できます。

図 1 循環リンクリスト

循環リンク・リストは循環的ですが、本質的にはリンク・リストであるため、循環リンク・リスト内でも先頭ポインタと先頭ノードを見つけることができることに注意してください。通常のリンク リストと比較した場合、循環リンク リストと通常のリンク リストの違いは、循環リンク リストが端から端まで接続されていることであり、その他はまったく同じです。

循環リンクリストはジョセフリングを実装します

ジョセフ リング問題は古典的な循環リンク リスト問題です

タイトルの意味は、既知のn人(数字1、2、3、...、nで表す)が円卓の周りに座り、k番の人から時計回りに数え始め、mまで数えた人が出ていくというものです。 . 行; 次の人は再び 1 から始めて時計回りに数え始め、m まで数えた人が再び外に出ます;円卓に 1 人だけ残るまで順番に繰り返します

図 2 に示すように、このとき円の周りに 5 人がいるとすると、3 番の人から時計回りに数えることになり、2 まで数えた人がアウトとなります。
 

循環リンクリストはジョセフリングを実装します


図2 ジョセフリングを実現する循環リンクリスト


掲載順は以下の通りです。

  • 3 番の人が 1 を数え始め、次に 4 が 2 を数えるので、4 が最初に出ます。
  • 4 がデキューされた後、5 から 1 をカウントし、1 が 2 をカウントするため、1 がデキューされます。
  • 1 がデキューされた後、2 から 1 をカウントし、3 から 2 をカウントして、3 がデキューされます。
  • 3 がデキューされた後、5 から 1 をカウントし、2 から 2 をカウントして、2 がデキューされます。
  • 最終的には5人だけ残ったので5人の勝ちです。

ジョセフ リング問題には、時計回りから反時計回りへの回転など、さまざまなバリエーションがあります。問題の詳細には多くの変数がありますが、問題を解決する中心的な考え方は同じです。循環リンクリスト。

上記の分析を通じて、C 言語コードの作成を試みることができます。完全なコードは次のとおりです。

 
 
  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. typedef 構造体ノード{
  4. 整数;
  5. 構造体ノード * 次;
  6. }人;
  7. 人 * initLink(int n){
  8. person * head=(person*)malloc(sizeof(person));
  9. 頭->番号=1;
  10. head->next=NULL;
  11. 人 * 巡回 = 頭;
  12. for (int i=2; i<=n; i++) {
  13. person * body=(person*)malloc(sizeof(person));
  14. 本体->番号=i;
  15. 本文->次=NULL;
  16. 循環->次=本体;
  17. サイクリック=サイクリック->次;
  18. }
  19. cyclic->next=head;//エンドツーエンド接続
  20. 頭を戻します。
  21. }
  22. void findAndKillK(人 * 頭,int k,int m){
  23. 人 * 尾 = 頭;
  24. //削除操作の準備として、リンク リストの最初のノードの前のノードを検索します。
  25. while (tail->next!=head) {
  26. 尾=尾->次;
  27. }
  28. 人 * p=頭;
  29. // 番号 k を持つ人を見つける
  30. while (p->number!=k) {
  31. 尾=p;
  32. p=p->次;
  33. }
  34. //k 番の人から始めて、p->next==p が満たされた場合にのみ、リンクされたリスト内の p ノードを除くすべての番号がリストされることを意味します。
  35. while (p->next!=p) {
  36. //p から番号 1 まで m を報告した人を見つけ、m-1de 人の数の位置末尾もわかるので、削除に便利です。
  37. for (int i=1; i<m; i++) {
  38. 尾=p;
  39. p=p->次;
  40. }
  41. tail->next=p->next;//リンクされたリストから p ノードを選択します
  42. printf("リストされている人物の番号は: %d\n", p->number);
  43. 無料(p);
  44. p=tail->next;//デキューされた番号の次の番号を指すために p ポインタを使用し続け、ゲームは続行します
  45. }
  46. printf("リストされている人物の番号は: %d\n", p->number);
  47. 無料(p);
  48. }
  49. int main() {
  50. printf("円卓の人数 n:");
  51. int n;
  52. scanf("%d",&n);
  53. 人 * head=initLink(n);
  54. printf("k人目から数え始める(k>1かつk<%d):",n);
  55. int k;
  56. scanf("%d",&k);
  57. printf("mまで数えた人: ");
  58. int m;
  59. scanf("%d",&m);
  60. findAndKillK(頭, k, m);
  61. 0を返します。
  62. }

出力結果:

円卓の人数 n:5 を入力し
、k 人目から数えます (k>1、k<5):3
m まで数える人が列挙されます:2
列挙される人数は:4 です
。記載されている人物: 1
記載されている人物の数: 3
記載されている人物の数: 2
記載されている人物の数: 5

最後に立っていた人が勝者です。もちろん、最後の人が見つかったときにその人の勝利に関する情報を出力するようにプログラムを改良することもできます。

要約する

循環リンク リストと動的リンク リストの唯一の違いは、そのエンドツーエンド接続です。循環リンク リストが使用される場合、この接続もリンク リストを横断することになります。

トラバーサルのプロセスでは、循環リンク リストは端から端まで接続されていますが、リンク リストに最初のノードと最後のノードがないという意味ではないことに注意することが特に重要です。したがって、ヘッドポインタの向きを任意に変更しないでください。

おすすめ

転載: blog.csdn.net/qq_38998213/article/details/132306491