[Basic Data Structure] 3. Linked list in linear list (headless + one-way + acyclic linked list)

=========================================================================

Relevant code can be obtained from gitee :

C language learning diary: keep working hard (gitee.com)

 =========================================================================

Continuing from the previous issue :

[Elementary Data Structure] 2. Sequence table in linear table_Gaogao Fatty's Blog-CSDN Blog

 =========================================================================

                   

introduction

Through the introduction and use of sequence tables in the previous issue

We can know that the sequence table has the following advantages and disadvantages:

            

Advantages of sequence table

                  

  • Tail insertion and tail deletion operations are fast enough
                   
  • Being able to use random access and modification of subscripts is very convenient

            

-----------------------------------------------------------------------------------------------

            

Disadvantages of sequence table

                  

  • Insertion and deletion operations in the head/middle are slower and the time complexity is O(N)
                              
  • Capacity expansion requires applying for new space , copying data , and releasing old space . There will be a lot of consumption .
                         
  • Capacity expansion generally increases by a factor of 2 , which will inevitably result in 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 .

            

            

The linked list to be introduced and implemented below will not have these problems.

         

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

             

1. Linked list

(1). Concept and structure of linked list:

           

The concept of linked list

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 pointer link order in the linked list . Singly linked lists are generally not used alone . Only head insertion and head deletion are simple and efficient to implement.

              

Linked list structure

  • Linked lists are also linear lists . When linear lists are physically stored , they are usually stored in the form of arrays (sequential lists) and linked structures (linked lists) . Linked structures are logically continuous , but not necessarily physically continuous.


                     
  • In reality, nodes are generally applied from the heap .
                  
  • The space applied for from the heap is allocated according to a certain strategy .
    The space applied for twice may be continuous or discontinuous.

Illustration:

                     


                    

(2). Classification of linked lists:

            

In practice , the structures of linked lists are very diverse . There are 8 types of linked list structures when combined with
the following situations :

           

One-way or two-way linked list

One-way linked list diagram

                

Doubly linked list diagram

            

            

---------------------------------------------------------------------------------------------

            

Headed or unheaded linked list

Headed linked list icon

               

Linked list without header icon

            

            

---------------------------------------------------------------------------------------------

            

Circular or acyclic linked list

Circular linked list icon

                  

Acyclic linked list diagram

            

            

Although there are so many linked list structures,
there are two structures we most commonly use in practice :

Headless one-way non-circular linked list and  headed two-way circular linked list

                     


                    

(3). Two commonly used linked list structures:

            

Headless one-way acyclic linked list

Introduction:

The structure is simple and is generally not used alone to store data .

In practice, it is more of a substructure of other data structures .

Such as hash buckets , adjacency lists of graphs , etc.

In addition, this structure appears a lot in written interviews .

          

Illustration:

            

            

---------------------------------------------------------------------------------------------

            

Headed two-way circular linked list

Introduction:

The structure is the most complex and is 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 structure is complex ,

But after using code to implement it, you will find that the structure will bring many advantages , and the implementation will be simpler .

          

Illustration:

         

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

             

2. Implementation of linked list
(headless + one-way + acyclic linked list)

(Detailed explanation is in the comments of the picture, and the code file is put at the end)

                  

Preparatory work before implementing specific functions

  • Create a singly linked list data type - the type of data stored in the data field of the linked list node
                    
  • Create a singly linked list node structure ( type ) - the node should have a data field and a pointer field

Illustration

            

            

---------------------------------------------------------------------------------------------

            

PrintSList function--traverse the printing list

  • The linked list head pointer phead needs to be used . For subsequent use , the head pointer cannot , so the head pointer must be replaced.
                     
  • Because the linked list does not end until the NULL (0x00000000) in the pointer field in the last node , you can use a while loop to traverse and print

                  
  • The final prompt points to the NULL pointer linked list , and the traversal ends.

Illustration

            

            

---------------------------------------------------------------------------------------------

            

SLTNode function--generates the nodes of the linked list

  • To open up the dynamic space required for nodes , it should be noted that the size of a node depends on the size of the data field and pointer field.
               
  • Check whether the space is successfully opened
               
  • Put the data to be put into the node data field into
               
  • Set the pointer field of the newly created node
               
  • Returns the pointer (address) of the new node created

Illustration

Test--PrintSList, SLTNode function

            

            

---------------------------------------------------------------------------------------------

            

