データ構造シリーズ学習 (4) - 循環リンクリスト

目次

導入:

勉強:

コード:

ヘッダー ファイル (Circular_Linked_List):

構造設計:

関数関数の宣言: 

コード:

ソース ファイル (Circular_Linked_List):

初期化関数 (Init_clist):

空判定関数(IsEmpty):

検索機能(検索):

有効長の取得関数 (Get_length):

クリア機能(クリア):

デストロイ機能(Destroy): 

印刷機能(表示): 

ヘッド挿入関数(Insert_head):

末尾挿入関数 (Insert_tail):

位置による関数の挿入 (Insert_pos): 

ヘッド削除関数(Delete_head): 

末尾削除関数 (Delete_tail): 

位置による関数の削除 (Delete_pos):

値による関数の削除 (Delete_val):

テスト: 

初期化関数、位置補間関数、および印刷関数をテストします。

テストヘッダープラグイン関数:

 テール補間関数をテストします。

位置補間関数をテストします。 

テストヘッドの削除数: 

テストテール削除関数:

位置による削除関数をテストします。 

値による削除関数をテストします。

テスト破壊関数:

ルックアップ関数のテスト:

要約:

考える:


導入:

データ構造学習ディレクトリ:

データ構造シリーズ学習 (1) - データ構造入門

データ構造系列学習(2) - 系列テーブル(Contiguous_List) 

データ構造シリーズの学習 (3) - 単一リンクリスト (Linked_List)

前回の記事では、C 言語を学習して使用して、ヘッド ノードのない単一リンク リストを実現しましたが、今回は、もう 1 つの連鎖ストレージ構造である循環リンク リストの一方向循環リンク リストを実装します。コードで実装します。

勉強:

リンク ストレージのもう 1 つのストレージ構造である循環リンク リストとして、まず、一方向循環リンク リストと通常の単一リンク リストの違いを明確にする必要があります。

これら 2 つのストレージ構造を区別するために 2 つの図を使用します。

先頭ノードを持たない単一リンクリストの構造は下図のようになります リンクリストの先頭ノードのデータフィールドにはデータが格納されず、ポインタフィールドには次ノードのアドレスが格納されます各ノードは、最後のノード ポインタ フィールドが空になるまで、次のノードのアドレスを保存し、データを保存します。

一方向循環リンクリストの構造は下図のようになります. ヘッドノードのデータフィールドにはデータが格納されず, 各ノードには次のノードのアドレスが格納されます. ヘッドノードのない単一リンクリストとは異なり,循環リンク リストの最後のノードにはヘッド ノードのアドレスが格納され、リンク リスト全体がリング構造を形成します。

 

一方向循環連結リストと単連結リストの構造上の違いは大体わかったところで、循環連結リストの存在意義は何でしょうか。

リング構造が形成されたので、リンク リスト内の任意のノードから開始して、リンク リスト全体のすべてのノードに移動できます。

もちろん、リンクリストの構造が変化すると、対応する関数もランダムに変化します。一方向循環リンク リストの終了ノードのポインタ フィールドは空ではなくなり、ヘッド ノードのアドレスを格納するため、リンク リストを初期化するときに、最後にデータを挿入し、位置ごとにデータを挿入します。データの削除、位置によるデータの削除、値によるデータの削除、データの印刷などを行う場合は、これらの関数の実装をそれに応じて変更する必要があります。

コード:

ヘッダー ファイル (Circular_Linked_List):

構造設計:

一方向循環リンクリストの構造設計は単方向リンクリストと同じで、int型の名前をElem_typeに変更し、CNodeという構造体変数を定義し、そのメンバーはElem_type型のデータフィールドと、構造体ポインタ型の次の領域。

typedef int Elem_type;//范型定义
typedef struct CNode
{
    Elem_type data;
    struct CNode *next;
}CNode, *PCnode;//结构体别名

関数関数の宣言: 

