【データ構造とアルゴリズム】 線形リスト内の連結リストを認識し、一方向連結リストを実現

この記事は上記に続き、線形テーブルの概念を理解し、静的および動的シーケンス テーブルを実現しました。次に、新しい概念のリンクされたリストを認識します。そして、一方向リンクリストの様々な操作を実現します。配列表がまだわからない場合は、この記事を読んでください。

(メッセージ数 13 件) [データ構造とアルゴリズム] 線形テーブルで静的および動的シーケンス テーブルを実現_Xiaowang Xuecode ブログ-CSDN ブログ

目次

1. リンクリストとは何ですか?

1. リンクリストの概念と構造

2. リンクリストの分類

1. 一方向または双方向

2. リードするかリードしないか

3. 周期的か非周期的か

第二に、リンクリストの実装

1. ヘッドレス一方向非巡回リンクリスト

2. 機能の実現

1. リンクリストを初期化して印刷する

2. ヘッドプラグとテールプラグ

3. 先頭削除と末尾削除

4. 単一リンクリスト検索

5. pos ノードの位置の前後にデータを挿入します。

6. posノード位置のデータを削除(delete posノード)

7. pos 位置以降のノードを削除します

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

3. 完全なコード

1.リンクリスト.h

2.リンクリスト.c

3.テスト.c


序文

私たちはデータ構造における線形テーブルの概念を知っており、シーケンシャル テーブルの構築には主に構造と配列に似た形式である動的メモリ管理機能が含まれるため、よりよく理解できるはずです。

私たちはそのような質問について考えなければなりません

1. 容量を増やすには、新しいスペースを申請し、データをコピーし、古いスペースを解放する必要があり、多額の費用がかかります。

2. 容量拡張は通常 2 倍の拡張であり、一定量のスペースが無駄になる場合があります。

そこで、上記の問題を解決するために、線形リストに連結リストの概念を導入しました。


1. リンクリストとは何ですか?

1. リンクリストの概念と構造

概念: リンク リストは、物理的な記憶構造における非順次および非順次の記憶構造であり、データ要素の論理的順序は、リンク リスト内のポインタのリンク順序によって実現されます。

回路図は以下の通りです:

上の図からわかるように、リンク リストの特徴は次のとおりです。

1. チェーン構造は論理的には連続していますが、物理的には必ずしも連続しているわけではありません。(各ノードのアドレスは必ずしも必要ではありません)

2. 実際のノードは通常、ヒープから適用されます。

3. ヒープから適用されるスペースは、編集者によって異なる特定の戦略に従って割り当てられ、再度適用されるスペースは連続的または不連続である場合があります。

2. リンクリストの分類

実際、リンクリストの構造は数多くあり、主なリンクリスト構造は以下の組み合わせで8つあります。

1. 一方向または双方向

2. リードするかリードしないか

ヘッド ノードを使用する場合、データ フィールドに値を割り当てる必要はなく、リンク リストを確立するためのベース ポイントとしてのみ機能します。ヘッド ノードを使用しない場合は、最初のノードに値を格納できます。最初のノードに対して新しいノードが作成されます。phead をクリックするだけで、ヘッド ノードのリンク リストが非ヘッド ノードのリンク リストになります。     

主なことは、最初のノードがその値フィールドを使用しているかどうかを確認することです。

最初のノード データ フィールドの非ヘッド ノードを使用する

役に立たない      ヘッドノード                        

3. 周期的か非周期的か

 

上記のタイプの状況では、通常、ヘッドレス一方向非循環リンク リストとヘッドレス双方向循環リンク リストの 2 つのタイプを使用します。

写真が示すように

1. ヘッドレス一方向非循環リンク リスト: 構造は単純であり、通常はデータを単独で保存するためには使用されません。実際には、これは、ハッシュ バケット、グラフの隣接リストなど、他のデータ構造の下部構造に相当します。また、この構造は筆記試験の面接でもよく現れます。
2. 主要な双方向循環リンク リスト: 最も複雑な構造で、一般にデータを個別に保存するために使用されます。実際に使用されるリンク リストのデータ構造は、リード付きの双方向循環リンク リストです。また、構造は複雑ですが、コードを使用して実装すると、その構造が多くの利点をもたらし、実装が簡単であることがわかります。

