Detailed explanation of single linked list (the end of the text contains all the codes that have been completely tested ~ they can be taken by themselves if necessary)

Posted by lando on Tue, 02 Nov 2021 07:12:18 +0100

catalogue

Zero. Preface

1. Linked list and sequence list

1. Defects and advantages of sequence table

advantage

shortcoming

2. Advantages and disadvantages of linked list

advantage

shortcoming

2. Logical and physical structure of linked list

1. Logical structure

2. Physical structure

3. Basic operation of single linked list

1. Define single linked list nodes

2. Create a single linked list node

3. Print single linked list

4. Destroy the single linked list

4. Tail deletion and tail insertion

1. Tail plug

2. End deletion

5. Header deletion and header insertion

1. Head insert

2. Header deletion

6. Directional insertion

1. Insert before pos

2. Insert after pos

7. Directional deletion

1. Delete pos

2. Nodes after deleting pos

8. Find

9. All codes and test results

1.SList.h file

2.SList.c file

3.test.c file

4. Test results

10. Summary

Zero. Preface

Single linked list plays a very important role in practical application, makes up for the shortage of sequential table, and has great advantages in space storage and memory occupation. This paper will introduce the basic operation of single linked list. At the end of the paper, there is a complete code that has been tested.

1. Linked list and sequence list

1. Defects and advantages of sequence table

advantage

The sequential table supports random access. Some algorithm structures need random access, such as binary search and optimized quick sorting. The random access here can only obtain data through the same subscript.

shortcoming

1. The sequence table requires data to be stored in sequence, so when inserting or deleting data, a large amount of data needs to be moved, which is too expensive.

2. When the space is insufficient, the sequence table needs to be expanded. In order to avoid frequent expansion, it is usually expanded to twice the original capacity, but it is not known how much data needs to be stored during expansion, so it may cause a waste of space.

2. Advantages and disadvantages of linked list

advantage

We designed the linked list according to the defects of the sequential list.

1. Apply for space on demand, release space without use, and there is no waste of space.

2. There is no need to move data during insertion and deletion.

shortcoming

1. Every time a data is stored, a pointer to the next structure needs to be stored.

2. Random access is not supported.

2. Logical and physical structure of linked list

1. Logical structure

Logical structure is an abstract structure that is convenient for us to understand

  On the left is the data field and on the right is the pointer field.

2. Physical structure

Physical structure is the structure actually stored in the computer

  In addition to storing data, each structure also stores the address of the next structure.

3. Basic operation of single linked list

1. Define single linked list nodes

typedef struct SListNode {
	DataType data;
	struct SListNode* next;
}SLTNode;

A structure variable is defined as the node of the single linked list, including the value data and the pointer next to the next structure.

2. Create a single linked list node

SLTNode* BuyListNode(DataType x)
{
	SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));//Make room for new nodes
	assert(newnode);//Judge whether the returned pointer is null
	newnode->data = x;
	newnode->next = NULL;//Assign a value to the new node
	return newnode;
}

First, create a new node newnode and make room for it. Record its data value as x and the next value as NULL temporarily.

3. Print single linked list

void print(SLTNode* phead)
{
	SLTNode* cur = phead;
	while (cur != NULL)
	{
		printf("%d->", cur->data);
		cur = cur->next;
	}//Traverse the entire linked list and stop printing when cur is empty
	printf("NULL\n");
}

Traverse from the beginning node until cur becomes empty, print all data values, and finally add NULL pointed to by the last node.

4. Destroy the single linked list

void SListDestroy(SLTNode** pphead)
{
	SLTNode* cur = *pphead;
	while (cur)
	{
		SLTNode* next = cur->next;
		free(cur);//Free up cur space
		cur = next;//Set cur as its next node
	}
	pphead = NULL;
}

Starting from the node, define a pointer cur that traverses the linked list. Each time cur = cur - > next, we need to define a next, because every time we release cur, we also need to release its next node, that is, cur - > next. If we don't need next to receive cur - > next in advance, we can't find its next element after cur is released.

When writing the code of single linked list, we should pay attention to a principle, that is, find the front and not the back, because the later can be modified by one or more next. When traversing with cur, we only need to finally set cur as the first element to change.

4. Tail deletion and tail insertion

1. Tail plug