この関数を一方向循環リンク リストに実装する必要があります。

初期化関数 (Init_clist);

空判定関数(IsEmpty);

検索機能(検索);

有効長を取得します (Get_length);

クリア機能(クリア);

破壊機能(Destroy);

破壊関数 (Destroy1);

印刷機能 (表示);

ヘッド挿入関数 (Insert_head);

末尾挿入関数 (Insert_tail);

位置による補間関数 (Insert_pos);

ヘッド削除関数(Delete_head);

末尾削除関数 (Delete_tail);

位置による関数の削除 (Delete_pos);

値による関数の削除 (Delete_val);

コード:

void Init_clist(PCnode cplist);
bool IsEmpty(PCnode cplist);
PCnode Search(PCnode cplist,Elem_type val);
int Get_length(PCnode cplist);
bool Clear(PCnode cplist);
bool Destroy(PCnode cplist);//无限头删
bool Destroy2(PCnode cplist);//不借助头节点,有两个辅助指针
void Show(PCnode cplist);
bool Insert_head(PCnode cplist,Elem_type val);
bool Insert_tail(PCnode cplist,Elem_type val);
bool Insert_pos(PCnode cplist,int pos,Elem_type val);
bool Delete_head(PCnode cplist);
bool Delete_tail(PCnode cplist);
bool Delete_pos(PCnode cplist,int pos);
bool Delete_val(PCnode cplist,Elem_type val);

ソース ファイル (Circular_Linked_List):

初期化関数 (Init_clist):

一方向循環リンク リストを導入したときに、一方向循環リンク リストの終了ノードにはヘッド ノードのアドレスが格納されると述べました。そのため、初期化すると、最初のノードの次のフィールドには実際にヘッド ノードのアドレスが格納されます。最初のノード自体。

void Init_clist(PCnode cplist)
{
    //问题:如果没有一个有效节点,那么头节点的next域应该指向谁? 答:指向自己
    assert(cplist != nullptr);
    cplist->next = cplist;
}

空判定関数(IsEmpty):

一方向循環リンクリストが空の場合は、リンクリストに有効なデータが保存されていないこと、つまりヘッドノードのみが存在し、ヘッドノードにはデータが存在しないことを意味します。保存するヘッド ノードのアドレスとしての値。ヘッド ノードの次のフィールドに指定できます。

bool IsEmpty(PCnode cplist)
{
    return cplist->next == cplist;
}

検索機能(検索):

lookup 関数の戻り値を、検索する値のアドレスとして設定します。最初に行う必要があるのは、リンク リストに対して null 操作を実行することです。リンク リストが空の場合は、空のアドレスに値を返します。ヘッドノードの後の最初の有効なノードを指す構造体型のポインタ変数 p を新規定義し、その値が検索される値は、p が指すノードのデータと同じです。フィールドに格納された値は、ノードのアドレスを返します。見つからない場合は、空のアドレスが返されます。

PCnode Search(PCnode cplist,Elem_type val)
{
    assert(cplist != nullptr);
    if(IsEmpty(cplist)){
        return nullptr;
    }
    PCnode p = cplist->next;
    for(;p != cplist;p = p->next){
        if(val == p->data){
            return p;
        }
    }
    return nullptr;
}

有効長の取得関数 (Get_length):

ヘッド ノードの後の最初の有効なノードを指すように構造体型のポインタ変数 p を定義し、次に有効なノードの数をカウントする整数値 count を定義し、ループを定義します。ループの終了条件は、次のノードを指していません。 head ノードをループし、実行されるたびに count の値に 1 を加算し、最終的な戻り値を count に設定します。

int Get_length(PCnode cplist)
{
    assert(cplist != nullptr);
    PCnode p = cplist->next;
    int count = 0;
    for(;p != cplist;p = p->next){
        count++;
    }
    return count;
}

クリア機能(クリア):

