【データ構造】 ダブルリンクリストを実装するC言語

目次

序文

二重リンクリストノード定義

インターフェース関数の実装

初期化関数

ノードの作成

二重リンクリストを印刷する

末尾挿入ノード

末尾削除ノード

 ヘッダーノード

 先頭削除ノード

 指定した位置の前に挿入

 指定した場所にあるノードを削除します

上書き挿入削除 

リンクされたリストが空かどうかを判断する

リンクされたリストの長さを計算する

リンクされたリストを破棄する

二重リンクリストの完全なコード

リンクリストとシーケンスリストについての簡単な説明


序文

以前にヘッドレスの一方向非循環リンク リストを実装しましたが、今回はヘッド付きの双方向循環リンク リストを実装します。

二重リンクリストノード定義

//双链表节点定义
typedef int LDataType;
typedef struct ListNode
{
	struct ListNode* prev;
	struct ListNode* next;
	LDataType data;
}LNode;

単一リンク リストとは異なり、二重リンク リストを実装する場合は、初期化関数を作成する必要があります。二重リンク リストの初期化にはヘッド ノードが必要で、このヘッド ノードの prev ポインタと next ポインタはそれ自体を指す必要があります。

インターフェース関数の実装

初期化関数

初期化関数はヘッド ノード (センチネル ガード) を作成しますが、このセンチネル ガードは値を保存せず、prev と next がそれ自体を指すようにします。これにより閉ループが形成されます。

//初始化双链表
LNode* LInit()
{
	LNode* newnode = (LNode*)malloc(sizeof(LNode));
	if (newnode == NULL)
	{
		perror("malloc fail");
		exit(-1);
	}
    //prev与next均指向自身
	newnode->next = newnode;
	newnode->prev = newnode;
	return newnode;
}

 単一リンク リストの実装と同様に、他の関数の実装を検出して支援する 2 つの関数も実装します。

ノードの作成

//创建节点、
LNode* BuyListNode(LDataType x)
{
	LNode* newnode = (LNode*)malloc(sizeof(LNode));
	if (newnode == NULL)
	{
		perror("malloc fail");
		exit(-1);
	}
	newnode->data = x;
    //创建后的节点prev与next均指向NULL
	newnode->next = NULL;
	newnode->prev = NULL;
	return newnode;
}

新しく作成したノードの前ポインタと次ポインタが NULL を指すようにするだけでよく、リンク関係も後で扱うため、NULL ポインタを指すようにするだけで済みます。 

二重リンクリストを印刷する

//打印双链表
void LPrint(LNode* phead)
{
	assert(phead);
	LNode* cur = phead->next;
	while (cur != phead)
	{
		printf("%d ", cur->data);
		cur = cur->next;
	}
	printf("\n");
}

追伸:

        リンクリスト情報の印刷が phead->next から始まるのは、phead には値が格納されておらず、リンクリストのノード情報が phead->next から始まるためです。

末尾挿入ノード

//尾插节点
void LPushBack(LNode* phead,LDataType x)
{
    //防止传入空指针
	assert(phead);
    //先将x存入创建的节点中
	LNode* newnode = BuyListNode(x);
    //链表最后一个节点就是phead的prev指针
	LNode* tail = phead->prev;
    //将newnode节点链接到链表中
	tail->next = newnode;
	phead->prev = newnode;
	newnode->next = phead;
	newnode->prev = tail;
}

        二重リンクリストの末尾に第 1 レベルのポインタを挿入するときにパラメータが渡されるのはなぜなのかと疑問に思う人もいるかもしれません。単一リンクリストの末尾にノードを挿入すると、セカンダリポインタが渡されます。それは、現在実装しているダブル リンク リストにはヘッド ノード (センチネル ガード) があり、まさにそのおかげで、セカンダリ ポインタを渡す必要がないからです。その理由は何でしょうか? 前に述べたように、単一リンク リストが渡される理由は 2 次ポインターです。これは、特殊な状況下では最初のノードが null ポインターになるためです。センチネル ガードの場合は異なります。二重リンク リストにノードが 1 つしかなく、それを削除する場合、phead の prev と next をそれ自身の隣にポイントするだけで済みます。センチネル ガードがない場合は、phead を削除する必要があります。これを null ポインターに設定すると、渡されるパラメーターが変更されるため、2 次ポインターを渡す必要があります。

        実際、1 点だけ、リンク リストにセンチネル ガードがある場合は、第 1 レベルのポインターを直接渡すだけです。

