0 はじめに
机上で学んだことは結局のところ浅いので、詳しくやらなければいけないことはわかっています。最初の数章では、構造体、共用体、ポインターについて学びました。この章は、この知識を包括的に応用したものです。
この本では例としてリンク リストを使用しています。厳密に言うと、リンク リストはデータ構造とアルゴリズムに関連しています。この知識について学びたい学生には、「Dahua Data Structure」という本をお勧めします。本の内容は分かりやすく、しかもしっかりとしたものになっています。
リンクウォッチとはその名の通り、鉄の鎖のように連動した時計のことです。連結リストは1つずつノードで結ばれており、各ノードはデータと次のノードへのポインタの2つで構成されます(データは複数ある場合もありますが、一般的な教科書や資料の例はすべて1つです)。 。
本書では、リンク リストの挿入操作に関する手順のみが説明されており、リンク リストの作成と削除の手順が記載されていないのが残念です(必須ではありませんが、あったほうがよいです)。本は実行できません。したがって、この記事では、その他の必要な関連手順を意図的に追加しました。
本書のイラストには一部間違っている箇所がありますので、よくご確認ください。
この章の概要:
1 つのリンクされたリスト
前述したように、リンク リストはノードから構成されるデータ構造です。通常、ノードは動的に割り当てられます。構造に応じて、シングル リンク リスト、ダブル リンク リスト、循環リンク リストに分類できます。この章では、単一リンク リストと二重リンク リストに焦点を当てます。
2 単一リンクリスト
単一リンク リストでは、各ノードにリンク リスト内の次のノードへのポインタが含まれます。リンク リストの最後のノードのポインタ フィールドは でありNULL
、リンク リストの後ろに他のノードがないことを示します。単一リンク リストの概略図を見てみましょう。
root
ノードはルート ノードとも呼ばれ、リンク リストはこれに基づいて作成されます。リンクリストの作成には先頭挿入方式と末尾挿入方式の2種類があります。
2.1 単一リンクリストへの挿入
チェーン構造により、リンク リストは新しいデータの挿入および削除の際に配列よりもはるかに効率 (平均速度) が高くなるため、リンク リストの挿入操作は特に重要です。この章では、単一リンクリストの挿入操作に焦点を当てます。
2.1.1 最初の試み
タイトルはあまり正確ではありませんが、より正確には、単一リンクされたリストにノードを挿入する必要があります。予約します最初のバージョンプログラムが完成すると、次の内容が得られました。
まずノードを定義し、関連する関数を宣言するため、定義.h
ファイルは次のようになります。
#pragma once
typedef struct NODE {
struct NODE *link;
int value;
}Node;
int sll_insert(Node *current, int new_value);
次に、插入函数
次の .c ファイルを定義して追加します。
#include <stdlib.h>
#include <stdio.h>
#include "sll_node.h"
#define FALSE 0
#define TRUE 1
int sll_insert(Node *current, int new_value)
{
Node *previous = (Node *)malloc(sizeof(Node));
Node *new;
while (current->value < new_value)
{
previous = current;
current = current->link;
}
new = (Node *)malloc(sizeof(Node));
if (new == NULL)
return FALSE;
new->value = new_value;
new->link = current;
previous->link = new;
return TRUE;
}
最後に、main 関数を記述し、次の .c ファイルを追加します。
#include <stdlib.h>
#include <stdio.h>
#include "sll_node.h"
//单链表的整表创建
void CreateList(Node *L, int n, int *point)
{
Node *p1, *r;
int i;
r = L;
for (i = 0; i < n; i++)
{
p1 = (Node *)malloc(sizeof(Node));
p1->value = point[i];
r->link = p1;
r = p1;
}
r->link = NULL;
}
//单链表的整表删除
void ClearList(Node *L)
{
Node *p, *q;
p = L->link;
while (p != NULL)
{
q = p->link;
free(p);
p = q;
}
L->link = NULL;
printf("clear all\n");
}
int main()
{
int ele[10];
//链表数值设置
for (int i = 0; i < 10; i++)
{
ele[i] = i * 5;
}
//定义根指针
Node *p = (Node *)malloc(sizeof(Node));
//定义第一个节点的指针
Node *new_head = NULL;
//创建链表
printf("原始链表:\n");
CreateList(p, 10, ele);
//读取并打印链表
new_head = p->link;
while (new_head->link != NULL)
{
printf("%d->\t", new_head->value);
new_head = new_head->link;
}
printf("%d", new_head->value);
printf("\n");
//插入值为8的节点
sll_insert(p, 8);
//再次读取并打印链表
printf("插入节点后的链表:\n");
new_head = p->link;
while (new_head->link != NULL)
{
printf("%d->\t", new_head->value);
new_head = new_head->link;
}
printf("%d", new_head->value);
printf("\n");
//删除整个链表
ClearList(p);
system("pause");
return 0;
}
ノード
が必要な位置に挿入されたことがわかります (位置はノードの値によって決まります) ノードの具体的な挿入プロセスを見てください (簡単にするために、最初の部分だけを示します) 3 つのノードが描画されます)。
まず正しい挿入位置を見つける必要があります。
while (current->value < new_value)
{
previous = current;
current = current->link;
}
コードのこの部分は理解しやすく、現在の値が新しい値より小さい場合は、次のノードに進みます。図に示すように、
現在の値が新しい値以上になるまでは次のようになります。
次に、新しい値を挿入する方法を見つけます。
new->value = new_value;
new->link = current;
previous->link = new;
3つのステップに分かれています:
- 新しく作成したノードに挿入する値を割り当てます。
- 新しいノードのポインタが現在のノードを指すようにします。
- 前のノードのポインタは新しいノードを指します。
具体的なプロセスを次の図に示します。
この時点で、リンク リストへの新しいノードの挿入が完了します。
2.1.2 最適化挿入機能
この本での最適化とは、最初のノードより小さい値がリンク リストに渡された場合、最初のノードが渡されるためプログラムは実行されないことを意味します。しかし、ルート ノードを渡しているため、この問題は発生しません。
最適化する必要がある小さな領域は 1 つだけです。つまり、挿入位置を探す前に、現在のポインターが null ポインターではないという保証を変更します。そうしないと、ポインターは存在しませんvalue
。変更したプログラムは次のとおりです(変更部分のみを示します)。
while (current != NULL && current->value < new_value)
{
previous = current;
current = current->link;
}
2.1.3 指定位置にノードを挿入する(補足)
また、指定した位置にノードを挿入する挿入方法もあります。実装も難しくありません。
// 在指定位置插入节点
void sll_insertNode(Node** head, int data, int position)
{
if (*head == NULL || position <= 1) {
// 如果链表为空或插入位置为头部,则直接在头部插入节点
Node* newNode = sll_createLinkedList(data);
newNode->link = *head;
*head = newNode;
}
else {
// 否则在指定位置插入节点
Node* temp = *head;
int currentPosition = 1;
while (currentPosition < position - 1 && temp->link != NULL) {
temp = temp->link;
currentPosition++;
}
if (currentPosition < position - 1) {
printf("插入位置超出链表长度\n");
return;
}
Node* newNode = sll_createLinkedList(data);
newNode->link = temp->link;
temp->link = newNode;
}
}
2.2 その他のリンクリスト操作
もちろん、リンク リスト自体については、挿入操作だけでなく、リンク リスト全体の作成と削除も行われます。上記のコードにはすでに反映されています。
2.2.1 単一リンクリストの作成
単一リンクリストを作成するには、先頭挿入と末尾挿入という 2 つの方法があります。上の例では、テール補間法を使用しています。これは、これが従来の考え方により近いためです。
//单链表的整表创建
void CreateList(Node *L, int n, int *point)
{
Node *p1, *r;
int i;
r = L;
for (i = 0; i < n; i++)
{
p1 = (Node *)malloc(sizeof(Node));
p1->value = point[i];
r->link = p1;
r = p1;
}
r->link = NULL;
}
リンク リストの作成プロセスを次の図に示します (最初の 2 つのノードの作成プロセスのみが示されており、後続の操作は同様です)。
2.2.2 単一リンクリストの削除
削除は指定位置のノードを削除する場合と、リンクリスト全体を削除する場合に分かれており、前者の手順が少し難しくなります。
2.2.2.1 指定位置のノードを削除する
指定した位置に挿入するのと同じです。削除する前に、まず位置 (もちろん前のノードの位置) を見つける必要があります。次に、前のノードのリンク ポインタが削除対象のノードをバイパスし、次のノードに直接ジャンプし、その後現在のノードを削除して操作が完了します。
// 删除指定位置的节点
void sll_deleteNode(Node** head, int position)
{
if (*head == NULL) {
return;
}
Node* temp = *head;
int currentPosition = 1;
//找到该节点的前一个节点
while (currentPosition < position - 1 && temp->link != NULL) {
temp = temp->link;
currentPosition++;
}
if (currentPosition < position - 1 || temp->link == NULL) {
printf("删除位置超出链表长度\n");
return;
}
//如果删除头节点,则该节点后移,否则无法访问该链表
if (temp == *head) {
*head = temp->link;
}
//获取当前节点指针,否则到时候无法获取
Node* next_free = temp->link;
//调整指针位置
temp->link = temp->link->link;
free(next_free);
}
2.2.2.2 リンクリスト全体を削除する
リンクされたリスト全体を削除するのは非常に簡単です。次のノードに 1 つずつアクセスして、現在のノードを削除するだけです。
//单链表的整表删除
void sll_deleteLinkedList(Node** head)
{
Node* current = *head;
Node* next;
while (current != NULL) {
next = current->link;
free(current);
current = next;
}
*head = NULL;
}
3 二重リンクリスト
単一リンク リストの代替として、二重リンク リストがあります。二重リンクリストでは、各ノードに 2 つのポインタ (前のノードへのポインタと次のノードへのポインタ)が含まれます。これにより、二重リンク リストを任意の方向にトラバースしたり、二重リンク リストに前後にアクセスしたりすることができます。次の図は二重リンクリストを示しています。
3.1 二重リンクリストへの挿入
3.1.1 順番に挿入する
シングル リンク リストと比較して、ダブル リンク リストはより多くのポインタを変更する必要があるため、明らかに困難です。ただし、基本的な考え方は同じです。つまり、まず挿入する必要がある場所を見つけてから、新しいノードを挿入します。
まずノードを定義します。
typedef struct NODE {
struct NODE *fwd;
struct NODE *bwd;
int value;
}Node;
挿入関数を再度定義します。単一リンク リストと比較して、二重リンク リストはより複雑です。
int dll_insert(Node *rootp, int value)
{
Node *this;
Node *next;
Node *newnode;
//查看value是否已经存在于链表中,如果是就返回
//否则,为新值创建一个节点
for (this = rootp; (next = this->fwd) != NULL; this = next)
{
if (next->value == value)
return 0;
if (next->value > value)
break;
}
newnode = (Node *)malloc(sizeof(Node));
if (newnode == NULL)
return FALSE;
newnode->value = value;
//把新值添加到链表中
if (next != NULL)
{
//情况1或2:并非位于链表尾部
if (this != rootp) /*情况1:并非位于链表的起始位置*/
{
newnode->fwd = next;
this->fwd = newnode;
newnode->bwd = this;
next->bwd = newnode;
}
else /*情况2:位于链表的起始位置*/
{
newnode->fwd = next;
rootp->fwd = newnode;
newnode->bwd = NULL;
next->bwd = newnode;
}
}
else
{
//情况3或4:位于链表的尾部
if (this != rootp)
{
newnode->fwd = NULL;
this->fwd = newnode;
newnode->bwd = this;
rootp->bwd = newnode;
}
else
{
newnode->fwd = NULL;
rootp->fwd = newnode;
newnode->bwd = NULL;
rootp->bwd = newnode;
}
}
return TRUE;
}
最後に、main 関数の関連プロシージャ:
#include <stdlib.h>
#include <stdio.h>
#include "dll_node.h"
int main()
{
Node* head = NULL;
// 创建链表(只有头节点,值为0)
head = dll_createLinkedList(0);
//插入节点
dll_insert(head, 5);
dll_insert(head, 10);
dll_insert(head, 15);
dll_insert(head, 20);
// 删除指定位置的节点
dll_deleteNode(&head, 3);
// 打印链表
dll_printLinkedList(head);
// 删除整个链表
dll_deleteLinkedList(&head);
system("pause");
return 0;
}
単一リンクリストを参照すると、マルチリンクリストプログラムを理解するのは難しくありません。
リンクされたリストの印刷機能もあります。
void dll_printLinkedList(Node* head) {
Node* temp = head;
while (temp != NULL) {
printf("%d ", temp->value);
if (temp->fwd != NULL)
printf("->");
temp = temp->fwd;
}
}
印刷出力:
リンク リストを作成し、必要な値を挿入した後、最後に10
value を含む 3 番目のセクションを削除すると、上記のように表示されることがわかります。
3.1.2 指定位置にノードを挿入(補足)
もちろん、指定した位置にノードを挿入することも可能です。
void dll_insertNode(Node** head, int data, int position) {
if (*head == NULL || position <= 1) {
// 如果链表为空或插入位置为头部,则直接在头部插入节点
Node* newNode = createLinkedList(data);
newNode->fwd = *head;
if (*head != NULL) {
(*head)->bwd = newNode;
}
*head = newNode;
}
else {
// 否则在指定位置插入节点
Node* temp = *head;
int currentPosition = 1;
while (currentPosition < position - 1 && temp->fwd != NULL) {
temp = temp->fwd;
currentPosition++;
}
if (currentPosition < position - 1) {
printf("插入位置超出链表长度\n");
return;
}
Node* newNode = createLinkedList(data);
newNode->bwd = temp;
newNode->fwd = temp->fwd;
if (temp->fwd != NULL) {
temp->fwd->bwd = newNode;
}
temp->fwd = newNode;
}
}
3.2 その他のリンクリスト操作
3.2.1 ダブルリンクリストの作成
単一リンク リストと同様に、二重リンク リストにも、リンク リスト全体の作成や削除などの同じ操作があります。
リンクリスト作成プログラム:
Node* dll_createLinkedList(int data)
{
Node* newNode = (Node*)malloc(sizeof(Node));
if (newNode == NULL) {
printf("内存分配失败\n");
exit(1);
}
newNode->value = data;
newNode->bwd = NULL;
newNode->fwd = NULL;
return newNode;
}
プログラムは比較的理解しやすいもので、ノードのメモリを確保し、ノードへのポインタを取得し、初期値を代入するだけで、前後にノードの接続はありません。
3.2.2 二重リンクリストの削除
3.2.2.1 指定位置のノードを削除する
二重リンクリストの場合、1 つのノードに同時に 2 つのポインターが含まれるため、以下で詳しく説明するように、指定された位置にあるノードを削除することは比較的困難です。
// 删除指定位置的节点
void dll_deleteNode(Node** head, int position)
{
if (*head == NULL) {
return;
}
Node* temp = *head;
int currentPosition = 1;
while (currentPosition < position && temp != NULL) {
temp = temp->fwd;
currentPosition++;
}
if (currentPosition < position || temp == NULL) {
printf("删除位置超出链表长度\n");
return;
}
if (temp == *head) {
*head = temp->fwd;
}
if (temp->bwd != NULL) {
temp->bwd->fwd = temp->fwd;
}
if (temp->fwd != NULL) {
temp->fwd->bwd = temp->bwd;
}
free(temp);
}
その中で最も重要なものはわずか 4 行です。
if (temp->bwd != NULL) {
temp->bwd->fwd = temp->fwd;
}
if (temp->fwd != NULL) {
temp->fwd->bwd = temp->bwd;
}
削除したいノードの前後のノードを直接アクセス可能にして、現在のノードを削除すれば削除は完了です。
3.2.2.2 リンクリスト全体を削除する
二重リンクリストの削除手順:
void deleteLinkedList(Node** head)
{
Node* current = *head;
Node* next;
while (current != NULL) {
next = current->fwd;
free(current);
current = next;
}
*head = NULL;
}
削除機能もわかりやすく、次のノードを見つけてから現在のノードのメモリを解放します。この順序を逆にすることはできません。逆にすると、リリース後に次のノードのポインタを取得できなくなり、削除が失敗します。
このプログラムではポインタを意識する必要はありませんがbwd
、カレントノードのメモリを解放すると、bwd
当然そのポインタは意味を持ちません、ポインタを格納していたメモリも解放され、他の情報が格納できるようになります。
4 まとめ
単一リンク リストは、ポインターを使用して値を格納するデータ構造です。リンク リストの各ノードには、リンク リストの次のノードを指すフィールドが含まれています。
二重リンク リストの各ノードには2 つlink
のフィールドが含まれます。1 つはリンク リストの次のノードを指し、もう 1 つはリンク リストの前のノードを指します。
さらに、循環リンク リストなどの一般的なデータ構造もありますが、これらはあまり一般的ではありません。リンク リストの作成、削除、挿入、および検索方法 (削除は難しくありません) をマスターすると、基本的にこの章の内容全体をマスターしたことになります。
本書で紹介されている連結リストの挿入操作は順番に挿入されていますが、実際にはあらかじめ決められた位置にノードが挿入されることが多いです。ここにはいくつかの違いがあります。この記事では、両方の方法を個別に説明します。
- - - - - - - - - - - - - - - - - - - -終わり - - - - - ------------------------