リンクリストの概要-一般的なリンクリスト、LRUキャッシュ削除アルゴリズムの実装方法、およびリンクリストのコードの記述方法

配列と対比

基本紹介

データ構造の保存に関しては、最も基本的な2つの保存方法である配列とリンクリストを簡単に考えることができます。関連するコンテンツを学習した後、2つの長所と短所を簡単に考えることができます。
配列
データに適しています検索はできますが、データの削除と挿入には非常に時間がかかります。内部スペースを申請する場合は、特定のサイズを決定する必要があります。宣言すると、連続メモリスペース全体が占有されます。宣言された配列が大きすぎると、システムに連続した十分なメモリ空間が割り当てられず、「メモリ不足」になる可能性があります。もちろん、C ++のベクトル配列がこの問題を解決し、Javaの対応するArraylistもこの問題を解決します。容量、それらの処理メカニズムは何ですか?彼らはより大きなスペースを申請してから、すべての元のデータをコピーします。データのコピーも非常に時間がかかる作業であるため、これには依然として欠点があります。
リンクリスト
データの挿入と削除に適していますが、データを見つけるのが難しく、移動に時間がかかります。リンクリスト自体にはサイズ制限がなく、動的拡張をサポートしています。これも配列との最大の違いだと思います。配列とは異なり、データを格納し、連続したスペースを必要としません。異なるスペースをポインターで接続します。
ここに画像の説明を挿入

分析

再度配列にランダムアクセスする場合、シリアル番号がわかっていれば直接アクセスできますが、挿入したい場合は、挿入されたシリアル番号をkとすると、kの後のすべての要素を1つ戻す必要があります。これが最後の番号の場合、データを最後の位置に直接挿入できます。現時点では、時間の複雑度はO(1)です。最初の数値の場合、時間の複雑度はO(n)です。平均時間の複雑さはO(n)です削除された場合、すべて1ビット前に移動し、平均時間の複雑度はO(n)です。
リンクされたリストにランダムにアクセスする場合、必要な値が見つかるまで、最初の数から後ろに1つずつトラバースする必要があります。最初の数値である場合、時間の複雑度はO(1)であり、最後の数値である場合、時間の複雑度はO(n)です。平均時間の複雑さはO(n)です

いくつかの一般的なリンクリスト

まず、最初にいくつかの一般的なリンクリストを以下に示します。

  1. 単一リスト
  2. 循環リンクリスト
  3. 二重リンクリスト
  4. 二重循環リンクリスト

単一リスト

ここに画像の説明を挿入
単一リンクリストは最も基本的なリンクリストであるため、マスターする必要があります。リンクリストは、散在するメモリブロックのグループをポインタで接続します。その中で、メモリブロックをリンクリストの「ノード」と呼びます。すべてのノードをつなぐために、データを保存するだけでなく、リンクリストの各ノードはチェーン上の次のノードのアドレスも記録する必要があります。図に示すように、次のノードのアドレスを記録するこのポインターを、後続ポインターとして次に呼びます。

循環リンクリスト

ここに画像の説明を挿入
循環リンクリストの単一リンクリストに基づいて、NULLへの最後のポインタがリセットされ、循環メカニズムを実現するためにヘッドノードをポイントします。

二重リンクリスト

ここに画像の説明を挿入
二重にリンクされたリストは、単一にリンクされたリストに基づいており、前のノードを指すprevポインターが追加されています。

二重循環リンクリスト

ここに画像の説明を挿入
二重循環リンクリストは、二重リンクリストと循環リンクリストを組み合わせたものです。これも最もよく使用されます。これは、スペースを時間で置き換える方法です。メモリを追加しますが、場合によっては、単一リンクリストよりも高速に実行されます。すぐに、データの一部を削除するとしましょう。いくつかの一般的なアルゴリズムの本では、1つのネックレステーブルの時間の複雑さはO(1)であると述べていますが、実際にはそうですか?詳しく見てみましょう。
通常、新しいデータには2つの状況が考えられます。

  1. ノード内の「指定された値と等しい値」を持つノードを削除します。
  2. 指定されたポインタが指すノードを削除します。

ケース1の場合、単一リンクリストを使用します。まず、削除する必要があるノードを見つけるために全探索する必要があります。したがって、この意味で、時間の複雑さは実際にはO(n)です(もちろん現時点では)同じことが二重循環リンクリストにも当てはまり、違いはありません。
ただし、ケース2の場合は異なります。特定の位置にあるノードを削除する場合は、その前のノードを見つける必要があるため、まずトラバースして、p-> next == qになるまで削除する必要があります。ここでの時間の複雑さはO(n)であり、二重循環リンクリスト(二重リンクリスト)は、q-> prevによって前のノードを直接検索して削除できます(ここではO(1))。

LRUキャッシュ削除アルゴリズムを実装する方法

LRUキャッシュ除去アルゴリズム

まず、これを書く前に、まず知っておく必要があります。LRUキャッシュ削除アルゴリズムとは
キャッシングは、データ読み取りのパフォーマンスを向上させるテクノロジーであり、一般的なCPUキャッシュ、データベースキャッシュ、ブラウザキャッシュなどのハードウェア設計およびソフトウェア開発で広く使用されています。キャッシュのサイズには制限がありますが、キャッシュがいっぱいになった場合、どのデータを消去し、どのデータを保持する必要がありますか?これには、決定するためのキャッシュ消去戦略が必要です。
3つの一般的な戦略があります:FIFO(先入れ先出し)、LFU(最も頻繁に使用されない)、LRU(最も最近使用されていない)
技術的な本をたくさん購入したとしましょう。ある日、これらの本が多すぎて学習スペースを使いすぎていることに気づき、いくつかの本を片付けて捨てなければなりません。現時点で、どの本を捨てることを選びますか?これに対応して、あなたの選択基準は上記の3つの戦略に似ていますか?

