1. 単一リンクリストの序文
前回は数列テーブルについてお話しましたが、よく勉強してみると、数列テーブルの長所と短所が見えてきます。
欠点 1: 先頭と中間の挿入と削除は効率的ではなく、時間と空間の複雑さは両方とも O(N) です。
デメリット 2: 十分なスペースがない場合、拡張にはある程度の費用がかかります (特に realloc のオフサイト拡張)。
デメリット3: 拡張ロジックの余地があるかもしれない 例えば、170個のデータを挿入するだけで済むのに、200個のデータのサイズを拡張したい場合、無駄が発生します。
利点 1: テールの挿入とテールの削除は十分に高速です。
利点 2: 添字のランダム アクセスと変更は高速で便利です。
私たちは、今日の単一リンクリストを通じてシーケンスベースのリストの欠点を解決します。
2. リンクリスト
2.1 リンクリストの基本概念と構造
概念: リンク リストは、非連続かつ非順次的な物理ストレージ構造です。データ要素の論理的順序は、リンク リスト内のポインタのリンク順序によって
実現されます。
構造図:
リンク リストとシーケンス リストも構造体を介して実装されますが、リンク リストの構造体には有効なデータと次の構造体を指す構造体ポインタのみが含まれるという違いがあります。
まずはコードを通じて簡単なリンク リストを実装してみましょう。
#include<stdio.h>
#include<stdlib.h>
typedef struct SListNode{
int data;
struct SListNode* next;
}SLTNode;
int main()
{
SLTNode*n1=(SLTNode*)malloc(sizeof(SLTNode));
n1->data=1;
SLTNode*n2=(SLTNode*)malloc(sizeof(SLTNode));
n2->data=2;
SLTNode*n3=(SLTNode*)malloc(sizeof(SLTNode));
n3->data=3;
n1->next=n2;
n2->next=n3;
n3->next=NULL;
SLTNode*plist=n1;
while(plist)
{
printf("%d->",plist->data);
plist=plist->next;
}
printf("NULL\n");
return 0;
}
ここでは、3 つの構造体用のスペースを開き、3 つの構造体ポインターを使用して各構造体を指し、次に各構造体の構造体ポインターで次の構造体を指し、各構造体を有効なデータとともに格納します。単純な 3 長のリンク リストです。
知らせ:
1. 上の図を見ると、論理的には連続しているように見えますが、物理アドレスのメモリでは連続している場合もあれば、不連続である場合もあります。
2. 実際のすべてのノードはヒープから開かれます
3. ヒープから割り当てられるスペースは特定の戦略に従っており、2 つの連続するスペース割り当ては連続的である場合もあれば、不連続である場合もあります。
2.2 リンクリストの分類
2.2.1 一方向または双方向
2.2.2 主導権を握るか否か
2.2.3 周期的か非周期的か
リンク リスト構造は非常に多くありますが、実際に最もよく使用される構造が 2 つあります。
1. ヘッドレス一方向非巡回リンクリスト: 単純な構造を持ち、通常はデータを単独で保存するためには使用されません。
実際には、ハッシュ バケット、グラフ隣接リストなどの他のデータ構造の下部構造として使用されることが多くなります。さらに、この構造は書面によるインタビューでもよく現れます。
2. 見出し付き双方向循環リンク リスト: この構造は最も複雑で、一般にデータを個別に保存するために使用されます。実際に使用されるリンク リスト データ構造は、
すべて見出し付きの双方向循環リンク リストです。また、この構造は複雑ですが、コードを使用して実装すると、この構造が多くの利点をもたらし、
実装がより簡単になることがわかります。それは、後でコードを実装するときにわかります。
3. ヘッドレス一方向非循環リンクリストの実装、リンクリストの追加、削除、確認、変更
3.1 リンクリストの作成
typedef int SLTDatatype;
typedef struct SListNode
{
SLTDatatype data;
struct SListNode* next;
}SLTNode;
3.2 データの入力と新しいノードのオープン
SLTNode* BuySLT(SLTDatatype x)
{
SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
if (newnode == NULL)
{
perror("malloc failed");
exit(-1);
}
newnode->data = x;
newnode->next = NULL;
return newnode;
}
リンク リストの実装は、次の構造リンクを指す構造内の構造ポインタに依存するため、新しいノードを返す必要があります。
返されるのは構造体ポインタであり、関数を定義するときに構造体ポインタも受け入れる必要があります。
3.3 リンクリストの印刷
void SLprint(SLTNode* phead)
{
SLTNode* cur = phead;
while (cur != NULL)
{
printf("%d->", cur->data);
cur = cur->next;
}
printf("NULL\n");
}
head構造体のポインタを頼りにリンクリストを見つけますが、有効なデータを出力するにはポインタを移動する必要があるため、最初にポインタを作成し、このポインタの動きを利用してデータを出力する必要があります。空を指しても印刷は停止しません。
3.4 有効なデータの検索
SLTNode* SLFind(SLTNode* phead, SLTDatatype x)
{
SLTNode* cur = phead;
while (cur)
{
if (cur->data = x)
{
return cur;
}
cur = cur->next;
}
return NULL;
}
有効なデータを出力するのと同様に、別のポインタを移動することで必要な有効なデータを見つけます。ポインタが空の場合、有効なデータは見つからず、NULL を返します。有効なデータが見つかった場合は、有効なデータを含む構造体へのポインタを返します。 ;
3.5 単一リンクリスト末尾の挿入
void SLTPushBack(SLTNode** pphead, SLTDatatype x)
{
SLTNode* newnode = BuySLT(x);
if (*pphead == NULL)
{
*pphead = newnode;
}
else
{
SLTNode* tail = *pphead;
while (tail->next)
{
tail = tail->next;
}
tail->next = newnode;
}
}
まず、オープンされた戻り値 (構造体ポインタ) を指す空の構造体ポインタが必要です。リンク リストがない場合、これはヘッド挿入と同等です。オープンされた戻り値 (構造体) を代入するだけで済みます。ポインタ、ヌル ポインタへのポインタ。現時点では、このリンク リストの構造は 1 つだけです。リンク リストが形成されたら、これが最後の挿入です。リンク リストの最後にあるヌル ポインタを見つけるには、別のポインタが必要です。この null ポインタを、開いた戻り値を持つ構造体ポインタにポイントします。末尾から連結することができます。
セカンダリ ポインタを使用する理由
null またはリンクリストの先頭を指すポインタがあります。最後に挿入する場合、先頭ポインタを移動してデータを追加する必要がありますが、このポインタは移動できません。移動するとメモリリークが発生するため、このポインタを移動するには、別のポインタを作成します。空のリストまたはリンクされたリストの先頭をポイントすると、リンクされたリストはポインタ操作を通じて直列に接続されます。このポインタを先頭ポインタと考えることもできます。このポインタは、 head ポインターですが、関数ドメイン内にいます。操作中、このスコープ以外のすべては破棄されます。これは何もないのと同じです。平たく言えば、ヘッド ポインタを操作していることになり、別のスコープで元の値を変更するにはセカンダリ ポインタを介してのみ実行できますが、ヘッド ポインタはすでにポインタであるため、セカンダリ ポインタが必要です。
3.6 テールを削除
void SLTPopBack(SLTNode** pphead)
{
if ((*pphead)->next == NULL)
{
free(*pphead);
*pphead = NULL;
}
else
{
SLTNode* tail = *pphead;
while (tail->next->next)
{
tail = tail->next;
}
free(tail->next);
tail->next = NULL;
}
}
まず、リンク リストに構造が 1 つだけあるかどうかを判断する必要があります。構造が 1 つしかない場合は、その構造を直接解放して破棄します。複数の構造リンクがある場合は、最後から 2 番目の構造でリンクを見つけるだけで済みます。末尾の削除は、最後の構造を指すポインタを解放することによって実現できます。
3.7 ヘッドプラグ
void SLPushFront(SLTNode** pphead, SLTDatatype x)
{
SLTNode* newnode = BuySLT(x);
if ((*pphead) == NULL)
{
*pphead = newnode;
}
else
{
SLTNode* newnode = BuySLT(x);
newnode->next = *pphead;
*pphead = newnode;
}
}
ヘッド ポインタが空でリンク リストがない場合は、このポインタが開いた構造体を指すだけです。空でない場合は、開いた構造体の次の構造体を指す構造体ポインタをヘッド ポインタにポイントします。開いた構造体への head ポインタ。
3.8 ヘッダーの削除
void SLPopFront(SLTNode** pphead)
{
if ((*pphead) == NULL)
{
free(*pphead);
*pphead = NULL;
}
else
{
SLTNode* newhead = (*pphead)->next;
free(*pphead);
*pphead = newhead;
}
}
まず、連結リストに構造が 1 つだけ含まれているかどうかを判断する必要がありますが、構造が 1 つだけ含まれている場合でも、末尾削除の方法は同じです。
複数の構造ヘッダーがある場合は、新しいポインターを作成して、最初の構造に格納されている 2 番目の構造体のポインターを保存するだけで済みます。その後、先頭のポインターを解放し、新しく作成したポインターをヘッダーに割り当てます。ポインターは末尾の削除を完了できます。 。
ここまでで、ヘッドレス片方向非巡回リンクリストの追加・削除・確認・変更の重要な内容と注意点を説明しましたが、次に、リストの途中でランダムな追加・削除・確認・変更を実装します。リンクされたリストです。1 つずつ説明するつもりはありません。ご自身で理解してください。
4. ヘッドレス一方向非巡回リンクリストの途中でのランダムな追加、削除、確認、変更
4.1 pos の位置の後に x を挿入する
void SLInsertAfter(SLTNode* pos, SLTDatatype x)
{
SLTNode* newnode = BuySLT(x);
newnode->next = pos->next;
pos->next = newnode;
}
4.2 pos の位置の後に x を挿入する
void SLInsert(SLTNode** pphead, SLTNode* pos, SLTDatatype x)
{
if (pos == *pphead)
{
SLPushFront(pphead, x);
}
else
{
SLTNode* newnode = BuySLT(x);
SLTNode* prev = *pphead;
while (prev->next != pos)
{
prev = prev->next;
}
prev->next = newnode;
newnode->next = pos;
}
}
4.3 pos 位置の値を削除する
void SLTErase(SLTNode** pphead, SLTNode* pos)
{
if ((*pphead) == pos)
{
SLPopFront(pphead);
}
else
{
SLTNode* prev = *pphead;
while (prev->next != pos)
{
prev = prev->next;
}
prev->next = pos->next;
free(pos);
pos = NULL;
}
}
4.4 pos 位置以降の値を削除する
void SLEraseAfter(SLTNode* pos)
{
assert(pos->next);
SLTNode* newnode = pos->next->next;
free(pos->next);
pos->next = newnode;
}
5. ヘッドレス一方向非巡回リンクリストを追加、削除、確認、変更するための完全なコード
#define _CRT_SECURE_NO_WARNINGS 67
#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
typedef int SLTDatatype;
typedef struct SListNode
{
SLTDatatype data;
struct SListNode* next;
}SLTNode;
//打印
void SLprint(SLTNode* phead)
{
SLTNode* cur = phead;
while (cur != NULL)
{
printf("%d->", cur->data);
cur = cur->next;
}
printf("NULL\n");
}
//开辟新的空间
SLTNode* BuySLT(SLTDatatype x)
{
SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
if (newnode == NULL)
{
perror("malloc failed");
exit(-1);
}
newnode->data = x;
newnode->next = NULL;
return newnode;
}
//查找
SLTNode* SLFind(SLTNode* phead, SLTDatatype x)
{
SLTNode* cur = phead;
while (cur)
{
if (cur->data = x)
{
return cur;
}
cur = cur->next;
}
return NULL;
}
//尾插
void SLTPushBack(SLTNode** pphead, SLTDatatype x)
{
SLTNode* newnode = BuySLT(x);
if (*pphead == NULL)
{
*pphead = newnode;
}
else
{
SLTNode* tail = *pphead;
while (tail->next)
{
tail = tail->next;
}
tail->next = newnode;
}
}
//尾删
void SLTPopBack(SLTNode** pphead)
{
if ((*pphead)->next == NULL)
{
free(*pphead);
*pphead = NULL;
}
else
{
SLTNode* tail = *pphead;
while (tail->next->next)
{
tail = tail->next;
}
free(tail->next);
tail->next = NULL;
}
}
//头插
void SLPushFront(SLTNode** pphead, SLTDatatype x)
{
SLTNode* newnode = BuySLT(x);
if ((*pphead) == NULL)
{
*pphead = newnode;
}
else
{
SLTNode* newnode = BuySLT(x);
newnode->next = *pphead;
*pphead = newnode;
}
}
//头删
void SLPopFront(SLTNode** pphead)
{
if ((*pphead) == NULL)
{
free(*pphead);
*pphead = NULL;
}
else
{
SLTNode* newhead = (*pphead)->next;
free(*pphead);
*pphead = newhead;
}
}
//在pos位置后插入x
void SLInsertAfter(SLTNode* pos, SLTDatatype x)
{
SLTNode* newnode = BuySLT(x);
newnode->next = pos->next;
pos->next = newnode;
}
//在pos位置前插入x
void SLInsert(SLTNode** pphead, SLTNode* pos, SLTDatatype x)
{
if (pos == *pphead)
{
SLPushFront(pphead, x);
}
else
{
SLTNode* newnode = BuySLT(x);
SLTNode* prev = *pphead;
while (prev->next != pos)
{
prev = prev->next;
}
prev->next = newnode;
newnode->next = pos;
}
}
void SLTErase(SLTNode** pphead, SLTNode* pos)
{
if ((*pphead) == pos)
{
SLPopFront(pphead);
}
else
{
SLTNode* prev = *pphead;
while (prev->next != pos)
{
prev = prev->next;
}
prev->next = pos->next;
free(pos);
pos = NULL;
}
}
void SLEraseAfter(SLTNode* pos)
{
assert(pos->next);
SLTNode* newnode = pos->next->next;
free(pos->next);
pos->next = newnode;
}
int main()
{
SLTNode* plist = NULL;
//尾插
SLTPushBack(&plist, 1);
SLTPushBack(&plist, 2);
SLTPushBack(&plist, 3);
SLTPushBack(&plist, 4);
SLTPushBack(&plist, 5);
SLTPushBack(&plist, 10);
printf("尾插:\n");
SLprint(plist);
//尾删
printf("尾删:\n");
SLTPopBack(&plist);
SLprint(plist);
//头插
printf("头插:\n");
SLPushFront(&plist, 10);
SLprint(plist);
//头删
printf("头删:\n");
SLPopFront(&plist);
SLprint(plist);
//在pos位置后插入x
SLTNode* pos1 = SLFind(plist, 1);
SLInsertAfter(pos1, 10);
printf("在pos位置后插入x\n");
SLprint(plist);
//在pos位置前插入x
SLTNode* pos2 = SLFind(plist, 1);
printf("在pos位置前插入x\n");
SLInsert(&plist, pos2, 20);
SLprint(plist);
//删除pos位置的值
SLTNode* pos3 = SLFind(plist, 10);
SLTErase(&plist, pos3);
printf("删除pos位置的值:\n");
SLprint(plist);
//删除pos后面的值
SLTNode* pos4 = SLFind(plist, 1);
SLEraseAfter(pos4);
printf("删除pos位置后面的值:\n");
SLprint(pos4);
return 0;
}
以上で、ヘッドレス一方向非巡回リンクリストの追加、削除、確認、修正の内容は全て説明が終わりましたので、ご質問や修正が必要な場合は、コメント欄に貴重なご意見を残していただければ幸いです。