[Data Structure] C--Single Linked List (Basic Knowledge for Beginners)

I wrote a blog about the sequence table some time ago, http://t.csdn.cn/0gCRp

Sequence tables sometimes have some unavoidable disadvantages:

question:
1. Insertion and deletion in the middle / head, the time complexity is O(N)
2. To increase capacity, you need to apply for new space, copy data, and release old space. There will be a lot of consumption.
3. The increase in capacity is generally a 2 -fold increase, and there is bound to be a certain amount of space wasted. For example, the current capacity is 100 , and when it is full, it will be increased to 200. We continue to insert 5 data, and no data will be inserted later, so 95 data spaces will be wasted.
Looking for other solutions:
1. No capacity expansion
2. Apply for release on demand
3. Solve the problem that data needs to be moved for head/middle insertion and deletion (a continuous physical space)

                       1. The concept, structure and advantages and disadvantages of singly linked list

1.1 Concept

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

1.2 Structure of singly linked list

Singly linked list is a linear structure composed of a series of nodes, each node contains two fields: data field and pointer field .

The data field is used to store data, and the pointer field is used to store the pointer of the next node. The head node of the singly linked list points to the first node, and the pointer field of the last node is empty.

The structure of a node:

Logical structure: In order to facilitate the understanding of the image, it is imagined.

 Physical structure: In actual memory, the real appearance.

1.3 Advantages and disadvantages of single linked list

Singly linked list is a common data structure, it has the following advantages and disadvantages:

advantage:

  1.         High efficiency of insertion and deletion operations: Since the nodes of the singly linked list contain pointers to the next node, when inserting and deleting nodes, only the pointing of the pointer needs to be modified, and there is no need to move a large number of data elements, so the efficiency is high.
  2.         High space utilization: The nodes of the singly linked list only contain two parts, data and pointers, and there is no need to pre-allocate memory space, so the space utilization is relatively high.
  3.         Variable length: The length of the singly linked list can be dynamically increased or reduced according to the needs, and there is no need to pre-define the size of the array, which has good flexibility.

shortcoming:

  1.         Low random access efficiency: Since the nodes of the singly linked list only contain pointers to the next node, it is impossible to directly access the node before a certain node. It needs to traverse from the head node to the node, so the random access efficiency is low.
  2.         Waste of storage space: Since each node of the singly linked list needs to store the pointer of the next node, in the case of storing the same amount of data, the singly linked list requires more storage space.
  3.         Loss of link information: There is only a pointer to the next node in the singly linked list, and the previous node cannot be directly accessed. Therefore, when it is necessary to traverse the linked list in reverse or delete a node, it is necessary to save the pointer of the previous node, otherwise the operation will not be completed.

2. Implementation of single linked list

Each interface function of single linked list

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
typedef int SLTDataType;//这样做的目的是为了增加代码的可读性和可维护性,以及提高代码的可移植性,
//因为如果将来需要更改 SLTDataType 的类型,只需要在 typedef 语句中修改一处即可,
// 如果我们在程序的其他地方需要修改 SLTDataType 的类型,
//只需在 typedef 语句中修改 int 为其他类型即可,不需要修改其他代码。
//typedef int SLTADataType;

typedef struct SListNode //--single Linked List
{
	SLTDataType data;//成员变量
	struct SListNode* next;
}SLTNode;

void SLTPrint(SLTNode* phead);

//void SLPushFront(SLTNode* pphead,SLTDataType x);
void SLPushFront(SLTNode** pphead, SLTDataType x);//头部插入

//void SLPushBack(SLTNode* phead, SLTDataType x);
void SLPushBack(SLTNode** pphead, SLTDataType x);//尾部插入

void SLPopFront(SLTNode** pphead);//头部删除

void SLPopBack(SLTNode** pphead);//尾部删除

//单链表查找
SLTNode* STFind(SLTNode* phead, SLTDataType x);

//单链表pos之前插入
void SLInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x);
//单链表pos之后插入
void SLInsertAfter(SLTNode* pos, SLTDataType x);
//单链表pos位置删除
void SLErase(SLTNode** pphead, SLTNode* pos);
//单链表pos之后删除
void SLEraseAfter(SLTNode* phead);

2.1 Definition of nodes

English abbreviation:

The English of the singly linked list is: Single linked list -- abbreviated as SL

The English of the sequence table is: Sequence table -- abbreviated as Seq

The English of the node is: node

The main functions of typedef are: it is mainly used to improve the readability and maintainability of the code, so that the readability of the code will be better, because the name SLTDataType explains the type meaning of the variable x, and a more Concise, clearer aliases, which make code more readable and maintainable.

typedef int SLTDataType;
typedef struct SListNode //--single Linked List
{
	SLTDataType data;//成员变量
	struct SListNode* next;
}SLTNode;

Defines a structure of singly linked list nodes  SLTNode, which contains two member variables: an  data int variable named , and a   pointer to the next node named. SLTDataTypenext

2.2 Printing of linked list

What you need to know:

                               "The essence of pointer assignment is to copy the address of one pointer variable to another pointer variable"

int *p1, *p2; 

p1 = (int*)malloc(sizeof(int)); // 为p1分配内存
*p1 = 10; // 设置p1指向的内存的值为10

p2 = p1; // 将p1的地址赋值给p2

In the above code, both p1 and p2 are pointer variables. p1 is allocated memory and points to that memory. The line of code p2 = p1 copies the address of p1 to p2, so that p2 also points to the memory pointed to by p1.

Therefore, after the pointer assignment, the two pointer variables point to the same block of memory, and the value of accessing the memory through any pointer variable will be the same.

Pointer assignment does not copy the memory content pointed to by the pointer , but only copies the address value in the pointer variable .

drawing:

 Code:

//函数的作用是遍历单链表,并将每个节点的数据元素打印到屏幕上。
void SLTPrint(SLTNode* phead)//参数是一个指向 SLTNode 类型的指针 phead,表示单链表的头节点。
{
	SLTNode* cur = phead;//头结点存储的地址给cur指针。
	while (cur != NULL)//使用一个while循环对单链表进行遍历,循环条件为 cur 不为 NULL。
	{    //cur 可以表示当前正在处理的节点的地址,
		//通过访问 cur->data 和 cur->next 成员变量,可以获取当前节点的数据元素和下一个节点的地址
		
		printf("%d->", cur->data);
		cur = cur->next;//cur是存着当前结点的地址,cur->next是下一结点地址
	}
	printf("NULL\n");
}


2.3 Create a new node

SLTNode* BuyLTNode(SLTDataType x)//表示要创建的节点的数据元素。
//函数的作用是创建一个新的单链表节点,并将其初始化为包含数据元素 x 的节点。
{
	//SLTNode node;//这样是不行,处于不同的空间,出了作用域是会销毁的。

	SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));//使用malloc函数动态分配了一个大小为SLTNode的内存块,
	//并将其强制转换为 SLTNode 指针类型,即创建了一个新的节点。
	
	if (newnode == NULL)
	{
		perror("malloc fail");
		return NULL;
	}
	//需要注意的是,在使用 malloc 函数动态分配内存时,需要手动释放内存,否则会导致内存泄漏
	//因此,在创建单链表节点后,我们应该在适当的时候使用 free 函数释放节点所占用的内存
	
	newnode->data = x;
	newnode->next = NULL;

	return newnode;//返回的是一个结点的地址
}

What this function does is create a new singly linked list node and initialize it to contain the data element x. The specific implementation process is to dynamically allocate a memory block by using the malloc function, and then cast it to the SLTNode pointer type, that is, a new node is created. Then set the data member of the node to x, the next member to NULL, and finally return the pointer address of the new node.

It should be noted that when using the malloc function to dynamically allocate memory, the memory needs to be released manually, otherwise it will cause memory leaks. Therefore, after creating a singly linked list node, we should use the free function to release the memory occupied by the node at an appropriate time.

2.4 Single linked list tail plug

Error code 1:

Illustration:

void SLPushBack(SLTNode* phead, SLTDataType x)
{
    SLTNode* tail = phead;
    while (tail != NULL)
    {
          tail = tail->next;
    }

    SLTNode* newnode = BuyLTNode(x);
    tail = newnode;//This place is not linked
}

Reasons:
1. The linked list is not linked
2. The pointer variable is destroyed out of scope, and the memory leaks

 Add the knowledge of memory leaks:

If the free function is not used to release the memory space allocated by the malloc function, these memory spaces will always occupy system resources until the end of the program or the restart of the operating system.

When the program ends, the operating system will reclaim all the memory space used by the program, including the space not released by the free function . However, during the running of the program , these unreleased memory spaces will always occupy system resources , which may lead to the exhaustion of system memory resources , thus affecting the stability and performance of the system.

Therefore, in order to avoid memory leaks, developers need to explicitly use the free function in the program to release the memory space that is no longer used, so as to ensure the effective use of system resources.

Error code 2:

void SLPushFront(SLTNode** pphead, SLTDataType x);
void SLPushBack(SLTNode* phead, SLTDataType x);
void TestSList1() 
{
    SLTNode* plist = NULL;
    SLPushFront(&plist,1);
    SLPushFront(&plist,2);
    SLPushFront(&plist,3);
    SLPushFront(&plist,4);

    SLTPrint(plist);
    SLPushBack(plist, 5);//Here is a first-level pointer
    SLTPrint(plist);

}
void SLPushBack(SLTNode* phead, SLTDataType x)
{
    SLTNode* tail = phead;
    while (tail->next!= NULL)
    {
        tail = tail->next;
    }

    SLTNode* newnode = BuyLTNode(x);
    tail->next = newnode;
}

In this case, the head is plugged first and then the tail is plugged in. The first plug needs a second-level pointer every time, but the tail plug needs to pass the second-level pointer for the first time, and the second-level pointer can be passed later, but the parameters are uniform and cannot be written. For two identical functions, the first-level pointer is passed, and the second-level pointer is passed later.

So pass the secondary pointer consistently.

The prerequisite for this situation is that the linked list is not empty, and the next of the structure can be directly modified to save the address of the new node.

implement:

Error code 3:

drawing:

void SLPushFront(SLTNode** pphead, SLTDataType x);
void SLPushBack(SLTNode* phead, SLTDataType x);

void TestSList2()
{
    SLTNode* plist = NULL;
    SLPushBack(plist, 1);
    SLPushBack(plist, 2);
    SLTPrint(plist);
}

void SLPushBack(SLTNode* phead, SLTDataType x)
{
    SLTNode* newnode = BuyLTNode(x);


    if (phead == NULL)
    {
        phead = newnode;
    }
    else
    {
        SLTNode* tail = phead;
        while (tail->next != NULL)
        {
            tail = tail->next;
        }

        tail->next = newnode;
    }
}

output:

 Enumerating so many wrong cases and then going to the correct case:

void SLPushBack(SLTNode** pphead, SLTDataType x)
void TestSList2()
{
	SLTNode* plist = NULL;
	SLPushBack(&plist, 1);
	SLPushBack(&plist, 2);
	SLPushBack(&plist, 3);
	SLPushBack(&plist, 4);
	SLTPrint(plist);
}
//要让新节点和tail链接起来,一定要去改tail的next
void SLPushBack(SLTNode** pphead, SLTDataType x)//尾插的本质是让上一个结点链接下一个结点
{
	SLTNode* newnode = BuyLTNode(x);
	// 1、空链表
	// 2、非空链表
	if (*pphead == NULL)
	{
		*pphead = newnode;
	}
	else
	{
		SLTNode* tail = *pphead;
		while (tail->next != NULL)
		{
			tail = tail->next;
		}
		tail->next = newnode;
	}

}

 Code execution:

2.5 single-link table plug

Head plug ideas:

The idea of ​​inserting the second node:

The code for plugging in the header can be written like this

void SLPushFront(SLTNode** pphead, SLTDataType x)
{
	SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));//使用malloc函数动态分配了一个大小为SLTNode的内存块,
	//并将其强制转换为 SLTNode 指针类型,即创建了一个新的节点。

	if (newnode == NULL)
	{
		perror("malloc fail");
		return NULL;
	}
	//需要注意的是,在使用 malloc 函数动态分配内存时,需要手动释放内存,否则会导致内存泄漏
	//因此,在创建单链表节点后,我们应该在适当的时候使用 free 函数释放节点所占用的内存

	newnode->data = x;
	newnode->next = NULL;

	//return newnode;//返回的是一个结点的地址
	newnode->next = *pphead;
	*pphead = newnode;//将头节点*pphead 更新为新节点的地址,以使新节点成为新的头节点。
}

