私の連絡先情報
郵便メール:
2024-07-12
한어Русский языкEnglishFrançaisIndonesianSanskrit日本語DeutschPortuguêsΕλληνικάespañolItalianoSuomalainenLatina
こんにちは~!奮闘中の小さな羊です。コメントやアドバイスをいただけて光栄です。
💥💥个人主页:奮闘する子羊
💥💥所属专栏:C言語
🚀本系列文章为个人学习笔记,在这里撰写成文一为巩固知识,二为展示我的学习过程及理解。文笔、排版拙劣,望见谅。
単一リンク リストは特定のノードのアドレスを通じて一方向にのみデータにアクセスできますが、二重リンク リストはノードのアドレスを通じて両方向にデータにアクセスでき、追加、削除、確認、変更の効率が高くなります。ただし、コードの実装は単一リンク リストよりも単純です。 の点では、二重リンク リストの利点がより明らかです。
連結リストは、見出し付き、見出しなし、一方向、双方向、循環型、非循環型の 8 つのタイプに分類されます。ここでは、一般的なもののみを学習します。リーダーなしの一方向非巡回リンク リストそして見出し付き双方向循環リンクリスト、 あれは単一リストそして二重リンクリスト。
二重リンクリスト:
実際、前の記事では、単連結リストの最初のノードをヘッド ノードと呼びましたが、これは不正確です。単連結リストにはヘッドがないため、わかりやすくするためです。二重リンクリストのみに と呼ばれるヘッドノードがあります。センチネルの位置。
- 二重リンクリストの先頭ノードには有効なデータが格納されておらず、無効なデータが格納されています。
- センチネルの位置は変更できません。変更できるのは方向のみです。
二重リンク リストのノードは、データと前後のノードのアドレスを格納する必要があるため、二重リンク リストのノードは次のようになります。
typedef int dlist_data_type;
//双向链表节点
typedef struct dlist
{
dlist_data_type data;
struct dlist* next
struct dlist* prev;
}dlist;
単一リンク リストとは異なり、二重リンク リストには少なくとも 1 つのノードが必要です。これがヘッド ノードであり、センチネル位置とも呼ばれます。
追加、削除、確認、変更を行う前に、二重リンク リストはセンチネル ビットを初期化する必要があり、センチネル ビットには無効なデータが格納されます。
適用されたノードには、最初はそれ自体を指す 2 つのポインターがあります。。
//申请节点
dlist* dlist_buy(dlist_data_type x)
{
dlist* newdlist = (dlist*)malloc(sizeof(dlist));
if (newdlist == NULL)
{
perror("malloc fail!");
return 1;
}
newdlist->data = x;
newdlist->next = newdlist;
newdlist->prev = newdlist;
return newdlist;
}
センチネル ビットを初期化するには 2 つの方法があります。
パラメータとして返されるもの:
void dlist_init(dlist** pphead)
{
assert(pphead);
*pphead = dlist_buy(-1);
}
このメソッドでは、セカンダリ ポインターを使用する必要があります。
戻り値として返されるもの:
dlist* dlist_init()
{
dlist* phead = dlist_buy(-1);
return phead;
}
この方法の方がシンプルで理解しやすいです。
データの印刷と検索は、単一リンク リストの場合とあまり変わらないため、詳細は説明しません。
注意が必要な唯一のことは、ループが終了する条件です。ポインタがセンチネル ビットを指したときではなく、ポインタがセンチネル ビットを指したときにループが終了します。NULL
。
//打印数据
void dlist_print(dlist* phead)
{
assert(phead);
dlist* pcur = phead->next;
while (pcur != phead)
{
printf("%d->", pcur->data);
pcur = pcur->next;
}
printf("NULLn");
}
//查找
dlist* dlist_find(dlist* phead, dlist_data_type x)
{
assert(phead);
dlist* pcur = phead->next;
while (pcur != phead)
{
if (pcur->data == x)
{
return pcur;
}
pcur = pcur->next;
}
return NULL;
}
単一リンク リストとは異なり、二重リンク リストの追加、削除、クエリ、変更操作はセンチネル ビットを変更しないため、値による呼び出しそれでおしまい。
二重リンクリストの挿入操作には、3 つのノードの変更が必要です。変更プロセス中には、次の点に注意してください。最初に新しいノードを変更し、次に次のノードを変更し、最後に前のノードを変更します。
//插入操作不改变哨兵位,因此传一级指针即可
//尾插
void dlist_push_back(dlist* phead, dlist_data_type x)
{
assert(phead);
dlist* newdlist = dlist_buy(x);
//新尾节点
newdlist->prev = phead->prev;
newdlist->next = phead;
//旧尾节点
phead->prev->next = newdlist;
//头节点(哨兵位)
phead->prev = newdlist;
}
//头插
void dlist_push_front(dlist* phead, dlist_data_type x)
{
assert(phead);
dlist* newdlist = dlist_buy(x);
//新节点
newdlist->prev = phead;
newdlist->next = phead->next;
//旧节点
phead->next->prev = newdlist;
//哨兵位
phead->next = newdlist;
}
削除操作の前提条件は、ノードが存在しないことです (センチネルの位置は考慮されません)、削除する前に、ノードのアドレスを保存し、二重リンク リストに再参加してから、ノードを解放します。
//尾删
void dlist_pop_back(dlist* phead)
{
assert(phead);
assert(phead->next != phead);//双向链表不能为空
dlist* del = phead->prev;
//新尾节点
del->prev->next = phead;
//哨兵位
phead->prev = del->prev;
free(del);
del = NULL;
}
//头删
void dlist_pop_front(dlist* phead)
{
assert(phead);
assert(phead->next != phead);
dlist* del = phead->next;
del->next->prev = phead;
phead->next = del->next;
free(del);
del = NULL;
}
//在指定位置之后插入数据
void dlist_insert_back(dlist* pos, dlist_data_type x)
{
assert(pos);
dlist* newdlist = dlist_buy(x);
newdlist->prev = pos;
newdlist->next = pos->next;
pos->next->prev = newdlist;
pos->next = newdlist;
}
//删除指定位置的节点
void dlist_erase(dlist* pos)
{
assert(pos);
dlist* del = pos;
pos->prev->next = pos->next;
pos->next->prev = pos->prev;
free(pos);
pos = NULL;
}
指定した場所のデータを削除した後、この関数は値によって呼び出されるため、指定された位置でノードを解放した後、仮パラメータ ポインタを次のように設定するだけです。NULL
、実際のパラメータ ポインタはまだこの場所を指しているため、関数呼び出しが終了した後、実際のパラメータ ポインタを設定する必要があります。NULL
。
二重リンク リストの破棄の終了条件は、トラバーサル ポインタがヘッド ノードを指しているときにループから抜け出すことです。最後に、センチネルの位置を個別に解放する必要があります、二重リンクリストの破棄関数呼び出しが完了した後、センチネルビットを指すポインタも設定する必要があります。NULL
。
//销毁
void dlist_destroy(dlist* phead)
{
assert(phead);
dlist* pcur = phead->next;
while (pcur != phead)
{
dlist* next = pcur->next;
free(pcur);
pcur = next;
}
free(pcur);
pcur = NULL;
}
dlist.h:
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
typedef int dlist_data_type;
//双向链表节点
typedef struct dlist
{
dlist_data_type data;
struct dlist* next;
struct dlist* prev;
}dlist;
//初始化
//插入数据之前,双向链表必须初始化到只有一个头节点(哨兵位)
//void dlist_init(dlist** pphead);//以参数的形式返回
dlist* dlist_init();//以返回值的形式返回
//打印数据
void dlist_print(dlist* phead);
//查找
dlist* dlist_find(dlist* phead, dlist_data_type x);
//尾插
void dlist_push_back(dlist* phead, dlist_data_type x);
//头插
void dlist_push_front(dlist* phead, dlist_data_type x);
//尾删
void dlist_pop_back(dlist* phead);
//头删
void dlist_pop_front(dlist* phead);
//在指定位置之后插入数据
void dlist_insert_back(dlist* pos, dlist_data_type x);
//删除指定位置的节点
void dlist_erase(dlist* pos);
//销毁
void dlist_destroy(dlist* phead);
dlist.c:
#define _CRT_SECURE_NO_WARNINGS
#include "dlist.h"
//申请节点
dlist* dlist_buy(dlist_data_type x)
{
dlist* newdlist = (dlist*)malloc(sizeof(dlist));
if (newdlist == NULL)
{
perror("malloc fail!");
return 1;
}
newdlist->data = x;
newdlist->next = newdlist;
newdlist->prev = newdlist;
return newdlist;
}
//初始化
//void dlist_init(dlist** pphead)
//{
// assert(pphead);
// *pphead = dlist_buy(-1);
//}
dlist* dlist_init()
{
dlist* phead = dlist_buy(-1);
return phead;
}
//打印数据
void dlist_print(dlist* phead)
{
assert(phead);
dlist* pcur = phead->next;
while (pcur != phead)
{
printf("%d->", pcur->data);
pcur = pcur->next;
}
printf("NULLn");
}
//查找
dlist* dlist_find(dlist* phead, dlist_data_type x)
{
assert(phead);
dlist* pcur = phead->next;
while (pcur != phead)
{
if (pcur->data == x)
{
return pcur;
}
pcur = pcur->next;
}
return NULL;
}
//插入操作不改变哨兵位,因此传一级指针即可
//尾插
void dlist_push_back(dlist* phead, dlist_data_type x)
{
assert(phead);
dlist* newdlist = dlist_buy(x);
//新尾节点
newdlist->prev = phead->prev;
newdlist->next = phead;
//旧尾节点
phead->prev->next = newdlist;
//头节点(哨兵位)
phead->prev = newdlist;
}
//头插
void dlist_push_front(dlist* phead, dlist_data_type x)
{
assert(phead);
dlist* newdlist = dlist_buy(x);
//新节点
newdlist->prev = phead;
newdlist->next = phead->next;
//旧节点
phead->next->prev = newdlist;
//哨兵位
phead->next = newdlist;
}
//尾删
void dlist_pop_back(dlist* phead)
{
assert(phead);
assert(phead->next != phead);//双向链表不能为空
dlist* del = phead->prev;
//新尾节点
del->prev->next = phead;
//哨兵位
phead->prev = del->prev;
free(del);
del = NULL;
}
//头删
void dlist_pop_front(dlist* phead)
{
assert(phead);
assert(phead->next != phead);
dlist* del = phead->next;
del->next->prev = phead;
phead->next = del->next;
free(del);
del = NULL;
}
//在指定位置之后插入数据
void dlist_insert_back(dlist* pos, dlist_data_type x)
{
assert(pos);
dlist* newdlist = dlist_buy(x);
newdlist->prev = pos;
newdlist->next = pos->next;
pos->next->prev = newdlist;
pos->next = newdlist;
}
//删除指定位置的节点
void dlist_erase(dlist* pos)
{
assert(pos);
dlist* del = pos;
pos->prev->next = pos->next;
pos->next->prev = pos->prev;
free(pos);
pos = NULL;
}
//销毁
void dlist_destroy(dlist* phead)
{
assert(phead);
dlist* pcur = phead->next;
while (pcur != phead)
{
dlist* next = pcur->next;
free(pcur);
pcur = next;
}
free(pcur);
pcur = NULL;
}
test.c:
#define _CRT_SECURE_NO_WARNINGS
#include "dlist.h"
void test()
{
//dlist* plist = NULL;
//dlist_init(&plist);
dlist* plist = dlist_init();
//尾插
dlist_push_back(plist, 1);
dlist_push_back(plist, 2);
dlist_push_back(plist, 3);
dlist_print(plist);
//头插
//dlist_push_front(plist, 1);
//dlist_push_front(plist, 2);
//dlist_push_front(plist, 3);
//dlist_print(plist);
//尾删
//dlist_pop_back(plist);
//dlist_pop_back(plist);
//dlist_pop_back(plist);
//dlist_print(plist);
//头删
//dlist_pop_front(plist);
//dlist_print(plist);
//dlist_pop_front(plist);
//dlist_print(plist);
//dlist_pop_front(plist);
//dlist_print(plist);
//dlist* find = dlist_find(plist, 2);
//dlist_insert_back(find, 4);
//dlist_print(plist);
//dlist* find = dlist_find(plist, 2);
//dlist_erase(find);
//find = NULL;
//dlist_print(plist);
dlist_destroy(plist);
plist = NULL;//手动置空
}
int main()
{
test();
return 0;
}
シーケンステーブル | リンクリスト(ダブルリンクリスト) | |
---|---|---|
収納スペースについて | 論理的かつ物理的に連続している | 論理的には連続的ですが、物理的には必ずしも連続的ではありません |
ランダムアクセス | 複雑さ O(1) | 複雑さ O(N) |
任意の場所にデータを挿入または削除する | データを移動する必要があるが、複雑さは O(N) | ポインタを次のように変更するだけです |
入れる | ダイナミックシーケンステーブルは、容量が足りない場合に拡張することができ、拡張自体が消費され、容量を無駄にしやすい。 | 容量という概念がない |
アプリケーションシナリオ | 効率的なデータストレージ + 頻繁なアクセス | 任意の場所でデータを頻繁に挿入および削除する |
キャッシュの使用率 | 高い | 低い |
シーケンス リストとリンク リストには補完的な利点があり、さまざまなアプリケーション シナリオでそれぞれの利点を発揮できます。
- アプリケーション シナリオで頻繁な検索と削除の操作が必要であり、より多くのメモリ消費を許容できる場合は、二重リンク リストの方が適している可能性があります。
- メモリが限られている場合、または検索および削除操作の効率が高くない場合は、単一リンク リストの方が適している可能性があります。