データ構造:二重リンクリストの実装(C実装)

ここに画像の説明を挿入

個人ホームページ:個人ホームページ
個人コラム:「データ構造」 「C言語」


序文

このブログで実装するのは代表的な双方向循環リンクリストであり、この構造により末尾の挿入と末尾の削除は O(1) の時間計算だけで実現されます。
その構造は次のとおりです。

ここに画像の説明を挿入


1. 実装アイデア

1. ノード構造(ListNode)

実装されるリンク リストは双方向ループであるため、ポインタ フィールドには前のノードへのポインタ次のノードへのポインタが必要です。双方向ループの場合、双方向ループを実現するには、末尾ノードの次のノードがヘッド ノードを指し、ヘッド ノードの前のノードが末尾ノードを指すだけで済みます。
次のように:
ここに画像の説明を挿入

typedef int LTDataType;//方便以后修改数据类型

typedef struct ListNode
{
    
    
	LTDataType data;
	struct ListNode* next;
	struct ListNode* prev;
}ListNode;

2. 新しいノード (BuyListNode) を作成します。

スペースを動的に開き、ノードの prev と next がそれ自体を指すようにし (ヘッダー構造の作成を容易にするため)、データに値を割り当てて、スペースのアドレスを返します。

ここに画像の説明を挿入

//创建新节点
ListNode* BuyListNode(LTDataType x)
{
    
    
	ListNode* newnode = (ListNode*)malloc(sizeof(ListNode));
	if (newnode == NULL)
	{
    
    
		perror("malloc");
		exit(-1);
	}

	newnode->data = x;
	newnode->next = newnode;
	newnode->prev = newnode;

	return newnode;
}

3. ヘッドノードの作成(ListCreate)

BuyListNode 関数を再利用して、ヘッド ノードのデータを無効にします (ここでは -1)。

//创建返回链表的头结点
ListNode* ListCreate()
{
    
    
	return BuyListNode(-1);
}

4. 二重リンクリストの破壊(ListDestroy)

リンクされたリストを破棄するには、リンクされたリストを走査する必要があります。では、リンクされたリストをどのように走査するのでしょうか?

  • 終了条件はヘッド ノードまでトラバースすることです
  • ヘッド ノードの次のノードからトラバースを開始します

上記のように、リンクされたリストをループできます。
ノードを破棄するには、2 つのポインター変数が必要です。1 つは解放されるノードを記録するため (cur)、もう 1 つは解放されるノードの次のノードを記録するため (curNext) です。free(cur) のたびに、cur = curNext になります。
ここに画像の説明を挿入

//双向链表的销毁
void ListDestroy(ListNode* pHead)
{
    
    
	assert(pHead);

	ListNode* cur = pHead->next;
	while (cur != pHead)
	{
    
    
		ListNode* curNext = cur->next;
		
		free(cur);
		cur = curNext;
	}
	free(pHead);
}

5. 二重リンクリストの印刷(ListPrint)

必要なのは、リンクされたリストを走査して出力することだけです。

  • 終了条件はヘッド ノードまでトラバースすることです
  • ヘッド ノードの次のノードからトラバースを開始します
//双向链表的打印
void ListPrint(ListNode* pHead)
{
    
    
	assert(pHead);

	ListNode* cur = pHead->next;
	printf("head<=>");
	while (cur != pHead)
	{
    
    
		printf("%d<=>", cur->data);
		cur = cur->next;
	}
	printf("head\n");
}

6. 二重リンクリストの末尾挿入(ListPushBack)

末尾ノード (tail) の次のノードが newnode を指すようにし、newnode の前のノードが末尾ノード (tail) を指すようにし、newnode の次のノードがヘッド ノードを指すようにし、ヘッド ノードの prev がnewnode. リンク リストが双方向サイクルであることがわかっている場合、
ヘッド ノードの前のノードがテール ノードになります。(単一のリンク リストと比較すると、リンク リストは末尾を見つけるためにトラバースする必要がなく、時間計算量は O(1) です。)
ここに画像の説明を挿入

//双向链表的尾插
void ListPushBack(ListNode* pHead, LTDataType x)
{
    
    
	assert(pHead);

	ListNode* newnode = BuyListNode(x);
	ListNode* tail = pHead->prev;

	tail->next = newnode;
	newnode->prev = tail;
	pHead->prev = newnode;
	newnode->next = pHead;
}

7. 二重リンクリストの末尾削除(ListPopBack)

必要なポインターは、tail (末尾ノードを指す)、tailPrev (末尾ノードの前のノードを指す) の 2 つだけです。
末尾を解放し、tailPrev の次のノードがヘッド ノードを指すようにし、ヘッド ノードの prev が tailPrev を指すようにします。

  • 注: head->next == head の場合、リンクされたリストには有効なデータがなく、最後にデータを削除することはできません。

