Singly linked list (data structure) (C language)

This refers specifically to the non-sentry bit one-way non-circular linked list

Table of contents

background

concept

Implementation of singly linked list

prospect hint

Singly linked list structure definition

Printing of singly linked list

About why phead is not asserted

About the logical structure and physical structure of singly linked list

Tail Insertion of Singly Linked List

About why the secondary pointer is used

About the nature of tail plug

Explanation of the whole process of tail finding

About why there is no need to pass the secondary pointer to print the singly linked list

Dynamic application node of singly linked list

Singly linked list head plug

Tail delete of singly linked list

Delete the head of a singly linked list

linked list lookup

The single linked list inserts x before the pos position (it can also be understood as inserting at the pos position)

The single linked list deletes the value before the pos position (it can also be understood as deleting the value of the pos position)

Singly linked list inserts x after pos position 

Singly linked list deletes the value after the pos position

About how to insert/delete before pos without passing the header pointer (ingenious)

Singly linked list destruction

The total code (if you want to see the result directly, you can see here)


background

In the last article we learned about sequence tables.

However, the sequential table requires continuous physical space, which leads to the following disadvantages:

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 waste. 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.
In order to better solve the above problems, we extended the linked list.

concept

The linked list is a non-sequential and non-sequential storage structure in the physical storage structure, and the logical order of the data elements is realized through the link order of the pointers . One piece of data in the linked list is stored in one memory block. The linked list is composed of a series of nodes (each element in the linked list is called a node), and the nodes can be dynamically generated at runtime. Each node consists of two parts: one is the data , and the other is the pointer .
Singly linked list is a chain access data structure, which uses a group of storage units with arbitrary addresses (this group of storage units can be either continuous or discontinuous) to store the data elements in the linear list. The data in the linked list is represented by nodes ( each node includes two parts: one is the data field for storing data elements, and the other is the pointer field for storing the address of the next node. ), each node Composition: element + pointer , the element is the storage , and the pointer is the address .
The storage address of each node in the singly linked list is stored in the next field of its predecessor node, and the start node has no predecessor, so the head pointer head is set to point to the start node. The linked list is uniquely , and the singly linked list can be named by the name of the head pointer. The terminal node has no successor, so the pointer field of the terminal node is empty, that is, NULL.

Implementation of singly linked list

prospect hint

SList.h   is used for the referenced header file, the definition of the singly linked list, and the declaration of the function.

SList.c   is used for function definition.

Test.c     is used for the test of linked list function.

Singly linked list structure definition

under SList.h

#pragma once//使同一个文件不会被包含(include)多次,不必担心宏名冲突

//先将可能使用到的头文件写上
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>

typedef int SLTDataType;//假设结点的数据域类型为 int

//单链表的结构体定义
typedef struct SListNode
{
	SLTDataType data;//结点的数据域,用来存储数据元素
	struct SListNode* next;//结点的指针域,用来存储下一个结点地址的指针域
    //next的意思就是下一个结点的指针,上一个结点存的是下一个结点的地址
    //每个结点都是结构体指针类型
	//有些人会把上一行代码写成SListNode* next;这是不对的,因为C语言中 
    //struct SListNode 整体才是一个类型(但C++可以)
	//或者写成SLTNode* next;这也是错的,因为编译器的查找规则是从上忘下找
}SLTNode;

Printing of singly linked list

To understand the singly linked list, first of all, we first write a print of the singly linked list.

under SList.h

//链表的打印——助于理解链表
void SLTPrint(SLTNode* phead);
under SList.c
#include "SList.h"//别忘了

//链表的打印
void SLTPrint(SLTNode* phead)
{
	//assert(phead);这里并不需要断言phead不为空
	//为什么这里不需要断言phead?
	//空链表可以打印,即phead==NULL可以打印,直接断言就不合适了
	//那空顺序表也可以打印,那它为什么就要断言呢?
	//因为phead是指向第一个存有数据的结点的
	//而顺序表的ps是指向一个结构体
	SLTNode* cur = phead;//将phead赋值给cur,所以cur也指向第一个结点
	while (cur != NULL)//或while(cur)
	{
		printf("%d->", cur->data);//打印的时候加了个箭头更方便理解
		cur = cur->next;//next是下一个结点的地址
		//++cur/cur++;这种是不行的,指针加加,加到的是连续的下一个位置
		//链表的每一个结点都是单独malloc出来的,我们不能保证结点之间的地址是连续的
	}
	printf("NULL\n");
}

