单向链表知识汇总

提示:本文章参考知乎大佬和一位博主大佬相关翻转的补充
环形链表的补充


1.前置知识(部分最好记忆)

1.1 链表组成

无论链表是否为空,均可通过 newcode->next = pheadnewcode 连接到 phead

注意:通常先处理 newcode->nextdata ,可先保存下一个节点的指向

最后,phead = newcode

phead 为指针变量,其保存着节点 1 的值,也就是说 phead 指向节点 1。
节点 1 中的 next 保存着节点 2 的地址,这意味着节点 1 的 next 指向节点 2,也可以表示节点 1 指向节点 2。(注:在 C 语言中,准确的表达应该是 node1->next = node2,其中 node1 和 node2 都是结构体变量的地址。)

在这里插入图片描述
在这里插入图片描述
在定义结构体类型后,可以通过

struct Node* newNode = (struct Node*)malloc(sizeof(struct Node));

这段代码用于创建一个结构体变量(空间),并返回该结构体变量的地址,即节点的地址。

每个节点具有两个属性(在定义结构体类型时设定):一个是 data,用于存储数据;另一个是 next,用于存储下一个节点的地址。
在这里插入图片描述

尾节点是最后一个节点,其后无节点(即 ptail->nextNULL)。在前一个节点连接后一个节点,并为尾节点连接空指针后,我们只需知晓头节点的地址,便可依据连接关系,访问整个链表。

因此,一般在进行增删查找操作时,都需要传递参数 ptail(头节点指针),它是链表的入口(链表的关键参数)。

1.2 链表插入分二种情况

总结来说:就说改变原链表phead指向的的为一类,其他的为另一类
头插 链表为空的尾插和头插 都需要改变phead值

1.2.1 头插 或 链表为空的插入 都需要重新给phead赋值

无论链表是否为空,均可通过 newcode->next = pheadnewcode 连接到 phead 。若链表为空,则 newcode->nextNULL ;若链表不为空,则 newcode 指向原链表的第一个节点。

注意:通常应先处理 newcode->nextdata ,可先保存下一个节点的指向。最后,令 phead = newcode
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

1.2.2 尾插或者中间插

插入的节点,需连接其前面和后面。
此处为何未考虑尾插且链表为空的情况呢?在上面 1.2.1 中“链表为空时的插入”已涵盖此情况。

在这里插入图片描述
在这里插入图片描述

1.3 链表的删除 注意:当无节点时,不可删除

总结来说:就说改变原链表phead指向的的为一类,其他的为另一类
即:头删为一类 其他的为另外一类

2.链表各种接口的实现

2.1 链表的打印

void SListPrint(SLTNode* phead)
{
	SLTNode* cur = phead;
	while (cur != NULL)
	{
		printf("%d->", cur->data);
		cur = cur->next;//相当于指针的后移一位
	}
	printf("NULL\n");
}

2.1 链表的节点的申请

// 动态申请一个结点
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  节点地址
}

2.2 单链表节点增加

2.2.1 单链表指定位置插入

方法1:
// 在pos的前面插入x
void SListInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{
	if (pos == *pphead||*pphead == NULL)   
	//头插  和  phead为空的情况下的头插和尾插
	{
		//发现plist不管是否为空,头插的方法都一样
		//why 头插都需要改变phead指向
		SLTNode* newnode = BuySListNode(x);
		newnode->next = *pphead;
		*pphead = newnode;
	}
	else
	{	//不包含空链表的尾插和中间插
		SLTNode* newnode = BuySListNode(x);
		SLTNode* prev = *pphead;
		while (prev->next != pos)
		{
			prev = prev->next;
		}
		
		//pos中已经保存着prev中的指向的下一个节点地址
		prev->next = newnode;
		newnode->next = pos;
		
	}
}
方法2:
/ 在链表中插入节点    positon
void insert(struct Node** headRef, int position, int value) {
    // 创建新节点并为其分配内存
    struct Node* newNode = (struct Node*)malloc(sizeof(struct Node));
    if (newNode == NULL) {
        printf("内存分配失败。\n");
        return;
    }
    newNode->data = value;

    // 如果要插入的位置是链表的头部或链表为空
    if (*headRef == NULL || position == 0) {
        newNode->next = *headRef;
        *headRef = newNode;
        return;
    }

    struct Node* current = *headRef;
    int count = 1;
    // 找到要插入位置的前一个节点
    while (current->next != NULL && count < position) {
        current = current->next;
        count++;
    }

    // 在指定位置插入节点
    newNode->next = current->next;
    current->next = newNode;
}
 

