二重リンクリスト(データ構造)(C言語)

目次

コンセプト

リード二重リンクリストの実装

リマインダー

二重リンクリストの構造定義

二重リンクリストの初期化

初期化関数を持たない頭部なし一方向非循環リンクリスト、シーケンステーブルと頭部付き双方向循環リンクリストについての考察

二重リンクリストは pos 位置の前に x を挿入します

二重リンクリストの印刷

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

二重リンクリストの末尾挿入

単一リンクリストの末尾挿入に関しては、第 2 レベルのポインタが必要ですが、二重リンクリストの場合は第 2 レベルのポインタは必要ありません。

二重連結リストの空判定

二重リンクリストの末尾削除

二重リンクリストのヘッダー 

二重リンクリストの先頭を削除

二重リンクリストは値 x を持つノードを検索します

二重リンクリストの破壊 

二重リンクリストの修正

値 x の二重リンクリスト削除ノード

 二重リンクリストはノードの総数を計算します(pheadを除く)

二重リンクリストは位置 i のノードを取得します

二重リンクリストをクリアする

合計コード (結果を直接確認したい場合は、ここで確認できます)


コンセプト

二重リンク リストは、二重リンク リストとも呼ばれ、リンク リストの一種であり、その中の各データ ノードには 2 つのポインタがあり、それぞれ直接後続ノードと直接先行ノードを指しますしたがって、二重リンク リスト内の任意のノードから開始して、その先行ノードと後続ノードに簡単にアクセスできます。通常、双方向の循環リンク リストを構築します。循環リンク リストは、最後のノードがヘッド ノードを指し、リングを形成する連鎖ストレージ構造ですしたがって、循環リンク リスト内の任意のノードから開始して、他の任意のノードを見つけることができます。


リード二重リンクリストの実装

リマインダー

List.h は、  参照されるヘッダー ファイル、二重リンク リストの定義、関数の宣言に使用されます。

関数の定義にはList.cを  使用します。

Test.c は、 二重リンク リストの機能をテストするために使用されます。

二重リンクリストの構造定義

List.h の下

#pragma once//使同一个文件不会被包含(include)多次,不必担心宏名冲突

//先将可能使用到的头文件写上
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>

typedef int LTDataType;//假设结点的数据域类型为 int
//给变量定义一个易于记忆且意义明确的新名字,并且便于以后存储其它类型时方便改动
//(比如我晚点想存double类型的数据,我就直接将 int 改为 double )

// 带哨兵位双向循环链表的结构体定义
typedef struct ListNode
{
	struct ListNode* prev;//前驱指针域:存放上一个结点的地址
	struct ListNode* next;//后继指针域:存放下一个结点的地址
	LTDataType data;//数据域
}LTNode;
//struct 关键字和 ListNode 一起构成了这个结构类型
//typedef 为这个结构起了一个别名,叫 LTNode,即:typedef struct ListNode LTNode 
//现在就可以像 int 和 double 那样直接使用 LTNode 来定义变量

二重リンクリストの初期化

List.h の下

// 双向链表的初始化

// 如果是单链表直接给个空指针就行,不需要单独写一个函数进行初始化
// 即:LTNode* plist = NULL;
// 那为什么顺序表、带头双向循环链表有呢?
// 因为顺序表、带头双向循环链表的结构并不简单,
// 如:    顺序表顺序表为空size要为0,还要看capacity是否要开空间,
// 若不开空间capacity=0,指针要给空,若开空间,还要检查malloc是否成功
//	      带头双向循环链表要开个结点,检查malloc是否成功,然后让结点自己指向自己
// 顺序表和双向循环链表的初始化有点复杂,最好构建一个函数
LTNode* LTInit();

List.c の下

#include"List.h"//别忘了

//动态申请一个结点
LTNode* BuyListNode(LTDataType x)
{
	LTNode* node = (LTNode*)malloc(sizeof(LTNode));
	if (node == NULL)//如果malloc失败
	{
		perror("malloc fail");
		return NULL;
	}
	//如果malloc成功
	//初始化一下,防止野指针,如果看到返回的是空指针,那逻辑可能有些错误
	node->next = NULL;
	node->prev = NULL;
	node->data = x;

	return node;
}

// 双向链表的初始化
LTNode* LTInit()
{
	LTNode* phead = BuyListNode(-1);//创建哨兵位
	//自己指向自己
	phead->next = phead;
	phead->prev = phead;

	return phead;
}