About why phead is not asserted

About the logical structure and physical structure of singly linked list

                     

                     

                   

                     

Before printing, we need to have data

Tail Insertion of Singly Linked List

under SList.h

// 单链表尾插
void SLTPushBack(SLTNode** pphead, SLTDataType x);
//用二级指针,解释看下文,x为要插入的数据

About why the secondary pointer is used

under SList.c

// 单链表尾插
void SLTPushBack(SLTNode** pphead, SLTDataType x)
{
	assert(pphead);//pphead是plist的地址,不能为空
    //注意区分几个断言的判断,plist有可能是空,pphead一定不能为空

	SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));//先创建个结点
	if (newnode == NULL)//如果malloc失败
	{
		perror("malloc fail");
		return;
	}
	//如果malloc成功
	newnode->data = x;//插入的数据
	newnode->next = NULL;//初始化为空
	//找尾(尾插之前先找到尾)
	if (*pphead == NULL)//若链表为空
	{
		*pphead = newnode;
	}
	else//若链表不为空
	{
		SLTNode* tail = *pphead;
		while (tail->next != NULL)//对于不为空的链表:尾插的本质
                                  //是原尾结点要存新尾结点的地址
		{
			tail = tail->next;
		}
		tail->next = newnode;
		/*有些同学会写成:
		while (tail != NULL)
		{
			tail = tail->next;
		}
		tail = newnode;*/
	}
}

About the nature of tail plug

 

 and

Explanation of the whole process of tail finding

 

 ↓

under Test.c

#include "SList.h"//别忘了

//用于函数功能的测试
void TestSList1()
{
	SLTNode* plist = NULL;
	SLTPushBack(&plist, 1);
	SLTPushBack(&plist, 2);
	SLTPushBack(&plist, 3);
	SLTPushBack(&plist, 4);

	SLTPrint(plist);
}

int main()
{
	TestSList1();
	return 0;
}

About why there is no need to pass the secondary pointer to print the singly linked list

Because printing the singly linked list doesn't change the pointer. If you want to change the passed pointer (actual parameter), then you need to pass the address of the actual parameter, and don't pass it if it doesn't change.

Dynamic application node of singly linked list

When writing the head plug, we found that both the tail plug and the head plug have to dynamically apply for a node, so we can first write a function to reuse.

under SList.c

// 动态申请一个结点
SLTNode* BuySLTNode(SLTDataType x)
{
	//同样不需要断言
	SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));//先创建个结点
	if (newnode == NULL)//如果malloc失败
	{
		perror("malloc fail");
		return NULL;
	}
	//如果malloc成功
	newnode->data = x;//插入的数据
	newnode->next = NULL;//初始化为空

	return newnode;//返回newnode
}

// 单链表尾插
void SLTPushBack(SLTNode** pphead, SLTDataType x)
{
    assert(pphead);//pphead是plist的地址,不能为空

	SLTNode* newnode = BuySLTNode(x);
	//找尾(尾插之前先找到尾)
	if (*pphead == NULL)//若链表为空
	{
		*pphead = newnode;
	}
	else//若链表不为空
	{
		SLTNode* tail = *pphead;
		while (tail->next != NULL)
        //对于不为空的链表:尾插的本质是原尾结点要存新尾结点的地址
		{
			tail = tail->next;
		}
		tail->next = newnode;
		/*有些同学会写成:
		while (tail != NULL)
		{
			tail = tail->next;
		}
		tail = newnode;*/
	}
}

Singly linked list head plug

 under SList.h

// 单链表的头插
void SLTPushFront(SLTNode** pphead, SLTDataType x);

under SList.c

// 单链表的头插
void SLTPushFront(SLTNode** pphead, SLTDataType x)
{
	//发现plist不管是否为空,头插的方法都一样
	SLTNode* newnode = BuySLTNode(x);
	newnode->next = *pphead;
	*pphead = newnode;
}

under Test.c

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

	SLTPrint(plist);
}

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

	SLTPrint(plist);
}


int main()
{
	TestSList1();
	TestSList2();
	return 0;
}

Tail delete of singly linked list

Does tail deletion require a secondary pointer? want!

under SList.h

