[Data Structure—Implementation of Single Linked List]

Tip: After the article is written, the table of contents can be automatically generated. For how to generate it, please refer to the help document on the right.


Preface

世上有两种耀眼的光芒,一种是正在升起的太阳,一种是正在努力学习编程的你!一个爱学编程的人。各位看官,我衷心的希望这篇博客能对你们有所帮助,同时也希望各位看官能对我的文章给与点评,希望我们能够携手共同促进进步,在编程的道路上越走越远!


提示:以下是本篇文章正文内容,下面案例可供参考

1. Concept and structure of linked list

Concept:A linked list is a physical storage structure that is non-continuous and non-sequential. The logical order of data elements is realized through the pointer link order in the linked list.

The structure of the linked list is similar to that of train carriages. During the off-season, the number of carriages in the train will be reduced accordingly, and in the peak season, the number of carriages in the train will be increased by a few extra carriages. You only need to remove/add a certain carriage in the train, and it will not affect other carriages. Each carriage exists independently.

The carriages are independent and each carriage has a door. Imagine this scenario. Assume that the doors of each carriage are locked and require different keys to unlock. How do you walk from the front to the back of the car when you can only carry one key at a time?

The simplest way: put a key to the next carriage in each carriage.

In the linked list, what does each "car" look like?

Different from the sequence list, each "car" in the linked list is an independently applied for space, which we call "node/node"

The node consists of two main parts:The data to be saved by the current node and the address of the next node (pointer variable).

The pointer variable plist in the figure saves the address of the first node. We say that plist "points" to the first node at this time. If we want plist to "point" to the second node, we only need to modify the content saved by plist to 0x0012FFA0. .

Why do we need a pointer variable to save the position of the next node?

Each node in the linked list is applied for independently (that is, it only applies for a node space when data needs to be inserted), we need The next node can be found from the current node by saving the next node position through a pointer variable.

Combining the structure knowledge learned previously, we can give the structure code corresponding to each node:

Assume that the currently saved node is an integer:

struct SListNode
{
 int data; //节点数据
 struct SListNode* next; //指针变量⽤保存下⼀个节点的地址
};

When we want to save an integer data, we actually apply for a piece of memory from the operating system. This memory not only needs to save the integer data, but also needs to save the address of the next node (when the next node is empty, the saved address is empty. ).

When we want to go from the first node to the last node, we only need to get the address of the next node (the key of the next node) from the previous node.

How to print nodes from beginning to end in a given linked list structure?

Thinking: When the data type we want to save is character type, floating point type or other custom type, how to modify it?

Additional instructions:

1. The chain mechanism is logically continuous, but not necessarily continuous in physical structure.

2. Nodes are generally applied for from the heap.

3. The space applied for from the heap is allocated according to a certain strategy. The space applied for each time may be continuous or discontinuous.

Disadvantages of sequence tables:

1: The space required for the sequence table is continuous, which may cause program consumption;

2: There is a certain waste of space in capacity expansion.

2. Implementation of singly linked list

2.1 Single linked list header file - definition of functional functions

Slist.h

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

//定义链表节点的结构:
typedef int SLDatatype;
typedef struct SlistNode
{
	SLDatatype data;//这个节点(结构体)内的数据
	struct SlistNode* next;//存放的是下一个节点(结构体)的地址
}SLNode;

//我们来创建几个节点组成一个链表,并打印链表
//phead:第一个节点的地址
void SLprint(SLNode* phead);

//尾插
void SLPushBack(SLNode** pphead, SLDatatype x);
//头插
void SLPushFront(SLNode** pphead, SLDatatype x);
//尾删
void SLPopBack(SLNode** pphead);
//头删
void SLPopFront(SLNode** pphead);

//找节点,这里的第一个参数是一级指针还是二级指针
//这里穿一级指针实际就可以了,因为不改变节点
//但是这里要写二级指针,因为要保持接口的一致性
SLNode* SLFind(SLNode** pphead, SLDatatype x);

//在指定位置之前插入数据
void SLInsert(SLNode** pphead, SLNode* pos, SLDatatype x);
//在指定位置之后插入数据
void SLInsertAfter(SLNode* pos, SLDatatype x);

//删除pos节点
void SLErase(SLNode** pphead, SLNode* pos);
//删除pos节点之后
void SLEraseAfter(SLNode* pos);

//链表的销毁
void SLDesTroy(SLNode** pphead);

2.2 Single linked list source file - implementation of functional functions

Slist.c