SLTPushBack function (difficult) -- Insert a node to the end of the linked list (tail insertion)

  • Because you want to insert a node , you first use the BuySListNode function to create a node.
               
  • When inserting at the end, it is necessary to determine whether the linked list already has a node .
    If there is no node , the address of the newly created node is
    assigned . Because the pointer itself is operated , a secondary pointer is used to operate the head pointer.

               
  • If there is already a node ,
    first create a pointer to replace the head pointer .
    (Here we operate on the node structure of the head pointer straight line , so a pointer is enough , no secondary pointer is needed .)
    Then use a while loop to obtain the last node. The address of the point ,
    and finally connect the nodes inserted at the end.

Illustration

(Can be understood in conjunction with the physical diagram of a singly linked list )

↓↓↓↓

Test--SLTPushBack function

            

            

---------------------------------------------------------------------------------------------

            

SLTPushFront function--Insert a node into the head of the linked list (head insertion)

  • Because you want to insert a node , you first use the BuySListNode function to create a node.
               
  • Use plist to assign the original head node address to the newly inserted head node pointer field.
               
  • Then change the head pointer to point to the newly inserted head node

Illustration

Test--SLTPushFront function

            

            

---------------------------------------------------------------------------------------------

            

SLTPopBack function--delete the tail node of the linked list (tail deletion)

  • Three situations need to be considered when deleting the end of a linked list :
    The first situation : the linked list is empty and does not have any nodes.
    In this case , just assert
               
  • The second case : the linked list has only one node.
    In this case, the only node must be released.
               
  • The third case : the linked list has more than one node.
    This case requires two pointers to operate.

Illustration

Test--SLTPopBack function

            

            

---------------------------------------------------------------------------------------------

            

SLTPopFront function--delete the head node of the linked list (head deletion)

  • It is enough to divide the head deletion into two situations : empty linked list and non-empty linked list.
               
  • Delete the empty linked list header :
    directly assert and pass
    (this is a funny point for no reason hahahahahahahaha)
               
  • Deleting the head of a non-empty linked list :
    first obtain the second node address through the plist head pointer , so that the original second node can be used as a new head node after deleting the head,
    then free the U-turn pointer , and
    finally let the plist head pointer point to the original The second node becomes the new head node

Illustration

Test--SLTPopFront function

            

            

---------------------------------------------------------------------------------------------

            

SLTFind function--find the address of the node where the specified value (x) is located

  • Create a variable instead of the head pointer
               
  • Use a while loop to traverse the linked list and find the specified value (x)

Illustration

Test--SLTFind function

            

            

---------------------------------------------------------------------------------------------

            

SLTInsert function--Insert a value (x) before the specified position (pos) in the linked list

  • Discuss in three situations :
    the linked list is empty (empty linked list) , inserting before the first node (head insertion) , non-head insertion
               
  • The linked list is empty :
    directly assert and pass.
               
  • Insert before the first node (head insertion) :
    reuse the head insertion SLTPushFront function for insertion
               
  • Non-head insertion :
    Use the head pointer of the linked list to find the previous node address prev of the pos node
  • Create a new node newnode
    and insert the new node newnode before the pos node and after the prev node.

Illustration

Test--SLTInsert function

            

            

---------------------------------------------------------------------------------------------

            

SLTInsertAfter function--Insert a value (x) after the specified position (pos) in the linked list

  • If the received pos node address is empty , the operation cannot continue.
    You must use assert to continue processing.
               
  • Because you want to insert a value into the linked list , you need to use
    the BuySListNode function to add a new node.
               
  • First, let the pointer field next of the newnode node point to the next node.
               
  • Then let the pos node point to the newnode node to
    connect the linked lists.

Illustration

Test--SLTInsertAfter function

            

            

---------------------------------------------------------------------------------------------

            

SLTErase function--delete the node at the specified position (pos) in the linked list

  • To prevent the pos node pointer to be deleted from being a null pointer ,
    use assert.
               
  • There are two situations :
    head deletion and non-head deletion.
               
  • Header deletion :
    directly reuse the header deletion SLTPopFront function
               
  • Non-head deletion :
    Find the previous node prev of the pos node , and connect the
    prev node to the node after the pos node .
               
  • Finally release (delete) the pos node to complete the deletion operation

Illustration

Test--SLTErase function

            

            

---------------------------------------------------------------------------------------------

            

SLTEraseAfter function--delete a node after the specified position (pos) in the linked list

  • First use the assert assertion to exclude the situation that the pos node pointer is empty and the pos node is the tail node
    . We are deleting the node after the pos node .
    If pos is the tail node , the operation will not be possible .
               
  • Save the posNext address of the next node of the pos node to be deleted first .
               
  • Then connect the pos node to the next node
               
  • Finally release (delete) the posNext node

Illustration

Test--SLTEraseAfter function

         

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

             