リンク リストの Empty と destroy は似ているため、clear 関数で destroy 関数を直接呼び出すことができます。

bool Clear(PCnode cplist)
{
    Destroy(cplist);
    return true;
}

デストロイ機能(Destroy): 

cp が空でない場合は、リンク リストが完全に空になるまで head delete 関数をループ実行します。

bool Destroy(PCnode cplist)//无限头删
{
    while(!IsEmpty(cplist)){
        Delete_head(cplist);
    }
    return true;
}

印刷機能(表示): 

構造体型のポインタ変数 p をヘッド ノードの後の最初の有効なノードを指すように定義し、ポインタ p に関するループを定義します。ループ終了条件はヘッド ノードを指していません。そして、指定されたノードのデータ フィールドを出力します。ループが実行されるたびに、サイクルが完了するまで p によって実行されます。

void Show(PCnode cplist)
{
    //判定:使用不需要前驱的for循环
    assert(cplist != nullptr);
    PCnode p = cplist->next;
    for(;p != cplist;p = p->next){
        printf("%5d",p->data);
    }
    printf("\n");
}

ヘッド挿入関数(Insert_head):

malloc 関数を使用してヒープ領域に新しいノードを適用し、新しいノードにメモリ領域を割り当て、挿入する値を新しいノードのデータ フィールドに割り当て、次のフィールドを置き換えて、最初のノードのアドレスを割り当てます。図に示すように、pnewnode の次のフィールドで、元のヘッド ノードに格納されている最初の有効なノードのアドレスを pnewnode のアドレスに置き換えます。

 

bool Insert_head(PCnode cplist,Elem_type val)
{
    assert(cplist != nullptr);
    PCnode pnewnode = (PCnode) malloc(1 * sizeof(CNode));
    assert(pnewnode != nullptr);
    pnewnode->data = val;
    pnewnode->next = cplist->next;
    cplist->next = pnewnode;
    return true;
}

末尾挿入関数 (Insert_tail):

malloc関数でヒープ領域に新規ノードを申請し、新規ノードにメモリ空間を割り当て、新規ノードのデータフィールドに挿入する値を代入し、先頭を指す構造体型のポインタpを定義します。ノードにループを定義し、ループの終了条件は p ポインタである 次のフィールドがヘッド ノードと等しくない (つまり、終了ノードまでトラバースする) ため、次のフィールドを置き換えます。元のエンド ノードの次のフィールドに格納されていたヘッド ノードを pnewnode の次のフィールドに割り当て、pnewnode のアドレスを割り当てます。図に示すように、値を元のエンド ノードの次のフィールドに割り当てます。

bool Insert_tail(PCnode cplist,Elem_type val)
{
    assert(cplist != nullptr);
    PCnode pnewnode = (PCnode) malloc(1 * sizeof(CNode));
    assert(pnewnode != nullptr);
    pnewnode->data = val;
    PCnode p = cplist;
    for(;p->next != cplist;p = p->next);
    pnewnode->next = p->next;
    p->next = pnewnode;
    return true;
}

位置による関数の挿入 (Insert_pos): 

malloc関数でヒープ領域に新規ノードを申請し、新規ノードにメモリ空間を割り当て、新規ノードのデータフィールドに挿入する値を代入し、先頭を指す構造体型のポインタpを定義します。ノード、ループを定義し、終了条件は i < pos です。ここで、p が挿入される位置の前のノードを指す場合、p の次のフィールドには挿入されるノードのアドレスが格納され、その次のフィールドには挿入されるノードのアドレスが格納されます。フィールドが置き換えられ、挿入される位置の後ろのノードのアドレスが pnewnode の次のフィールドに割り当てられます。その後、pnewnode 自体のアドレスを、挿入される位置の前のノードを指すポインタ p の次のフィールドに代入するだけです。挿入される。

  

