单链表的实现(数据结构)

一. 单链表的实现

我们在上一篇中简单的认识了链表的组成和结构,并打印出链表,那么今天就来具体实现一下单链表对于数据增加、删减、插入等。

接下来就是我们在链表中对于数据的增、删、插的实现,对于我们的链表来说在任何地方增加数据都需要来申请一个新的结点,首先呢,SLDatatype是我们更改int类型的名称之后得来的,SL是结构体的名称,我们先申请一个结构体大小的新结点node,然后再将新结点中的x的值赋给data,再让新结点的next指向NULL,申请新结点的函数写好之后我们就来写关于数据的增、删、插等,所以我们直接先来完成申请新结点的代码:

cs 复制代码
SLDatatype* SLBuyNode(SLDatatype x)
{
	SL* node = (SL*)malloc(sizeof(SL));
	if (node == NULL)
	{
		perror("malloc fail!");
		exit(1);
	}
	node->data = x;
	node->next = NULL;
	return node;
}

尾插

cs 复制代码
//尾插函数
void SLPuchBack(SL** pphead, SLDatatype x)
{
	//申请新结点
	//*pphead-->&plist
	SL* newnode = SLBuyNode(x);
	if (*pphead == NULL)
	{
		*pphead = newnode;
	}
	else
	{
        //尾结点-->新结点
	    SL* pcur = *pphead;//找尾结点
	    while (pcur->next)
	    {
		pcur = pcur->next;
	    }
	    pcur->next = newnode;
	}

}
void SLTest01()
{
	SL* plist = NULL;
	SLPuchBack(&plist, 1);
	SLprintf(plist);
	SLPuchBack(&plist, 2);
	SLPuchBack(&plist, 3);
	SLprintf(plist);
}

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

在实现让任何代码之前,我们都因该将思路理清楚,尾插该注意什么,怎么去是实现?首先一定是要找到最后一个结点,pphead是我们的头结点,我们一贯会将pphead赋给一个新的指针pcur,使用while循环找到最后一个结点,但是如果while里面是pcur的那我们不就直接跳出循环,那我们还怎么找最后一个结点呢?所以我们不如使用while(pcur->next),也就是当我们pcur指向3的时候我们的next刚好指向的是NULL,停止了循环,刚好pcur还指向尾结点,然后我们在将newnode赋值给我们的最后一个结点。在上面的代码中我们要注意的一点是SLPuchBack函数中传入的是形参,这个时候我们要使用传值调用,因为如果使用传值调用的话,形参的改变并不影响实参,所以在测试函数SLTest01中传入的是&plist,然而plist本身就是一个指针,所以我们在SLPuchBack中要使用二级指针即**pphead。

头插:对于头插就简单很多,我们只需要申请一个新的结点,然后将结点与原本的头结点连接,在让pphead指向现在新的结点。

cs 复制代码
//头插函数
void SLPuchFront(SL** pphead, SLDatatype x)
{
	assert(pphead);
	SL* newnode = SLBuyNode(x);
	newnode->next = *pphead;
	*pphead = newnode;
}

尾删:对于尾删我们并不能简单的将最后一个结点删除,因为这样会让前面一个指针变成野指针,可以把最后一个结点前面的结点的next指向NULL,然后将最后一个结点释放掉。