3. Corresponding code

SList.h -- singly linked list header file

//链表头文件:
#pragma once

//先包含之后要用到的头文件:
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>


//重命名一个单链表数据类型
typedef int SLTDataType;
//SLT -- single link table

//创建一个单链表结点结构体 
//这里数据域是int类型4字节,指针域指针也是4个字节,
//所以一个结点就是8个字节
typedef struct SListNode
{
	//结点数据域:
	SLTDataType data;

	//结点指针域:
	//因为是指向单链表结点结构体的指针,
	//所以是单链表结点结构体类型的指针
	struct SListNode* next;
	//第一个结点要找到第二个结点,物理空间不连续
	//通过上个结点指向下个结点,实现逻辑连续

}SLTNode; //将struct SListNode重命名为SLTNode


//创建遍历打印单链表的函数 -- 接收单链表头指针
void PrintSList(SLTNode* phead);

   
//创建生成结点的函数 -- 接收要存入结点数据域的数据;返回该结点的指针
SLTNode* BuySListNode(SLTDataType x);


//创建尾插函数 -- 
//使用二级指针接收单链表头指针的地址 和 接收要插入的值
void SLTPushBack(SLTNode** pphead, SLTDataType x);


//创建头插函数 -- 
//使用二级指针接收单链表头指针的地址 和 接收要插入的值
void SLTPushFront(SLTNode** pphead, SLTDataType x);



//创建尾删函数 --
//使用二级指针接收单链表头指针的地址
void SLTPopBack(SLTNode** pphead);


//创建头删函数 --
//使用二级指针接收单链表头指针的地址
void SLTPopFront(SLTNode** pphead);


//创建查找函数 --
//查找指定值(x)所在结点的地址
//接收 链表头指针phead、查找值x
SLTNode* SLTFind(SLTNode* phead, SLTDataType x);


//创建指定位置插入函数1 --
//在链表指定位置(pos)之前插入一个值(x)
//接收 链表头指针地址pphead、指定结点指针pos、插入值x
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x);


//创建指定位置插入函数2 --
//在链表指定位置(pos)之后插入一个值(x)
//接收 指定结点指针pos、插入值x
void SLTInsertAfter(SLTNode* pos, SLTDataType x);


//创建删除指定结点函数1 --
//在链表删除指定位置(pos)结点
///接收 链表头指针地址pphead、指定结点指针pos
void SLTErase(SLTNode** pphead, SLTNode* pos);


//创建删除指定结点函数2 --
//在链表删除指定位置(pos)之后一个结点
//接收 指定结点指针pos
void SLTEraseAfter(SLTNode* pos);

            

            

---------------------------------------------------------------------------------------------

SList.c -- Single linked list function implementation file

//链表实现文件:
#define _CRT_SECURE_NO_WARNINGS 1

//包含链表头文件:
#include "SList.h"

//创建遍历单链表的函数:接收单链表结点的头指针
void PrintSList(SLTNode* phead)
{
	//phead头指针指向第一个结点,
	//之后还要多次使用phead头指针,
	//所以不要改变phead头指针,
	//所以可以创建一个和phead头指针一样类型的指针cur代替phead头指针
	SLTNode* cur = phead;

	//因为链表到最后结点的指针域为NULL才结束
	//所以只要cur不为NULL就继续遍历结点进行打印:
	while (cur != NULL)
	{
		//通过cur当前指向的结点打印cur里数据域的内容:
		printf("%d->", cur->data);

		//通过结点里指针域指向下一个结点,方便打印下个结点的数据域内容
		cur = cur->next;
	}

	//最后提示已指向NULL指针链表,遍历结束:
	printf("NULL\n");
}



//创建生成结点的函数 -- 接收要存入结点数据域的数据;返回该结点的指针
SLTNode* BuySListNode(SLTDataType x)
{
	//给结点开辟动态空间(注意开辟空间的大小是SLTNode结点的大小--8个字节)
	SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode)); 
	//返回该结点地址

	//检查是否开辟成功:
	if (newnode == NULL) //返回空指针,说明开辟失败
	{
		//打印错误信息:
		perror("malloc fail");
		//开辟失败直接退出程序:
		exit(-1);
	}

	//将接收的值x赋给该结点的数据域:
	newnode->data = x;

	//设置新创建结点的指针域:
	//因为是最新的结点,即最尾部的结点,
	//所以指针域的指针应是NULL(链表末尾结束)
	//之后通过指针域连接各个结点的操作要看情况操作,先都初始化为NULL
	newnode->next = NULL;

	//返回该新结点的指针(地址)
	return newnode;
}