void SListPushBack(SLTNode** pphead, DataType x)
{
	assert(*pphead);
	SLTNode* newnode= BuyListNode(x);
	SLTNode* cur = *pphead;
	while (cur->next)
	{
		cur = cur->next;
	}//Find the last node
	cur->next = newnode;
}

The premise of tail insertion is to find the tail node first. We still need to establish a cur traversing the linked list to find the last node, and then insert it. Note that we need to pass in the address of the pointer. Although we can modify the value of the linked list if only the pointer is passed, if there are no elements in the linked list, that is, phead=NULL, we need to set phead as a new node. At this time, direct assignment cannot be completed, so try to use secondary pointers for assignment.

2. End deletion

void SListPopBack(SLTNode** pphead)
{
	assert(*pphead);//Judge whether there are nodes
	if ((*pphead)->next == NULL)
	{
		free(*pphead);
		*pphead = NULL;//When there is only one node, the header pointer is assigned NULL
	}
	else
	{
		SLTNode* cur = *pphead;
		while (cur->next->next != NULL)
		{
			cur = cur->next;
		}//Find the node that changes first, that is, the penultimate node, in order to assign its next value to NULL
		free(cur->next);
		cur->next = NULL;
	}
}

The idea of tail deletion is the same. First, find the last element through cur traversal, and then release it. Because the next of the last element is NULL, and we need to set the next pointing to the element to be empty after releasing the last element, so we need to get this element. When only one traversal node cur is used, We want cur to traverse to the penultimate node and modify its next pointer. So we use cur - > next to find the last node.

5. Header deletion and header insertion

1. Head insert

void SListPushFront(SLTNode** pphead, DataType x)
{
	SLTNode* newnode = BuyListNode(x);
    newnode->next= *pphead;
	*pphead = newnode;//Modify the header node, so you need to use the secondary pointer
}

Create a new node, make its next point to the original head node, and then point the head node pointer to the new node.

2. Header deletion

void SListPopFront(SLTNode** pphead)
{
	assert(*pphead);
	SLTNode* next = (*pphead)->next;
	free(*pphead);
	*pphead = next;//As long as the header node changes, you need to use the secondary pointer
}

First, you need to judge whether the linked list is empty. If it is empty, the program exits abnormally. If it is not empty, first save the next position of the header node with a pointer next, and then release the header node. When writing the linked list, you should also pay attention to saving the elements that have changed due to the change of an element and cannot be found after the change of the element.

6. Directional insertion

1. Insert before pos

void SListInsertFront(SLTNode**pphead,SLTNode* pos, DataType x)
{
	assert(pphead);
	assert(pos);
	if (*pphead == pos)
	{
		SListPushFront(pphead, x);//Judge whether pos is the first element. If yes, it becomes header insertion
	}
	else
	{
	    SLTNode*newnode=BuyListNode(x);
		SLTNode* cur = *pphead;
		while (cur->next != pos)
		{
			cur = cur->next;
		}
		cur->next = newnode;
		newnode->next = pos;//Insert newnode before pos
	}
}

Insert in front of the pos, adhering to the principle of looking before and not after. The previous element of the pos has also changed (- > next). Therefore, the final cur should point to the previous element of the pos, so that cur - > next points to the newly established node, and the next of the new node points to the pos.

2. Insert after pos

void SListInsertBack(SLTNode** pphead,SLTNode* pos,DataType x)
{
	assert(pos);//Judge whether pos is empty
	SLTNode* newnode = BuyListNode(x);
	newnode->next = pos->next;
	pos->next = newnode;//Insert newnode after pos
}

After the POS is inserted, the first change is the POS, and we have obtained the POS node, so we don't need to traverse. Just point the next of the POS to the newly established node, and the next of the newly established node to pos - > next.

7. Directional deletion

1. Delete pos

void SListErase(SLTNode** pphead, SLTNode* pos)
{
	assert(pos);//If pos does not exist, an error is reported
	if (pos == *pphead)
	{
		SListPopFront(pphead);//If pos is the first node, it becomes header deletion
	}
	else
	{
		SLTNode* cur = *pphead;
		while (cur->next != pos)
		{
			cur = cur->next;
		}
		cur->next = pos->next;
		free(pos);
	}
}

To delete POS, you also need to find the previous node of POS, that is, after traversal, set cur as the previous node of POS, make cur - > next = pos - > next, and then release the space of POS.

2. Nodes after deleting pos