2.2.2 单链表尾插

tail为空指针的前一个节点 尾节点指向NULL 即 tail->next

// 单链表尾插
void SLTPushBack(SLTNode** pphead, SLTDataType x)
{
    assert(pphead);//pphead是phead的地址,不能为空
 
	SLTNode* newnode = BuySLTNode(x);
	//找尾(尾插之前先找到尾)
	if (*pphead == NULL)//若链表为空
	{
		*pphead = newnode;//把新节点作为头结点
	}
	else//若链表不为空
	{
		SLTNode* tail = *pphead;
		while (tail->next != NULL)   
		//tail为空指针的前一个节点  //尾节点指向NULL  即tail->next
        //对于不为空的链表:尾插的本质是原尾结点要存新尾结点的地址
		{
			tail = tail->next;
		}
		tail->next = newnode;//挪动的指针指向新节点  也就是挪动的指针等于新节点的指针
	}
}

2.2.3 单链表头插

void SListPushFront(SLTNode** pphead, SLTDataType x)
{
	//发现plist不管是否为空,头插的方法都一样
	//why 头插都需要改变phead指向
	SLTNode* newnode = BuySListNode(x);
	newnode->next = *pphead;
	*pphead = newnode;
}

2.3 单链表节点删除

2.3.1 单链表指定位置删除

方法一
// 删除pos位置的值
void SListErase(SLTNode** pphead, SLTNode* pos)
{
	assert(*pphead);//防止链表为空,还删除节点
	if (pos == *pphead)
	{	
		SLTNode* next = (*pphead)->next;
		//free(节点之前,需要先找到下一个节点,之后才能删除)
		free(*pphead);
		*pphead = next;
	}
	else
	{
		SLTNode* prev = *pphead;
		while (prev->next != pos)
		{
			prev = prev->next;
		}

		prev->next = pos->next;
		free(pos);
	}
}
方法二
// 在链表中删除节点
void delete(struct Node** headRef, int value) {
    struct Node* current = *headRef;
    struct Node* prev = NULL;

    // 处理头节点为目标节点的情况
    if (current != NULL && current->data == value) {
        *headRef = current->next;
        free(current);
        return;
    }

    // 遍历链表找到要删除的节点
    while (current != NULL && current->data != value) {
        prev = current;
        current = current->next;
    }

    // 如果找到了目标节点,则删除它
    if (current != NULL) {
        prev->next = current->next;
        free(current);
    }
}

2.3.1 单链表头删

void SListPopFront(SLTNode** pphead)
{
	SLTNode* next = (*pphead)->next;
	//free(节点之前,需要先找到下一个节点,之后才能删除)
	free(*pphead);

	*pphead = next;
}

2.3.1 单链表尾删

void SListPopBack(SLTNode** pphead)
{
	// 1、空
	// 2、一个节点
	// 3、一个以上的节点
	if (*pphead == NULL)
	{
		return;
	}
	else if ((*pphead)->next == NULL)
	{
		free(*pphead);
		*pphead = NULL;
	}
	else
	{
		SLTNode* prev = NULL;
		SLTNode* tail = *pphead;
		while (tail->next != NULL)
		{
			prev = tail;
			tail = tail->next;
		}

		free(tail);
		prev->next = NULL;//尾删之后,前一个节点要置空
	}
}

##2.4 查找某个值在链表中位置,返回该地址,找不到返回NULL

SLTNode* SListFind(SLTNode* phead, SLTDataType x)
{
	SListNode* cur = phead;
	//while (cur != NULL)
	while (cur)
	{
		if (cur->data == x)
		{
			return cur;
		}

		cur = cur->next;
	}

	return NULL;
}

2.4 链表节点的释放

void SListDestroy(SLTNode** pphead)
{
	SLTNode* current = *pphead;
	SLTNode* cur_next;
	while (current != NULL)//等价于while(current)
	{
		cur_next = current->next;
		free(current);
		current = cur_next;
	}

经典单链表例题:

1.逆序 1>2>3>4>5 变成 5>4>3>2>1

需要定义三个指针变量,记录当前节点,前一个节点,和后移节点的地址

在这里插入图片描述

struct ListNOde* reverselist(struct ListNode* head)
{
struct ListNode* n1,*n2,*n3;
n1=NULL;
n3=head->next;
while(n2==NULL) //循环终止条件
{
	n2->next=n1;
	n2=n3;
	n3=n3->next;
}
  • 22
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值