初期化関数を持たない頭部なし一方向非循環リンクリスト、シーケンステーブルと頭部付き双方向循環リンクリストについての考察

ヘッドレス一方向非循環リンク リスト構造は単純すぎるため、初期化には null ポインターを直接割り当てるだけでよく、初期化用の別の関数を作成する必要はありません。

即:LTNode* plist = NULL;

では、なぜシーケンシャルテーブルと先頭の双方向循環リンクリストは初期化のために別の関数を書くのでしょうか?
なぜなら、シーケンシャルリストと先頭の双方向循環リンクリストの構造は単純ではないからです。

好き:

シーケンステーブルが空の場合はサイズが0である必要があり、容量をオープンする必要があるかどうかにも依存します スペースがオープンされていない場合はポインタが空である必要があります スペースがオープンされている場合はチェックが必要ですmalloc が成功したかどうか。

先頭の双方向循環リンク リストは、ノードを開き、malloc が成功したかどうかを確認してから、ノードがそれ自体を指すようにする必要があります。

逐次リストと二重循環リンクリストの初期化は少し複雑なので、関数を構築するのが最善です。

二重リンクリストは pos 位置の前に x を挿入します

List.h の下

// 双向链表在pos位置之前进行插入
void LTInsert(LTNode* pos, LTDataType x);

List.c の下

// 双向链表在pos位置之前进行插入
void LTInsert(LTNode* pos, LTDataType x)
{
	assert(pos);//pos肯定不为空

	LTNode* prev = pos->prev;
	LTNode* newnode = BuyListNode(x);//创建一个需要插入的结点

	prev->next = newnode;
	newnode->prev = prev;

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

二重リンクリストの印刷

List.h の下

// 双向链表打印
void LTPrint(LTNode* phead);

List.c の下

// 双向链表打印
void LTPrint(LTNode* phead)
{
	assert(phead);//有哨兵位
	printf("<=>phead<=>");
	LTNode* cur = phead->next;//cur指向第一个要打印的结点
	while (cur != phead)//cur等于头结点时打印就结束了
	{
		printf("%d<=>", cur->data);
		cur = cur->next;
	}
	printf("\n");
}

Test.c の下

#include"List.h"//别忘了

//测试函数
void TestList1()
{
	LTNode* plist = LTInit();
	LTInsert(plist, 1);
	LTInsert(plist, 2);
	LTInsert(plist, 3);
	LTInsert(plist, 4);
	LTPrint(plist);
}

int main()
{
	TestList1();
	return 0;
}

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

List.h の下

// 双向链表删除pos位置的结点
void LTErase(LTNode* pos);

List.c の下

// 双向链表删除pos位置的结点
void LTErase(LTNode* pos)
{
	assert(pos);//pos肯定不为空

	LTNode* posprev = pos->prev;
	LTNode* posnext = pos->next;

	posprev->next = posnext;
	posnext->prev = posprev;

	free(pos);
	pos = NULL;
    //这个置空其实已经没有意义了,形参的改变不会改变实参
}

Test.c の下

//测试函数
void TestList1()
{
	LTNode* plist = LTInit();
	LTInsert(plist, 1);
	LTInsert(plist, 2);
	LTInsert(plist, 3);
	LTInsert(plist, 4);
	LTPrint(plist);

	LTErase(plist->next);
	LTPrint(plist);

}

int main()
{
	TestList1();
	return 0;
}

二重リンクリストの末尾挿入

List.h の下

//双向链表优于单链表的点——不需要找尾、二级指针
//(我们改的不是结构体的指针,改的是结构体的变量)
// 双向链表的尾插
void LTPushBack(LTNode* phead, LTDataType x);

List.c の下

方法 1: (初心者が二重リンクリストの末尾挿入をよりよく理解するのに便利です) 

// 双向链表的尾插
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;
}

方法 2: 関数の再利用 (簡単で便利)

// 双向链表的尾插
void LTPushBack(LTNode* phead, LTDataType x)
{
	assert(phead);//有哨兵位

	//法二:函数复用(简单方便)
	LTInsert(phead, x);
}

単一リンクリストの末尾挿入に関しては、第 2 レベルのポインタが必要ですが、二重リンクリストの場合は第 2 レベルのポインタは必要ありません。

単一リンクリストは構造体のポインタを変更し、二重リンクリストは構造体の変数を変更します。