void SListEraseBack( SLTNode* pos)
{
	assert(pos->next);//Judge whether the node after pos is legal
	SLTNode* next = pos->next;//Record the next node of the node after the pos, which is convenient for assigning values to the next node of the pos
	pos->next = next->next;
	free(next);
}

There is no need to traverse at this time, because the first changed node (POS) has been obtained. We can directly set pos - > next = pos - > next - > next, and then release pos - > next. Because the POS node has been deleted, we need to create a new next node to store pos - > next before that.

8. Find

SLTNode* SListFind(SLTNode* pphead, DataType x)
{
	SLTNode* cur = pphead;
	while (cur)
	{
		if (cur->data == x)
		{
			return cur;//Find and return the node
		}
		cur = cur->next;
	}
	return NULL;//Return null not found
}

Traverse all nodes, return the node if found, and return NULL if not found.

9. All codes and test results

1.SList.h file

#define _CRT_SECURE_NO_WARNINGS 1
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#define DataType int
typedef struct SListNode {
	DataType data;
	struct SListNode* next;
}SLTNode;
void print(SLTNode* phead);//Print
SLTNode* BuyListNode(DataType x);//Create linked list node
void SListDestroy(SLTNode** pphead);//Destroy linked list
void SListPushBack(SLTNode** pphead, DataType x);//Tail insertion
void SListPopBack(SLTNode** pphead);//Tail deletion
void SListPushFront(SLTNode** pphead, DataType x);//Head insert
void SListPopFront(SLTNode** pphead);//Header deletion
void SListInsertFront(SLTNode**pphead,SLTNode* pos, DataType x);//Fixed front insert
void SListInsertBack(SLTNode** pphead, SLTNode* pos, DataType x);//Fixed rear insert
void SListErase(SLTNode** pphead, SLTNode* pos);//Fixed point deletion
void SListEraseBack(SLTNode* pos);//Delete the next node after the fixed point
SLTNode* SListFind(SLTNode* pphead, DataType x);//lookup

2.SList.c file

#include "SList.h"
SLTNode* BuyListNode(DataType x)
{
	SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
	assert(newnode);
	newnode->data = x;
	newnode->next = NULL;
	return newnode;
}
void print(SLTNode* phead)
{
	SLTNode* cur = phead;
	while (cur != NULL)
	{
		printf("%d->", cur->data);
		cur = cur->next;
	}
	printf("NULL\n");
}
void SListDestroy(SLTNode** pphead)
{
	SLTNode* cur = *pphead;
	while (cur)
	{
		SLTNode* next = cur->next;
		free(cur);
		cur = next;
	}
	pphead = NULL;
}
void SListPushBack(SLTNode** pphead, DataType x)
{
	assert(*pphead);
	SLTNode* newnode= BuyListNode(x);
	SLTNode* cur = *pphead;
	while (cur->next)
	{
		cur = cur->next;
	}
	cur->next = newnode;
}
void SListPopBack(SLTNode** pphead)
{
	assert(*pphead);
	if ((*pphead)->next == NULL)
	{
		free(*pphead);
		*pphead = NULL;
	}
	else
	{
		SLTNode* cur = *pphead;
		while (cur->next->next != NULL)
		{
			cur = cur->next;
		}
		free(cur->next);
		cur->next = NULL;
	}
}
void SListPushFront(SLTNode** pphead, DataType x)
{
	SLTNode* newnode = BuyListNode(x);
    newnode->next= *pphead;
	*pphead = newnode;
}
void SListPopFront(SLTNode** pphead)
{
	assert(*pphead);
	SLTNode* next = (*pphead)->next;
	free(*pphead);
	*pphead = next;
}
void SListInsertFront(SLTNode**pphead,SLTNode* pos, DataType x)
{
	assert(pphead);
	assert(pos);
	if (*pphead == pos)
	{
		SListPushFront(pphead, x);
	}
	else
	{
	    SLTNode*newnode=BuyListNode(x);
		SLTNode* cur = *pphead;
		while (cur->next != pos)
		{
			cur = cur->next;
		}
		cur->next = newnode;
		newnode->next = pos;
	}
}
void SListInsertBack(SLTNode** pphead,SLTNode* pos,DataType x)
{
	assert(pos);
	SLTNode* newnode = BuyListNode(x);
	newnode->next = pos->next;
	pos->next = newnode;
}
void SListErase(SLTNode** pphead, SLTNode* pos)
{
	assert(pos);
	if (pos == *pphead)
	{
		SListPopFront(pphead);
	}
	else
	{
		SLTNode* cur = *pphead;
		while (cur->next != pos)
		{
			cur = cur->next;
		}
		cur->next = pos->next;
		free(pos);
	}
}
void SListEraseBack( SLTNode* pos)
{
	assert(pos->next);
	SLTNode* next = pos->next;
	pos->next = next->next;
	free(next);
}
SLTNode* SListFind(SLTNode* pphead, DataType x)
{
	SLTNode* cur = pphead;
	while (cur)
	{
		if (cur->data == x)
		{
			return cur;
		}
		cur = cur->next;
	}
	return NULL;
}