末尾削除ノード

//尾删节点
void LPopBack(LNode* phead)
{
	assert(phead);
    //防止传入的链表是一个空链表
	assert(phead->prev != phead);
	LNode* tail = phead->prev;
	LNode* tailPrev = tail->prev;
    //每个节点都是动态开辟出来的,记得释放
	free(tail);
	phead->prev = tailPrev;
	tailPrev->next = phead;
}

 ヘッダーノード

//头插节点
void LPushFront(LNode* phead, LDataType x)
{
	assert(phead);
	LNode* newnode = BuyListNode(x);
	LNode* first = phead->next;
	phead->next = newnode;
	first->prev = newnode;
	newnode->prev = phead;
	newnode->next = first;
}

 先頭削除ノード

//头删节点
void LPopFront(LNode* phead)
{
	assert(phead);
    //防止链表为空
	assert(phead->next != phead);
	LNode* first = phead->next;
	LNode* newfirst = first->next;

	free(first);
	phead->next = newfirst;
	newfirst->prev = phead;
}

 指定した位置の前に挿入

//指定位置前插入
void LInsert(LNode* pos, LDataType x)
{
	assert(pos);
	LNode* newnode = BuyListNode(x);
	LNode* posPrev = pos->prev;
   
	newnode->next = pos;
	pos->prev = newnode;
	posPrev->next = newnode;
	newnode->prev = posPrev;
}

 指定した場所にあるノードを削除します

//删除指定位置节点
void LErase(LNode* pos)
{
	assert(pos);
	LNode* posNext = pos->next;
	LNode* posPrev = pos->prev;
	free(pos);

	posPrev->next = posNext;
	posNext->prev = posPrev;
}

上書き挿入削除 

指定位置のノードを削除する機能と、指定位置より前に挿入する機能により、末尾挿入と末尾削除、先頭プラグ削除の機能を書き換えることができます。

//尾插节点
void LPushFront(LNode* phead, LDataType x)
{
	LInsert(phead, x);
}
//尾删节点
void LPopBack(LNode* phead)
{
	LErase(phead->prev);
}
//头插节点
void LPushFront(LNode* phead, LDataType x)
{
	LInsert(phead->next, x);
}
//头删节点
void LPopFront(LNode* phead)
{
	LErase(phead->next);
}

リンクされたリストが空かどうかを判断する

//判断链表是否为空
bool LIsEmpty(LNode* phead)
{
	assert(phead);
	return phead == phead->next;
}

リンクされたリストの長さを計算する

//计算链表长度
int LSize(LNode* phead)
{
	int count = 0;
	LNode* cur = phead->next;
	while (cur != phead)
	{
		count++;
		cur = cur->next;
	}
	return count;
}

リンクされたリストを破棄する

//销毁链表
void LDestroy(LNode* phead)
{
	assert(phead);
	LNode* cur = phead->next;
    //将每个节点都释放
	while (cur != phead)
	{
		LNode* next = cur->next;
		free(cur);
		cur = next;
	}
	//自己手动将phead置为空指针
	free(phead);
}

二重リンクリストの完全なコード

リスト.h

#pragma once
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <assert.h>
#include <stdbool.h>

typedef int LDataType;
typedef struct ListNode
{
	struct ListNode* prev;
	struct ListNode* next;
	LDataType data;
}LNode;

//初始化双链表
LNode* LInit();

//打印双链表
void LPrint(LNode* phead);

//创建一个节点
LNode* BuyListNode(LDataType x);

//尾插尾删
void LPushBack(LNode* phead, LDataType x);
void LPopBack(LNode* phead);

//头插头删
void LPushFront(LNode* phead, LDataType x);
void LPopFront(LNode* phead);

//在指定位置前插入,删除指定位置
void LErase(LNode* pos);
void LInsert(LNode* pos, LDataType x);

//销毁链表
void LDestroy(LNode* phead);

//判断链表是否为空
bool LIsEmpty(LNode* phead);

//计算链表节点个数
size_t LSize(LNode* phead);

リスト.c

#include "List.h"


LNode* LInit()
{
	LNode* newnode = (LNode*)malloc(sizeof(LNode));
	if (newnode == NULL)
	{
		perror("malloc fail");
		exit(-1);
	}
	newnode->next = newnode;
	newnode->prev = newnode;
	return newnode;
}