However, in order to avoid repeated writing of programs for creating new nodes, you can use the above BuyLTNode(x) function to define new nodes to achieve the purpose of abbreviation, or the function that can be plugged in can also be written like this:

void SLPushFront(SLTNode** pphead, SLTDataType x)
{
	SLTNode* newnode = BuyLTNode(x);

	newnode->next = *pphead;
	*pphead = newnode;
}

In  SLPushFront the function, we first call  BuyLTNode(x) the function to create a new node, then point  next the pointer of the new node to the head node of the current linked list, and then point the pointer of the head of the linked list to the new node, thus completing the operation of inserting a new node at the head of the linked list.

Code execution:

2.6 Singly linked list tail deletion 

Error case:

 method 1:

Method 2:

 Code example:

void SLPopBack(SLTNode** pphead)
{
	//没有节点(空链表)
	//暴力检查
	assert(*pphead);

	//温柔检查
	if (*pphead == NULL)
	{
		return;
	}
	//一个节点
	if ((*pphead)->next == NULL)
	{
		free(*pphead);
		*pphead = NULL;
	}//多个节点
	else
	{
		SLTNode* prev = NULL;
		SLTNode* tail = *pphead;
		//找尾
		//方法一
		/*while (tail->next)
		{
			prev = tail;
			tail = tail->next;
		}
		free(tail);
		prev->next = NULL;*/

		//方法二
		SLTNode* tail = *pphead;
		while (tail->next->next)
		{
			tail = tail->next;
		}

		free(tail->next);
		tail->next = NULL;
	}
}

implement: 

 

 2.7 Singly-linked header deletion

Ideas:

void SPopFront(SLTNode** pphead)
{
	//没有节点
	//暴力检查
	assert(*pphead);
	//温柔检查
	if (*pphead == NULL)
		return;// 一个节点
	if ((*pphead)->next == NULL)
	{
		free(*pphead);
		*pphead = NULL;
	}
	//多个节点
	else
	{
		SLTNode* del = *pphead;//相当于一个标记,删掉的标记
		//写法一
		//*pphead = del->next;
		//写法二 
		*pphead = (*pphead)->next;
		free(del);
	}



}

implement:

2.8 Singly linked list to find/modify a value

Use tail insertion to insert 1, 2, 3, and 4 into the linked list in order, then find the element with a value of 3 in the linked list, and change it to 30 (you can access the node whose data is 3 by defining the pointer of the structure type point, and modify directly through pos->next=30)

Note: You can directly define a test function in the main function to modify the value, and there is no need to define a new function.

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

implement:

2.9 Singly linked list is inserted before pos

Ideas:

void SLInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{
	assert(pphead);//&plist
	assert(pos);
	//assert(*pphead);
	//一个节点
	if (*pphead == NULL)
	{
		SLPushFront(pphead, x);
	}
	else//多个节点
	{
		SLTNode* prev = *pphead;
		while (prev->next != pos)
		{
			prev = prev->next;
		}
		SLTNode* newnode = BuyLTNode(x);
		prev->next = newnode;
		newnode->next = pos;
	}
}

 implement:

2.10 The singly linked list is inserted after pos

Ideas:

void SLInsertAfter(SLTNode* pos, SLTDataType x)
{
	assert(pos);

	SLTNode* newnode = BuyLTNode(x);
	newnode->next = pos->next;
	pos->next = newnode;
}

2.11 Singly linked list deletes the value of pos

Ideas: 

void SLErase(SLTNode** pphead, SLTNode* pos)
{
	assert(pphead);
	assert(pos);

	if (pos == *pphead)
	{
		SLPopFront(pphead);
	}
	else
	{
		SLTNode* prev = *pphead;
		while (prev->next!=pos)
		{
			prev = prev->next;
		}
		prev->next = pos->next;
		free(pos);
	}
}

2.12 Singly linked list deletes the value after pos

Ideas:

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

 

3. Case function realization

3.1 Test

void TestSList1() 
{
	SLTNode* plist = NULL;
	SLPushFront(&plist,1);
	SLPushFront(&plist,2);
	SLPushFront(&plist,3);
	SLPushFront(&plist,4);

	SLTPrint(plist);
	SLPushBack(plist, 5);
	SLTPrint(plist);

}