//创建尾插函数
//使用二级指针接收单链表头指针的地址 和 接收要插入的值
void SLTPushBack(SLTNode** pphead, SLTDataType x)
{
	//改变结构体,要用结构体指针
	//改变结构体指针,要用结构体指针的指针(二级指针)
	   
	//先使用newnode函数为要尾插的值创建一个结点
	//并返回该结点地址
	SLTNode* newnode = BuySListNode(x);

	//判断phead是否是NULL,如果是说明链表还没开辟过一个结点
	if (*pphead == NULL)
	{
		//如果为空,则将上面开辟的newnode结点地址赋给phead
		*pphead = newnode;
		//要改变结构体的指针,就要用二级指针

		//这里pphead是二级指针,存放链表头指针plist的地址
		//因为如果用一级指针SLTNode* phead的话,
		//phead形参只是plist实参的临时拷贝,两者空间相互独立
		//改变phead的话不会改变plist,plist还会是空指针
		//所以要用二级指针pphead存放plist指针的地址,
		//*pphead解引用就能得到一级指针plist,
		//即			*pphead = plist
		//所以实际上上面的代码就相当于:plist = newnode
		//这样就让本来指向空指针的头指针指向了新创建的结点指针
		//链表就能够连接起来
	}
	else
		//不为空则往尾部插入新结点:
	{
		//创建另一个指针存放单链表头指针
		SLTNode* tail = *pphead; //*pphead == plist

		//使用while循环获得最后一个结点的地址
		while (tail->next != NULL)
		//如果下个结点的next指针域不为NULL,
		//则继续往后找,直到tail等于最后结点的地址
		{
			//把当前结点指针域里下个结点的地址给到tail,
			//进行while循环下次判断:
			tail = tail->next;
		}

		//tail找到最后结点地址后,
		//把尾插的新结点地址给到tail的next指针域,
		//连接起链表:
		tail->next = newnode;
		//要改变结构体,用结构体的指针即可

		//因为newnode、tail、pphead都是临时(局部)变量,
		//所以函数运行结束后都会销毁,
		//但malloc函数开辟的空间(结点)都在堆上不会销毁
		//通过解引用二级指针pphead改变plist也没有问题
	}
}



//创建头插函数 -- 
//使用二级指针接收单链表头指针的地址 和 接收要插入的值
void SLTPushFront(SLTNode** pphead, SLTDataType x)
{
	//先使用newnode函数为要头插的值创建一个结点
	//并返回该结点地址
	SLTNode* newnode = BuySListNode(x);

	//因为也要用到链表头指针本身,所以也要使用二级指针
	//因为plist指向原本头结点地址,
	//所以可以使用plist把原本头结点地址赋给新插入头结点指针域:
	newnode->next = *pphead;

	//再把头指针改为指向新插入头结点:
	*pphead = newnode;
}



//创建尾删函数 --
//使用二级指针接收单链表头指针的地址
void SLTPopBack(SLTNode** pphead)
{
	//尾删需要考虑三种情况:
	//注:*pphead 就是 plist

	// 第一种情况:链表为空 -- 没有任何结点
	//没有结点那就删不了了,使用assert接收到空指针就报警告
	assert(*pphead);

	// 第二种情况:链表只有一个结点
	if ((*pphead)->next == NULL)
		// -> 也是解引用(结构体的),优先级比 * 高
		//所以 *pphead 要用() 括起来
	{
		//只有一个结点,又要尾删,那就直接把这唯一的结点删掉:
		free(*pphead); //直接free释放掉plist指向的结点

		//再把释放掉的plist置为空指针,防止成为野指针:
		*pphead = NULL;
	}
	else
	// 第三种情况:链表有一个以上结点
	{
		//这种情况额外两个指针,
		//一个tail指针 -- 用来找到最后一个结点地址并将其释放,
		//还有一个tailPrev指针 -- 指向tail指针的前一个结点地址,
		//用来改变该结点的指针域,
		//防止原本指向tail结点的指针域变成野指针

		//先替代头指针plist:
		SLTNode* tail = *pphead;

		//创建tailPrev指针,
		//之后操作会指向tail结点的前一个结点,
		//即倒数第二个结点
		SLTNode* tailPrev = NULL;

		//再使用while循环找到尾结点:
		//和尾插的操作类似
		while (tail->next != NULL)
		{
			//tail查找之前先把当前指向结点地址给tailPrev
			//这样最后tail会指向尾结点,
			//tailPrev会指向倒数第二个结点
			tailPrev = tail;

			tail = tail->next;
		}

		//结束while循环后tail指向尾结点地址,
		//因为是尾删,所以free释放掉tail就可以“删掉”尾结点
		free(tail);
		//因为tail是局部(临时)变量,出了函数就销毁,
		//所以不置为指针也可以,不用担心成为野指针

		//删除原尾结点后,倒数第二个结点成为尾结点,
		//要把其指针域next置为空指针,成为链表新结尾
		tailPrev->next = NULL;
	}
}