LNode* BuyListNode(LDataType x)
{
	LNode* newnode = (LNode*)malloc(sizeof(LNode));
	if (newnode == NULL)
	{
		perror("malloc fail");
		exit(-1);
	}
	newnode->data = x;
	newnode->next = NULL;
	newnode->prev = NULL;
	return newnode;
}

void LPushBack(LNode* phead,LDataType x)
{
	assert(phead);
	LNode* newnode = BuyListNode(x);

	LNode* tail = phead->prev;
	tail->next = newnode;
	phead->prev = newnode;
	newnode->next = phead;
	newnode->prev = tail;

	//LInsert(phead, x);
}

void LPopBack(LNode* phead)
{
	assert(phead);
	assert(phead->prev != phead);
	LNode* tail = phead->prev;
	LNode* tailPrev = tail->prev;

	free(tail);
	phead->prev = tailPrev;
	tailPrev->next = phead;
	//LErase(phead->prev);
}

void LPrint(LNode* phead)
{
	assert(phead);
	LNode* cur = phead->next;
	while (cur != phead)
	{
		printf("%d ", cur->data);
		cur = cur->next;
	}
	printf("\n");
}

void LPushFront(LNode* phead, LDataType x)
{
	assert(phead);
	LNode* newnode = BuyListNode(x);

	LNode* first = phead->next;
	phead->next = newnode;
	first->prev = newnode;
	newnode->prev = phead;
	newnode->next = first;

	//LInsert(phead->next, x);
}

void LPopFront(LNode* phead)
{
	//assert(phead);
	//assert(phead->next != phead);
	//LNode* first = phead->next;
	//LNode* newfirst = first->next;

	//free(first);
	//phead->next = newfirst;
	//newfirst->prev = phead;

	LErase(phead->next);
}

LNode* LFind(LNode* phead, LDataType x)
{
	assert(phead);
	LNode* cur = phead->next;
	while (cur != phead)
	{
		if (cur->data == x)
		{
			return cur;
		}
		cur = cur->next;
	}
	return NULL;
}

void LErase(LNode* pos)
{
	assert(pos);
	LNode* posNext = pos->next;
	LNode* posPrev = pos->prev;
	free(pos);

	posPrev->next = posNext;
	posNext->prev = posPrev;
}

void LInsert(LNode* pos, LDataType x)
{
	assert(pos);
	LNode* newnode = BuyListNode(x);
	LNode* posPrev = pos->prev;

	newnode->next = pos;
	pos->prev = newnode;
	posPrev->next = newnode;
	newnode->prev = posPrev;
}

void LDestroy(LNode* phead)
{
	assert(phead);
	LNode* cur = phead->next;
	while (cur != phead)
	{
		LNode* next = cur->next;
		free(cur);
		cur = next;
	}
	//自己手动置为空指针
	free(phead);
}

bool LIsEmpty(LNode* phead)
{
	assert(phead);
	return phead == phead->next;
}

int LSize(LNode* phead)
{
	int count = 0;
	LNode* cur = phead->next;
	while (cur != phead)
	{
		count++;
		cur = cur->next;
	}
	return count;
}

リンクリストとシーケンスリストについての簡単な説明

リンク リストとシーケンシャル リストはどちらも線形リストですが、両者の類似点と相違点は何ですか? 次のような形式です。

違い シーケンステーブル リンクされたリスト
収納スペースについて 物理的に連続していなければならない 論理的には連続的ですが、物理的には必ずしも連続的ではありません
ランダムアクセス サポートO(1) O(N) はサポートされていません
任意の場所に要素を挿入および削除する 要素を移動する必要がある場合がありますが、これは非効率です; O(N) ポインタを変更するだけです
入れる 十分なスペースがない場合は、ダイナミック シーケンス テーブルを拡張する必要があります。 容量という概念がない
アプリケーションシナリオ 要素の効率的な保管 + 頻繁なアクセス どの場所でも頻繁に挿入と削除が行われる
キャッシュの使用率 高い 低い

以上がダブルリンクリスト実装の内容です。皆さんのお役に立てれば幸いです。何か間違っている場合は、コメント欄(弓)で修正してください。

おすすめ

転載: blog.csdn.net/m0_74459723/article/details/128521866