#define _CRT_SECURE_NO_WARNINGS 1
#include "Slist.h"
//顺序表在逻辑上是线性的,在物理空间上也是线性的,顺序表中的数据都是连续的
//链表在逻辑结构上是线性的,在物理空间上不是线性的,链表中的数据不是连续的
//一个节点中存放两个东西:1:存储的数据;2:下一个节点的地址

链表节点的结构:
//struct SlistNode
//{
//	int data;//这个节点(结构体)内的数据
//	struct SlistNode* next;//存放的是下一个节点(结构体)的地址
//};

void SLprint(SLNode* phead)//phead:是形参,指向第一个节点的地址
{
	//循环打印
	//可以使用phead直接来访问,但是注意,当代码走到
	//第27行的时候,此时的phead就是NULL了,
	//若代码没有写完,我们还要继续使用指向第一个节点的地址时,
	//这时我们就找不到第一个节点的地址了
	SLNode* pcur = phead;//申请一个变量,用来存放第一个节点的地址
	while (pcur != NULL)
	{
		printf("%d ->", pcur->data);
		pcur = pcur->next;//pcur:是第一个节点的地址,在节点中找到下一个节点的地址,赋值给pcur
	}
	printf("NULL\n");
}

//申请新节点插入数据
SLNode* SLBuyNode(SLDatatype x)
{
	SLNode* node = (SLNode*)malloc(sizeof(SLNode));
	node->data = x;
	node->next = NULL;
	return node;
}
//尾插
void SLPushBack(SLNode** pphead, SLDatatype x)
{
	//对指针加以限制
	assert(pphead);//传过来的指针不能为空
	SLNode* node = SLBuyNode(x);
	if (*pphead == NULL)
	{
		*pphead = node;
		return;
	}
	//当链表不为空时,找尾
	SLNode* pcur = *pphead;
	while (pcur->next)
	{
		pcur = pcur->next;
	}
	pcur->next = node;
}

//头插
void SLPushFront(SLNode** pphead, SLDatatype x)
{
	assert(pphead);
	//链表为空和不为空的代码是一样的
	SLNode* node = SLBuyNode(x);
	//让新节点跟头节点连接起来
	node->next = *pphead;//*pphead:第一个节点的地址
	//让新的节点成为头节点
	*pphead = node;
}

//尾删
void SLPopBack(SLNode** pphead)
{
	assert(pphead);
	//第一个节点不能为空(链表为空,不能尾删)
	assert(*pphead);
	//只有一个节点的情况下
	if ((*pphead)->next == NULL)
	{
		//直接把头节点删除
		free(*pphead);
		*pphead = NULL;
	}
	else
	{
	    //有多个节点的情况下
	   //找到尾节点和尾节点的前一个节点
		SLNode* prev = NULL;
		SLNode* ptail = *pphead;//第一个节点的地址
		while (ptail->next != NULL)
		{
			prev = ptail;
			ptail = ptail->next;
			//当ptail->next == NULL的时候,prev刚好是尾节点的前一个节点
		}
		//prev的next指针不在指向ptail,而是指向ptail的下一个节点
		prev->next = ptail->next;//next指向NULL
		free(ptail);
		//为什么必须要置为空?代码后面明明没有在使用ptail?
		ptail = NULL;
		//因为打印的时候会判断地址是否为空,如果不置为空,那么会造成非法访问
	}
}
//头删
void SLPopFront(SLNode** pphead)
{
	assert(pphead);
	assert(*pphead);
	//无论是一个节点或者是多个节点都是可以的
	SLNode* del = *pphead;//第一个节点的地址
	*pphead = (*pphead)->next;//->的优先级高于*
	free(del);
	del = NULL;//出于代码规范的写法
}

//查找第一个为x的节点
SLNode* SLFind(SLNode** pphead, SLDatatype x)
{
	assert(pphead);
	SLNode* pcur = *pphead;
	while (pcur)
	{
		if (pcur->data == x)
		{
			return pcur;
		}
		pcur = pcur->next;
	}
	return NULL;
}


//在指定位置之前插入数据
void SLInsert(SLNode** pphead, SLNode* pos, SLDatatype x)
{
	assert(pphead);
	//我们约定链表不能为空,pos也不能为空(链表为空,pos也绝对为空)
	assert(*pphead);
	assert(pos);
	SLNode* node = SLBuyNode(x);
	//只有一个节点的情况下(pos必须指向第一个节点的位置)
	//pos刚好是头节点,头插
	if ((*pphead)->next == NULL || pos == *pphead)
	{
		node->next = *pphead;
		*pphead = node;
		return;
	}
	//找pos的前一个节点
	SLNode* prev = *pphead;
	while (prev->next != pos)//第一次循环的时候(缺少pos刚好是第一个节点的位置)
	{
		prev = prev->next;
	}
	prev->next = node;
	node->next = pos;
}
//在指定位置之后插入数据
void SLInsertAfter(SLNode* pos, SLDatatype x)
{
	assert(pos);
	SLNode* node = SLBuyNode(x);
	node->next = pos->next;
	pos->next = node;
}