第二に、リンクリストの実装

1. ヘッドレス一方向非巡回リンクリスト

構造は次のとおりです。

typedef int SLDataType;
//单向链表的实现、
typedef struct ListNode {
	SLDataType data;//数据域
	struct ListNode* next;//指针域
}List;

実装する関数は次のとおりです。

//打印单链表
void ListPrint(List* ps);
//单链表的尾插
void ListPushBack(List** ps, SLDataType data);
//单链表的头插
void ListPushFront(List** ps, SLDataType data);
//单链表的尾删
void ListPopBack(List** ps);
//单链表的头删
void ListPopFront(List** ps);
//单链表的查找
List* ListFind(List* ps);

//在pos位置上插入数据
void ListInsertBefore(List** ps, SLDataType x, List* pos);

//在pos位置之后插入数据
void ListInsertAfter(List** ps, SLDataType x, List* pos);

//在pos位子删除数据
void ListErase(List** ps, List* pos);

//在pos位置之后一位删除数据
void ListEraseAfter(List* pos);
//单链表的摧毁
void ListDestory(List** ps);

2. 機能の実現

1. リンクリストを初期化して印刷する

//初始化链表
void InitList(List* ps) {
	ps->data = 0;
	ps->next = NULL;
}
//打印单链表
void ListPrint(List* ps) {
	List* cur = ps;
	while ((cur) != NULL) {
		printf("%d -> ", cur->data);
		cur = cur->next;
	}
	printf("NULL\n");
}

2. ヘッドプラグとテールプラグ

テール挿入図は次のとおりです。

コードは以下のように表示されます:

//创建一个新节点
List* CreateNode(SLDataType x) {
	List* newNode = (List*)malloc(sizeof(List));
	if (newNode == NULL) {
		perror("malloc fail\n");
		exit(-1);
	}
	else {
		newNode->data = x;
		newNode->next = NULL;
	}
	return newNode;
}
//单链表的尾插
void ListPushBack(List** ps, SLDataType data) {
	//创建新的节点
	assert(ps);//断言
	List* newNode = CreateNode(data);
	if (*ps == NULL) {
		//说明是空链表
		*ps = newNode;
	}
	else {
		List* tail = *ps;
		while (tail->next != NULL) {
			tail = tail->next;
		}
		tail->next = newNode;
	}
}

次のようにヘッダーを挿入します。

コードは以下のように表示されます:

//单链表的头插
void ListPushFront(List** ps, SLDataType data) {
	//先断言是否为空
	assert(ps);
	//将新地址指向头结点下一个next结点的地址,然后在用头结点指向新节点
	List* newNode = CreateNode(data);
	newNode->next = (*ps);  //new指向ps当前的位置,然后new是第一个位置了,将new赋值给ps,这样new就作为头部连接链表了
	(*ps) = newNode;//原本ps位置的数值不变,这样的话就成 new->next=ps,new数值在前,ps的数值在后

}

3. 先頭削除と末尾削除

末尾の削除を図に示します。

 コードデモ:

//单链表的尾删
void ListPopBack(List** ps) {
	assert(ps);//断言
	//三种情况
	//1.空链表
	//2.一个节点
	//3.多个节点
	if (*ps == NULL) {
		return;
	}
	//只有一个节点的情况为
	else if ((*ps)->next == NULL) {
		free(*ps); //如果只有一个头节点的话
		*ps = NULL;
	}
	else {
		//多个节点的情况下、
		List* tail = *ps;
		while (tail->next->next!= NULL) {
			tail = tail->next;
		}
		free(tail->next);
		tail->next= NULL;
	}
}

図に示すように、ヘッダーが削除されます。

 コードは以下のように表示されます:

//单链表的头删
void ListPopFront(List** ps) {
	assert(ps);
	//1.空
	//2.非空
	if (*ps == NULL) {
		//为空
		return;
	}
	else {
		List* tail = (*ps)->next;//创建临时变量tail,将头节点之后的地址给tail
		free(*ps);//滞空头节点
		*ps = NULL;//可有可不有,接下来也要用
		*ps = tail;//将tail也就是ps的下一个List节点给ps

	}
}

4. 単一リンクリスト検索

コードは以下のように表示されます:

//单链表的查找

List* ListFind(List* ps,SLDataType data) {
	//进行查找就是进行判断是否为空链表,为空直接返回
	if (ps == NULL) {
		printf("链表为空、无法查找\n");
		return;
	}
	List* tail = ps;
	while (tail != NULL) {//从头节点开始,进行循环,
		if (tail->data == data) {
			return tail;
		}
		tail = tail->next;
	}
	return tail;//最后还找不到data,tail就为NULL了
}

5. pos ノードの位置の前後にデータを挿入します。

図に示すように、pos ノードの位置の前にデータを挿入します。

コードは以下のように表示されます:

//在pos位置上插入数据
void ListInsertBefore(List** ps, SLDataType x, List* pos) {
	//先判断是否为空
	assert(ps);
	assert(pos);
	//空链表排除
	//1.pos是第一个节点
	//2.pos不是第一个节点
	if (*ps == pos) {
		//是第一个节点,那就直接头插
		ListPushFront(ps, x);
	}
	else {
		List* prev = *ps;
		while (prev->next != pos) {
			prev = prev->next;
		}
		List* newnode = CreateNode(x);
		prev->next = newnode;
		newnode->next = pos;
	}
}

図に示すように、pos ノードの位置の後にノードを挿入します。

 コードは以下のように表示されます:
 

//在pos位置之后插入数据
void ListInsertAfter(List** ps, SLDataType x, List* pos) {
	assert(ps);
	//assert(pos);//断言
	List* newnode = CreateNode(x);
	newnode->next = pos->next;
	pos->next = newnode;
}

6. posノード位置のデータを削除(delete posノード)

写真が示すように:

コードは以下のように表示されます:

//在pos位子删除数据
void ListErase(List** ps, List* pos) {
	assert(ps);
	assert(pos);

	if (*ps == pos) {
		ListPopFront(ps);
	}
	else {
		List* next = *ps;
		while (next->next != pos) {
			next = next->next;
		}
		//这个时候next->next == pos
		next->next = next->next->next;
		/*free(next->next);*/
		free(pos);
		pos = NULL;
	}
}

7. pos 位置以降のノードを削除します

写真が示すように:

コードは以下のように表示されます:

//在pos位置之后一位删除数据
void ListEraseAfter(List* pos) {
	assert(pos);
	List* next = pos->next;//将pos 的下一个结点赋值给next
	if (next != NULL) {
		pos->next = pos->next->next;//表示pos的下一个的下一个结点的地址赋值给pos的指针域  实质上是将pos的下一个结点给跳过
		free(next);  //将pos的下一个结点给free释放
		next = NULL;  //next指向为NULL  防止野指针
	}
}

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

コードは以下のように表示されます:

//链表的摧毁  直接将头指针指针域指向NULL
void ListDestory(List** ps) {
	//assert(ps);  //防止空链表
	一个结点一个结点释放
	//List* next = *ps;
	//while (next) {
	//	List* cur = next->next;
	//	free(next);
	//	next = cur;
	//}
	*ps = NULL;
}

これはセカンダリ ポインタであるため、直接 *ps=NULL を実行するか、1 つずつ解放することができます。

3. 完全なコード

1.リンクリスト.h

#define _CRT_SECURE_NO_WARNINGS

#include<stdio.h>
#include<stdlib.h>
#include<malloc.h>
#include<assert.h>
typedef int SLDataType;
//单向链表的实现、
typedef struct ListNode {
	SLDataType data;//数据域
	struct ListNode* next;//指针域
}List;

