C language data structure-singly linked list


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 achieved through pointer connections in the linked list.
Insert image description here
Assume that the door of each carriage is locked and requires a different key to lead to the next carriage. How can you use one key at the front of the car to go to the last carriage?
We put the key of one carriage in each carriage. Nope!
Insert image description here
Each carriage in the linked list is an independently applied space. We refer to "node"/"node". The main components of the node are: the data saved by the current node and the address (pointer variable) of the next node saved.
Each node in the linked list is applied for independently. We need to use a pointer variable to save the position of the next node in order to go from the current node to the next node.

Insert image description here

2 Implementation of singly linked list

Create SList.ha header file to define functions, and SList.ca source file to declare a function
test.ctest linked list. I have test.ccreated 10 test functions, corresponding to 10 functions one by one, to facilitate observation and debugging by latecomers.

Insert image description here
Insert image description here

We are new to linked lists and define the structure of the linked list as an integer

//定义链表的结构
typedef int SLDataType;
typedef struct SListNode
{
    
    
	SLDataType data;	//保存的数据
	struct SListNode* next;		//指针变量存放下一个节点的地址
}SLNode;

2.1 Print linked list

First print: pcur pointer variable saves the address of the head node, dereferences pcur to get the address of the next pointer variable, assigns it to pcur, and enters the loop judgment

void SLPrint(SLNode* phead)
{
    
    
	SLNode* pcur = phead;
	while (pcur)
	{
    
    
		printf("%d -> ", pcur->data);
		pcur = pcur->next;
	}
	printf("NULL\n"); //手动添加NULL  
}

2.2 Tail plug

Inserting a function requires opening up memory space, so writing a function that creates a new node will also be used by subsequent functions.

`SList.c`下
//创建新的节点的函数
SLNode* SLBuyNode(SLDataType x)
{
    
    
	SLNode* node = (SLNode*)malloc(sizeof(SLNode));
	node->data = x;
	node->next = NULL;
	return node;
}

First, we need to understand that the secondary pointer stores the address of the primary pointer.
plist is a pointer and stores the address. SLPushBack(plist, 1); passing plist to phead means passing the value of plist to phead.
However, SLPushBack should pass plist The address of the first node is passed to phead, so a secondary pointer is used.
The first node *plist needs the formal parameter **phead to receive
the address of the first node plist. The formal parameter *phead needs to receive
the address of the pointer to the first node&plist needs the formal parameter. When receiving phead
, identify it and write it as pphead.

Insert image description here

`SList.c`下
void SLPushBack(SLNode** pphead, SLDataType x)	//保存第一个节点的指针的地址
{
    
    
	assert(pphead);		//尾插节点的指针地址不能为NULL
	//创建一个节点
	SLNode* node = SLBuyNode(x);
	//链表为空,返回NULL
	if (*pphead == NULL)
	{
    
    
		*pphead = node;
		return;
	}
	//链表不为空,找尾
	SLNode* pcur =*pphead;
	while (pcur->next)//相当于pcur->next!=NULL
	{
    
    
		pcur = pcur->next;
	}
	pcur->next = node;
}

2.3 Head plug

Insert image description here

SList.c

void SLPushFront(SLNode** pphead, SLDataType x)
{
    
    
	assert(pphead);
	SLNode* node = SLBuyNode(x);
	//新结点跟头结点连接起来
	node->next = *pphead;
	//让新节点成为头结点
	*pphead = node;
}

2.4 Tail deletion

Insert image description here

void SLPopBack(SLNode** pphead)
{
    
    
	assert(pphead);

	assert(*pphead);	//第一个节点不能为空
	//判断只有一个结点的情况
	if ((*pphead)->next == NULL)
	{
    
    
		free(*pphead);
		*pphead = NULL;
		return;
	}

	//找到尾节点和尾节点的前一个节点
	SLNode* pre = NULL;
	SLNode* ptail = *pphead;
	while (ptail->next!=NULL)
	{
    
    
		pre = ptail;
		ptail = ptail->next;
	}
	//pre的next指针不指向ptail,而是指向ptail的下一个节点也就是NULL
	//pre->next = NULL;都行
	pre->next = ptail->next;
	
	//因为在SLPrint(SLNode* phead)函数中有判断节点是否为空while (pcur),所以置空
	free(ptail);
	ptail = NULL;

}