//删除pos节点
void SLErase(SLNode** pphead, SLNode* pos)
{
	assert(pphead);
	assert(*pphead);
	assert(pos);
	//pos刚好是头节点
	if (pos == *pphead)
	{
		*pphead = (*pphead)->next;
		free(pos);
		pos = NULL;
		return;
	}
	//当pos不是头节点,找pos的前一个节点
	SLNode* prev = *pphead;
	while (prev->next != pos)
	{
		prev = prev->next;
	}
	prev->next = pos->next;
	free(pos);
	pos = NULL;//只是规范
}
//删除pos节点之后
void SLEraseAfter(SLNode* pos)
{
	//pos节点不能为空,pos之后的节点不能为空
	assert(pos && pos->next);
	SLNode* del = pos->next;
	pos->next = del->next;
	free(del);
	del = NULL;
}

//链表的销毁
void SLDesTroy(SLNode** pphead)
{
	assert(pphead);
	SLNode* pcur = *pphead;
	//循环删除
	while (pcur)
	{
		SLNode* next = pcur->next;
		free(pcur);
		pcur = next;
	}
	*pphead = NULL;
}

2.3 Single linked list source file - function test

test.c

#define _CRT_SECURE_NO_WARNINGS 1

#include "Slist.h"

void slttest()
{
	SLNode* node1 = (SLNode*)malloc(sizeof(SLNode));
	node1->data = 1;
	SLNode* node2 = (SLNode*)malloc(sizeof(SLNode));
	node2->data = 2;
	SLNode* node3 = (SLNode*)malloc(sizeof(SLNode));
	node3->data = 3;
	SLNode* node4 = (SLNode*)malloc(sizeof(SLNode));
	node4->data = 4;
	
	node1->next = node2;
	node2->next = node3;
	node3->next = node4;
	node4->next = NULL;

	//打印链表
	SLNode* plist = node1;
	SLprint(plist);
}
//检查一下写的代码是否正确
void slttest01()
{
	//plist:是第一个节点的地址
	SLNode* plist = NULL;
	//尾插
	SLPushBack(&plist, 1);
	SLPushBack(&plist, 2);
	SLPushBack(&plist, 3);
	SLPushBack(&plist, 4);
	SLprint(plist);
	//尾删
	//SLPopBack(&plist);
	//SLPopBack(&plist);
	//SLPopBack(&plist);
	//SLPopBack(&plist);
	//SLprint(plist);

	头删
	//SLPopFront(&plist);
	//SLPopFront(&plist);
	//SLPopFront(&plist);
	//SLPopFront(&plist);

	//SLNode* find = SLFind(&plist, 2);
	//SLInsert(&plist, find, 11);
	//SLInsertAfter(find, 100);
	SLDesTroy(&plist);
	SLprint(plist);
}

int main()
{
	//slttest();
	slttest01();
	return 0;
}

3. Detailed understanding of operation diagrams

4. Classification of linked lists

The structures of linked lists are very diverse. There are 8 (2 x 2 x 2) linked list structures in the following combinations:

Although there are so many linked list structures, there are two structures we most commonly use in practice:

Single linked list and two-way headed circular linked list.

1. Headless one-way acyclic linked list: It has a simple structure and is generally not used to store data alone. In practice, it is more often used as a substructure of other data structures, such as hash buckets, adjacency lists of graphs, etc. In addition, this structure appears a lot in written interviews.

2. Headed two-way circular linked list: The structure is the most complex and is generally used to store data separately. The linked list data structures used in practice are all headed bidirectional circular linked lists. In addition, although this structure is complex, after using the code to implement it, you will find that the structure will bring many advantages, and the implementation will be simple. We will know it later when we implement the code. 


Summarize

Okay, this blog ends here. If you have a better point of view, please leave a message in time. I will watch it carefully and learn from it.
If you don’t accumulate steps, you can’t reach a thousand miles; if you don’t accumulate small streams, you can’t become a river or sea.

Guess you like

Origin blog.csdn.net/2301_79585944/article/details/134664504