目次
単連結リストについては先ほど学びましたが、単連結リストは先頭と非先頭(セントリー)、サイクルと非サイクルに応じて4種類あり、二重リンクリストも同様です。一般的に使用されるものは、ヘッドレス一方向非循環リンク リストと主要な双方向循環リンク リストです。今日は、誰もがリンク リストをより深く理解できるように、この二重リンク リストに関する関連知識を説明します。 。
二重リンクリストの定義
typedef int LTDataType;
typedef struct ListNode
{
struct ListNode* prev;
struct ListNode* next;
LTDataType data;
}LTNode;
初期化
考えてみてください。二重リンク リストの初期化をどのように定義するか?
単一リンク リストの初期化とシーケンス テーブルを比較するとよいでしょう。シーケンス テーブルは連続ストレージ構造です。初期化を完了するには、この構造を使用して size=0 にする必要があります (容量によって初期領域を割り当てることができます)。単結合リストはポインタでリンクされたノードです。はい、新しいノードを作成した後、そのポインタを直接空に設定します。基本的に初期化関数を記述する必要はないと言えます。
先頭の循環二重リンク リストは異なります。その初期化では、まずセンチネル ノードを作成する必要があります。サイクルを形成するには、その 2 つのポインタがそれ自体を指している必要があります。
ノードを作成する
まず、ノードを作成する関数を作成します。
LTNode* BuyListNode(LTDataType x)
{
LTNode* node = (LTNode*)malloc(sizeof(LTNode));//开空间
if (node == NULL)
{
perror("malloc fail");
exit(-1);
}
LTNode* next = nullptr;
LTNode* prev = nullptr;
node->data = x;
return node;
}
ポインタが変更されるので戻り値で受け取ります。
次に、2 つの構造体の 2 つのメンバー ポインターが自分自身 (セントリー ノード) を指すようにして、初期化を完了します。
LTNode* LTInit()
{
LTNode* phead = BuyListNode(-1);//数据无意义
phead->next = phead;
phead->prev = phead;
return phead;
}
初期化は一度で済むので、ここでも戻りと受け取りに第1レベルのポインタを使用していますが、もちろんポインタのアドレスを渡してポインタを変更することも可能です。
テールプラグ
末尾挿入の実装は非常に簡単で、単一リンク リストのように末尾を見つけるためにトラバースする必要はなく、末尾は先頭のprev ノードから直接挿入できます。そして、後続の追加、削除、および変更操作では、セカンダリ ポインターを渡す必要はありません(ヘッド ノードは変更されません)。
void LTPushBack(LTNode* phead, LTDataType x)//尾插
{
assert(phead);//避免人为传错
LTNode* newnode = BuyListNode(x);
LTNode* tail = phead->prev;
tail->next = newnode;
newnode->prev = tail;
newnode->next = phead;
phead->prev = newnode;
}
ヘッド ノードを自分自身に向ける目的は理解できるはずです。
コードを見るのではなく、自分でイメージを考えると、リンクのプロセスが非常にスムーズになると感じられます。ここで、なぜ新しいノード ポインタが NULL に設定されないのかというと、双方向ノードの各ポインタは前/後のオブジェクトを指しているため、NULL ポインタを指しても問題はありません。
末尾削除
末尾の削除は末尾の挿入と似ていますが、同様に素晴らしい点は、削除操作がポインタの向きを変更してスペースを解放するだけであり、複雑ではないことです。
ノードは 1 つだけです。
ノードを 1 つだけ削除した場合、末尾の前のノードがたまたま先頭ノードとなり、削除後は元の状態に戻ります。
void LTPopBack(LTNode* phead)//尾删
{
assert(phead);
assert(phead->next != phead);//防止删掉自己
LTNode* tail = phead->prev;
LTNode* tailPrev = tail->prev;
phead->prev = tailPrev;
tailPrev->next = phead;
free(tail);
}
プラグ
head プラグは、次のノードが見つからないように、次のノードのアドレスを保存するか、最初に newnode を次のノードに接続してから phead を使用して newnode に接続するように注意する必要があります。
void LTPushFront(LTNode* phead, LTDataType x)//头插
{
assert(phead);
ListNode* newnode = BuyListNode(x);
newnode->next = phead->next;
phead->next->prev = newnode;
phead->next = newnode;
newnode->prev = phead;
//顺序无关
/*LTNode* first = phead->next;
phead->next = newnode;
newnode->next = first;
newnode->prev = phead;
first->prev = newnode;*/
}
頭の削除
自身を削除できるかどうかは慎重に判断してください。
void LTPopFront(LTNode* phead)//头删
{
assert(phead);
assert(phead->next != phead);//空
LTNode* first = phead->next;
LTNode* second = first->next;
free(first);
second->prev = phead;
phead->next = second;
}
印刷する
まず print 関数を実装して、前のコードをテストしましょう。印刷は逆方向または順方向に印刷できます。ここでは順方向印刷を実装します。ヘッダーは印刷できないことに注意してください。
void LTPrint(LTNode* phead)//打印
{
assert(phead);
LTNode* cur = phead->next;//正向
while (cur != phead)//结束条件
{
printf("%d ", cur->data);
cur = cur->next;
}
printf("\n");
}
テールプラグとテールデリート:
ヘッドプラグの削除:
見上げる
先頭ノードの次のノードから検索が開始されます。データが見つかったら、返されたポインターを介して。
LTNode* LTFind(LTNode* phead, LTDataType x)
{
assert(phead);
LTNode* cur = phead->next;//起始位置
while (cur != phead)
{
if (cur->data == x)
{
return cur;
}
cur = cur->next;
}
return nullptr;//找不到返回空
}
テスト:
pos 挿入
単一リンクリストとは異なり、挿入は第 2 レベルの head のポインタを渡さずに実現でき、前のノード(Prev pointer )を見つけるためにループする必要はありません。これは、このリンクされたリストの利点も反映しています。
void LTInsert(LTNode* pos, LTDataType x)
{
assert(pos);
LTNode* prev = pos->prev;
LTNode* newnode = BuyListNode(x);
// prev newnode pos
prev->next = newnode;
newnode->prev = prev;
newnode->next = pos;
pos->prev = newnode;
}
ヘッダー多重化
void LTPushFront(LTNode* phead, LTDataType x)//头插
{
assert(phead);
LTInsert(phead->next, x);
}
テール多重化
テールプラグ多重化は一般に最後のノードで多重化すると考えられていますが、実際にはヘッド ノードを通過します。挿入位置はpos の前のノードであるため、最後のノードが渡された場合はその前に挿入され、 phead が渡された場合は最後のノードが見つかり、そのprev ノードから挿入されます。注意してください。これ。
void LTPushBack(LTNode* phead, LTDataType x)//尾插
{
assert(phead);
LTInsert(phead,x);
}
位置削除
削除するには、位置の前後のノードを見つけて削除するだけです。末尾と先頭の削除を多重化する場合は、削除するノードの位置を直接渡すだけで十分です。
void LTErase(LTNode* pos)//pos删除
{
assert(pos);
LTNode* prev = pos->prev;
LTNode* next = pos->next;
free(pos);
prev->next = next;
next->prev = prev;
}
ヘッダー逆多重化
void LTPopFront(LTNode* phead)//头删
{
assert(phead);
assert(phead->next != phead);//空
LTErase(phead->next);
}
末端削除多重化
void LTPopBack(LTNode* phead)//尾删
{
assert(phead);
assert(phead->next != phead);
LTErase(phead->prev);
}
テスト:
空の
bool LTEmpty(LTNode* phead)//判空
{
return phead->next == phead;
}
サイズ
このインターフェースは通常あまり使用されず、一度通過するだけで長さを取得できます。考えてみてください。センチネル ビットを使用してその長さを記録できますか?
size_t LTSize(LTNode* phead)
{
assert(phead);
size_t size = 0;
LTNode* cur = phead->next;
while (cur != phead)
{
++size;
cur = cur->next;
}
return size;
}
破壊する
単一リンク リストを破棄するプロセスと同じように、最後にヘッド ノードも解放する必要があることに注意してください。
単一リンク リストと同様に、先頭の二重リンク リストも、ポインターを正しく空にするために 2 次ポインターを渡す必要があります (シーケンス テーブルはメンバー ポインターを直接空にします)。最初のレベルでのポインターの一貫性を維持するために、上部スタック フレームの最後でnull 操作を実行することもできます。
void LTDestroy(LTNode* phead)
{
assert(phead);
LTNode* cur = phead->next;
while (cur != phead)
{
LTNode* next = cur->next;
free(cur);
cur = next;
}
free(phead);
}
テスト:
完全なコード
//List.h
#pragma once
#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
#include <stdbool.h>//判空
typedef int LTDataType;
typedef struct ListNode
{
struct ListNode* prev;
struct ListNode* next;
LTDataType data;
}LTNode;
LTNode* BuyListNode(LTDataType x);//创建节点
LTNode* LTInit();//初始化
void LTPushBack(LTNode* phead, LTDataType x);//尾插
void LTPopBack(LTNode* phead);//尾删
void LTPushFront(LTNode* phead, LTDataType x);//头插
void LTPrint(LTNode* phead);//打印
void LTPopFront(LTNode* phead);//头删
LTNode* LTFind(LTNode* phead, LTDataType x);//查找
void LTInsert(LTNode* pos, LTDataType x);//pos插入
void LTErase(LTNode* pos);//pos删除
bool LTEmpty(LTNode* phead);//判空
size_t LTSize(LTNode* phead);
void LTDestroy(LTNode* phead);//销毁
//List.cpp
#include"List.h"
LTNode* BuyListNode(LTDataType x)
{
LTNode* node = (LTNode*)malloc(sizeof(LTNode));//开空间
if (node == NULL)
{
perror("malloc fail");
exit(-1);
}
LTNode* next = nullptr;
LTNode* prev = nullptr;
node->data = x;
return node;
}
LTNode* LTInit()
{
LTNode* phead = BuyListNode(-1);//数据无意义
phead->next = phead;
phead->prev = phead;
return phead;
}
void LTPushBack(LTNode* phead, LTDataType x)//尾插
{
assert(phead);//可以不断言
LTNode* newnode = BuyListNode(x);
LTNode* tail = phead->prev;
tail->next = newnode;
newnode->prev = tail;
newnode->next = phead;
phead->prev = newnode;
//LTInsert(phead, x);
}
void LTPopBack(LTNode* phead)//尾删
{
assert(phead);
assert(phead->next != phead);//防止删掉自己
LTNode* tail = phead->prev;
LTNode* tailPrev = tail->prev;
phead->prev = tailPrev;
tailPrev->next = phead;
free(tail);
LTErase(phead->prev);
}
void LTPushFront(LTNode* phead, LTDataType x)//头插
{
assert(phead);
ListNode* newnode = BuyListNode(x);
newnode->next = phead->next;
phead->next->prev = newnode;
phead->next = newnode;
newnode->prev = phead;
//顺序无关
/*LTNode* first = phead->next;
phead->next = newnode;
newnode->next = first;
newnode->prev = phead;
first->prev = newnode;*/
//LTInsert(phead->next, x);
}
void LTPopFront(LTNode* phead)//头删
{
assert(phead);
assert(phead->next != phead);//空
LTNode* first = phead->next;
LTNode* second = first->next;
free(first);
second->prev = phead;
phead->next = second;
//LTErase(phead->next);
}
LTNode* LTFind(LTNode* phead, LTDataType x)
{
assert(phead);
LTNode* cur = phead->next;
while (cur != phead)
{
if (cur->data == x)
{
return cur;
}
cur = cur->next;
}
return nullptr;//找不到返回空
}
void LTInsert(LTNode* pos, LTDataType x)
{
assert(pos);
LTNode* prev = pos->prev;
LTNode* newnode = BuyListNode(x);
// prev newnode pos
prev->next = newnode;
newnode->prev = prev;
newnode->next = pos;
pos->prev = newnode;
}
void LTErase(LTNode* pos)//pos删除
{
assert(pos);
LTNode* prev = pos->prev;
LTNode* next = pos->next;
free(pos);
prev->next = next;
next->prev = prev;
}
void LTPrint(LTNode* phead)//打印
{
assert(phead);
LTNode* cur = phead->next;//正向
while (cur != phead)//结束条件
{
printf("%d ", cur->data);
cur = cur->next;
}
printf("\n");
}
bool LTEmpty(LTNode* phead)//判空
{
return phead->next == phead;
}
size_t LTSize(LTNode* phead)
{
assert(phead);
size_t size = 0;
LTNode* cur = phead->next;
while (cur != phead)
{
++size;
cur = cur->next;
}
return size;
}
void LTDestroy(LTNode* phead)
{
assert(phead);
LTNode* cur = phead->next;
while (cur != phead)
{
LTNode* next = cur->next;
free(cur);
cur = next;
}
free(phead);
}