//创建头删函数 --
//使用二级指针接收单链表头指针的地址
void SLTPopFront(SLTNode** pphead)
{
	//分两种情况:
	
	//第一种情况:链表里没有结点(空链表)
	//没有结点可以删,直接assert断言pass掉:
	assert(*pphead);


	//第二种情况:链表有一个和多个结点(非空链表)
	//因为是删掉头结点,所以不用考虑找尾结点
	//所以不用细分一个或多个结点的情况:
	
	//这种情况要先通过plist拿到第二个结点的地址:
	SLTNode* newhead = (*pphead)->next;
	//使用newhead存储第二个结点地址

	//拿到第二个结点地址后,再释放掉头结点,
	//实现头删效果:
	free(*pphead);

	//这时要让第二个结点成为新的头结点:
	*pphead = newhead; 
	//头指针指向原本的第二个结点
}



//创建查找函数 --
//查找指定值(x)所在结点的地址
//接收 链表头指针phead、查找值x
SLTNode* SLTFind(SLTNode* phead, SLTDataType x)
{
	//像SLTFind和PrintSList函数只用头指针遍历
	//不改变指针本身就不需要用到二级指针

	//创建个变量代替头指针:
	SLTNode* cur = phead;

	//使用while循环对链表进行遍历查找:
	while (cur != NULL)
		//只要cur指针指向地址不为空就继续循环
//while遍历时,(cur->next != NULL) 和 (cur != NULL) 的区别:
//(cur->next != NULL):这个条件最后cur会是最后结点的地址
//(cur != NULL):这个条件最后cur会是NULL
//(cur->next != NULL) 会比 (cur != NULL) 少一次循环
	{
		//遍历过程中依次查找各结点数据域数据是否与x相同:
		if (cur->data == x)
		{
			//找到了一个结点数据域数据是x,返回该结点地址
			return cur;
		}
		//这里如果while循环的条件是(cur->next != NULL)
		//最后一个结点进不来,不能判断最后结点数据域数据是不是x

		cur = cur->next; //改变循环条件,指向下个结点
	}

	//如果能指向到这说明没找到,返回NULL:
	return  NULL;
}



//创建指定位置插入函数1 -- 
//在链表指定位置(pos)之前插入一个值(x)
//接收 链表头指针地址pphead、指定结点指针pos、插入值x
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{
	//第一种情况:空指针
	//先排除空指针的情况:
	assert(pos);

	//第二种情况:头插
	if (pos == *pphead)
		// *pphead == plist
		//如果pos是pphead即第一个指针,
		//则在第一个指针前插入一个值,相当于头插
	{
		//直接复用头插函数SLTPustFront:
		SLTPushFront(pphead, x);
		//直接传pphead二级指针过去
	}
	else
	//第三种情况:非头插
	{
		//从头开始找pos结点的前一个结点:
		//先获得头指针
		SLTNode* prev = *pphead;

		//当前结点的指针域不是指向pos结点则继续循环
		while (prev->next != pos)
		{
			prev = prev->next;
		}

		//此时prev已指向pos结点的前一个结点

		//为要插入的结点创建一个结点newnode:
		//插入位置是pos结点之前,prev结点之后
		SLTNode* newnode = BuySListNode(x);

		//让prev结点指针域指向新插入结点地址:
		prev->next = newnode;

		//newnode结点指针域指向pos结点:
		newnode->next = pos;

		//此时newnode新结点就插入完成了
	}
}



//创建指定位置插入函数2 --
//在链表指定位置(pos)之后插入一个值(x)
//接收 指定结点指针pos、插入值x
void SLTInsertAfter(SLTNode* pos, SLTDataType x)
{
	//因为是在pos结点后插入一个值(结点)
	//所以不可能会有头插的情况,那就不需要头指针plist

	//pos指针存储结点地址,可能会接收到空指针
	//使用assert断言pass掉
	assert(pos);

	//先为插入值创建一个新结点newnode:
	SLTNode* newnode = BuySListNode(x);

	//先让newnode的指针域next指向后一个结点:
	//这里后一个结点就是pos结点指针域里的地址
	//因为未插入前pos就是指向后一个地址
	newnode->next = pos->next;

	//再让pos的指针域next指向newnode:
	pos->next = newnode;
}