bool Insert_pos(PCnode cplist,int pos,Elem_type val)
{
    assert(cplist != nullptr);
    PCnode pnewnode = (PCnode) malloc(1 * sizeof(CNode));
    assert(pnewnode != nullptr);
    pnewnode->data = val;
    PCnode p = cplist;
    for(int i = 0;i < pos;i++){
        p = p->next;
    }
    pnewnode->next = p->next;
    p->next = pnewnode;
    return true;
}

ヘッド削除関数(Delete_head): 

まず、リンク リストが空であると判断します。リンク リストが空の場合は、直接 false を返します。ヘッド ノードの後の最初の有効なノード (つまり、対象となるノード) を指す構造体型のポインタ変数 p を定義します。図に示すように、 に保存された最初の有効なノードのアドレスを 2 番目の有効なノードのアドレスに置き換えてから、p ポインタを解放します。

 

bool Delete_head(PCnode cplist)
{
    assert(cplist != nullptr);//安全性处理
    if(IsEmpty(cplist)){
        return false;
    }
    PCnode p = cplist->next;//找到待删除节点
    cplist->next = p->next;//跨越指向
    free(p);//释放删除节点
    return true;
}

末尾削除関数 (Delete_tail): 

まず、リンクリストが空であると判定し、リンクリストが空の場合は直接 false を返します 先頭ノードを指す構造体型のポインタ変数 p を定義し、p に関するループを定義し、ループの終了条件はp の次のフィールドはヘッド ノードではありません (また、最後のノードまでトラバースします)。次に、ヘッド ノードを指す構造体型のポインタ変数 q を定義し、q に関するループを定義します。ループの終了条件は次のとおりです。 q の次のフィールドが p ではない (つまり、p A ノードが指すノードにトラバースする) 場合、p が指すノードの次のフィールドに元々格納されていたヘッド ノード アドレスをノードの次のフィールドにコピーします。図に示すように、末尾の削除操作が完了しました。

bool Delete_tail(PCnode cplist)
{
    assert(cplist != nullptr);
    if(IsEmpty(cplist)){
        return false;
    }
    PCnode p = cplist;
    for(;p->next != cplist;p = p->next);
    PCnode q = cplist;
    for(;q->next!=p;q = q->next);
    q->next = p->next;
    free(p);
    return true;
}

位置による関数の削除 (Delete_pos):

まず、連結リストが空であると判定し、空の場合は直接 false を返します 先頭ノードを指す構造体型のポインタ変数 q を定義し、ループを定義し、ループの終了条件を i とします< pos. このとき、q が指すものは、ノードの前のノードを削除し、q の次のフィールドが削除対象のノードとなり、次のフィールドを指す構造体型のポインタ変数 p を定義します。 q の場合、p の次のフィールドが削除されるノードの次のノードとなり、クロスポイント操作を実行して、p の次のフィールドを、最初にノードのアドレスを格納していた q の次のフィールドにコピーします。削除するものを選択し、p ポインタを放します。

bool Delete_pos(PCnode cplist,int pos)
{
    assert(cplist != nullptr);
    if(IsEmpty(cplist)){
        return false;
    }
    PCnode q = cplist;
    for(int i = 0;i < pos;i++){
        q = q->next;
    }
    PCnode p = q->next;
    q->next = p->next;
    free(p);
    return true;
}

値による関数の削除 (Delete_val):

まずリンクリストが空であると判定し、リンクリストが空の場合は直接 false を返し、検索関数の戻り値を格納する構造体型のポインタ変数 p を定義し、p ポインタが空の場合は false を返し、ポインタ変数 q はヘッド ノードを指し、ループを定義します。ループの終了条件は、q の次のフィールドが p ではないことです (つまり、値ノードの前のノードにトラバースして、削除された)を実行し、クロスポイント操作を実行し、q の元の次のフィールドに、削除するノードのアドレスを p に置き換えた次のフィールドを格納し、ポインタ p を解放します。