ここに画像の説明を挿入

//双向链表的尾删
void ListPopBack(ListNode* pHead)
{
    
    
	assert(pHead);
	//pHead->next == pHead时,链表没有元素
	assert(pHead->next != pHead);

	ListNode* tail = pHead->prev;
	ListNode* tailPrev = tail->prev;

	pHead->prev = tailPrev;
	tailPrev->next = pHead;
	free(tail);
}

8. 二重リンクリストの先頭挿入(ListPushFront)

最初に必要なのはポインター (ヘッド ノードの次のノード) だけです。そのため、first の prev は newnode を指し、newnode の next は first を指し、head の次は newnode を指し、newnode の prev は head を指します。 。

ここに画像の説明を挿入
ヘッド ノードはセンチネル ビットであり、有効なデータは格納されません。

//双向链表的头插
void ListPushFront(ListNode* pHead, LTDataType x)
{
    
    
	assert(pHead);

	ListNode* newnode = BuyListNode(x);
	ListNode* first = pHead->next;

	newnode->next = first;
	first->prev = newnode;
	newnode->prev = pHead;
	pHead->next = newnode;
}

9. 二重リンクリスト(ListPopFront)の先頭を削除

最初に (ヘッド ノードの次のノードを指す)、2 番目 (ヘッド ノードの次のノードを指す) の 2 つのポインターが必要です。
最初のノードを解放して、2 番目の前のノードがヘッド ノードを指し、ヘッド ノードの次のノードが 2 番目のノードを指すようにします。

  • 注: head->next == head の場合、リンクされたリストが空であり、head を削除できないことを意味します。

ここに画像の説明を挿入

//双向链表的头删
void ListPopFront(ListNode* pHead)
{
    
    
	assert(pHead);
	assert(pHead->next != pHead);

	ListNode* first = pHead->next;
	ListNode* second = first->next;

	second->prev = pHead;
	pHead->next = second;
	free(first);
}

10. 二重リンクリスト検索 (ListFind)

リンク リストを走査し、リンク リスト要素が検索対象のオブジェクトであるかどうかを比較する必要があります。見つかった場合は、ノードのアドレスを返します。見つからない場合は NULL を返します。

//双向链表的查找
ListNode* ListFind(ListNode* pHead, LTDataType x)
{
    
    
	assert(pHead);

	ListNode* cur = pHead->next;
	while (cur != pHead)
	{
    
    
		if (cur->data == x)
			return cur;
		
		cur = cur->next;
	}

	return NULL;
}

11. 二重リンクリストを pos の前に挿入します (ListInsert)

ポインター posPrev (pos の前のノードを指す) が必要です。
pos の prev は newnode を指し、newnode の next は pos を指し、posPrev の次は newnode を指し、newnode の prev は posPrev を指します。

  • pos がヘッド ノード (セントリー位置) を指している場合、ListInsert は末尾の挿入を完了することと同じです。
  • pos がヘッド ノードの次のノード (セントリー位置) を指している場合、ListInsert はヘッドの挿入と同等です。

ここに画像の説明を挿入