// 单链表的尾删
void SLTPopBack(SLTNode** pphead);

under Test.c

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

	SLTPopBack(&plist);
	SLTPrint(plist);
}


int main()
{
	TestSList2();
	return 0;
}

under SList.c

Some people start by writing:

// 单链表的尾删
void SLTPopBack(SLTNode** pphead)
{
    assert(pphead);//pphead是plist的地址,不能为空

	SLTNode* tail = *pphead;
	while (tail->next != NULL)
	{
		tail = tail->next;
	}
	
	free(tail);
	tail = NULL;
}

turn out:

Random values ​​appear --> most likely because of wild pointers.

 

 why?

Here are two methods for the changed SList.c

Method one:

// 单链表的尾删
void SLTPopBack(SLTNode** pphead)
{
	assert(pphead);//pphead是plist的地址,不能为空

    //法一:
	SLTNode* prev=NULL;
	SLTNode* tail = *pphead;
	while (tail->next != NULL)
	{ 
		prev = tail;
		tail = tail->next;
	}
	
	free(tail);
	tail = NULL;
	
	prev->next = NULL;
}

Law 2:

// 单链表的尾删
void SLTPopBack(SLTNode** pphead)
{
	assert(pphead);//pphead是plist的地址,不能为空

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

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

 But let's test a few more sets

under Test.c

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

    //尾删四个数据
	SLTPopBack(&plist);
	SLTPrint(plist);
	SLTPopBack(&plist);
	SLTPrint(plist);
	SLTPopBack(&plist);
	SLTPrint(plist);
	SLTPopBack(&plist);
	SLTPrint(plist);
}


int main()
{
	TestSList2();
	return 0;
}

result:

There is only one left in the end of the two methods!

The reason is that the case of only one node or no nodes is not considered.

Here is SList.c after changing again

// 单链表的尾删
void SLTPopBack(SLTNode** pphead)
{
	assert(pphead);//pphead是plist的地址,不能为空

	//检查有无结点
	assert(*pphead != NULL);
	//1.只有一个结点
	if ((*pphead)->next == NULL)
	{
		free(*pphead);
		*pphead = NULL;
	}
	else
	{
		//2.有多个结点
		/*//法一:
		SLTNode* prev=NULL;
		SLTNode* tail = *pphead;
		while (tail->next != NULL)
		{
			prev = tail;
			tail = tail->next;
		}

		free(tail);
		tail = NULL;

		prev->next = NULL;*/
		//法二:
		SLTNode* tail = *pphead;
		while (tail->next->next != NULL)
		{
			tail = tail->next;
		}

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

only one node

no nodes 

Delete the head of a singly linked list

under SList.h

// 单链表头删
void SLTPopFront(SLTNode** pphead);

under SList.c

// 单链表头删
void SLTPopFront(SLTNode** pphead)
{
	//检查有无结点
	assert(*pphead != NULL);

	SLTNode* first = *pphead;
	*pphead = first->next;
	free(first);
	first = NULL;
}

under Test.c

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

	SLTPopFront(&plist);
	SLTPrint(plist);
	SLTPopFront(&plist);
	SLTPrint(plist);
	SLTPopFront(&plist);
	SLTPrint(plist);
	SLTPopFront(&plist);
	SLTPrint(plist);
}

int main()
{
	TestSList3();
	return 0;
}

linked list lookup

under SList.h

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

under SList.c

// 单链表查找
SLTNode* SLTFind(SLTNode* phead, SLTDataType x)
{
	SLTNode* cur = phead;//用cur去遍历,不用phead
	while (cur)//找x
	{
		if (cur->data == x)//如果找到了
		{
			return cur;
		}
		cur = cur->next;
	}
	return NULL;//如果找不到
}

under Test.c

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

	//将寻找和修改结合
	//eg:值为2的结点*2
	SLTNode* ret = SLTFind(plist, 2);
	ret->data *= 2;
	SLTPrint(plist);
}


int main()
{
	TestSList4();
	return 0;
}

The single linked list inserts x before the pos position (it can also be understood as inserting at the pos position)

under SList.h

//单链表在pos位置之前插入x(效率较低)(得传头指针)(也可以理解为在pos位置插入)
void SLTInsertBefore(SLTNode** pphead, SLTNode* pos, SLTDataType x);

under SList.c