bool Delete_val(PCnode cplist,Elem_type val)
{
    assert(cplist != nullptr);
    if(IsEmpty(cplist)){
        return false;
    }
    PCnode p = Search(cplist,val);
    if(p == nullptr){
        return false;
    }
    PCnode q = cplist;
    for(;q->next != p;q = q->next);
    q->next = p->next;
    free(p);
    return true;
}

テスト: 

初期化関数、位置補間関数、および印刷関数をテストします。

#include<cstdio>
#include<cassert>
#include<cstdlib>
#include "Circular_Linked_List.h"
int main()
{
    CNode circle;
    Init_clist(&circle);
    for(int i = 0;i < 10;i++){
        Insert_pos(&circle,i,i + 1);
    }
    printf("原始数据为:\n");
    Show(&circle);
/*
    其他测试函数添加位置
*/
    return 0;
}

操作結果:

 

テストヘッダープラグイン関数:

ここでは、リンクされたリストの先頭に 100 を挿入します。

    Insert_head(&circle,100);
    printf("\n头插后的数据为:\n");
    Show(&circle);

操作結果: 

 テール補間関数をテストします。

ここでは、リンクされたリストの最後に 100 を挿入します。

    Insert_tail(&circle,100);
    printf("\n头删后的数据为:\n");
    Show(&circle);

操作結果:

 

位置補間関数をテストします。 

ここでは、ヘッド ノードの後の 2 番目の有効なノードの後に​​ 100 を挿入します。

    Insert_pos(&circle,2,100);
    printf("\n头删后的数据为:\n");
    Show(&circle);

操作結果:

 

テストヘッドの削除数: 

ここでは、リンク リストのヘッド ノードの後の最初の有効なノードを削除します。

    Delete_head(&circle);
    printf("\n头删后的数据为:\n");
    Show(&circle);
    return 0;

操作結果:

 

テストテール削除関数:

ここでは、リンクされたリストの終了ノードを削除します。

    Delete_tail(&circle);
    printf("\n尾删后的数据为:\n");
    Show(&circle);
    return 0;

操作結果:

 

位置による削除関数をテストします。 

ここでは、リンク リストのヘッド ノードの後の 4 番目の有効なノードの後のノードを削除します。

    Delete_pos(&circle,4);
    printf("\n按位置删后的数据为:\n");
    Show(&circle);
    return 0;

操作結果:

 

値による削除関数をテストします。

ここでは、リンクされたリストの値 3 を削除します。

    Delete_val(&circle,3);
    printf("\n按位置删后的数据为:\n");
    Show(&circle);
    return 0;

操作結果:

 

テスト破壊関数:

    Destroy(&circle);
    printf("\n头删后的数据为:\n");
    Show(&circle);

 操作結果:

ルックアップ関数のテスト:

    PCnode p = Search(&circle,5);
    printf("%p",p);
    return 0;

操作結果:

  

探している値をリンク リストに存在しない値に置き換えると、空のアドレス (nullptr) が返されます。

要約:

一般に、一方向循環リンク リストは、ヘッド ノードのない単一リンク リストとよく似ています。コードを記述する前に、一方向循環リンク リストの構造を整理し、両者の違いを明確にする必要があります。このようにして、コードを記述する際の効率が向上します。

考える:

/*面试笔试喜欢问的问题:
 * 第一题:单链表怎么进行逆置?
 * 第二题:单链表如何获取倒着打印的数据?
 * 第三题:如何判断两个单链表是否存在交点?如果存在相交点则找到第一个相交点?
 * 第四题:如果判断一个单链表是否存在环?如果存在环,则找到入环点。
 * 第五题:给一个随机节点的地址,如何在O(1)时间内删除这个节点?(这个随机节点不能是尾节点)
 * 第六题:如何判断一个单链表是否是回文链表
 */

2022年11月7日 23:00 

おすすめ

転載: blog.csdn.net/weixin_45571585/article/details/127724891