//创建删除指定结点函数1 --
//在链表删除指定位置(pos)结点
///接收 链表头指针地址pphead、指定结点指针pos
void SLTErase(SLTNode** pphead, SLTNode* pos)
{
	//防止要删的pos结点指针为空指针
	//使用assert断言:
	assert(pos);

	//分两种情况:

	// 1.头删:
	if (pos == *pphead)
		//pos结点 == 头结点 --> 头删
	{
		//直接复用SLTPopFront头删函数:
		SLTPopFront(pphead);
	}
	else
	// 2.非头删:
	//尾删不用单独分出一种情况,因为还得判断是不是尾结点
	//直接用通用逻辑删除也可以处理尾删的情况
	{
		//从头开始找pos结点的前一个结点:
		//先获得头指针
		SLTNode* prev = *pphead;

		//当前结点的指针域不是指向pos结点则继续循环
		while (prev->next != pos)
		{
			prev = prev->next;
		}

		//此时prev已指向pos结点的前一个结点

		//因为要删除pos结点,
		//所以要让pos前一个和后一个结点连接起来:
		prev->next = pos->next;

		//连接成功后再把pos结点释放,实现删除效果:
		free(pos);
	}
}



//创建删除指定结点函数2 --
//在链表删除指定位置(pos)之后一个结点
//接收 指定结点指针pos
void SLTEraseAfter(SLTNode* pos)
{
	//删除pos结点后的一个结点,
	//那么就不可能出现头删的情况,
	//pos结点是尾结点也没用,
	//因为尾结点后就没有结点可以删了
	//使用assert断言处理:
	assert(pos); //防止接收“空结点”
	assert(pos->next); //防止接收尾结点

	//将要删的pos结点的下个结点posNext先保存起来:
	SLTNode* posNext = pos->next;

	//再把pos结点和下下个结点连接起来:
	pos->next = posNext->next;
	//posNext的指针域next就是pos结点的下下个结点地址

	//最后释放(删除)posNext结点:
	free(posNext);
}

            

            

---------------------------------------------------------------------------------------------

test.c -- singly linked list test file

//链表测试文件:
#define _CRT_SECURE_NO_WARNINGS 1

/* 链表学习引入小测试:

#include <stdio.h>

//重命名一个单链表数据类型
typedef int SLTDataType;
//SLT -- single link table

//创建一个单链表结点结构体
typedef struct SListNode
{
	//结点数据域:
	SLTDataType data; 

	//结点指针域:
	//因为是指向单链表结点结构体的指针,
	//所以是单链表结点结构体类型的指针
	struct SListNode* next; 

}SLTNode; //将struct SListNode重命名为SLTNode

//创建遍历单链表的函数:接收单链表结点的头指针
void PrintSList(SLTNode* phead)
{
	//phead头指针指向第一个结点,
	//之后还要多次使用phead头指针,
	//所以不要改变phead头指针,
	//所以可以创建一个和phead头指针一样类型的指针cur代替phead头指针
	SLTNode* cur = phead;

	//因为链表到最后结点的指针域为NULL才结束
	//所以只要cur不为NULL就继续遍历结点进行打印:
	while (cur != NULL)
	{
		//通过cur当前指向的结点打印cur里数据域的内容:
		printf("%d->", cur->data);

		//通过结点里指针域指向下一个结点,方便打印下个结点的数据域内容
		cur = cur->next;
	}

	//最后提示已指向NULL指针链表,遍历结束:
	printf("NULL\n");
}

int main()
{
	//创建单链表结点:
	SLTNode* n1 = (SLTNode*)malloc(sizeof(SLTNode));
	n1->data = 10; //在该结点数据域存放数据

	SLTNode* n2 = (SLTNode*)malloc(sizeof(SLTNode));
	n2->data = 20; //在该结点数据域存放数据


	SLTNode* n3 = (SLTNode*)malloc(sizeof(SLTNode));
	n3->data = 30; //在该结点数据域存放数据

	n1->next = n2; //n1的指针域指向结点n2
	n2->next = n3; //n2的指针域指向结点n3
	n3->next = NULL; //n3的指针域指向NULL(结束)

	PrintSList(n1); //调用函数打印单链表

	return 0;
}

*/


//包含链表头文件:
#include "SList.h"

