数据结构——单向无头不循环链表

注意:本文章主要实现的是单向无头不循环链表

一、链表的定义和结构

**1、链表的结构:**在结构上是线性的

2、链表的定义: 链表其实就像火车车厢一样,一个接一个的串起来。

所以链表在逻辑上是线性的,在物理上是非线性的,因为链表是一个一个的节点串起来,空间上不是连续的,所以在物理上是非线性的

链表的节点又分为两部分:

1、数据域:存储具体数据的成员

2、指针域:存储下一个节点的成员

二、链表的分类

链表可以分为三大类:

1、带头或不带头

不带头:链表的第一个节点就是首元节点

带头:链表的第一个节点是哨兵节点(头节点),头节点是没有有效的数据,但是头节点会指向首元节点

2、单向和双向

单向就是只有指向下一个节点的指针

双向不止有指向下一个节点的指针还有指向上一个节点的指针

如下图所示:

3、循环和不循环

不循环:尾节点不指向第一个存储有效元素的节点

循环:尾节点会指向第一个存储有效元素的节点,形成闭环

三、链表的核心功能

1、链表的创建、动态申请节点、销毁和打印

1.1链表结构的创建

cpp 复制代码
typedef int DataType;
typedef struct SListNode
{
	DataType date;//数据域
	struct SListNode* next;//指针域
}SLTNode;

1.2、链表节点的动态申请并存储数据

cpp 复制代码
SLTNode* StlBuyNode(DataType x)
{
	SLTNode* pcur = (SLTNode*)malloc(sizeof(SLTNode));
	if (pcur == NULL)
	{
		perror("malloc");
		exit(1);
	}
	pcur->date = x;
	pcur->next = NULL;
	return pcur;
}

1.3、链表的打印

打印链表的原理:从头对链表进行遍历并打印它的数据域,当链表的当前节点为空了就说明链表已经遍历完了

cpp 复制代码
void SltPrint(SLTNode* phead)
{
	SLTNode* pcur = phead;
	while (pcur)
	{
		printf("%d->",pcur->date);
		pcur = pcur->next;
	}
	printf("NULL\n");
}

1.4、链表的销毁

链表的销毁其实和链表的打印是一个道理,都是通过对链表的遍历来实现的

cpp 复制代码
void SLTDestory(SLTNode** pphead)
{
	assert(pphead);
	SLTNode* pcur = *pphead;
	while (pcur)
	{
		SLTNode* pnext = pcur->next;
		free(pcur);
		pcur = pnext;
	}
	*pphead = NULL;
}

2、链表的增加、删除和查找

2.1、链表的尾插

cpp 复制代码
void StlPushback(SLTNode** pphead, DataType x)
{
	assert(pphead);
	SLTNode* plist = StlBuyNode(x);
	if (*pphead == NULL)//当链表为空时,插入节点就是首元节点
	{
	*pphead  = plist;
	}
	else
//当链表不为空时,要先找到最后一个节点(尾节点),再让最后一个节点的指针域指向需要插入节点的地址
	{	
		//找尾
		SLTNode* pcur = *pphead;
		while (pcur->next)
		{
			pcur = pcur->next;
		}
		pcur->next = plist;
	}
}

2.2、链表的头插

头插:让被插入的节点指向首元节点,被插入节点就成了新的首元节点

cpp 复制代码
void StlPushFront(SLTNode** pphead, DataType x)
{
	assert(pphead);
	SLTNode* plist = StlBuyNode(x);
	plist->next = *pphead;
	*pphead = plist;
}

2.3、链表的尾删

cpp 复制代码
void StlPopback(SLTNode** pphead)
{
	assert(*pphead&&pphead);//链表不能为空
	//有两种情况
	//链表只有一个节点
	
	if ((*pphead)->next == NULL)
	{
		free(*pphead);
		*pphead = NULL;
	}
	//链表有多个节点
	else
	{
		SLTNode* prev = *pphead;//被尾删的前一个节点
		SLTNode* pcur = *pphead;//被尾删的节点
    //找尾
		while (pcur->next)
		{
			prev = pcur;//保存当前节点
			pcur = pcur->next;//不满足条件则继续遍历链表
		}
    //满足条件时prev里面存储的就是被删节点的前节点
    //而pcur就是被删除节点
		free(pcur);
		pcur = NULL;
		prev->next = NULL;
//删除完了之后我们需要使被删除节点的前驱结点的指针域指向NULL保持链表的完整性
	}
}

2.4、链表的头删

头删:删除首元节点,使首元节点的后继节点成为新的首元节点

cpp 复制代码
void StlPopFront(SLTNode** pphead)
{
	assert(pphead&&*pphead);//只要是删除,就都会判断被删除的容器是否为空
	SLTNode* pcur = (*pphead) -> next;
	free(*pphead);
	*pphead = pcur;
}

2.5、链表的查找