第 2 レベルのポインタと第 1 レベルのポインタの違いは、ポインタが指す変数のレベルが異なることです。第 1 レベルのポインタは構造体の変数を指し、第 2 レベルのポインタは構造体の変数を指します。構造体ポインタのアドレス
単一リンクリストでは、リンクリスト内のノードを削除または挿入するときに、ノード間のポインタを更新する必要があります。第 1 レベルのポインターが使用される場合、操作によりノードを指すポインターが直接変更されるため、目的を達成することが困難になります。したがって、関数がノード ポインターを指すアドレスを変更できるように、つまり前のノード ポインター変数に格納されているアドレスを変更できるように、セカンダリ ポインターを渡す必要があります。
二重リンクリストでは、各ノードは次のノードへのポインタを保存するだけでなく、前のノードへのポインタも保存しており、ノード間の双方向のポインタ関係により、ノードの挿入と削除がより便利になります。ノードの削除および挿入操作では、現在のノードの前後のノードのポインタのみを最初に前後のノードのポインタ変数に格納されているアドレス。
要約すると、単一リンク リストには次のノードへのポインタのみがあり、ノード間のポインタ関係は 2 次ポインタを渡すことで変更され、操作がより柔軟になります。一方、二重リンク リストのノードには 2 つのポインタがあります。 -way ポインタ関係、不要 フロントおよびリアのノード ポインタ変数に格納されているアドレスを直接変更するため、1 レベルのポインタだけを渡す必要があります

単一リンクリスト (比較):

Test.c の下

//测试函数
void TestList2()
{
	LTNode* plist = LTInit();
	LTPushBack(plist, 5);
	LTPushBack(plist, 6);
	LTPushBack(plist, 7);
	LTPushBack(plist, 8);
	LTPrint(plist);
}

int main()
{
	TestList2();
	return 0;
}

二重連結リストの空判定

末尾の削除/先頭の削除の前に、まずリンクされたリストが空かどうかを判断する必要があります。

List.h の下

// 双向链表的判空
bool LTEmpty(LTNode* phead);

List.c の下

// 双向链表的判空
bool LTEmpty(LTNode* phead)
{
	assert(phead);

	return phead->next == phead;
	//两者相等就是空链表(返回真),两者不相等就不是空链表(返回假)
}

二重リンクリストの末尾削除

List.h の下

// 双向链表的尾删
void LTPopBack(LTNode* phead);

List.c の下

方法 1: (初心者が二重リンクリストの末尾削除をよりよく理解するのに便利です)