//测试函数1:测试--PrintSList、BuySListNode函数
void TestList1()
{
	int n; //存放链表长度

	//打印提示信息:
	printf("请输入链表的长度:>");
	//接收链表长度
	scanf("%d", &n); 
	
	//打印提示信息:
	printf("\n请依次输入每个结点的值:>");

	SLTNode* plist = NULL; //链表头指针,一开始链表没数据所以为NULL

	//使用for循环,循环创建结点并赋值,形成链表:
	for (int i = 0; i < n; i++)
	{
		int val; //存放结点数据域数据
		scanf("%d", &val); //接收结点数据域数据
		
		//使用BuySListNode函数创建结点并给数据域赋值:
		SLTNode* newnode = BuySListNode(val);

		//头插: 使用头插把链表连接起来
		
		//把链表头指针plist指向的头结点地址赋给新创建结点的指针域next
		//这样新结点的指针域next就能指向原来的头结点地址
		newnode->next = plist;

		//再把新创建结点的地址赋给头指针,
		//这样头指针就指向了新创建结点地址,让其成为新的头结点
		plist = newnode;
	}

	//使用PrintSList函数打印链表
	PrintSList(plist); //接收头指针后打印


	//使用SLTPushBack函数手动向链表尾部插入数据(尾插):
	SLTPushBack(plist, 10000);
	//再使用PrintSList函数打印插入后的链表
	PrintSList(plist);

	//plist和phead都是单链表头指针,
	//但 plist是实参  phead是形参
	//phead 是 plist 的一份临时拷贝
}

//测试函数2:测试--SLTPushBack、SLTPushFront函数
void TestList2()
{
	//创建单链表头指针:
	SLTNode* plist = NULL;

	//使用尾插随机插入几个值: 
	//(此时头指针指向NULL还没有开辟空间创建结点)
	SLTPushBack(&plist, 1);
	SLTPushBack(&plist, 2);
	SLTPushBack(&plist, 3);
	SLTPushBack(&plist, 4);
	SLTPushBack(&plist, 5);
	  
	//使用头插随机插入几个值:
	SLTPushFront(&plist, 10);
	SLTPushFront(&plist, 20);
	SLTPushFront(&plist, 30);
	SLTPushFront(&plist, 40);

	//使用SLTPrintf函数打印该链表:
	PrintSList(plist);
}

//测试函数3:测试--SLTPopBack(尾删)函数
void TestList3()
{
	//创建单链表头指针:
	SLTNode* plist = NULL;

	//使用尾插随机插入几个值: 
	//(此时头指针指向NULL还没有开辟空间创建结点)
	SLTPushBack(&plist, 1);
	SLTPushBack(&plist, 2);
	SLTPushBack(&plist, 3);
	//使用SLTPrintf函数打印该链表:
	printf("尾删前链表:\n");
	PrintSList(plist);

	//使用尾删函数:
	SLTPopBack(&plist);
	//使用SLTPrintf函数打印该链表:
	printf("尾删后链表:\n");
	PrintSList(plist);

	SLTPopBack(&plist);
	//使用SLTPrintf函数打印该链表:
	printf("尾删后链表:\n");
	PrintSList(plist);

	SLTPopBack(&plist);
	//使用SLTPrintf函数打印该链表:
	printf("尾删后链表:\n");
	PrintSList(plist);

	SLTPopBack(&plist);
	//使用SLTPrintf函数打印该链表:
	printf("尾删后链表:\n");
	PrintSList(plist);

	//使用SLTPrintf函数打印该链表:
	printf("尾删后链表:\n");
	PrintSList(plist);
}

//测试函数4:测试--SLTPopFront(头删)函数
void TestList4()
{
	//创建单链表头指针:
	SLTNode* plist = NULL;

	//使用尾插随机插入几个值: 
	//(此时头指针指向NULL还没有开辟空间创建结点)
	SLTPushBack(&plist, 1);
	SLTPushBack(&plist, 2);
	SLTPushBack(&plist, 3);
	//使用SLTPrintf函数打印该链表:
	printf("头删前链表:\n");
	PrintSList(plist);

	SLTPopFront(&plist);
	//使用SLTPrintf函数打印该链表:
	printf("头删后链表:\n");
	PrintSList(plist);

	SLTPopFront(&plist);
	//使用SLTPrintf函数打印该链表:
	printf("头删后链表:\n");
	PrintSList(plist);

	SLTPopFront(&plist);
	//使用SLTPrintf函数打印该链表:
	printf("头删后链表:\n");
	PrintSList(plist);

	SLTPopFront(&plist);
	//使用SLTPrintf函数打印该链表:
	printf("头删后链表:\n");
	PrintSList(plist);
}