2.5 Header deletion

Insert image description here

void SLPopFront(SLNode** pphead)
{
    
    
	assert(pphead);
	assert(*pphead);	//第一个节点不能为空
	SLNode* del = *pphead;
	*pphead = (*pphead)->next;
	free(del);
	del = NULL;
}
void test5()
{
    
    
	SLNode* plist = NULL;
	SLPushBack(&plist, 1);
	SLPushBack(&plist, 2);
	SLPushBack(&plist, 3);//1->2->3->NULL

	SLPopFront(&plist); //2->3->NULL
	SLPopFront(&plist); //3->NULL
	//SLPopFront(&plist); //NULL

	//SLPopFront(&plist); //assert成功
	SLPrint(plist);
	SLDestory(&plist);
}

2.6 Insert data before fixed point

The SLFind function is based on the position of the data x that appears for the first time. This is a flaw . The SLInsert function handles the relationship between the nodes before and after the newly added node.
Insert image description here

//这个函数是有缺陷的,它是查到的第一次出现x的位置,若想在第二次出现x的位置插入数据是不能的
SLNode* SLFind(SLNode** pphead, SLDataType x)  
{
    
    
	assert(pphead);
	SLNode* pcur = *pphead;
	while (pcur)
	{
    
    
		if (pcur->data == x) {
    
    
			return pcur;
		}
		pcur = pcur->next;
	}
}

void SLInsert(SLNode** pphead, SLNode* pos, SLDataType x)
{
    
    
	assert(pphead);
	assert(pos);	//pos不能为空
	assert(*pphead);  //NULL前插入数据是荒谬的,所以assert第一个节点
	SLNode* node = SLBuyNode(x);

	//当只有一个结点(pos就是第一个节点)
	//if (((*pphead)->next) == NULL||pos==*pphead),可以简化成以下代码
	if (pos == *pphead) {
    
    
		node->next = *pphead;
		*pphead = node;
		return;
	}

	//找到pos的前一个节点
	SLNode* pre = *pphead;
	while (pre->next != pos)
	{
    
    
		pre = pre->next;
	}
	//处理pre node pos的位置
	node->next = pos;
	pre->next = node;
}

test.c

void test6()
{
    
    
	SLNode* plist = NULL;
	SLPushBack(&plist, 1);
	SLPushBack(&plist, 3);
	SLPushBack(&plist, 2);
	SLPushBack(&plist, 3);//1->3->2->3->NULL
	printf("定点插入数据前\n");
	SLPrint(plist);

	printf("定点插入数据后\n");
	SLNode* find = SLFind(&plist, 3);
	SLInsert(&plist, find, 9);//1->9->3->2->3->NULL

	SLPrint(plist);
	SLDestory(&plist);
}

2.7 Insert data after fixed point

Insert image description here

void SLInsertAfter(SLNode* pos, SLDataType x)
{
    
    
	assert(pos);
	SLNode* node = SLBuyNode(x);
	node->next = pos->next;
	pos->next = node;
}
void test7()
{
    
    
	SLNode* plist = NULL;
	SLPushBack(&plist, 1);
	SLPushBack(&plist, 2);
	SLPushBack(&plist, 3);//1->2->3->NULL
	printf("定点后插入数据前\n");
	SLPrint(plist);

	printf("定点后插入数据后\n");
	SLNode* find = SLFind(&plist, 2);
	SLInsertAfter(find, 10);	//1->2->10->3->NULL

	SLPrint(plist);
	SLDestory(&plist);
}

2.8 Delete pos node

Insert image description here