3.2 head plug

void TestSList2()
{
	SLTNode* plist = NULL;
	SLPushFront(&plist, 1);
	SLPushFront(&plist, 2);
	SLPushFront(&plist, 3);
	SLPushFront(&plist, 4);

	SLTPrint(plist);
	SLPushBack(&plist, 5);

	SLTPrint(plist);
}

3.3 tail plug

void TestSList3()
{
	SLTNode* plist = NULL;
	SLPushBack(&plist, 1);
	SLTPrint(plist);
	SLPushBack(&plist, 2);
	SLTPrint(plist);
	SLPushBack(&plist, 3);
	SLTPrint(plist);
	SLPushBack(&plist, 4);
	SLTPrint(plist);
}

3.4 header deletion

void TestSList4()
{
	SLTNode* plist = NULL;
	SLPushBack(&plist, 1);
	SLPushBack(&plist, 2);
	SLPushBack(&plist, 3);
	SLPushBack(&plist, 4);
	SLTPrint(plist);
	//头删

	SLPopFront(&plist);
	SLTPrint(plist);
	
	SLPopFront(&plist);
	SLTPrint(plist);
	
	SLPopFront(&plist);
	SLTPrint(plist);

	SLPopFront(&plist);
	SLTPrint(plist);
}

3.5 tail deletion

void TestSList5()
{
	SLTNode* plist = NULL;
	SLPushBack(&plist, 1);
	SLPushBack(&plist, 2);
	SLPushBack(&plist, 3);
	SLPushBack(&plist, 4);
	SLTPrint(plist);

	SLPopBack(&plist);
	SLTPrint(plist);

	SLPopBack(&plist);
	SLTPrint(plist);

	SLPopBack(&plist);
	SLTPrint(plist);
}

3.6 Find/modify a value

void TestSList6()
{
	SLTNode* plist = NULL;
	SLPushBack(&plist, 1);
	SLPushBack(&plist, 2);
	SLPushBack(&plist, 3);
	SLPushBack(&plist, 4);
	SLTPrint(plist);

	SLTNode* pos = STFind(plist,3);
	if (pos)
		pos->data = 30;

	SLTPrint(plist);
}

3.7 insert before pos 

void TestSList7()//pos之前插入 
{
	SLTNode* plist = NULL;
	SLPushBack(&plist, 1);
	SLPushBack(&plist, 2);
	SLPushBack(&plist, 3);
	SLPushBack(&plist, 4);
	SLTPrint(plist);

	SLTNode* pos = STFind(plist,3);
	if (pos)
	{
		SLInsert(&plist, pos, 30);
	}
	SLTPrint(plist);
}

3.8 insert after pos 

void TestSList8()//pos之后插入 
{
	SLTNode* plist = NULL;
	SLPushBack(&plist, 1);
	SLPushBack(&plist, 2);
	SLPushBack(&plist, 3);
	SLPushBack(&plist, 4);
	SLTPrint(plist);

	SLTNode* pos = STFind(plist, 3);
	if (pos)
	{
		SLInsertAfter(pos, 50);
	}
	SLTPrint(plist);
}

3.9 Delete the value of pos position

void TestSList9() {
	SLTNode* plist = NULL;
	SLPushBack(&plist, 1);
	SLPushBack(&plist, 2);
	SLPushBack(&plist, 3);
	SLPushBack(&plist, 4);
	SLTPrint(plist);

	SLTNode* pos = STFind(plist, 3);
	if (pos)
	{
		SLErase(&plist,pos);
	}
	SLTPrint(plist);


}

3.10 Delete the value after pos

void TestSList10() {
	SLTNode* plist = NULL;
	SLPushBack(&plist, 1);
	SLPushBack(&plist, 2);
	SLPushBack(&plist, 3);
	SLPushBack(&plist, 4);
	SLPushBack(&plist, 5);
	SLTPrint(plist);

	SLTNode* pos = STFind(plist, 2);
	if (pos)
	{
		SLEraseAfter(pos);
	}
	SLTPrint(plist);
}

At the end of this chapter, if there are any mistakes, you are welcome to point out, thank you for your visit.

Guess you like

Origin blog.csdn.net/weixin_65186652/article/details/131777464