// 双向链表的尾删
void LTPopBack(LTNode* phead)
{
	assert(phead);//有哨兵位
	assert(!LTEmpty(phead));//判空

    //法一:(便于新手更好地理解双向链表的尾删)
	LTNode* tail = phead->prev;
	LTNode* tailPrev = tail->prev;

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

方法 2: 関数の再利用 (簡単で便利)

// 双向链表的尾删
void LTPopBack(LTNode* phead)
{
	assert(phead);//有哨兵位
    assert(!LTEmpty(phead));//判空
	
    //法二:函数复用
	LTErase(phead->prev);
}

Test.c の下

//测试函数
void TestList2()
{
	LTNode* plist = LTInit();
	LTPushBack(plist, 5);
	LTPushBack(plist, 6);
	LTPushBack(plist, 7);
	LTPushBack(plist, 8);
	LTPrint(plist);

	LTPopBack(plist);
	LTPopBack(plist);
	LTPrint(plist);
}

int main()
{
	TestList2();
	return 0;
}

二重リンクリストのヘッダー 

List.h の下

// 双向链表头插
void LTPushFront(LTNode* phead, LTDataType x);

List.c の下

方法 1: phead と newnode の 2 つのポインターのみを使用します (初心者が二重リンク リストの先頭への挿入をよりよく理解するため)

// 双向链表头插
void LTPushFront(LTNode* phead, LTDataType x)
{
	assert(phead);//有哨兵位

	LTNode* newnode = BuyListNode(x);//创建一个要插入的结点
    
    //法一:只用phead和newnode两个指针(便于新手更好地理解双向链表的头插)
    //顺序很重要!!!
	newnode->next = phead->next;
	phead->next->prev = newnode;

	phead->next = newnode;
	newnode->prev = phead;
}

方法 2: first を使用して最初のノードの位置を記録します (初心者が二重リンクリストの先頭挿入をよりよく理解するのに便利です)

// 双向链表头插
void LTPushFront(LTNode* phead, LTDataType x)
{
	assert(phead);//哨兵位

	LTNode* newnode = BuyListNode(x);//创建一个要插入的结点

	//法二:多用了first先记住第一个结点(便于新手更好地理解双向链表的头插)
	LTNode* first = phead->next;
	phead->next = newnode;
	newnode->prev = phead;

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

方法 3: 関数の再利用 (簡単で便利)

// 双向链表头插
void LTPushFront(LTNode* phead, LTDataType x)
{
	assert(phead);//有哨兵位
	
	//法三:函数复用(简单方便)
	LTInsert(phead->next, x);
}

Test.c の下

//测试函数
void TestList3()
{
	LTNode* plist = LTInit();
	LTPushFront(plist, 1);
	LTPushFront(plist, 2);
	LTPushFront(plist, 3);
	LTPushFront(plist, 4);
	LTPrint(plist);
}

int main()
{
	TestList3();
	return 0;
}

二重リンクリストの先頭を削除

List.h の下

// 双向链表头删
void LTPopFront(LTNode* phead);

List.c の下

方法 1:(初心者が二重リンクリストの先頭削除をよりよく理解するのに便利です)

// 双向链表头删
void LTPopFront(LTNode* phead)
{
	assert(phead);//有哨兵位
	assert(!LTEmpty(phead));//判空
	
    //法一:(便于新手更好地理解双向链表的头删)
	LTNode* head = phead->next;
	LTNode* headnext = head->next;

	phead->next = headnext;
	headnext->prev = phead;

	free(head);
	head = NULL;
}

方法 2: 関数の再利用 (簡単で便利) 

// 双向链表头删
void LTPopFront(LTNode* phead)
{
	assert(phead);//有哨兵位
    assert(!LTEmpty(phead));//判空
	
    //法二:函数复用(简单方便)
	LTErase(phead->next);
}

Test.c の下

//测试函数
void TestList3()
{
	LTNode* plist = LTInit();
	LTPushFront(plist, 1);
	LTPushFront(plist, 2);
	LTPushFront(plist, 3);
	LTPushFront(plist, 4);
	LTPrint(plist);

	LTPopFront(plist);
	LTPopFront(plist);
	LTPrint(plist);
}

int main()
{
	TestList3();
	return 0;
}

二重リンクリストは値 x を持つノードを検索します

List.h の下

// 双向链表查找值为x的结点
LTNode* LTFind(LTNode* phead, LTDataType x);

List.c の下

// 双向链表查找值为x的结点
LTNode* LTFind(LTNode* phead, LTDataType x)
{
	assert(phead);//有哨兵位

	LTNode* cur = phead->next;
	while (cur != phead)//让cur去遍历
	{
		if (cur->data == x)//如果找到
		{
			return cur;
		}
		cur = cur->next;
	}
	return NULL;//如果没找到
}

Test.c の下

//测试函数
TestList4()
{
	LTNode* plist = LTInit();
	LTInsert(plist, 1);
	LTInsert(plist, 2);
	LTInsert(plist, 3);
	LTInsert(plist, 4);
	LTPrint(plist);

	LTNode* pos = LTFind(plist, 3);
	if (pos)
	{
		LTErase(pos);
		pos = NULL;
	}

	LTPrint(plist);
}

int main()
{
	TestList4();
	return 0;
}

二重リンクリストの破壊 

List.h の下

// 双向链表的销毁
void LTDestory(LTNode* phead);

List.c の下

   

  

 

// 双向链表的销毁
void LTDestory(LTNode* phead)
{
	assert(phead);

	LTNode* cur = phead->next;//让cur遍历
	while (cur != phead)
	{
		LTNode* curnext = cur->next;
		free(cur);
		cur = curnext;
	}
	free(phead);
	phead = NULL;
	//这个置空其实已经没有意义了,形参的改变不会改变实参
	//我们为了保持接口的一致性,不传二级指针,选择在测试的时候置空
}

Test.c の下

//测试函数
TestList4()
{
	LTNode* plist = LTInit();
	LTInsert(plist, 1);
	LTInsert(plist, 2);
	LTInsert(plist, 3);
	LTInsert(plist, 4);
	LTPrint(plist);

	LTNode* pos = LTFind(plist, 3);
	if (pos)
	{
		LTErase(pos);
		pos = NULL;
	}
	LTPrint(plist);

	LTDestory(plist);
	plist = NULL;//在这里置空
}

二重リンクリストの修正

List.h の下

// 双向链表的修改,修改pos位置的值为x
void LTModify(LTNode* pos, LTDataType x);

List.c の下

// 双向链表的修改,修改pos位置的值为x
void LTModify(LTNode* pos, LTDataType x)
{
	assert(pos);
	pos->data = x;
}

Test.c の下

//测试函数
TestList5()
{
	LTNode* plist = LTInit();
	LTInsert(plist, 1);
	LTInsert(plist, 2);
	LTInsert(plist, 3);
	LTInsert(plist, 4);
	LTPrint(plist);

	LTModify(plist->next,5);
	LTPrint(plist);
}

int main()
{
	TestList5();
	return 0;
}

値 x の二重リンクリスト削除ノード

List.h の下

// 双向链表删除值为x的结点
void LTRemove(LTNode* phead,LTDataType x);

List.c の下

// 双向链表删除值为x的结点
void LTRemove(LTNode* phead, LTDataType x)
{
	assert(phead);//有哨兵位
	LTNode* pos = phead->next;
	while (pos != phead)
	{
		pos = LTFind(phead, x);
		if (pos == NULL)//如果遍历完
		{
			return NULL;
		}
		LTErase(pos);
		pos = pos->next;
	}
}

Test.c の下

TestList6()
{
	LTNode* plist = LTInit();
	LTInsert(plist, 1);
	LTInsert(plist, 3);
	LTInsert(plist, 3);
	LTInsert(plist, 4);
	LTInsert(plist, 3);
	LTPrint(plist);

	LTRemove(plist, 3);
	LTPrint(plist);
}

int main()
{
	TestList6();
	return 0;
}

 二重リンクリストはノードの総数を計算します(pheadを除く)

List.h の下

// 双向链表计算结点总数(不计phead)
int LTTotal(LTNode* phead);

List.c の下

// 双向链表计算结点总数(不计phead)
int LTTotal(LTNode* phead)
{
	assert(phead);//有哨兵位

	int count = 0;//count来计数
	LTNode* cur = phead->next;//让cur去遍历
	while (cur != phead)
	{
		count++;
		cur = cur->next;
	}
	return count;
}

Test.c の下

TestList6()
{
	LTNode* plist = LTInit();
	LTInsert(plist, 1);
	LTInsert(plist, 3);
	LTInsert(plist, 3);
	LTInsert(plist, 4);
	LTInsert(plist, 3);
	LTPrint(plist);

	LTRemove(plist, 3);
	LTPrint(plist);

	printf("%d\n", LTTotal(plist));
}

int main()
{
	TestList6();
	return 0;
}

二重リンクリストは位置 i のノードを取得します

List.h の下

// 双向链表获取第i位置的结点
LTNode* LTGet(LTNode* phead, int i);

List.c の下

// 双向链表获取第i位置的结点
LTNode* LTGet(LTNode* phead, int i)
{
	assert(phead);//有哨兵位

	int length = LTTotal(phead);
	LTNode* cur = phead->next;
	if (i == 0)
	{
		return phead;
	}
	else if (i<0 || i>length)//位置不合法
	{
		return NULL;
	}
	else if (i <= (length / 2))//从表头开始遍历
	{
		cur = phead->next;
		for (int count = 1; count < i; count++)
		{
			cur = cur->next;
		}
	}
	else//从表尾开始遍历
	{
		cur = phead->prev;
		for (int count = 1; count <= length - i; count++)
		{
			cur = cur->prev;
		}
	}
	return cur;
}

Test.c の下

//测试函数
TestList7()
{
	LTNode* plist = LTInit();
	LTInsert(plist, 5);
	LTInsert(plist, 6);
	LTInsert(plist, 7);
	LTInsert(plist, 8);
	LTInsert(plist, 9);
	LTPrint(plist);

	LTNode* pos = LTGet(plist,3);
	if (pos)
	{
		LTErase(pos);
		pos = NULL;
	}
	LTPrint(plist);
}

int main()
{
	TestList7();
	return 0;
}

二重リンクリストをクリアする

List.h の下

// 双向链表的清空
void LTClean(LTNode* phead);

List.c の下

// 双向链表的清空
void LTClear(LTNode* phead)
{
	assert(phead);//有哨兵位

	while (!LTEmpty(phead))//如果不为空就一直头删
	{
		LTPopFront(phead);
	}
}

Test.c の下

//测试函数
TestList8()
{
	LTNode* plist = LTInit();
	LTInsert(plist, 5);
	LTInsert(plist, 6);
	LTInsert(plist, 7);
	LTInsert(plist, 8);
	LTInsert(plist, 9);
	LTPrint(plist);

	LTClear(plist);
	LTPrint(plist);
}

int main()
{
	TestList8();
	return 0;
}

合計コード (結果を直接確認したい場合は、ここで確認できます)

List.h の下

#pragma once//使同一个文件不会被包含(include)多次,不必担心宏名冲突


//先将可能使用到的头文件写上
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>

typedef int LTDataType;//假设结点的数据域类型为 int
//给变量定义一个易于记忆且意义明确的新名字,并且便于以后存储其它类型时方便改动
//(比如我晚点想存double类型的数据,我就直接将 int 改为 double )

// 带哨兵位双向循环链表的结构体定义
typedef struct ListNode
{
	struct ListNode* prev;//前驱指针域:存放上一个结点的地址
	struct ListNode* next;//后继指针域:存放下一个结点的地址
	LTDataType data;//数据域
}LTNode;
//struct 关键字和 ListNode 一起构成了这个结构类型
//typedef 为这个结构起了一个别名,叫 LTNode,即:typedef struct ListNode LTNode 
//现在就可以像 int 和 double 那样直接使用 LTNode 来定义变量



// 双向链表的初始化

// 如果是单链表直接给个空指针就行,不需要单独写一个函数进行初始化
// 即:LTNode* plist = NULL;
// 那为什么顺序表、带头双向循环链表有呢?
// 因为顺序表、带头双向循环链表的结构并不简单,
// 如:顺序表顺序表为空size要为0,还要看capacity是否要开空间,
//若不开空间capacity=0,指针要给空,若开空间,还要检查malloc是否成功
// 带头双向循环链表要开个结点,检查malloc是否成功,然后让结点自己指向自己
// 顺序表和双向循环链表的初始化有点复杂,最好构建一个函数
LTNode* LTInit();

// 双向链表在pos位置之前进行插入x
void LTInsert(LTNode* pos, LTDataType x);

// 双向链表的打印
void LTPrint(LTNode* phead);

// 双向链表删除pos位置的结点
void LTErase(LTNode* pos);

//双向链表优于单链表的点——不需要找尾、二级指针
// (我们改的不是结构体的指针,改的是结构体的变量)
// 双向链表的尾插
void LTPushBack(LTNode* phead, LTDataType x);

// 双向链表的判空
bool LTEmpty(LTNode* phead);

// 双向链表的尾删
void LTPopBack(LTNode* phead);

// 双向链表头插
void LTPushFront(LTNode* phead, LTDataType x);

// 双向链表头删
void LTPopFront(LTNode* phead);

// 双向链表查找值为x的结点
LTNode* LTFind(LTNode* phead, LTDataType x);

// 双向链表的销毁
void LTDestory(LTNode* phead);

// 双向链表的修改,修改pos位置的值为x
void LTModify(LTNode* pos, LTDataType x);

// 双向链表删除值为x的结点
void LTRemove(LTNode* phead, LTDataType x);

// 双向链表计算结点总数(不计phead)
int LTTotal(LTNode* phead);

// 双向链表获取第i位置的结点
LTNode* LTGet(LTNode* phead, int i);

// 双向链表的清空
void LTClear(LTNode* phead);

List.c の下

#include"List.h"

//动态申请一个结点
LTNode* BuyListNode(LTDataType x)
{
	LTNode* node = (LTNode*)malloc(sizeof(LTNode));
	if (node == NULL)//如果malloc失败
	{
		perror("malloc fail");
		return NULL;
	}
	//如果malloc成功
	//初始化一下,防止野指针,如果看到返回的是空指针,那逻辑可能有些错误
	node->next = NULL;
	node->prev = NULL;
	node->data = x;

	return node;
}

// 双向链表的初始化
LTNode* LTInit()
{
	LTNode* phead = BuyListNode(-1);
	//自己指向自己
	phead->next = phead;
	phead->prev = phead;

	return phead;
}

// 双向链表在pos位置之前进行插入x
void LTInsert(LTNode* pos, LTDataType x)
{
	assert(pos);//pos肯定不为空

	LTNode* prev = pos->prev;
	LTNode* newnode = BuyListNode(x);//创建一个需要插入的结点

	prev->next = newnode;
	newnode->prev = prev;

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

// 双向链表的打印
void LTPrint(LTNode* phead)
{
	assert(phead);//有哨兵位
	printf("<=>phead<=>");
	LTNode* cur = phead->next;//cur指向第一个要打印的结点
	while (cur != phead)//cur等于头结点时打印就结束了
	{
		printf("%d<=>", cur->data);
		cur = cur->next;
	}
	printf("\n");
}

// 双向链表删除pos位置的结点
void LTErase(LTNode* pos)
{
	assert(pos);//pos肯定不为空

	LTNode* posprev = pos->prev;
	LTNode* posnext = pos->next;

	posprev->next = posnext;
	posnext->prev = posprev;

	free(pos);
	pos = NULL;
	//这个置空其实已经没有意义了,形参的改变不会改变实参
}

// 双向链表的尾插
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);
}

// 双向链表的判空
bool LTEmpty(LTNode* phead)
{
	assert(phead);

	return phead->next == phead;
	//两者相等就是空链表(返回真),两者不相等就不是空链表(返回假)
}

// 双向链表的尾删
void LTPopBack(LTNode* phead)
{
	assert(phead);//有哨兵位

	//法一:(便于新手更好地理解双向链表的尾删)
	//assert(!LTEmpty(phead));//判空

	//LTNode* tail = phead->prev;
	//LTNode* tailPrev = tail->prev;

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

	//法二:函数复用
	LTErase(phead->prev);
}

// 双向链表头插
void LTPushFront(LTNode* phead, LTDataType x)
{
	assert(phead);//有哨兵位

	//LTNode* newnode = BuyListNode(x);//创建一个要插入的结点

	//法一:只用phead和newnode两个指针(便于新手更好地理解双向链表的头插)
	//newnode->next = phead->next;
	//phead->next->prev = newnode;

	//phead->next = newnode;
	//newnode->prev = phead;

	//法二:多用了first先记住第一个结点(便于新手更好地理解双向链表的头插)
	//LTNode* first = phead->next;
	//phead->next = newnode;
	//newnode->prev = phead;

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

	//法三:函数复用(简单方便)
	LTInsert(phead->next, x);
}

// 双向链表头删
void LTPopFront(LTNode* phead)
{
	assert(phead);//有哨兵位
	assert(!LTEmpty(phead));//判空

	//法一:(便于新手更好地理解双向链表的头删)
	//LTNode* head = phead->next;
	//LTNode* headnext = head->next;

	//phead->next = headnext;
	//headnext->prev = phead;

	//free(head);
	//head = NULL;

	//法二:函数复用(简单方便)
	LTErase(phead->next);
}

// 双向链表查找值为x的结点
LTNode* LTFind(LTNode* phead, LTDataType x)
{
	assert(phead);//有哨兵位

	LTNode* cur = phead->next;
	while (cur != phead)//让cur去遍历
	{
		if (cur->data == x)
		{
			return cur;
		}
		cur = cur->next;
	}
	return NULL;
}

// 双向链表的销毁
void LTDestory(LTNode* phead)
{
	assert(phead);

	LTNode* cur = phead->next;
	while (cur != phead)
	{
		LTNode* curnext = cur->next;
		free(cur);
		cur = curnext;
	}
	free(phead);
	phead = NULL;
	//这个置空其实已经没有意义了,形参的改变不会改变实参
	//我们为了保持接口的一致性,不传二级指针,选择在测试的时候置空
}

// 双向链表的修改,修改pos位置的值为x
void LTModify(LTNode* pos, LTDataType x)
{
	assert(pos);//pos肯定不为空
	pos->data = x;
}

// 双向链表删除值为x的结点
void LTRemove(LTNode* phead, LTDataType x)
{
	assert(phead);//有哨兵位
	LTNode* pos = phead->next;
	while (pos != phead)
	{
		pos = LTFind(phead, x);
		if (pos == NULL)//如果遍历完
		{
			return NULL;
		}
		LTErase(pos);
		pos = pos->next;
	}
}

// 双向链表计算结点总数(不计phead)
int LTTotal(LTNode* phead)
{
	assert(phead);//有哨兵位

	int count = 0;//count来计数
	LTNode* cur = phead->next;//让cur去遍历
	while (cur != phead)
	{
		count++;
		cur = cur->next;
	}
	return count;
}

// 双向链表获取第i位置的结点
LTNode* LTGet(LTNode* phead, int i)
{
	assert(phead);//有哨兵位

	int length = LTTotal(phead);
	LTNode* cur = phead->next;
	if (i == 0)
	{
		return phead;
	}
	else if (i<0 || i>length)//位置不合法
	{
		return NULL;
	}
	else if (i <= (length / 2))//从表头开始遍历
	{
		cur = phead->next;
		for (int count = 1; count < i; count++)
		{
			cur = cur->next;
		}
	}
	else//从表尾开始遍历
	{
		cur = phead->prev;
		for (int count = 1; count <= length - i; count++)
		{
			cur = cur->prev;
		}
	}
	return cur;
}

// 双向链表的清空
void LTClear(LTNode* phead)
{
	assert(phead);//有哨兵位

	while (!LTEmpty(phead))//如果不为空就一直头删
	{
		LTPopFront(phead);
	}
}

Test.c の下

#include"List.h"

//测试函数
void TestList1()
{
	LTNode* plist = LTInit();
	LTInsert(plist, 1);
	LTInsert(plist, 2);
	LTInsert(plist, 3);
	LTInsert(plist, 4);
	LTPrint(plist);

	LTErase(plist->next);
	LTPrint(plist);

}

//测试函数
void TestList2()
{
	LTNode* plist = LTInit();
	LTPushBack(plist, 5);
	LTPushBack(plist, 6);
	LTPushBack(plist, 7);
	LTPushBack(plist, 8);
	LTPrint(plist);

	LTPopBack(plist);
	LTPopBack(plist);
	LTPrint(plist);

}

//测试函数
void TestList3()
{
	LTNode* plist = LTInit();
	LTPushFront(plist, 1);
	LTPushFront(plist, 2);
	LTPushFront(plist, 3);
	LTPushFront(plist, 4);
	LTPrint(plist);

	LTPopFront(plist);
	LTPopFront(plist);
	LTPrint(plist);
}

//测试函数
TestList4()
{
	LTNode* plist = LTInit();
	LTInsert(plist, 1);
	LTInsert(plist, 2);
	LTInsert(plist, 3);
	LTInsert(plist, 4);
	LTPrint(plist);

	LTNode* pos = LTFind(plist, 3);
	if (pos)
	{
		LTErase(pos);
		pos = NULL;
	}
	LTPrint(plist);

	LTDestory(plist);
	plist = NULL;//在这里置空
}

//测试函数
TestList5()
{
	LTNode* plist = LTInit();
	LTInsert(plist, 1);
	LTInsert(plist, 2);
	LTInsert(plist, 3);
	LTInsert(plist, 4);
	LTPrint(plist);

	LTModify(plist->next, 5);
	LTPrint(plist);

}

TestList6()
{
	LTNode* plist = LTInit();
	LTInsert(plist, 1);
	LTInsert(plist, 3);
	LTInsert(plist, 3);
	LTInsert(plist, 4);
	LTInsert(plist, 3);
	LTPrint(plist);

	LTRemove(plist, 3);
	LTPrint(plist);

	printf("%d\n", LTTotal(plist));
}

//测试函数
TestList7()
{
	LTNode* plist = LTInit();
	LTInsert(plist, 5);
	LTInsert(plist, 6);
	LTInsert(plist, 7);
	LTInsert(plist, 8);
	LTInsert(plist, 9);
	LTPrint(plist);

	LTNode* pos = LTGet(plist, 3);
	if (pos)
	{
		LTErase(pos);
		pos = NULL;
	}
	LTPrint(plist);

	LTClear(plist);
	LTPrint(plist);
}

//测试函数
TestList8()
{
	LTNode* plist = LTInit();
	LTInsert(plist, 5);
	LTInsert(plist, 6);
	LTInsert(plist, 7);
	LTInsert(plist, 8);
	LTInsert(plist, 9);
	LTPrint(plist);

	LTClear(plist);
	LTPrint(plist);
}

int main()
{
	//TestList1();
	//TestList2();
	//TestList3();
	//TestList4();
	//TestList5();
	//TestList6();
	//TestList7();
	TestList8();
	return 0;
}

修正は大歓迎です❀

 

おすすめ

転載: blog.csdn.net/outdated_socks/article/details/130413379