//打印单链表
void ListPrint(List* ps);
//单链表的尾插
void ListPushBack(List** ps, SLDataType data);
//单链表的头插
void ListPushFront(List** ps, SLDataType data);
//单链表的尾删
void ListPopBack(List** ps);
//单链表的头删
void ListPopFront(List** ps);
//单链表的查找
List* ListFind(List* ps);

//在pos位置上插入数据
void ListInsertBefore(List** ps, SLDataType x, List* pos);

//在pos位置之后插入数据
void ListInsertAfter(List** ps, SLDataType x, List* pos);

//在pos位子删除数据
void ListErase(List** ps, List* pos);

//在pos位置之后一位删除数据
void ListEraseAfter(List* pos);
//单链表的摧毁
void ListDestory(List** ps);

2.リンクリスト.c

#define _CRT_SECURE_NO_WARNINGS
#include"单向链表.h"

//链表的使用,在插入上面
// 如果是尾部插入,如果是空链表直接将新节点给ps 是先找到链表尾部,然后创建新节点,连接即可
// 如果是头部插入,先进行断言判空,之后创建新节点,将新节点的数据
// new->next=ps  这个是找到对应的位置,连接起来
// ps=new;  将新节点的信息传递给ps,这样ps还是头节点
// 
// 
// 
// 
// 


//进行单链表的实现

//初始化链表
void InitList(List* ps) {
	ps->data = 0;
	ps->next = NULL;
}
//打印单链表
void ListPrint(List* ps) {
	List* cur = ps;
	while ((cur) != NULL) {
		printf("%d -> ", cur->data);
		cur = cur->next;
	}
	printf("NULL\n");
}

//创建一个新节点
List* CreateNode(SLDataType x) {
	List* newNode = (List*)malloc(sizeof(List));
	if (newNode == NULL) {
		perror("malloc fail\n");
		exit(-1);
	}
	else {
		newNode->data = x;
		newNode->next = NULL;
	}
	return newNode;
}
//单链表的尾插
void ListPushBack(List** ps, SLDataType data) {
	//创建新的节点
	assert(ps);//断言
	List* newNode = CreateNode(data);
	if (*ps == NULL) {
		//说明是空链表
		*ps = newNode;
	}
	else {
		List* tail = *ps;
		while (tail->next != NULL) {
			tail = tail->next;
		}
		tail->next = newNode;
	}
}
//单链表的头插
void ListPushFront(List** ps, SLDataType data) {
	//先断言是否为空
	assert(ps);
	//将新地址指向头结点下一个next结点的地址,然后在用头结点指向新节点
	List* newNode = CreateNode(data);
	newNode->next = (*ps);  //new指向ps当前的位置,然后new是第一个位置了,将new赋值给ps,这样new就作为头部连接链表了
	(*ps) = newNode;//原本ps位置的数值不变,这样的话就成 new->next=ps,new数值在前,ps的数值在后

}

//单链表的尾删
void ListPopBack(List** ps) {
	assert(ps);//断言
	//三种情况
	//1.空链表
	//2.一个节点
	//3.多个节点
	if (*ps == NULL) {
		return;
	}
	//只有一个节点的情况为
	else if ((*ps)->next == NULL) {
		free(*ps); //如果只有一个头节点的话
		*ps = NULL;
	}
	else {
		//多个节点的情况下、
		List* tail = *ps;
		while (tail->next->next!= NULL) {
			tail = tail->next;
		}
		free(tail->next);
		tail->next= NULL;
	}
}


//单链表的头删
void ListPopFront(List** ps) {
	assert(ps);
	//1.空
	//2.非空
	if (*ps == NULL) {
		//为空
		return;
	}
	else {
		List* tail = (*ps)->next;//创建临时变量tail,将头节点之后的地址给tail
		free(*ps);//滞空头节点
		*ps = NULL;//可有可不有,接下来也要用
		*ps = tail;//将tail也就是ps的下一个List节点给ps

	}
}

//单链表的查找