void SLErase(SLNode** pphead, SLNode* pos)
{
    
    
	assert(*pphead);
	assert(pphead);
	assert(pos);
	//pos是头结点
	if (pos == *pphead)
	{
    
    
		*pphead = (*pphead)->next;
		free(pos);
		return;
	}
	//pos不是头结点,找pos的前一个节点
	SLNode* pre = *pphead;
	while (pre->next != pos)
	{
    
    
		pre = pre->next;
	}
	pre->next = pos->next;
	free(pos);
	pos = NULL;   //代码规范
}
void test8()
{
    
    
	SLNode* plist = NULL;
	SLPushBack(&plist, 1);
	SLPushBack(&plist, 2);
	SLPushBack(&plist, 1);
	SLPushBack(&plist, 3);//1->2->1->3->NULL

	SLNode* find = SLFind(&plist, 1);  //找到1的下标
	SLErase(&plist, find);//2->1->3->NULL

	SLPrint(plist);
	SLDestory(&plist);
}

2.9 Delete the node after pos

Insert image description here

void SLEraseAfter(SLNode* pos)
{
    
    
	//删除pos之后的节点也不能空
	assert(pos&&pos->next);
	SLNode* del = pos->next;
	pos->next = del->next;
	free(del);
}

2.10 Destroy linked list

Remember to leave the head node blank!
Remember to leave the head node blank!
Remember to leave the head node blank!

void SLDestory(SLNode** pphead)
{
    
    
	assert(pphead);
	SLNode* pcur = *pphead;
	while (pcur)
	{
    
    
		SLNode* next = pcur->next;
		free(pcur);
		pcur = next;
	}
	//头结点记得置空
	*pphead = NULL;
}

3 complete code

3.1 SList.h

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


//定义链表的结构
typedef int SLDataType;
typedef struct SListNode
{
    
    
	SLDataType data;	//保存的数据
	struct SListNode* next;		//指针变量存放下一个节点的地址
}SLNode;

//1 打印链表
void SLPrint(SLNode* phead);

//2 尾插
void SLPushBack(SLNode** pphead, SLDataType x);

//3 头插
void SLPushFront(SLNode** pphead, SLDataType x);

//4 尾删
void SLPopBack(SLNode** pphead);

//5 头删
void SLPopFront(SLNode** pphead);

//SLFind()找到要查找数据的下标
//找节点的函数这里传一级实际上就可以了,因为不改变头结点
//但是这里要写二级指针,因为要保持接口一致性
//缺点:这个函数有一定的局限性,他找到数据x的下标是第一次出现的x的下标后,不会找后续的x的下标
SLNode* SLFind(SLNode** pphead, SLDataType x);

//6 定点前插入数据
void SLInsert(SLNode** pphead, SLNode* pos,SLDataType x);

//7 定点后插入数据
void SLInsertAfter(SLNode* pos,SLDataType x);

//8 删除pos节点
void  SLErase(SLNode** pphead, SLNode* pos);

//9 删除pos后的节点
void SLEraseAfter(SLNode* pos);

//10 销毁链表
void SLDestory(SLNode** pphead);

3.2 SList.c

#include"SList.h"

void SLPrint(SLNode* phead)
{
    
    
	SLNode* pcur = phead;
	while (pcur)
	{
    
    
		printf("%d -> ", pcur->data);
		pcur = pcur->next;
	}
	printf("NULL\n"); //手动添加NULL  
}

//创建新的节点的函数
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->next!=NULL
	{
    
    
		pcur = pcur->next;
	}
	pcur->next = node;
}

void SLPushFront(SLNode** pphead, SLDataType x)
{
    
    
	assert(pphead);
	SLNode* node = SLBuyNode(x);
	//新结点跟头结点连接起来
	node->next = *pphead;
	//让新节点成为头结点
	*pphead = node;
}