3.test.c file

#include"SList.h"
void menu()
{
	printf("****1.Print linked list****2.Tail insertion****\n");
	printf("****3.Tail deletion********4.Head insert****\n");
	printf("****5.Header deletion********6.Fixed front insert\n");
	printf("****7.Fixed rear insert****8.Fixed delete\n");
	printf("****9.Delete after fixing****0.Delete linked list\n");
}
int main()
{
	int input = 0;
	SLTNode* phead = (SLTNode*)malloc(sizeof(SLTNode));
	phead->data = 0;
	phead->next = NULL;
	SListPushBack(&phead, 1);
	SListPushBack(&phead, 2);
	SListPushBack(&phead, 3);
	SListPushBack(&phead, 4);
	SListPushBack(&phead, 5);
	SListPushBack(&phead, 6);
	print(phead);
	do {
		menu();
		scanf("%d", &input);
		int x;
		int y;
		SLTNode* pos;
		switch (input)
		{
		case 1:	print(phead);
			break;
		case 2:scanf("%d", &x);
			SListPushBack(&phead, x);
			print(phead);
			break;
		case 3:SListPopBack(&phead);
			print(phead);
			break;
		case 4:scanf("%d", &x);
			SListPushFront(&phead, x);
			print(phead);
			break;
		case 5:SListPopFront(&phead);
			print(phead);
			break;
		case 6:scanf("%d", &x);
			scanf("%d", &y);
		   pos=SListFind(phead, x);
		   SListInsertFront(&phead, pos, y);
		   print(phead);
		   break;
		case 7:scanf("%d", &x);
			scanf("%d", &y);
			pos = SListFind(phead, x);
			SListInsertBack(&phead, pos, y);
			print(phead);
			break;
		case 8:scanf("%d", &x);
			pos = SListFind(phead, x);
			SListErase(&phead, pos);
			print(phead);
			break;
		case 9:
			scanf("%d", &x);
			pos = SListFind(phead, x);
			if (pos == NULL)
			{
				printf("can't find!\n");
				continue;
			}
			SListEraseBack(pos);
			print(phead);
			break;
		case 0:SListDestroy(&phead);
			break;
		default:printf("wrong input!\n");
			break;
		}
	} while (input);
	return 0;
}

4. Test results

1. Tail insertion: successful

2. Tail deletion: delete the tail until no error is reported. It is successful

3. Head insertion: successful

4. Header deletion: delete the header until no error is reported. It is successful

5. Directional header insertion: insert header in front of the first element, insert header in front of other elements, and insert an error message at the position where there is no element. Success

6. Directional tail insertion: insert other elements and insert errors after no elements exist. Success is achieved

7. Fixed deletion: delete the linked list empty and report an error. The deletion is successful if no element is reported an error

8. Delete after fixing: delete and report an error after the last element, delete the non-existent element, delete the linked list and report an error. Success

9. Find: verified in the orientation, successful

0. Delete linked list: successful

10. Summary

When writing the code of single linked list, we should pay attention to the disadvantage of single linked list, that is, we can't find data through subscript. Therefore, most operations of the single linked list need to be traversed. While traversing, we need to find the element that changes first and then record it. When we can change it, we can find the element after it according to it. When performing some deletion operations, in order to prevent chain breakage, it is also necessary to record the position of the next element before deletion. I think these two principles are very important when traversing and deleting again. Most of the OJ questions we use are single linked lists without header nodes, so there are no header nodes here. You can take what you need. If it helps you, don't forget to pay attention or praise ~.

Topics: C data structure linked list