[Data structure] Implementation of linked list in C language (singly linked list part)

Table of contents

Preface

linked list

Classification of linked lists

1. One-way or two-way

 2. To take the lead or not to take the lead

 3. Cyclic or non-cyclic

Single linked list implementation

define node

Interface function implementation

Create node

Print linked list

tail insert node

Tail delete node

header node

 Head delete node

 singly linked list search

Delete the node after the specified position

 Insert node after specifying position

Delete specified location

Insert node at specified position

Destroy linked list

  Complete code of singly linked list


Preface

We have implemented a sequence table before. For the sequence table, we also have to think about the following questions:

1. Insertion and deletion of the middle/head, the time complexity is O(N).
2. Capacity expansion requires applying for new space, copying data, and releasing old space. There will be a lot of consumption.
3. Capacity expansion generally increases by 2 times, which will inevitably lead to a certain amount of wasted space. For example, the current capacity is 100, and when it is full, the capacity is increased to 200. We continue to insert 5 data, and no data will be inserted later, so 95 data spaces are wasted.
Thinking: How to solve the above problem? The structure of the linked list is given below to take a look.

linked list

concept and structure

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 link order of pointers in the linked list .

Compared with the sequence list, the linked list is not continuous in physical structure, but it is continuous in logical structure. As shown in the picture:

 

Notice:

1. As can be seen from the above figure, the chain structure is continuous in logical structure, but not necessarily continuous in physical structure.

2. In reality, nodes are generally applied from the heap.

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

Classification of linked lists

In practice, the structures of linked lists are very diverse. There are a total of 8 linked list structures in the following situations:

1. One-way or two-way

 2. To take the lead or not to take the lead

 3. Cyclic or non-cyclic

 Although there are so many linked list structures, there are two structures that are most commonly used in practice:

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

2. Headed two-way circular linked list: the most complex structure, 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 linked list structure is complicated, if you use code to implement it, you will find that this structure has many advantages, and it is easier to implement.

Single linked list implementation

define node

A single node of the linked list consists of a data field and a pointer field. The pointer stored in the pointer field of the node is the address of the next node.

One thing to note here is that the pointer type of next is struct SListNode* , not SLTNode* .

typedef int SLTDataType;
typedef struct SListNode
{
	SLTDataType x;
	struct SListNode* next;
}SLTNode;

Interface function implementation

PS:

        Before implementing the add, delete, check and modify function, we need to know that a singly linked list does not require an initialization function. We only need to create a struct SListNode* plist = NULL; our singly linked list will be managed by the plist pointer.

As a type of data structure, the linked list's function is nothing more than adding, deleting, checking and modifying. Before implementing these functions, we first implement a function to create nodes.

Create node

//创建节点
SLTNode* BuySLTNode(SLTDataType x)
{
	SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
    //创建节点失败就退出程序
	if (newnode == NULL)
	{
		perror("malloc fail");
		exit(-1);
	}
	newnode->x = x;
	newnode->next = NULL;
	return newnode;
}

 After having the create node function, we are implementing a function that prints linked list information, so that when we implement other functions, we can observe whether the function function is correct by printing the linked list.

Print linked list

//打印链表
void SLTPrint(SLTNode* phead)
{
	if (phead == NULL)
	{
		printf("NULL\n");
		return;
	}
	SLTNode* cur = phead;
	while (cur)
	{
		printf("%d->", cur->x);
		cur = cur->next;
	}
	printf("NULL\n");
}

If our linked list is 1 2 3 4 5; the printed result is 1->2->3->4->5->NULL

tail insert node

//尾插
void SLT_PushBack(SLTNode** pphead, SLTDataType x)
{
	SLTNode* newnode = BuySLTNode(x);
    //如果链表为空,那么此时就是第一次向链表中插入节点,
    //那么只需要将我们创建的节点的指针赋值给*pphead就可以了
	if (*pphead == NULL)
	{
		*pphead = newnode;
		return;
	}
    //如果链表不为空,我们这时需要找到该链表的最后一个节点,
    //然后将我们创建的节点链接在最后一个节点的后面。
	SLTNode* cur = *pphead;
	while (cur->next != NULL)
	{
		cur = cur->next;
	}
	cur->next = newnode;
}

 

 

Why is the parameter passed in a secondary pointer?       

       As we said before, the initialization of a singly linked list is very simple. We only need to create a struct SListNode* plist. However, in the function that adds a new node, why is our parameter a secondary pointer? That's because, in the function we implement, the parameters in the function are just a copy of the actual parameters. When we want to change them, we must pass in its pointer (address). When our linked list has no nodes, plist is NULL. After we add nodes to the linked list, the previously empty linked list becomes a linked list with one node, and then plist needs to be changed. So we need to pass in the pointer (address) of the plist so that the plist can be changed.

       To put it simply, when an empty linked list is passed in and we want to add a new node to the linked list, the plist itself will change, so we have to pass in the pointer of the plist.

       The subsequent tail deletion, head insertion, and head deletion functions pass in secondary pointers. All because of this reason.