//测试函数5:测试 -- SLTFind函数
void TestList5()
{
	//创建单链表头指针:
	SLTNode* plist = NULL;

	//使用尾插随机插入几个值: 
	//(此时头指针指向NULL还没有开辟空间创建结点)
	SLTPushBack(&plist, 1);
	SLTPushBack(&plist, 2);
	SLTPushBack(&plist, 3);
	//使用SLTPrintf函数打印该链表:
	printf("操作前链表:\n");
	PrintSList(plist);

	//用SLTFind函数查找链表中数据域为3的结点的地址
	SLTNode* pos = SLTFind(plist, 1);
	if (pos != NULL)
	{	
		//找到了可以对该结点进行修改
		pos->data *= 10;
		
		//所以SLTFind查找函数可以负责查找和修改的操作
	}

	printf("操作后链表:\n");
	PrintSList(plist);
}

//测试函数6:测试 -- SLTInsert函数
void TestList6()
{
	//创建单链表头指针:
	SLTNode* plist = NULL;

	//使用尾插随机插入几个值: 
	//(此时头指针指向NULL还没有开辟空间创建结点)
	SLTPushBack(&plist, 1);
	SLTPushBack(&plist, 2);
	SLTPushBack(&plist, 3);
	//使用SLTPrintf函数打印该链表:
	printf("操作前链表:\n");
	PrintSList(plist);

	//用SLTFind函数查找链表中数据域为3的结点的地址
	SLTNode* pos = SLTFind(plist, 2);
	if (pos != NULL)
	{
		int x; //接收要插入的值
		scanf("%d", &x); //输入要插入的值
		SLTInsert(&plist, pos, x); //在2前面插入x 
	}

	printf("操作后链表:\n");
	PrintSList(plist);
}

//测试函数7:测试 -- SLTInsertAfter函数
void TestList7()
{
	//创建单链表头指针:
	SLTNode* plist = NULL;

	//使用尾插随机插入几个值: 
	//(此时头指针指向NULL还没有开辟空间创建结点)
	SLTPushBack(&plist, 1);
	SLTPushBack(&plist, 2);
	SLTPushBack(&plist, 3);
	//使用SLTPrintf函数打印该链表:
	printf("操作前链表:\n");
	PrintSList(plist);

	//用SLTFind函数查找链表中数据域为3的结点d的地址
	SLTNode* pos = SLTFind(plist, 2);
	if (pos != NULL)
	{
		int x; //接收要插入的值
		scanf("%d", &x); //输入要插入的值
		SLTInsertAfter(pos, x); //在2后面插入x 
	}

	printf("操作后链表:\n");
	PrintSList(plist);
}

//测试函数8:测试 -- SLTErase函数
void TestList8()
{
	//创建单链表头指针:
	SLTNode* plist = NULL;

	//使用尾插随机插入几个值: 
	//(此时头指针指向NULL还没有开辟空间创建结点)
	SLTPushBack(&plist, 1);
	SLTPushBack(&plist, 2);
	SLTPushBack(&plist, 3);
	//使用SLTPrintf函数打印该链表:
	printf("操作前链表:\n");
	PrintSList(plist);

	//用SLTFind函数查找链表中数据域为3的结点d的地址
	SLTNode* pos = SLTFind(plist, 2);
	if (pos != NULL)
	{
		int x; //接收要插入的值
		scanf("%d", &x); //输入要插入的值
		SLTErase(&plist, pos); //删除pos所在结点
		//pos结点指针删除(释放)后,要将其置为空:
		pos = NULL;
	}

	printf("操作后链表:\n");
	PrintSList(plist);
}

//测试函数9:测试 -- SLTEraseAfter函数
void TestList9()
{
	//创建单链表头指针:
	SLTNode* plist = NULL;

	//使用尾插随机插入几个值: 
	//(此时头指针指向NULL还没有开辟空间创建结点)
	SLTPushBack(&plist, 1);
	SLTPushBack(&plist, 2);
	SLTPushBack(&plist, 3);
	//使用SLTPrintf函数打印该链表:
	printf("操作前链表:\n");
	PrintSList(plist);

	//用SLTFind函数查找链表中数据域为3的结点d的地址
	SLTNode* pos = SLTFind(plist, 2);
	if (pos != NULL)
	{
		int x; //接收要插入的值
		scanf("%d", &x); //输入要插入的值
		SLTEraseAfter(pos); //删除pos结点的下个结点
		//pos结点指针删除(释放)后,要将其置为空:
		pos = NULL;
	}

	printf("操作后链表:\n");
	PrintSList(plist);
}

//主函数:
int main()
{
	//TestList1();
	//TestList2();
	//TestList3();
	//TestList4();
	//TestList5();
	//TestList6();
	//TestList7();
	//TestList8();
	TestList9();

	return 0;
}

Guess you like

Origin blog.csdn.net/weixin_63176266/article/details/132781490