cpp 复制代码
SLTNode* SLTFind(SLTNode* phead, DataType x)
{
	SLTNode* pcur = phead;
	while ( pcur != NULL)
	{
		if (pcur ->date	== x)
		{
			printf("找到了\n");
			return pcur;
		}
		pcur = pcur->next;
	}
	printf("没找到\n");
	return NULL;
}

3、链表在指定位置之后的增加和删除

3.1、在指定位置之后插入数据

cpp 复制代码
void SLTInsertAfter(SLTNode* pos, DataType x)
{
	assert(pos);//首先pos节点不能为空
	SLTNode* pcur = StlBuyNode(x);//申请一个新节点
	pcur->next = pos->next;//使新节点的指针域指向,pos节点的后继节点的地址
	pos->next = pcur;//之后再时pos节点的指针域指向新节点的地址
}

3.2、在指定位置之后删除数据

cpp 复制代码
void SLTEraseAfter(SLTNode* pos)
{
	assert(pos&&pos->next);
	SLTNode* pcur = pos->next;
	pos->next = pcur->next;
	free(pcur);
	pcur = NULL;
}

四、链表的代码实现

首先我们要先创建好三个文件

SList.h:头文件

SList.c:链表功能实现文件

SList_test.c:链表测试文件

SList.h代码如下:

cpp 复制代码
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
typedef int DataType;
typedef struct SListNode
{
	DataType date;
	struct SListNode* next;
}SLTNode;

//打印单链表
void SltPrint(SLTNode* phead);

//尾插
void StlPushback(SLTNode** pphead,DataType x);

//创建新的空间
SLTNode* StlBuyNode(DataType x);

//头插
void StlPushFront(SLTNode** pphead, DataType x);

//尾删
void StlPopback(SLTNode** pphead);

//头删
void StlPopFront(SLTNode** pphead);

//销毁链表
void SLTDestory(SLTNode** pphead);

//在指定位置之前插入
void SLTInsert(SLTNode** pphead, SLTNode* pos, DataType x);

//删除pos节点
void  SLTErase(SLTNode** pphead, SLTNode* pos);

//在指定位置之后插入
void SLTInsertAfter(SLTNode* pos, DataType x);

//删除pos之后的节点
void SLTEraseAfter(SLTNode* pos);

//查找
SLTNode* SLTFind(SLTNode* phead, DataType x);

SList.c文件代码如下:

cpp 复制代码
#define _CRT_SECURE_NO_WARNINGS
#include"SList.h"
//打印链表
void SltPrint(SLTNode* phead)
{
	SLTNode* pcur = phead;
	while (pcur)
	{
		printf("%d->",pcur->date);
		pcur = pcur->next;
	}
	printf("NULL\n");
}

//创建空间并赋值
SLTNode* StlBuyNode(DataType x)
{
	SLTNode* pcur = (SLTNode*)malloc(sizeof(SLTNode));
	if (pcur == NULL)
	{
		perror("malloc");
		exit(1);
	}
	pcur->date = x;
	pcur->next = NULL;
	return pcur;
}

//尾插
void StlPushback(SLTNode** pphead, DataType x)
{
	assert(pphead);
	SLTNode* plist = StlBuyNode(x);
	if (*pphead == NULL)
	{
	*pphead  = plist;
	}
	else
	{	
		//找尾
		SLTNode* pcur = *pphead;
		while (pcur->next)
		{
			pcur = pcur->next;
		}
		pcur->next = plist;
	}
}
//头插
void StlPushFront(SLTNode** pphead, DataType x)
{
	assert(pphead);
	SLTNode* plist = StlBuyNode(x);
	plist->next = *pphead;
	*pphead = plist;
}
//尾删
void StlPopback(SLTNode** pphead)
{
	assert(*pphead&&pphead);
	//有两种情况
	//链表只有一个节点
	
	if ((*pphead)->next == NULL)
	{
		free(*pphead);
		*pphead = NULL;
	}
	//链表有多个节点
	else
	{
		SLTNode* prev = *pphead;
		SLTNode* pcur = *pphead;
		while (pcur->next)
		{
			prev = pcur;
			pcur = pcur->next;
		}
		free(pcur);
		pcur = NULL;
		prev->next = NULL;
	}
}
//头删
void StlPopFront(SLTNode** pphead)
{
	assert(pphead&&*pphead);
	SLTNode* pcur = (*pphead) -> next;
	free(*pphead);
	*pphead = pcur;
}


//指定位置插入
void SLTInsert(SLTNode** pphead, SLTNode* pos, DataType x)
{
	assert(pphead && *pphead);//当头节点为NULL时,无法插入,因为没有位置可插入
	assert(pos);
	if (pos == *pphead)//当插入位置为头结点时,说明是头插
	{
		StlPushFront(pphead, x);
	}
	else
	{
		SLTNode* plist = StlBuyNode(x);
		SLTNode* pcur = *pphead;
		while (pcur->next != pos)
		{
			pcur = pcur->next;
		}
		plist->next = pos;
		pcur->next = plist;
	}
}