//单链表在pos位置之前插入x(也可以理解为在pos位置插入)
void SLTInsertBefore(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{
	assert(pphead);//pphead是plist的地址,不能为空

	assert(pos);//默认pos一定会找到
	if (pos == *pphead)//如果pos在第一个位置——那就是头插
	{
		SLTPushFront(pphead, x);
	}
	else//如果pos不是第一个位置
	{
		//找到pos的前一个位置
		SLTNode* prev = *pphead;
		while (prev->next != pos)
		{
			prev = prev->next;
		}
		SLTNode* newnode = BuySLTNode(x);
		prev->next = newnode;
		newnode->next = pos;
	}
}

under Test.c

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

	//寻找值为2的结点
	SLTNode* ret = SLTFind(plist, 2);
	SLTInsertBefore(&plist, ret, 20);//在该结点前插入值为20的结点
	SLTPrint(plist);

}

int main()
{
	TestSList5();
	return 0;
}

The single linked list deletes the value before the pos position (it can also be understood as deleting the value of the pos position)

under SList.h

// 单链表删除pos位置之前的值(效率较低)(得传头指针)(也可以理解为删除pos位置的值)
void SLTEraseBefore(SLTNode** pphead, SLTNode* pos);

under SList.c

// 单链表删除pos位置之前的值(也可以理解为删除pos位置的值)
void SLTEraseBefore(SLTNode** pphead, SLTNode* pos)
{
	assert(pphead);
	assert(pos);

	if (*pphead == pos)//如果pos在第一个位置
	{
		SLTPopFront(pphead);//头删
	}
	else//如果不在第一个位置
	{
		SLTNode* prev = *pphead;
		while (prev->next != pos)
		{
			prev = prev->next;
		}
		prev->next = pos->next;
		free(pos);
		//pos = NULL;形参的改变不影响实参,加不加这句话都可以
	}
}

under Test.c

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

	//寻找值为2的结点
	SLTNode* ret = SLTFind(plist, 2);
	SLTEraseBefore(&plist, ret);
	ret = NULL;//一般在这里置空
	SLTPrint(plist);
}

int main()
{
	TestSList6();
	return 0;
}

Singly linked list inserts x after pos position 

under SList.h

// 单链表在pos位置之后插入x,单链表比较适合这种
void SLTInsertAfter(SLTNode* pos, SLTDataType x);

under SList.c

Some people will write like this:

//单链表在pos位置之后插入x
void SLTInsertAfter(SLTNode* pos, SLTDataType x)
{
	assert(pos);
	SLTNode* newnode = BuySLTNode(x);
	pos->next = newnode;
	newnode->next=pos->next;
}

as a result of:

 So the orange and purple two steps should swap places

The changed SList.c

//单链表在pos位置之后插入x
void SLTInsertAfter(SLTNode* pos, SLTDataType x)
{
	assert(pos);
	SLTNode* newnode = BuySLTNode(x);
	newnode->next = pos->next;
	pos->next = newnode;
}

under Test.c

TestSList7()
{
	SLTNode* plist = NULL;
	SLTPushBack(&plist, 1);
	SLTPushBack(&plist, 2);
	SLTPushBack(&plist, 3);
	SLTPushBack(&plist, 4);
	SLTPrint(plist);

	SLTNode* ret = SLTFind(plist, 2);
	SLTInsertAfter(ret, 20);
	SLTPrint(plist);
}

int main()
{
	TestSList7();
	return 0;
}

Singly linked list deletes the value after the pos position

under SList.h

// 单链表删除pos位置之后的值,单链表比较适合这种
void SLTEraseAfter(SLTNode* pos);

under SList.c

// 单链表删除pos位置之后的值
void SLTEraseAfter(SLTNode* pos)
{
	assert(pos);
	assert(pos->next);

	SLTNode* del = pos->next;//保存要删除的结点
	pos->next = pos->next->next;//或者写成pos->next=del->next;
	free(del);
	del = NULL;
}

under Test.c

TestSList8()
{
	SLTNode* plist = NULL;
	SLTPushBack(&plist, 1);
	SLTPushBack(&plist, 2);
	SLTPushBack(&plist, 3);
	SLTPushBack(&plist, 4);
	SLTPrint(plist);

	SLTNode* ret = SLTFind(plist, 2);
	SLTEraseAfter(ret);
	SLTPrint(plist);
}