//双向链表在pos前进行插入
void ListInsert(ListNode* pos, LTDataType x)
{
    
    
	assert(pos);

	ListNode* newnode = BuyListNode(x);
	ListNode* posPrev = pos->prev;

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

12. 二重リンクリストは位置 pos のノードを削除します (ListErase)

posPrev (pos の前のノードのアドレスを記録する)、posNext (pos の次のノードのアドレスを記録する) という 2 つのポインターが必要です。pos を自由にドロップし、posNext の前の部分が posPrev を指すようにし、posPrev の次の部分が posNext を指すようにします。

  • pos がヘッド ノードの次のノードを指している場合、ListErase はヘッドの削除と同等です。
  • pos がヘッド ノードの前のノード (つまり、末尾ノード) を指している場合、ListErase は末尾の削除と同等です。

ここに画像の説明を挿入


//双向链表删除pos位置的节点
void ListErase(ListNode* pos)
{
    
    
	assert(pos);

	ListNode* posNext = pos->next;
	ListNode* posPrev = pos->prev;

	posPrev->next = posNext;
	posNext->prev = posPrev;
	free(pos);
}

2. コードの実装

先頭挿入関数と末尾挿入関数は ListInsert 関数を再利用しました。
先頭削除関数と末尾削除関数には ListErase 関数を再利用しました。

//Lish.h 文件

#pragma once

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>


typedef int LTDataType;

typedef struct ListNode
{
    
    
	LTDataType data;
	struct ListNode* next;
	struct ListNode* prev;
}ListNode;


//创建返回链表的头结点
ListNode* ListCreate();

//创建新节点
ListNode* BuyListNode(LTDataType x);

//双向链表的销毁
void ListDestroy(ListNode* pHead);

//双向链表的打印
void ListPrint(ListNode* pHead);

//双向链表的尾插
void ListPushBack(ListNode* pHead, LTDataType x);

//双向链表的尾删
void ListPopBack(ListNode* pHead);

//双向链表的头插
void ListPushFront(ListNode* pHead, LTDataType x);

//双向链表的头删
void ListPopFront(ListNode* pHead);

//双向链表的查找
ListNode* ListFind(ListNode* pHead, LTDataType x);

//双向链表在pos前进行插入
void ListInsert(ListNode* pos, LTDataType x);

//双向链表删除pos位置的节点
void ListErase(ListNode* pos);


//Lish.c  文件

#include "List.h"


//创建返回链表的头结点
ListNode* ListCreate()
{
    
    
	return BuyListNode(-1);
}


//创建新节点
ListNode* BuyListNode(LTDataType x)
{
    
    
	ListNode* newnode = (ListNode*)malloc(sizeof(ListNode));
	if (newnode == NULL)
	{
    
    
		perror("malloc");
		exit(-1);
	}

	newnode->data = x;
	newnode->next = newnode;
	newnode->prev = newnode;

	return newnode;
}


//双向链表的打印
void ListPrint(ListNode* pHead)
{
    
    
	assert(pHead);

	ListNode* cur = pHead->next;
	printf("head<=>");
	while (cur != pHead)
	{
    
    
		printf("%d<=>", cur->data);
		cur = cur->next;
	}
	printf("head\n");
}

//双向链表的尾插
void ListPushBack(ListNode* pHead, LTDataType x)
{
    
    
	assert(pHead);

	/*ListNode* newnode = BuyListNode(x);
	ListNode* tail = pHead->prev;

	tail->next = newnode;
	newnode->prev = tail;
	pHead->prev = newnode;
	newnode->next = pHead;*/
	
	ListInsert(pHead, x);
}


//双向链表的尾删
void ListPopBack(ListNode* pHead)
{
    
    
	assert(pHead);
	//pHead->next == pHead时,链表没有元素
	assert(pHead->next != pHead);

	/*ListNode* tail = pHead->prev;
	ListNode* tailPrev = tail->prev;

	pHead->prev = tailPrev;
	tailPrev->next = pHead;
	free(tail);*/

	ListErase(pHead->prev);
}


//双向链表的头插
void ListPushFront(ListNode* pHead, LTDataType x)
{
    
    
	assert(pHead);

	/*ListNode* newnode = BuyListNode(x);
	ListNode* first = pHead->next;

	newnode->next = first;
	first->prev = newnode;
	newnode->prev = pHead;
	pHead->next = newnode;*/

	ListInsert(pHead->next, x);
}


//双向链表的头删
void ListPopFront(ListNode* pHead)
{
    
    
	assert(pHead);
	assert(pHead->next != pHead);

	/*ListNode* first = pHead->next;
	ListNode* second = first->next;

	second->prev = pHead;
	pHead->next = second;
	free(first);*/

	ListErase(pHead->next);
}



//双向链表的查找
ListNode* ListFind(ListNode* pHead, LTDataType x)
{
    
    
	assert(pHead);

	ListNode* cur = pHead->next;
	while (cur != pHead)
	{
    
    
		if (cur->data == x)
			return cur;
		
		cur = cur->next;
	}

	return NULL;
}



//双向链表在pos前进行插入
void ListInsert(ListNode* pos, LTDataType x)
{
    
    
	assert(pos);

	ListNode* newnode = BuyListNode(x);
	ListNode* posPrev = pos->prev;

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



//双向链表删除pos位置的节点
void ListErase(ListNode* pos)
{
    
    
	assert(pos);

	ListNode* posNext = pos->next;
	ListNode* posPrev = pos->prev;

	posPrev->next = posNext;
	posNext->prev = posPrev;
	free(pos);
}


//双向链表的销毁
void ListDestroy(ListNode* pHead)
{
    
    
	assert(pHead);

	ListNode* cur = pHead->next;
	while (cur != pHead)
	{
    
    
		ListNode* curNext = cur->next;
		
		free(cur);
		cur = curNext;
	}
	free(pHead);
}

要約する

以上が二重リンクリストの実現です!
ここに画像の説明を挿入

おすすめ

転載: blog.csdn.net/li209779/article/details/132068457