//删除pos节点
void  SLTErase(SLTNode** pphead, SLTNode* pos)
{
	assert(pphead&&pos&&*pphead);
	if (*pphead == pos)
	{
		StlPopFront(pphead);
	}
	else
	{
		SLTNode* pcur = *pphead;
		while (pcur->next != pos)
		{
			pcur = pcur->next;
		}
		pcur ->next = pos->next;
		free(pos);
		pos = NULL;
	}
}

//在指定位置之后插入数据
void SLTInsertAfter(SLTNode* pos, DataType x)
{
	assert(pos);
	SLTNode* pcur = StlBuyNode(x);
	pcur->next = pos->next;
	pos->next = pcur;
}

//删除pos之后的节点
void SLTEraseAfter(SLTNode* pos)
{
	assert(pos&&pos->next);
	SLTNode* pcur = pos->next;
	pos->next = pcur->next;
	free(pcur);
	pcur = NULL;
}

//查找
SLTNode* SLTFind(SLTNode* phead, DataType x)
{
	SLTNode* pcur = phead;
	while ( pcur != NULL)
	{
		if (pcur ->date	== x)
		{
			printf("找到了\n");
			return pcur;
		}
		pcur = pcur->next;
	}
	printf("没找到\n");
	return NULL;
}

//链表的销毁
void SLTDestory(SLTNode** pphead)
{
	assert(pphead);
	SLTNode* pcur = *pphead;
	while (pcur)
	{
		SLTNode* pnext = pcur->next;
		free(pcur);
		pcur = pnext;
	}
	*pphead = NULL;
}

SList_test.c文件代码如下:

cpp 复制代码
#define _CRT_SECURE_NO_WARNINGS
#include "SList.h" 

void Test() {
    SLTNode* pList = NULL;

    printf("\n1. 尾插测试:插入 3、1\n");
    StlPushback(&pList, 3);
    StlPushback(&pList, 1);
    printf("尾插后链表:");
    SltPrint(pList);

   
    printf("\n2. 头插测试:插入 4、2\n");
    StlPushFront(&pList, 4);
    StlPushFront(&pList, 2);
    printf("头插后链表:");
    SltPrint(pList);  
  
    printf("\n3. 查找测试:查找 4、5\n");
    SLTNode* findNode1 = SLTFind(pList, 4);
    SLTNode* findNode2 = SLTFind(pList, 5);


    printf("\n4. 指定位置之后插入测试:在 4 之后插入 6\n");
    SLTNode* findNode3 = SLTFind(pList, 4);  // 重新查找 4
    if (findNode3 != NULL) {
        SLTInsertAfter(findNode3, 6);
    }
    printf("插入后链表:");
    SltPrint(pList);

    
    printf("\n5. 指定位置之后删除测试:删除 4 之后的 6\n");
    if (findNode3 != NULL) {
        SLTEraseAfter(findNode3);
    }
    printf("删除后链表:");
    SltPrint(pList);  


    printf("\n6. 尾删测试:删除末尾节点 1\n");
    StlPopback(&pList);
    printf("尾删后链表:");
    SltPrint(pList); 

    printf("\n7. 头删测试:删除头部节点 2\n");
    StlPopFront(&pList);
    printf("头删后链表:");
    SltPrint(pList); 

    printf("\n8. 指定位置删除测试:删除节点 4\n");
    SLTNode* findNode4 = SLTFind(pList, 4);  // 查找 4
    if (findNode4 != NULL) {
        SLTErase(&pList, findNode4);
    }
    printf("删除后链表:");
    SltPrint(pList);

  
    printf("\n9. 销毁链表测试:\n");
    SLTDestory(&pList);
    SltPrint(pList); 


}

int main() {
    Test();  // 执行所有测试
    return 0;
}
相关推荐
ss2734 小时前
阻塞队列:三组核心方法全对比
java·数据结构·算法
埃伊蟹黄面4 小时前
算法 --- hash
数据结构·c++·算法·leetcode
南棱笑笑生4 小时前
20251215给飞凌OK3588-C开发板适配Rockchip原厂的Buildroot【linux-5.10】后调通typeC1接口
linux·c语言·开发语言·rockchip
fei_sun5 小时前
【数据结构】2025年真题
数据结构
我在人间贩卖青春5 小时前
线性表之队列
数据结构·队列
1024小神5 小时前
swift中 列表、字典、集合、元祖 常用的方法
数据结构·算法·swift
star learning white5 小时前
xm C语言12
服务器·c语言·前端
超级大福宝5 小时前
C++中1 << 31 - 1相当于INT_MAX吗?
c语言·c++
Java水解5 小时前
基于Rust实现爬取 GitHub Trending 热门仓库
数据结构·后端