int main()
{
	TestSList8();
	return 0;
}

About how to insert/delete before pos without passing the header pointer (ingenious)

Insertion : first use the singly linked list to insert the function of x after the position of pos, and then exchange the values ​​of pos and pos->next.

under SList.h

// 不传头指针,在pos前插入x(也可以理解为在pos位置插入)
void SLTInsertBefore1(SLTNode* pos, SLTDataType x);

under SList.c

// 不传头指针,在pos前插入x(也可以理解为在pos位置插入)
void SLTInsertBefore1(SLTNode* pos, SLTDataType x)
{
	assert(pos);

	//调用单链表在pos位置之后插入x的函数
	SLTInsertAfter(pos, x);
	
	//交换pos和pos->next的值
	SLTDataType temp;
	temp = pos->data;
	pos->data = pos->next->data;
	pos->next->data = temp;
}

under Test.c

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

	SLTNode* ret = SLTFind(plist, 2);
	SLTInsertBefore1(ret,20);
	SLTPrint(plist);
}

int main()
{
	TestSList9();
	return 0;
}

Delete : first assign the value of pos->next to pos, and then use the single linked list to delete the function after the position of pos. ( However, this method cannot be tail-deleted )

under SList.h

// 不传头指针,删除pos位置之前的值(也可以理解为删除pos位置的值)
void SLTEraseBefore1(SLTNode* pos);

under SList.c

// 不传头指针,删除pos位置之前的值(也可以理解为删除pos位置的值)
void SLTEraseBefore1(SLTNode* pos)
{
	assert(pos);
	assert(pos->next);//不能尾删
	
	SLTNode* del = pos->next;
	pos->data = pos->next->data;
	pos->next = pos->next->next;
	free(del);
	del = NULL;
}

under Test.c

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

	SLTNode* ret = SLTFind(plist, 2);
	SLTEraseBefore1(ret);
	SLTPrint(plist);
}

int main()
{
	TestSList10();
	return 0;
}

Singly linked list destruction

Method 1 (do not pass the secondary pointer):

under SList.h

// 单链表的销毁,不传二级
void SLTDestroy(SLTNode* phead);

under SList.c

// 单链表的销毁
void SLTDestroy(SLTNode* phead)
{
	SLTNode* cur = phead;
	/*//有些人一开始会这样写
	while (cur)
	{
		//free不是销毁这个指针指向的内存,而是将指针指向的内存还给操作系统
		free(cur);//cur依旧指向free之前的地址
		cur = cur->next;
	}*/

	while (cur)
	{
		SLTNode* tmp = cur->next;
		free(cur);
		cur = tmp;
	}
}

under Test.c

TestSList11()
{
	SLTNode* plist = NULL;
	SLTPushBack(&plist, 1);
	SLTPushBack(&plist, 2);
	SLTPushBack(&plist, 3);
	SLTPushBack(&plist, 4);
	SLTPrint(plist);

	SLTDestroy(plist);
	plist = NULL;
}


int main()
{
	TestSList11();
	return 0;
}

Method 2 (passing the secondary pointer):

under SList.h

// 单链表的销毁,传二级
void SLTDestroy1(SLTNode** pphead);

under SList.c

// 单链表的销毁,传二级
void SLTDestroy1(SLTNode** pphead)
{
	assert(pphead);
	SLTNode* cur = *pphead;
	while (cur)
	{
		SLTNode* tmp = cur->next;
		free(cur);
		cur = tmp;
	}
	*pphead = NULL;
}

under Test.c

TestSList12()
{
	SLTNode* plist = NULL;
	SLTPushBack(&plist, 1);
	SLTPushBack(&plist, 2);
	SLTPushBack(&plist, 3);
	SLTPushBack(&plist, 4);
	SLTPrint(plist);

	SLTDestroy1(&plist);
	SLTPrint(plist);
}

int main()
{
	TestSList12();
	return 0;
}

The total code (if you want to see the result directly, you can see here)

under SList.h

#pragma once//使同一个文件不会被包含(include)多次,不必担心宏名冲突

//先将可能使用到的头文件写上
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>

typedef int SLTDataType;//假设结点的数据域类型为 int