void SLPopBack(SLNode** pphead)
{
    
    
	assert(pphead);

	assert(*pphead);	//第一个节点不能为空
	//判断只有一个结点的情况
	if ((*pphead)->next == NULL)
	{
    
    
		free(*pphead);
		*pphead = NULL;
		return;
	}

	//找到尾节点和尾节点的前一个节点
	SLNode* pre = NULL;
	SLNode* ptail = *pphead;
	while (ptail->next!=NULL)
	{
    
    
		pre = ptail;
		ptail = ptail->next;
	}
	//pre的next指针不指向ptail,而是指向ptail的下一个节点
	//pre->next = ptail->next;
	pre->next = NULL;
	//因为在SLPrint(SLNode* phead)函数中有判断节点是否为空while (pcur),所以置空
	free(ptail);
	ptail = NULL;

}

void SLPopFront(SLNode** pphead)
{
    
    
	assert(pphead);
	assert(*pphead);	//第一个节点不能为空
	SLNode* del = *pphead;
	*pphead = (*pphead)->next;
	free(del);
	del = NULL;
}

SLNode* SLFind(SLNode** pphead, SLDataType x)
{
    
    
	assert(pphead);
	SLNode* pcur = *pphead;
	while (pcur)
	{
    
    
		if (pcur->data == x) {
    
    
			return pcur;
		}
		pcur = pcur->next;
	}
}

void SLInsert(SLNode** pphead, SLNode* pos, SLDataType x)
{
    
    
	assert(pphead);
	assert(pos);	//pos不能为空
	assert(*pphead);  //NULL前插入数据是荒谬的,所以assert第一个节点
	SLNode* node = SLBuyNode(x);

	//当只有一个结点(pos就是第一个节点)
	//if (((*pphead)->next) == NULL||pos==*pphead),可以简化成以下代码
	if (pos == *pphead) {
    
    
		node->next = *pphead;
		*pphead = node;
		return;
	}

	//找到pos的前一个节点
	SLNode* pre = *pphead;
	while (pre->next != pos)
	{
    
    
		pre = pre->next;
	}
	//处理pre node pos的位置
	node->next = pos;
	pre->next = node;
}

//7 定点后插入数据
void SLInsertAfter(SLNode* pos, SLDataType x)
{
    
    
	assert(pos);
	SLNode* node = SLBuyNode(x);
	node->next = pos->next;
	pos->next = node;
}

//8 删除pos节点
void SLErase(SLNode** pphead, SLNode* pos)
{
    
    
	assert(*pphead);
	assert(pphead);
	assert(pos);
	//pos是头结点
	if (pos == *pphead)
	{
    
    
		*pphead = (*pphead)->next;
		free(pos);
		return;
	}
	//pos不是头结点,找pos的前一个节点
	SLNode* pre = *pphead;
	while (pre->next != pos)
	{
    
    
		pre = pre->next;
	}
	pre->next = pos->next;
	free(pos);
	pos = NULL;   //代码规范
}

//9 删除pos后的节点
void SLEraseAfter(SLNode* pos)
{
    
    
	//删除pos之后的节点也不能空
	assert(pos&&pos->next);
	SLNode* del = pos->next;
	pos->next = del->next;
	free(del);
}

void SLDestory(SLNode** pphead)
{
    
    
	assert(pphead);
	SLNode* pcur = *pphead;
	while (pcur)
	{
    
    
		SLNode* next = pcur->next;
		free(pcur);
		pcur = next;
	}
	//头结点记得置空
	*pphead = NULL;
}



3.3 test.c

Originally I wrote a test() function because it is convenient for the author to debug, but in order to make it easier for readers to read, it is divided into 10 test functions.

#include"SList.h"

void SLPrint(SLNode* phead)
{
    
    
	SLNode* pcur = phead;
	while (pcur)
	{
    
    
		printf("%d -> ", pcur->data);
		pcur = pcur->next;
	}
	printf("NULL\n"); //手动添加NULL  
}

//创建新的节点的函数
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->next!=NULL
	{
    
    
		pcur = pcur->next;
	}
	pcur->next = node;
}