Tail delete node

//尾删节点
void SLT_PopBack(SLTNode** pphead)
{
    //如果传入的是空链表,那么就没有删除节点的必要
	assert(*pphead);
    //如果链表只有一个节点,那么
	if ((*pphead)->next == NULL)
	{
		free(*pphead);
		*pphead = NULL;
		return;
	}
    //在删除最后一个节点的同时,我们需要找到最后一个节点的前一个节点
    //将它的next置成NULL
	SLTNode* cur = *pphead;
	SLTNode* prev = *pphead;
	while (cur->next != NULL)
	{
		prev = cur;
		cur = cur->next;
	}
    //记得将最后一个节点释放,虽然删除后我们也拿不到这个节点的地址
    //但是,这个节点是由我们手动开辟的,一定要置空,防止内存泄漏
	free(cur);
	prev->next = NULL;
}

 

header node

//头插节点
void SLT_PushFront(SLTNode** pphead, SLTDataType x)
{    
    //如果链表为空,直接将创建的节点赋值给*pphead
	if (*pphead == NULL)
	{
		*pphead = BuySLTNode(x);
		return;
	}
	SLTNode* newnode = BuySLTNode(x);
	SLTNode* cur = *pphead;
	newnode->next = cur;
	*pphead = newnode;
}

 Head delete node

//头删节点
void SLT_PopFront(SLTNode** pphead)
{
    //如果传入的是空链表,就没有必要删除
	assert(*pphead);
	SLTNode* cur = *pphead;
	SLTNode* next = (*pphead)->next;
	free(cur);
    //将第一个节点删除之后,头节点改变。
	*pphead = next;
}

 

 singly linked list search

//单链表查找节点
SLTNode* SLT_Find(SLTNode* phead, SLTDataType x)
{
	assert(phead);
	SLTNode* cur = phead;
	while (cur)
	{
        //找到就返回该节点指针
		if (cur->x == x)
		{
			return cur;
		}
		cur = cur->next;
	}
    //找不到就返回空指针
	return NULL;
}

Delete the node after the specified position

//删除指定位置后节点
void SLT_EraseAfter(SLTNode* pos)
{
    //如果pos是最后一个节点,那么就没有必要删除
	assert(pos->next);
	SLTNode* nextnode = pos->next->next;
    //动态开辟的节点,一定要释放
	free(pos->next);
	pos->next = nextnode;
}

 Insert node after specifying position

//指定位置后插入节点
void SLT_InsertAfter(SLTNode* pos, SLTDataType x)
{
    //断言,防止传入空指针
	assert(pos);
	SLTNode* newnode = BuySLTNode(x);
	SLTNode* next = pos->next;
	pos->next = newnode;
	newnode->next = next;
}

 

Delete specified location

//删除指定位置
void SLT_Erase(SLTNode** pphead, SLTNode* pos)
{
	assert(pphead);
	assert(pos);
    //如果删除的节点是第一个节点,那么就是头删
	if (*pphead == pos)
	{
		SLTNode* cur = *pphead;
		*pphead = (*pphead)->next;
		free(cur);
		return;
	}
	SLTNode* cur = *pphead;
	SLTNode* prev = NULL;
	while (cur != pos)
	{
		prev = cur;
		cur = cur->next;
	}
	prev->next = cur->next;
	free(cur);
}

 

 

Insert node at specified position

//pos位置插入节点
void SLT_Insert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{
	assert(pos);
    //如果pos是第一个节点,那么就是头插
	if (*pphead == pos)
	{
		SLTNode* newnode = BuySLTNode(x);
		newnode->next = *pphead;
		*pphead = newnode;
		return;
	}
	SLTNode* cur = *pphead;
	SLTNode* prev = NULL;
	while (cur!=pos)
	{
		prev = cur;
		cur = cur->next;
	}
	SLTNode* newnode = BuySLTNode(x);
	prev->next = newnode;
	newnode->next = pos;
}

Destroy linked list

//销毁链表
void SLT_Destroy(SLTNode** pphead)
{
	assert(pphead);
	assert(*pphead);
	SLTNode* cur = *pphead;
	SLTNode* nextnode = *pphead;
	while (cur)
	{
		nextnode = cur->next;
		free(cur);
		cur = nextnode;
	}
    //最后将*pphead置空,防止非法访问
	*pphead = NULL;
}

  Complete code of singly linked list

SList.h

#pragma once
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>

typedef int SLTDataType;

typedef struct SListNode
{
	SLTDataType x;
	struct SListNode* next;
}SLTNode;

//创建一个节点
SLTNode* BuySLTNode(SLTDataType x);

//打印链表
void SLTPrint(SLTNode* phead);

//尾删尾插
void SLT_PushBack(SLTNode** pphead, SLTDataType x);
void SLT_PopBack(SLTNode** pphead);

