[Data structure] Animation detailed one-way linked list

Table of contents

1. What is a linked list

        1. Question introduction

        2. The concept and structure of linked list

        3. Problem Solving

2. Implementation of the one-way linked list interface

        1. Interface 1, 2---head plug, tail plug

        2. Interface 3, 4---head delete, tail delete

        3. Interface 5---Find

         4. Interface 6, 7---insert, delete

        5. Interface 8---Print

         6. Summary of Precautions

3. Complete code and effect display 


1. What is a linked list

        1. Question introduction

        In the last issue, we explained the basic concept and implementation method of the sequence table (Portal: Detailed sequence table ). But there are three problems in the sequence table:

  1. The insertion and deletion in the middle and at the head of the sequence table requires moving the original data, and the time complexity is O(N), and the cost is high
  2. When using realloc to increase capacity, you need to apply for new space, release old space, and copy data, which consumes a lot.
  3. Since we cannot know how much space the user actually needs, there may often be a large amount of space left when the capacity is increased. For example, when the capacity is 100, the capacity is doubled to 200. If you only need to insert 5 data later, 95 data spaces will be wasted; and if we only expand 1 data space at a time, when we need to insert 95 data , it is necessary to perform realloc operations 95 times, and the cost is relatively high.

        So how to solve these problems? Through the linked list , we can easily solve the above problems. Next, let us feel the charm of the linked list!

        2. The concept and structure of linked list

The linked list is a non-continuous and non-sequential storage structure         in the physical storage structure . The logical order of the data elements is realized through the link order of the pointers in the linked list . Its structure is as follows (taking a one-way linked list as an example):

        We call the structure formed by date and next a node of the linked list. We can see that the linked list is a non-continuous linear structure linked by nodes . Different nodes are connected by the next pointer. The last node The next pointer points to null . A linked list is also a type of linear list.

Of course, in practical applications, the structure of the linked list is various, and there are 8 kinds of linked list structures formed by combining the following situations :

1. One-way, two-way

2. Leading node, not leading node

3. Cyclic, non-cyclic


In this issue, we explain one of the two most commonly used structures in practical applications: one-way non-leading non-circular linked list . (The other is the leading two-way circular linked list )

        3. Problem Solving

        Through the above linked list structure, we can well solve the limitations of the sequence list:

  1. Whenever we need to add new data, we only need to apply for a new node to save the data, and then use the pointer to link the linked list with the new node without copying the data.
  2. Since the data stored in the linked list is always fully loaded , each node is valid data, and when it needs to be inserted, a new node is applied for and connected to the linked list, so there is no space waste.
  3. The structure of the linked list is linear and non-continuous , and each node is connected by a pointer. If you need to insert data at the head or in the middle, you only need to change the pointing of the node pointer without moving the data.

2. Implementation of the one-way linked list interface

        First, before implementing various interface functions, we need to define a structure to represent each node, use date to save the data in the node, and use next to save the address of the next node. Similarly, we use typedef to rename the type to facilitate code writing and maintenance. as follows:

Since the one-way linked list we implemented does not have a head node, we also need to define a pointer to the first node of the linked list (hereinafter referred to as the head pointer).

        1. Interface 1, 2---head plug, tail plug

        For head insertion , we only need to create a new node to save data, and then point the head pointer to the new node, and the new node points to the node pointed to by the original head pointer. The dynamic effect is as follows:

        The specific code is as follows:

//用于创建新结点
SLNode* CreateNode(SLDateType x)
{
	SLNode* cur = (SLNode*)malloc(sizeof(SLNode));
	cur->date = x;
	cur->next = NULL;
	return cur;
}

//头插
void SListPushFront(SLNode** pphead, SLDateType x)
{
	SLNode* NewNode = CreateNode(x); //获取新结点
	NewNode->next = *pphead; 
	*pphead = NewNode; //修改头指针
}

It is worth noting that what we pass in the function is the address of the head pointer . This is because we need to modify the head pointer to point to a new node, so we need to adopt the method of address transfer and use the secondary pointer to receive it, otherwise it will only modify the temporary variable and cause an error.


         For tail insertion , we need to find the tail node of the linked list first, and then point the next of the tail node to the new node. The dynamic effect is as follows:

        code show as below: 

//尾插,初稿
void SListPushBack(SLNode** pphead, SLDateType x)
{
	SLNode* NewNode = CreateNode(x);
	SLNode* tail = *pphead;
	while (tail->next != NULL) //不为尾结点
	{
		tail = tail->next; //tail指向下一结点
	}

    //找到尾结点
	tail->next = NewNode;
}

        However, the above code is problematic . We know that -> is equivalent to a kind of dereferencing . When the linked list is empty , that is, when tail==NULL , it is obviously illegal for us to perform tail->next operation at this time, so we still need to make a decision when the linked list is empty. Discussion, the final code is as follows:

//尾插,终稿
void SListPushBack(SLNode** pphead, SLDateType x)
{
	SLNode* NewNode = CreateNode(x);
	if (*pphead == NULL)
	{
		*pphead = NewNode; //为空就直接修改头指针指向新结点,因此需要传二级指针
	}
	else
	{
		SLNode* tail = *pphead;
		while (tail->next != NULL) //不为尾结点
		{
			tail = tail->next; //指向下一结点
		}

        //找到尾结点
		tail->next = NewNode; 
	}
}

        2. Interface 3, 4---head delete, tail delete

        For head deletion , we only need to point the head pointer to the next position, and then free() the space originally pointed to. If the linked list is empty, we let the function return directly. The specific dynamic effect is as follows:

        In the code implementation, we need to create a temporary variable next to save the address of the next node, because the address of the next node cannot be obtained through -> after free(). The specific code is as follows:

//头删
void SListPopFront(SLNode** pphead)
{
	if (*pphead == NULL)
	{
		return;  //链表为空直接返回
	}
	else
	{
		SLNode* next = (*pphead)->next; //存储下一个结点地址
		free(*pphead);
		*pphead = next; //改变头指针指向,因此需要传二级指针
	}
}

        For tail deletion , we also need to find the tail node. The difference from tail insertion here is that in addition to finding the tail node, we also need to find the previous node of the tail node and set its next to empty, so we can use two pointers prev and tail to move, and prev points to The last node of tail, the specific animation is as follows:

        The specific code is as follows:

//尾删,初稿
void SListPopBack(SLNode** pphead)
{
	if (*pphead == NULL) //没有结点
	{
		return;
	}
	else  //一个以上结点
	{
		SLNode* tail = *pphead;
		SLNode* prev = NULL;
		while (tail->next != NULL) //tail不为尾结点
		{
			prev = tail;
			tail = tail->next; //tail指向下一结点
		}

        //tail为尾结点,此时prev为尾结点的前一个结点
        free(tail); //释放掉尾结点
        tail=NULL;
		prev->next = NULL;
	}
}

        Here is the same as head deletion, let the function return directly when the linked list is empty. However, there is still a problem with the above code , that is, when the linked list has only one node , tail directly points to the tail node. At this time, prev is NULL , and the -> operation is prohibited. So we also need to discuss the case of only one node, the final code is as follows:

//尾删,终稿
void SListPopBack(SLNode** pphead)
{
	if (*pphead == NULL) //没有结点
	{
		return;
	}
	else if ((*pphead)->next == NULL)  //只有一个结点
	{
		free(*pphead);
		*pphead = NULL; //直接将结点释放掉,头指针改为NULL,因此需要传二级指针
	}
	else  //一个以上结点
	{
		SLNode* tail = *pphead;
		SLNode* prev = NULL;
		while (tail->next != NULL) //tail不为尾结点
		{
			prev = tail;
			tail = tail->next; //tail指向下一结点
		}

        //tail为尾结点,此时prev为尾结点的前一个结点
        free(tail); //释放掉尾结点
        tail=NULL;
		prev->next = NULL;
	}
}

        3. Interface 5---Find

        For the search, we only need to traverse all the nodes of the linked list, so we don't need the head pointer, just pass the first-level pointer. When the date to be searched is found, the pointer to the corresponding node is returned. If not found or the linked list is empty, NULL is returned. The code is as follows:

//查找
SLNode* SListFind(SLNode* phead, SLDateType x)
{
	while (phead)
	{
		if (phead->date == x)
		{
			return phead; //找到了,返回结点指针
		}
		phead = phead->next; //向后查找
	}

    //没有找到,返回空指针
	return NULL;
}

         4. Interface 6, 7---insert, delete

        For insertion , we can implement an interface that inserts a new node before the specified node, and we can obtain this specified node by looking up the interface. Similarly, when inserting, we also need to get the address of the previous node, we use prev to store it, and then change prev->next to the address of the new node, and the next of the new node is the node we passed in the address of. The dynamic effect is as follows:

//插入,初稿
void SListInsert(SLNode** pphead, SLNode* pos, SLDateType x)
{
	if (pos == NULL) //指定结点为空直接返回
	{
		return;
	}
	SLNode* NewNode = CreateNode(x); //获取新结点
	SLNode* prev = *pphead;
	while (prev->next != pos) //prev不指向pos上一结点
	{
		prev = prev->next; //prev指向下一结点
	}

    //当prev指向pos上一结点,插入新结点
	prev->next = NewNode;
	NewNode->next = pos;
}

         (Hey, I’m here again qwq), the above code actually still has a bug, that is, when the specified node we pass in is the head node, prev->next can never be equal to pos, and prev is constantly updated backwards. Eventually NULL causes the program to crash. The dynamic analysis is as follows:

         Therefore, we need to discuss by category, and we found that if pos points to the head node, isn't inserting in front of it equivalent to head insertion? So we can directly call the plug-in interface, the improved code is as follows:

//插入,终稿
void SListInsert(SLNode** pphead, SLNode* pos, SLDateType x)
{
	if (pos == NULL) //指定结点为空直接返回
	{
		return;
	}
	if (*pphead==pos) //pos为头结点指针,直接头插
	{
		SListPushFront(pphead,x);
	}
    else
    {
	    SLNode* NewNode = CreateNode(x); //获取新结点
	    SLNode* prev = *pphead;
	    while (prev->next != pos) //prev不指向pos上一结点
	    {
		    prev = prev->next; //prev指向下一结点
	    }

        //当prev指向pos上一结点,插入新结点
	    prev->next = NewNode;
	    NewNode->next = pos;
    }
}

        For deletion , we can also implement an interface to delete a specified node, and we can still obtain this specified node by searching the interface. Similarly, in addition to free()ing the specified node, it is also necessary to point the next node of the previous node to the next node of pos. Like inserting, you need to find the position of the previous node, and the problems that may arise during this process are also the same, so I won’t go into details here, just upload the code directly:

//删除
void SListErase(SLNode** pphead, SLNode* pos)
{
	if (pos == NULL) //指定结点为空直接返回
	{
		return;
	}
	if (*pphead == pos) //pos为头结点指针,直接头删
	{
		SListPopFront(pphead);
	}
	else
	{
		SLNode* prev = *pphead;
		while (prev->next != pos) //prev不指向pos上一结点
		{
			prev = prev->next; //使prev指向下一个结点
		}

        //当prev指向pos上一结点,删除pos指向结点,更新prev->next
		prev->next = pos->next;
		free(pos);
	}
}

        5. Interface 8---Print

        For printing , just start from the head node, traverse the linked list backwards, print the date of each node until it reaches the end of the linked list, and the pointer points to NULL at this time. Since the head pointer is not modified, it is sufficient to pass by value and receive with a first-level pointer. code show as below:

//打印
void SListPrint(SLNode* phead)
{
	while (phead != NULL) //遍历链表直到链表尾
	{
		printf("%d->", phead->date);
		phead = phead->next;
	}

    //到达链表尾,打印NULL
	printf("NULL");
}

         6. Summary of Precautions

Through the implementation of the above interfaces, we get the following two points worth noting:

  1. When we need to modify the head pointer, such as inserting and deleting operations, we need to use address transfer, and use the secondary pointer to receive the address of the head pointer. And if we don’t need to modify the head pointer, such as printing, searching, etc., it is recommended to use value transfer and use a first-level pointer to receive to avoid accidental modification of the head pointer.
  2. When implementing the linked list interface, it is necessary to always consider whether there will be bugs in special cases such as when the linked list is empty and the linked list has only one node, operating on the head node and operating on the tail node.

3. Complete code and effect display 

        We can use the form of multi-file writing , put the definition and implementation of the above interface in the SList.c file, and then put the declaration of the interface and the definition of the structure in the SList.h header file to achieve the effect of encapsulation . In this way, if we need to use a one-way linked list, we only need to include the corresponding header file SList.h in the file to use the various interfaces we defined above. The following is the complete code and effect display of the one-way linked list implemented in this article:

//SList.h文件,用于声明接口函数,定义结构体
#pragma once
#include<stdio.h>
#include<stdlib.h>
typedef int SLDateType;
struct SListNode
{
	SLDateType date;
	struct SListNode* next;
};
typedef struct SListNode SLNode;


// 不会改变链表的头指针,传一级指针
void SListPrint(SLNode* phead);
SLNode* SListFind(SLNode* phead, SLDateType x);

// 可能会改变链表的头指针,传二级指针
void SListPushBack(SLNode** pphead, SLDateType x);
void SListPushFront(SLNode** pphead, SLDateType x);
void SListPopFront(SLNode** pphead);
void SListPopBack(SLNode** pphead);
// 在pos的前面插入x
void SListInsert(SLNode** phead, SLNode* pos, SLDateType x);
// 删除pos位置的值
void SListErase(SLNode** phead, SLNode* pos);
//SList.c文件,用于定义接口函数
#define _CRT_SECURE_NO_WARNINGS 1
#include "SList.h"
void SListPrint(SLNode* phead)
{
	while (phead != NULL)
	{
		printf("%d->", phead->date);
		phead = phead->next;
	}
	printf("NULL");
}
SLNode* CreateNode(SLDateType x)
{
	SLNode* cur = (SLNode*)malloc(sizeof(SLNode));
	cur->date = x;
	cur->next = NULL;
	return cur;
}