// 单链表的结构体定义
//		↓结点  单链表 Singly Linked List
typedef struct SListNode
{
	SLTDataType data;//结点的数据域,用来存储数据元素
	struct SListNode* next;//结点的指针域,用来存储下一个结点地址的指针域
	//next的意思就是下一个结点的指针,上一个结点存的是下一个结点的地址
	//每个结点都是结构体指针类型
	//有些人会把上一行代码写成SListNode* next;
    //这是不对的,因为C语言中 struct SListNode 整体才是一个类型(但C++可以)
	//或者写成SLTNode* next;这也是不对的,因为编译器的查找规则是从上忘下找
}SLTNode;


// 链表的打印——助于理解链表
void SLTPrint(SLTNode* phead);

// 单链表尾插
void SLTPushBack(SLTNode** pphead, SLTDataType x);//用二级指针,x为要插入的数据

// 单链表的头插
void SLTPushFront(SLTNode** pphead, SLTDataType x);

// 单链表的尾删
void SLTPopBack(SLTNode** pphead);

// 单链表头删
void SLTPopFront(SLTNode** pphead);

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

// 单链表在pos位置之前插入x(效率较低)(得传头指针)(也可以理解为在pos位置插入)
void SLTInsertBefore(SLTNode** pphead, SLTNode* pos, SLTDataType x);

// 单链表删除pos位置之前的值(效率较低)(得传头指针)(也可以理解为删除pos位置的值)
void SLTEraseBefore(SLTNode** pphead, SLTNode* pos);

// 单链表在pos位置之后插入x,单链表比较适合这种
void SLTInsertAfter(SLTNode* pos, SLTDataType x);

// 单链表删除pos位置之后的值,单链表比较适合这种
void SLTEraseAfter(SLTNode* pos);

// 不传头指针,在pos前插入x(也可以理解为在pos位置插入)
void SLTInsertBefore1(SLTNode* pos, SLTDataType x);

// 不传头指针,删除pos位置之前的值(也可以理解为删除pos位置的值)
void SLTEraseBefore1(SLTNode* pos);

// 单链表的销毁,不传二级
void SLTDestroy(SLTNode* phead);

// 单链表的销毁,传二级
void SLTDestroy1(SLTNode** pphead);

under SList.c

#include"SList.h"//别忘了

//链表的打印
void SLTPrint(SLTNode* phead)
{
	//assert(phead);这里并不需要断言phead不为空
	//为什么这里不需要断言?
	//空链表可以打印,即phead==NULL可以打印,直接断言就不合适了
	//那空顺序表也可以打印,那它为什么就要断言呢?
	//因为phead是指向第一个存有数据的结点的
	//而顺序表的ps是指向一个结构体
	SLTNode* cur = phead;//将phead赋值给cur,所以cur也指向第一个结点
	while (cur != NULL)//或while(cur)
	{
		printf("%d->", cur->data);//打印的时候加了个箭头更方便理解
		cur = cur->next;//next是下一个结点的地址
		//++cur/cur++;这种是不行的,指针加加,加到的是连续的下一个位置
		//链表的每一个结点都是单独malloc出来的,我们不能保证结点之间的地址是连续的
	}
	printf("NULL\n");
}

// 动态申请一个结点
SLTNode* BuySLTNode(SLTDataType x)
{
	//同样不需要断言
	SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));//先创建个结点
	if (newnode == NULL)//如果malloc失败
	{
		perror("malloc fail");
		return NULL;
	}
	//如果malloc成功
	newnode->data = x;//插入的数据
	newnode->next = NULL;//初始化为空

	return newnode;//返回newnode
}

// 单链表尾插
void SLTPushBack(SLTNode** pphead, SLTDataType x)
{
	assert(pphead);//pphead是plist的地址,不能为空
	//注意区分几个断言的判断,plist有可能是空,pphead一定不能为空

	SLTNode* newnode = BuySLTNode(x);
	//找尾(尾插之前先找到尾)
	if (*pphead == NULL)//若链表为空
	{
		*pphead = newnode;
	}
	else//若链表不为空
	{
		SLTNode* tail = *pphead;
		while (tail->next != NULL)
        //对于不为空的链表:尾插的本质是原尾结点要存新尾结点的地址
		{
			tail = tail->next;
		}
		tail->next = newnode;
		/*有些同学会写成:
		while (tail != NULL)
		{
			tail = tail->next;
		}
		tail = newnode;*/
	}
}