void SLPushFront(SLNode** pphead, SLDataType x)
{
    
    
	assert(pphead);
	SLNode* node = SLBuyNode(x);
	//新结点跟头结点连接起来
	node->next = *pphead;
	//让新节点成为头结点
	*pphead = node;
}

void SLPopBack(SLNode** pphead)
{
    
    
	assert(pphead);

	assert(*pphead);	//第一个节点不能为空
	//判断只有一个结点的情况
	if ((*pphead)->next == NULL)
	{
    
    
		free(*pphead);
		*pphead = NULL;
		return;
	}

	//找到尾节点和尾节点的前一个节点
	SLNode* pre = NULL;
	SLNode* ptail = *pphead;
	while (ptail->next!=NULL)
	{
    
    
		pre = ptail;
		ptail = ptail->next;
	}
	//pre的next指针不指向ptail,而是指向ptail的下一个节点
	//pre->next = ptail->next;
	pre->next = NULL;
	//因为在SLPrint(SLNode* phead)函数中有判断节点是否为空while (pcur),所以置空
	free(ptail);
	ptail = NULL;

}

void SLPopFront(SLNode** pphead)
{
    
    
	assert(pphead);
	assert(*pphead);	//第一个节点不能为空
	SLNode* del = *pphead;
	*pphead = (*pphead)->next;
	free(del);
	del = NULL;
}

SLNode* SLFind(SLNode** pphead, SLDataType x)
{
    
    
	assert(pphead);
	SLNode* pcur = *pphead;
	while (pcur)
	{
    
    
		if (pcur->data == x) {
    
    
			return pcur;
		}
		pcur = pcur->next;
	}
}

void SLInsert(SLNode** pphead, SLNode* pos, SLDataType x)
{
    
    
	assert(pphead);
	assert(pos);	//pos不能为空
	assert(*pphead);  //NULL前插入数据是荒谬的,所以assert第一个节点
	SLNode* node = SLBuyNode(x);

	//当只有一个结点(pos就是第一个节点)
	//if (((*pphead)->next) == NULL||pos==*pphead),可以简化成以下代码
	if (pos == *pphead) {
    
    
		node->next = *pphead;
		*pphead = node;
		return;
	}

	//找到pos的前一个节点
	SLNode* pre = *pphead;
	while (pre->next != pos)
	{
    
    
		pre = pre->next;
	}
	//处理pre node pos的位置
	node->next = pos;
	pre->next = node;
}

//7 定点后插入数据
void SLInsertAfter(SLNode* pos, SLDataType x)
{
    
    
	assert(pos);
	SLNode* node = SLBuyNode(x);
	node->next = pos->next;
	pos->next = node;
}

//8 删除pos节点
void SLErase(SLNode** pphead, SLNode* pos)
{
    
    
	assert(*pphead);
	assert(pphead);
	assert(pos);
	//pos是头结点
	if (pos == *pphead)
	{
    
    
		*pphead = (*pphead)->next;
		free(pos);
		return;
	}
	//pos不是头结点,找pos的前一个节点
	SLNode* pre = *pphead;
	while (pre->next != pos)
	{
    
    
		pre = pre->next;
	}
	pre->next = pos->next;
	free(pos);
	pos = NULL;   //代码规范
}

//9 删除pos后的节点
void SLEraseAfter(SLNode* pos)
{
    
    
	//删除pos之后的节点也不能空
	assert(pos&&pos->next);
	SLNode* del = pos->next;
	pos->next = del->next;
	free(del);
}

void SLDestory(SLNode** pphead)
{
    
    
	assert(pphead);
	SLNode* pcur = *pphead;
	while (pcur)
	{
    
    
		SLNode* next = pcur->next;
		free(pcur);
		pcur = next;
	}
	//头结点记得置空
	*pphead = NULL;
}



おすすめ

転載: blog.csdn.net/m0_73678713/article/details/134803943
おすすめ