void SListPushBack(SLNode** pphead, SLDateType x)
{
	SLNode* NewNode = CreateNode(x);
	if (*pphead == NULL)
	{
		*pphead = NewNode; //为空就直接修改头指针指向新结点,因此需要传二级指针
	}
	else
	{
		SLNode* tail = *pphead;
		while (tail->next != NULL) //不为尾结点
		{
			tail = tail->next; //指向下一结点
		}
		//找到尾结点
		tail->next = NewNode;
	}
}

void SListPushFront(SLNode** pphead, SLDateType x)
{
	SLNode* NewNode = CreateNode(x); //获取新结点
	NewNode->next = *pphead;
	*pphead = NewNode; //修改头指针
}

void SListPopFront(SLNode** pphead)
{
	if (*pphead == NULL)
	{
		return;  //链表为空直接返回
	}
	else
	{
		SLNode* next = (*pphead)->next; //存储下一个结点地址
		free(*pphead);
		*pphead = next; //改变头指针指向,因此需要传二级指针
	}
}

void SListPopBack(SLNode** pphead)
{
	if (*pphead == NULL) //没有结点
	{
		return;
	}
	else if ((*pphead)->next == NULL)  //只有一个结点
	{
		free(*pphead);
		*pphead = NULL; //直接将结点释放掉,头指针改为NULL,因此需要传二级指针
	}
	else  //一个以上结点
	{
		SLNode* tail = *pphead;
		SLNode* prev = NULL;
		while (tail->next != NULL) //tail不为尾结点
		{
			prev = tail;
			tail = tail->next; //tail指向下一结点
		}
		//tail为尾结点,此时prev为尾结点的前一个结点
		free(tail); //释放掉尾结点
		tail = NULL;
		prev->next = NULL;
	}
}

SLNode* SListFind(SLNode* phead, SLDateType x)
{
	while (phead)
	{
		if (phead->date == x)
		{
			return phead; //找到了,返回结点指针
		}
		phead = phead->next; //向后查找
	}
	//没有找到,返回空指针
	return NULL;
}

void SListInsert(SLNode** pphead, SLNode* pos, SLDateType x)
{
	if (pos == NULL) //指定结点为空直接返回
	{
		return;
	}
	if (*pphead == pos) //pos为头结点指针,直接头插
	{
		SListPushFront(pphead, x);
	}
	else
	{
		SLNode* NewNode = CreateNode(x); //获取新结点
		SLNode* prev = *pphead;
		while (prev->next != pos) //prev不指向pos上一结点
		{
			prev = prev->next; //prev指向下一结点
		}
		//prev指向pos上一结点,插入新结点
		prev->next = NewNode;
		NewNode->next = pos;
	}
}



void SListErase(SLNode** pphead, SLNode* pos)
{
	if (pos == NULL) //指定结点为空直接返回
	{
		return;
	}
	if (*pphead == pos)
	{
		SListPopFront(pphead);
	}
	else
	{
		SLNode* prev = *pphead;
		while (prev->next != pos)
		{
			prev = prev->next;
		}
		prev->next = pos->next;
		free(pos);
	}
}

       Finally, we call each interface of the one-way linked list in the text.c file for testing, as follows:

//text.c文件,用于测试
#define _CRT_SECURE_NO_WARNINGS 1
#include "SList.h"
void SListText()
{
	SLNode* phead = NULL;
	printf("起始数据: \n");
	SListPrint(phead);
	//头插
	SListPushFront(&phead, 1);
	SListPushFront(&phead, 2);
	SListPushFront(&phead, 3);
	printf("\n头插入数据后: \n");
	SListPrint(phead);
	//尾插
	SListPushBack(&phead, 4);
	SListPushBack(&phead, 5);
	SListPushBack(&phead, 6);
	printf("\n尾插入数据后: \n");
	SListPrint(phead);
	//头删
	SListPopFront(&phead);
	printf("\n头删数据后: \n");
	SListPrint(phead);
	//尾删
	SListPopBack(&phead);
	printf("\n尾删数据后: \n");
	SListPrint(phead);
	//找出数据为5的结点并在其前面插入8
	SLNode* cur1 = SListFind(phead, 5);
	if (cur1)
	{
		SListInsert(&phead, cur1, 8);
	}
	printf("\n5前面插入数据后: \n");
	SListPrint(phead);
	//删除数据为1的结点
	SLNode* cur2 = SListFind(phead, 1);
	if (cur2)
	{
		SListErase(&phead, cur2);
	}
	printf("\n删除数据1的结点后: \n");
	SListPrint(phead);
}
int main()
{
	SListText();
	return 0;
}

        The following is the final effect of the test:


 The above is the whole content of this issue.

It's not easy to make, can you give it a thumbs up before leaving? qwq

Guess you like

Origin blog.csdn.net/m0_69909682/article/details/128798482