// 单链表的头插
void SLTPushFront(SLTNode** pphead, SLTDataType x)
{
	assert(pphead);//pphead是plist的地址,不能为空

	//发现plist不管是否为空,头插的方法都一样
	SLTNode* newnode = BuySLTNode(x);
	newnode->next = *pphead;
	*pphead = newnode;
}

// 单链表的尾删
void SLTPopBack(SLTNode** pphead)
{
	assert(pphead);//pphead是plist的地址,不能为空

	//检查有无结点
	assert(*pphead != NULL);//或者写成assert(*pphead);
	//1.只有一个结点
	if ((*pphead)->next == NULL)
	{
		free(*pphead);
		*pphead = NULL;
	}
	else
	{
		//2.有多个结点
		/*//法一:
		SLTNode* prev=NULL;
		SLTNode* tail = *pphead;
		while (tail->next != NULL)
		{
			prev = tail;
			tail = tail->next;
		}

		free(tail);
		tail = NULL;

		prev->next = NULL;*/
		//法二:
		SLTNode* tail = *pphead;
		while (tail->next->next != NULL)
		{
			tail = tail->next;
		}

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

// 单链表头删
void SLTPopFront(SLTNode** pphead)
{
	assert(pphead);//pphead是plist的地址,不能为空

	//检查有无结点
	assert(*pphead != NULL);

	SLTNode* first = *pphead;
	*pphead = first->next;
	free(first);
	first = NULL;
}

// 单链表查找
SLTNode* SLTFind(SLTNode* phead, SLTDataType x)
{
	SLTNode* cur = phead;//用cur去遍历,不用phead
	while (cur)//找x
	{
		if (cur->data == x)//如果找到了
		{
			return cur;
		}
		cur = cur->next;
	}
	return NULL;//如果找不到
}

//单链表在pos位置之前插入x(也可以理解为在pos位置插入)
void SLTInsertBefore(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{
	assert(pos);//默认pos一定会找到
	assert(pphead);//pphead是plist的地址,不能为空
	if (pos == *pphead)//如果pos在第一个位置——那就是头插
	{
		SLTPushFront(pphead, x);
	}
	else//如果pos不是第一个位置
	{
		//找到pos的前一个位置
		SLTNode* prev = *pphead;
		while (prev->next != pos)
		{
			prev = prev->next;
		}
		SLTNode* newnode = BuySLTNode(x);
		prev->next = newnode;
		newnode->next = pos;
	}
}

// 单链表删除pos位置之前的值(也可以理解为删除pos位置的值)
void SLTEraseBefore(SLTNode** pphead, SLTNode* pos)
{
	assert(pphead);
	assert(pos);

	if (*pphead == pos)//如果pos在第一个位置
	{
		SLTPopFront(pphead);//头删
	}
	else//如果不在第一个位置
	{
		SLTNode* prev = *pphead;
		while (prev->next != pos)
		{
			prev = prev->next;
		}
		prev->next = pos->next;
		free(pos);
		//pos = NULL;形参的改变不影响实参,加不加这句话都可以
	}
}

//单链表在pos位置之后插入x
void SLTInsertAfter(SLTNode* pos, SLTDataType x)
{
	assert(pos);
	SLTNode* newnode = BuySLTNode(x);
	newnode->next = pos->next;
	pos->next = newnode;
}

// 单链表删除pos位置之后的值
void SLTEraseAfter(SLTNode* pos)
{
	assert(pos);
	assert(pos->next);

	SLTNode* del = pos->next;//保存要删除的结点
	pos->next = pos->next->next;//或者写成pos->next=del->next;
	free(del);
	del = NULL;
}

// 不传头指针,在pos前插入x(也可以理解为在pos位置插入)
void SLTInsertBefore1(SLTNode* pos, SLTDataType x)
{
	assert(pos);

	//调用单链表在pos位置之后插入x的函数
	SLTInsertAfter(pos, x);

	//交换pos和pos->next的值
	SLTDataType temp;
	temp = pos->data;
	pos->data = pos->next->data;
	pos->next->data = temp;
}

// 不传头指针,删除pos位置之前的值(也可以理解为删除pos位置的值)
void SLTEraseBefore1(SLTNode* pos)
{
	assert(pos);
	assert(pos->next);//不能尾删

	SLTNode* del = pos->next;
	pos->data = pos->next->data;
	pos->next = pos->next->next;
	free(del);
	del = NULL;
}

// 单链表的销毁
void SLTDestroy(SLTNode* phead)
{
	SLTNode* cur = phead;
	/*//有些人一开始会这样写
	while (cur)
	{
		//free不是销毁这个指针指向的内存,而是将指针指向的内存还给操作系统
		free(cur);//cur依旧指向free之前的地址
		cur = cur->next;
	}*/

	while (cur)
	{
		SLTNode* tmp = cur->next;
		free(cur);
		cur = tmp;
	}
}

// 单链表的销毁,传二级
void SLTDestroy1(SLTNode** pphead)
{
	assert(pphead);
	SLTNode* cur = *pphead;
	while (cur)
	{
		SLTNode* tmp = cur->next;
		free(cur);
		cur = tmp;
	}
	*pphead = NULL;
}

under Test.c

#include"SList.h"

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

	SLTPrint(plist);
}

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

	SLTPopBack(&plist);
	SLTPrint(plist);
	SLTPopBack(&plist);
	SLTPrint(plist);
	SLTPopBack(&plist);
	SLTPrint(plist);
	SLTPopBack(&plist);
	SLTPrint(plist);
}

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

	SLTPopFront(&plist);
	SLTPrint(plist);
	SLTPopFront(&plist);
	SLTPrint(plist);
	SLTPopFront(&plist);
	SLTPrint(plist);
	SLTPopFront(&plist);
	SLTPrint(plist);
}

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

	//将寻找和修改结合
	//eg:值为2的结点*2
	SLTNode* ret = SLTFind(plist, 2);
	ret->data *= 2;
	SLTPrint(plist);
}

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

	//寻找值为2的结点
	SLTNode* ret = SLTFind(plist, 2);
	SLTInsertBefore(&plist, ret, 20);//在该结点前插入值为20的结点
	SLTPrint(plist);

}

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

	//寻找值为2的结点
	SLTNode* ret = SLTFind(plist, 2);
	SLTEraseBefore(&plist, ret);
	ret = NULL;//一般在这里置空
	SLTPrint(plist);
}