cs 复制代码
//尾删函数
void SLPopBack(SL** pphead)
{
	assert(pphead && *pphead);
	//假如原本只有一个结点
	if ((*pphead)->next == NULL)
	{
		free(*pphead);
		*pphead = NULL;
	}
	else
	{
       SL* pcur = *pphead;
	   SL* front = NULL;
	   while (pcur->next)
	   {
		  front = pcur;
		  pcur = pcur->next;
	   }
       front->next = NULL;
	   free(pcur);
	   pcur = NULL;
	}

头删

cs 复制代码
//头删函数
void SLPopFront(SL** pphead)
{
	assert(pphead && *pphead);
	SL* pcur = (*pphead)->next;
	free(*pphead);
	*pphead = pcur;
}

在指定位置之前插入数据

cs 复制代码
void SLInsertFront(SL** pphead, SL* pos, SLDatatype x)
{
	assert(pphead && pos);
	SL* newnode = SLBuyNode(x);
	SL* pcur = *pphead;
	if (pos == *pphead)
	{
		newnode->next = pos;
		*pphead = newnode;
	}
	else
	{
		while (pcur->next != pos)
		{
			pcur = pcur->next;
		}
		newnode->next = pos;
		pcur->next = newnode;
	}

}

在指定位置之后插入数据

cs 复制代码
void SLInsertAfert(SL* pos, SLDatatype x)
{
	assert(pos);
	SL* newnode = SLBuyNode(x);
	newnode->next = pos->next;
	pos->next = newnode; 
}

删除pos结点

cs 复制代码
void SLErase(SL** pphead, SL* pos)
{
	assert(pphead && *pphead);
	assert(pos);
	SL* pcur = *pphead;
	if (pos == *pphead)
	{
		SLPopFront(pphead);
	}
	else
	{
		while (pcur->next != pos)
		{
			pcur = pcur->next;

		}
		pcur->next = pos->next;
		free(pos);
		pos = NULL;
	}
	

删除pos之后的结点

cs 复制代码
void SLEraseAfert(SL* pos)
{
	assert(pos&&pos->next);
	SL* deal = pos->next;
	pos->next = pos->next->next;
	free(deal);
	deal = NULL;
}

销毁链表

cs 复制代码
void SLDestroy(SL** pphead)
{
	SL* pcur = *pphead;
	while (pcur)
	{
		SL* next = pcur->next;
		free(pcur);
		pcur = next;
	}
	*pphead = NULL;
}

上面关于链表的指定位置插入、删除等算法,我们要注意的就是在我们更改链表中的数据的时候,要更改的这个数据会影响其他的数据吗?如果影响我们应该怎么做,提前设置好几个指针变量还是其他的办法,寻找某一个结点的时候while循环的条件是什么,这些我们都要注意,上面已经给出较为详细的代码,大家可以参考,另外就是注意free指针之后一定要将其置为空指针NULL,单链表完成之后我们就要拿一些算法题来练手,如何使用单链表来完成一些算法题。

二. 算法题

移除链表元素

https://leetcode.cn/problems/remove-linked-list-elements/description/

这个题目是移除链表元素,我们有两个思路一个就是利用我们原本的单链表对指定位置的结点进行删除,还有就是创建一个新的链表然后将符合的元素移到新的链表中,我们可以来实现第二中思路: 创建一个新链表,遍历原链表,将不等于给定值 val 的节点依次添加到新链表中。

  1. 创建新链表的哑节点(dummy node):
  • 目的是简化操作,避免处理新链表头节点为空的特殊情况。
  • 例如,可以创建一个值为 0 的哑节点,将其 next 指针初始化为 null 。
  1. 遍历原链表:
  • 从原链表的头节点 head 开始遍历。
  • 检查当前节点的值是否等于 val 。
  • 如果不等于 val ,则将该节点添加到新链表中。
  1. 添加节点到新链表:
  • 将原链表中不等于 val 的节点的 next 指针指向新链表的末尾节点的 next 。
  • 更新新链表的末尾节点为该节点。
  1. 返回新链表的头节点:
  • 新链表的头节点为哑节点的 next 。
    通过以上步骤,就可以使用创建新链表的方法删除原链表中所有值为 val 的节点,并返回新的头节点。
cs 复制代码
#include <stdio.h>
#include <stdlib.h>

struct ListNode {
    int val;
    struct ListNode* next;
};

struct ListNode* createNode(int val) {
    struct ListNode* newNode = (struct ListNode*)malloc(sizeof(struct ListNode));
    newNode->val = val;
    newNode->next = NULL;
    return newNode;
}

struct ListNode* removeElements(struct ListNode* head, int val) {
    struct ListNode* dummy = createNode(0);
    struct ListNode* curr = dummy;
    while (head) {
        if (head->val!= val) {
            curr->next = head;
            curr = curr->next;
        }
        head = head->next;
    }
    curr->next = NULL;
    return dummy->next;
}

void printList(struct ListNode* head) {
    while (head) {
        printf("%d -> ", head->val);
        head = head->next;
    }
    printf("NULL\n");
}

void freeList(struct ListNode* head) {
    struct ListNode* temp;
    while (head) {
        temp = head;
        head = head->next;
        free(temp);
    }
}
int main() {
    // 创建链表 1->2->6->3->4->5->6
    struct ListNode* head = createNode(1);
    head->next = createNode(2);
    head->next->next = createNode(6);
    head->next->next->next = createNode(3);
    head->next->next->next->next = createNode(4);
    head->next->next->next->next->next = createNode(5);
    head->next->next->next->next->next->next = createNode(6);

    int val = 6;
    struct ListNode* newHead = removeElements(head, val);
    printList(newHead);
    freeList(newHead);
    return 0;
}
相关推荐
无敌岩雀几秒前
C++设计模式结构型模式———桥接模式
c++·设计模式·桥接模式
Monly2110 分钟前
Java:获取HttpServletRequest请求参数
java
朱玥玥要每天学习12 分钟前
交换排序与快速排序
数据结构·算法·排序算法
非概念15 分钟前
常见的排序算法(一)
数据结构·算法·排序算法
不想睡觉的橘子君19 分钟前
【Redis】一种常见的Redis分布式锁原理简述
java·redis
Ning_.19 分钟前
力扣第39题:组合总和(C语言解法)
c语言·算法·leetcode
麻由由哒哟37 分钟前
CSP-J2023T4 旅游巴士(同余最短路)
c++·算法
吴冰_hogan38 分钟前
spring-mvc源码
java·spring·mvc
点点滴滴的记录1 小时前
56. 数组中只出现一次的数字
java·数据结构·算法
Fms_Sa1 小时前
数据结构第七章-顺序查找(顺序存储结构+链式存储结构)
数据结构·算法