List* ListFind(List* ps,SLDataType data) {
	//进行查找就是进行判断是否为空链表,为空直接返回
	if (ps == NULL) {
		printf("链表为空、无法查找\n");
		return;
	}
	List* tail = ps;
	while (tail != NULL) {//从头节点开始,进行循环,
		if (tail->data == data) {
			return tail;
		}
		tail = tail->next;
	}
	return tail;
}

//在pos位置上插入数据
void ListInsertBefore(List** ps, SLDataType x, List* pos) {
	//先判断是否为空
	assert(ps);
	assert(pos);
	//空链表排除
	//1.pos是第一个节点
	//2.pos不是第一个节点
	if (*ps == pos) {
		//是第一个节点,那就直接头插
		ListPushFront(ps, x);
	}
	else {
		List* prev = *ps;
		while (prev->next != pos) {
			prev = prev->next;
		}
		List* newnode = CreateNode(x);
		prev->next = newnode;
		newnode->next = pos;
	}
}
//在pos位置之后插入数据
void ListInsertAfter(List** ps, SLDataType x, List* pos) {
	assert(ps);
	//assert(pos);//断言
	List* newnode = CreateNode(x);
	newnode->next = pos->next;
	pos->next = newnode;
}
//在pos位子删除数据
void ListErase(List** ps, List* pos) {
	assert(ps);
	assert(pos);

	if (*ps == pos) {
		ListPopFront(ps);
	}
	else {
		List* next = *ps;
		while (next->next != pos) {
			next = next->next;
		}
		//这个时候next->next == pos
		next->next = next->next->next;
		/*free(next->next);*/
		free(pos);
		pos = NULL;
	}
}
//在pos位置之后一位删除数据
void ListEraseAfter(List* pos) {
	assert(pos);
	List* next = pos->next;//将pos 的下一个结点赋值给next
	if (next != NULL) {
		pos->next = pos->next->next;//表示pos的下一个的下一个结点的地址赋值给pos的指针域  实质上是将pos的下一个结点给跳过
		free(next);  //将pos的下一个结点给free释放
		next = NULL;  //next指向为NULL  防止野指针
	}
}
//链表的摧毁  直接将头指针指针域指向NULL
void ListDestory(List** ps) {
	//assert(ps);  //防止空链表
	一个结点一个结点释放
	//List* next = *ps;
	//while (next) {
	//	List* cur = next->next;
	//	free(next);
	//	next = cur;
	//}
	*ps = NULL;
}

3.テスト.c

#define _CRT_SECURE_NO_WARNINGS

#include"单向链表.h"
void test()
{
	List* phead=NULL;//作为头节点

	//单链表的尾插
	ListPushBack(&phead, 1);
	ListPushBack(&phead, 2);
	ListPushBack(&phead, 3);
	ListPushBack(&phead, 4);
	ListPushBack(&phead, 5);
	ListPrint(phead);

	ListPushFront(&phead, 1);
	ListPrint(phead);

	ListPopBack(&phead);
	ListPrint(phead);
	
	ListPopFront(&phead);
	ListPrint(phead);
	ListErase(&phead, phead->next);
	ListInsertAfter(&phead, 10, phead->next);
	ListEraseAfter(phead->next);

	ListPrint(phead);
	ListDestory(&phead);
}
int main()
{
	test();
	return 0;
}

要約する

この記事では主にリンク リストの分類について説明します. よく使用される 2 つのタイプ, ヘッドレス片方向非循環リンク リストとヘッドレス双方向循環リンク リストがあります. 私たちはヘッドレス片方向非循環リンク リストを実装しました,これは比較的単純なリンク リストです。実装では、パラメータを渡すために第 2 レベルのポインタを使用します。もちろん、パラメータを渡すために第 1 レベルのポインタを使用することも可能です。主な実装関数は、先頭から末尾までです。挿入、先頭から末尾への削除、および pos の前後の要素の追加または削除は、ノードの位置を指定します。

次に、双方向循環リンク リストの先頭に立って、最も一般的に使用されるリンク リストの別の形式を以下に紹介します。

おすすめ

転載: blog.csdn.net/qq_63319459/article/details/128766484