TestSList7()
{
	SLTNode* plist = NULL;
	SLTPushBack(&plist, 1);
	SLTPushBack(&plist, 2);
	SLTPushBack(&plist, 3);
	SLTPushBack(&plist, 4);
	SLTPrint(plist);

	SLTNode* ret = SLTFind(plist, 2);
	SLTInsertAfter(ret, 20);
	SLTPrint(plist);
}

TestSList8()
{
	SLTNode* plist = NULL;
	SLTPushBack(&plist, 1);
	SLTPushBack(&plist, 2);
	SLTPushBack(&plist, 3);
	SLTPushBack(&plist, 4);
	SLTPrint(plist);

	SLTNode* ret = SLTFind(plist, 2);
	SLTEraseAfter(ret);
	SLTPrint(plist);
}

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

	SLTNode* ret = SLTFind(plist, 2);
	SLTInsertBefore1(ret, 20);
	SLTPrint(plist);
}

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

	SLTNode* ret = SLTFind(plist, 2);
	SLTEraseBefore1(ret);
	SLTPrint(plist);
}

TestSList11()
{
	SLTNode* plist = NULL;
	SLTPushBack(&plist, 1);
	SLTPushBack(&plist, 2);
	SLTPushBack(&plist, 3);
	SLTPushBack(&plist, 4);
	SLTPrint(plist);

	SLTDestroy(plist);
	plist = NULL;
}

TestSList12()
{
	SLTNode* plist = NULL;
	SLTPushBack(&plist, 1);
	SLTPushBack(&plist, 2);
	SLTPushBack(&plist, 3);
	SLTPushBack(&plist, 4);
	SLTPrint(plist);

	SLTDestroy1(&plist);
	SLTPrint(plist);
}

int main()
{
	//TestSList1();
	//TestSList2();
	//TestSList3();
	//TestSList4();
	//TestSList5();
	//TestSList6();
	//TestSList7();
	//TestSList8();
	//TestSList9();
	//TestSList10();
	//TestSList11();
	TestSList12();
	return 0;
}

Corrections are welcome❀

Guess you like

Origin blog.csdn.net/outdated_socks/article/details/130071786