LRUキャッシュ削除アルゴリズムを実装する方法

順序付けられた単一リンクリストを維持します。リンクリストの末尾に近いノードは、以前にアクセスされます。新しいデータがアクセスされると、リンクリストの先頭から順にリンクリストをトラバースします。
データの一部が入ってくると、リンクされたリストに表示されるかどうかを確認するための変数が必要です。

  1. 表示:このデータをリンクリストの先頭に移動します
  2. 何も表示されない:
    a。現時点でキャッシュがいっぱいでない場合、このノードをリンクリストの先頭に直接挿入します
    。b。現時点でキャッシュがいっぱいである場合、リンクリストの終了ノードが削除され、新しいデータノードがリンクリストに挿入されます。頭。

このように、リンクリストを使用したLRUキャッシュ削除アルゴリズムを実装しました。

リンクリストコードの書き方

1.ポインタの意味に注意してください

リンクされたリストには多くのポインターが含まれているため、ポインターの意味が理解されていないと、リンクされたリストのコードを記述することは当然非常に困難です。したがって、ポインターの意味を理解する必要があるため、ポインターをどのように理解すればよいでしょうか?
変数をポインターに割り当てると、実際には変数のアドレスがポインターに割り当てられます。逆の場合も同様です。変数のメモリアドレスはポインターに格納され、ポインターを介して変数を見つけることができます。
具体的な例を次に示します。

p->next=q表示p的next指针存储了q的内存地址
p->next=p->next->next表示p 结点的 next 指针存储了 p 结点的下下一个结点的内存地址。

2.ポインタの損失とメモリリークに注意する

言い方を変えると、大まかにコードを書き直すとき、いくつかのエラーが原因で、リンクリストが半分になるか、一部のフラグメントが制御不能になり、次のようなポインターで移動できなくなります。ここに画像の説明を挿入

//插入一个结点(将q插到p的下一个)
p->next=q;
q->next=p->next;

p-> nextはすでにqですが、q-> nextはまだqであるため、このコードは間違いであることが簡単にわかります。元のp-> nextがリンクリストにないため、これは明らかに間違っています。それを修正するには?2つのコードの順序を変更するだけで十分です。したがって、ノードを挿入するときは、操作の順序に注意を払う必要があります。新しいノードの次のノードを決定し、新しいノードをポイントする必要があります。
同様に、リンクリストノードを削除するときは、メモリ領域を手動で解放することも忘れないでください。そうしないと、メモリリークも発生します。

3.ヘッダーノードを追加してコードを簡略化する

通常の操作中に新しいノードを挿入する必要がある場合、必要な操作は次のとおりです。

q->next=p->next;
p->next=q;

ただし、これが空のリンクリストである場合は、上記のコードに適合しないため、次のようにする必要があります。

if (p==NULL)
	node* p = new node;

同様に、リンクリストのノードを削除する場合、必要なコードは次のとおりです。

p->next=p->next->next;

ただし、リンクされたリストの最後の位置を削除したい場合、この操作は再び問題を引き起こしますか?私たちは書く必要があります:

if(p->next==NULL)
	p=NULL;

したがって、何か問題があると思いますか、この問題をどのように解決しますか?ヘッドノードを追加する
ここに画像の説明を挿入
だけです。ヘッドノードはデータを含まないノードであり、コードを統合して記述を容易にするためだけに追加されます。

4.境界条件の取り扱いに注意する

リンクリストのコードを記述するときは、次のように、コードが境界範囲内で満たされているかどうかにさらに注意してください。

  1. リストが空でも満足ですか
  2. リンクリストに含まれるノードが1つだけの場合、コードは正常に機能しますか?
  3. リンクリストに含まれるノードが2つだけの場合、コードは正常に機能しますか?
  4. ヘッドノードとエンドノードを処理するときに、コードロジックは正常に機能しますか?

問題がなければ、基本的に問題はありません。

5.コードを理解して書くのに役立つ画像を描くことができます

例えば、私が上で描いた絵はリンクされたリストをリンクすることですが、絵があれば、コードを書くことがより便利になり、エラーが起こりにくくなるかもしれません。
ここに画像の説明を挿入

6.もっと書いて練習しよう

IQが特に高い人ではない場合は、リンクされたリストにさらに練習問題を書くことをお勧めします。もっと練習すれば、自然かつスムーズに書くことができます。ここでは、いくつかの練習問題を挙げます。興味がある場合は、それについて書いてください。

  1. 単一リンクリストの反転
  2. リンクされたリストのリングの検出
  3. 2つの順序付けられたリンクリストがマージされます
  4. リンクリストの下部からn番目のノードを削除します
  5. リンクされたリストの中央のノードを見つける

私の別のブログで、1、3、4を書きましたが、必要に応じて、そのブログを参照し、以下のリンクを添付できます。
リンク:C ++リンクリストの基本操作-リンクリストの構造、リンクリストの破棄、リンクリストの挿入、リンクリストの削除、リンクリストの反転、ヘッド補間の反転

これでブログは終了です。役立つ場合は、高く評価してください。

おすすめ

転載: blog.csdn.net/Freedom_cao/article/details/107408721