//头插头删
void SLT_PushFront(SLTNode** pphead, SLTDataType x);
void SLT_PopFront(SLTNode** pphead);

//单链表查找
SLTNode* SLT_Find(SLTNode* plist, SLTDataType x);

// 单链表在pos位置之后插入x
void SLT_InsertAfter(SLTNode * pos, SLTDataType x);

//在pos位置插入x
void SLT_Insert(SLTNode* phead, SLTNode* pos, SLTDataType x);

// 单链表删除pos位置之后的值
void SLT_EraseAfter(SLTNode* pos);

//删除pos位置
void SLT_Erase(SLTNode** pphead, SLTNode* pos);

//销毁链表
void SLT_Destroy(SLTNode** pphead);

SList.c

#include "SList.h"

SLTNode* BuySLTNode(SLTDataType x)
{
	SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
	if (newnode == NULL)
	{
		perror("malloc fail");
		exit(-1);
	}
	newnode->x = x;
	newnode->next = NULL;
	return newnode;
}

void SLTPrint(SLTNode* phead)
{
	if (phead == NULL)
	{
		printf("NULL\n");
		return;
	}
	SLTNode* cur = phead;
	while (cur)
	{
		printf("%d->", cur->x);
		cur = cur->next;
	}
	printf("NULL\n");
}

void SLT_PushBack(SLTNode** pphead, SLTDataType x)
{
	SLTNode* newnode = BuySLTNode(x);
	if (*pphead == NULL)
	{
		*pphead = newnode;
		return;
	}
	SLTNode* cur = *pphead;
	while (cur->next != NULL)
	{
		cur = cur->next;
	}
	cur->next = newnode;
}

void SLT_PopBack(SLTNode** pphead)
{
	assert(*pphead);
	if ((*pphead)->next == NULL)
	{
		free(*pphead);
		*pphead = NULL;
		return;
	}
	SLTNode* cur = *pphead;
	SLTNode* prev = *pphead;
	while (cur->next != NULL)
	{
		prev = cur;
		cur = cur->next;
	}
	free(cur);
	prev->next = NULL;
}

void SLT_PushFront(SLTNode** pphead, SLTDataType x)
{
	if (*pphead == NULL)
	{
		*pphead = BuySLTNode(x);
		return;
	}
	SLTNode* newnode = BuySLTNode(x);
	SLTNode* cur = *pphead;
	newnode->next = cur;
	*pphead = newnode;
}

void SLT_PopFront(SLTNode** pphead)
{
	assert(*pphead);
	SLTNode* cur = *pphead;
	SLTNode* next = (*pphead)->next;
	free(cur);
	*pphead = next;
}

SLTNode* SLT_Find(SLTNode* phead, SLTDataType x)
{
	assert(phead);
	SLTNode* cur = phead;
	while (cur)
	{
		if (cur->x == x)
		{
			return cur;
		}
		cur = cur->next;
	}
	return cur;
}

void SLT_InsertAfter(SLTNode* pos, SLTDataType x)
{
	assert(pos);
	SLTNode* newnode = BuySLTNode(x);
	SLTNode* next = pos->next;
	pos->next = newnode;
	newnode->next = next;
}

void SLT_Insert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{
	assert(pos);
	if (*pphead == pos)
	{
		SLTNode* newnode = BuySLTNode(x);
		newnode->next = *pphead;
		*pphead = newnode;
		return;
	}
	SLTNode* cur = *pphead;
	SLTNode* prev = NULL;
	while (cur!=pos)
	{
		prev = cur;
		cur = cur->next;
	}
	SLTNode* newnode = BuySLTNode(x);
	prev->next = newnode;
	newnode->next = pos;
}

void SLT_EraseAfter(SLTNode* pos)
{
	assert(pos->next);
	SLTNode* newnode = pos->next->next;
	free(pos->next);
	pos->next = newnode;
}

void SLT_Erase(SLTNode** pphead, SLTNode* pos)
{
	assert(pphead);
	assert(pos);
	if (*pphead == pos)
	{
		SLTNode* cur = *pphead;
		*pphead = (*pphead)->next;
		free(cur);
		return;
	}
	SLTNode* cur = *pphead;
	SLTNode* prev = NULL;
	while (cur != pos)
	{
		prev = cur;
		cur = cur->next;
	}
	prev->next = cur->next;
	free(cur);
}

void SLT_Destroy(SLTNode** pphead)
{
	assert(pphead);
	assert(*pphead);

	SLTNode* cur = *pphead;
	SLTNode* nextnode = *pphead;
	while (cur)
	{
		nextnode = cur->next;
		free(cur);
		cur = nextnode;
	}
	*pphead = NULL;
}

The above is all the content of singly linked list implementation, I hope it can help everyone. Please correct me if there are any mistakes.

Guess you like

Origin blog.csdn.net/m0_74459723/article/details/128514038