链表SListNode(无哨兵位)c语言实现

本文详细介绍了链表(特别是SListNode)的概念,与顺序表的区别,以及单链表的接口实现,包括动态节点申请、打印、尾插、头插、尾删、头删、查找、插入和删除操作的函数和示例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

数据结构-链表SListNode详解

1.链表与顺序表的关系

链表也是顺序表的一种,顺序表是相同元素,逻辑上连续排列的一种数据结构。但是在物理上,链表的存储是随机的而不是连续的。

2.链表与顺序表的对比

顺序表的缺点

  • 进行指定位置插入或者删除时,需要移动元素,花费大量时间
  • 进行扩容时,扩大了浪费,少了需要多次扩容

链表的优点

  • 进行指定位置插入或者删除时,不需要进行元素的移动
  • 物理上不是连续的,不需要进行扩容。添加一次申请一次内存,不会浪费额外空间。

3.链表的接口以及实现

#pragma once
// slist.h
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
typedef int SLTDateType;
typedef struct SListNode
{
	SLTDateType data;
	struct SListNode* next;
}SListNode;

// 动态申请一个节点
SListNode* BuySListNode(SLTDateType x);
// 单链表打印
void SListPrint(SListNode* plist);
// 单链表尾插
void SListPushBack(SListNode** pplist, SLTDateType x);
// 单链表的头插
void SListPushFront(SListNode** pplist, SLTDateType x);
// 单链表的尾删
void SListPopBack(SListNode** pplist);
// 单链表头删
void SListPopFront(SListNode** pplist);
// 单链表查找
SListNode* SListFind(SListNode* plist, SLTDateType x);
// 单链表在pos位置之后插入x
// 分析思考为什么不在pos位置之前插入?
//因为只能知道指针A的next是什么,不能知道哪一个指针的next时A,是向后链接的
void SListInsertAfter(SListNode* pos, SLTDateType x);
// 单链表删除pos位置之后的值
// 分析思考为什么不删除pos位置?
//无法获得pos前一个指针
void SListEraseAfter(SListNode* pos);


3.1动态申请一个节点

SListNode* BuySListNode(SLTDateType x) {
	SListNode* p = malloc(sizeof(SListNode));
	if (p == NULL)
	{
		perror("malloc Node fail");
		return;
	}
	p->data = x;
	p->next = NULL;
	return p;
}

当链表需要添加元素的时候,才需要申请节点。这时需要手动malloc一个节点,接节点的val设为传入的参数,节点的next为NULL。返回这个节点的地址

3.2单链表打印

void SListPrint(SListNode* plist) {
	SListNode* p = plist;
	while (p!= NULL)
	{
		printf("%d->", p->data);
		p = p->next;
	}
	printf("NULL\n");
}

使用指针遍历链表,当指针指向不为空时,输出指针的val,将指针移动到next

test

image-20231211232956184

void test1(){
	SListNode* pplist = NULL;
	SListPushBack(&pplist, 1);
	SListPushBack(&pplist, 2);
	SListPushBack(&pplist, 3);
	SListPushBack(&pplist, 4);
	SListPrint(pplist);
}

3.3单链表尾插

image-20231211231207501

void SListPushBack(SListNode** pplist, SLTDateType x) {
	SListNode* phead = *pplist;
	if (phead == NULL)
	{    //链表为空时
		*pplist = BuySListNode(x);
	}
	else
	{
        //链表不为空时
		while (phead->next != NULL)
		{
			phead = phead->next;
		}
		phead->next = BuySListNode(x);
	}
}

3.4单链表的头插

头插也需要使用二级指针,而且与链表有没有元素没有关系。因为要改变链表的头就代表要改变外部的结构体指针,要改变指针的值就要使用二级指针

void SListPushFront(SListNode** pplist, SLTDateType x) {
	SListNode* p = BuySListNode(x);
	p->next = *pplist;
	*pplist = p;
}

test

void testSListPushFront() {
	SListNode* pplist = NULL;
	SListPushFront(&pplist, 1);
	SListPushFront(&pplist, 2);
	SListPushFront(&pplist, 3);
	SListPushFront(&pplist, 4);
	SListPushBack(&pplist, 5);
	SListPrint(pplist);
}

image-20231211233202959

3.5单链表的尾删

当链表只有一个值的时候,需要改变指针的指向

void SListPopBack(SListNode** pplist) {
	assert(*pplist);
	SListNode* pre = NULL;
	SListNode* tail = *pplist;
	while (tail->next) {
		pre = tail;
		tail = tail->next;
	}
	if (!pre)
	{
		free(*pplist);
		*pplist = NULL;
	}
	else
	{
		pre->next = NULL;
		free(tail);
	}
}

test

void testSListPopBack2() {
	SListNode* pplist = NULL;
	SListPushFront(&pplist, 1);
	SListPushFront(&pplist, 2);
	SListPushFront(&pplist, 3);
	SListPushFront(&pplist, 4);
	SListPrint(pplist);
	SListPopBack(&pplist);
	SListPrint(pplist);
	SListPopBack(&pplist);
	SListPrint(pplist);
	SListPopBack(&pplist);
	SListPrint(pplist);
	SListPopBack(&pplist);
	SListPrint(pplist);
}

image-20231211233417104

3.6单链表的头删

头删也要改变外面指针的指向,所以也需要二级指针

void SListPopFront(SListNode** pplist) {
	assert(*pplist);
	SListNode* p = *pplist;
	*pplist = p->next;
	free(p);
}

test

void testSListPopFront() {
	SListNode* pplist = NULL;
	SListPushFront(&pplist, 1);
	SListPushFront(&pplist, 2);
	SListPushFront(&pplist, 3);
	SListPushFront(&pplist, 4);
	SListPrint(pplist);
	SListPopFront(&pplist);
	SListPrint(pplist);
	SListPopFront(&pplist);
	SListPrint(pplist);
	SListPopFront(&pplist);
	SListPrint(pplist);
	SListPopFront(&pplist);
	SListPrint(pplist);
}

image-20231211235313919

3.7单链表的查找

查找只需要返回指针的地址,不需要进行修改,所以不需要使用二级指针,遍历链表,当x与链表的val相等时,返回节点地址。

SListNode* SListFind(SListNode* plist, SLTDateType x) {
	assert(plist);
	while (plist)
	{
		if (plist->data == x)
		{
			return plist;
		}
		plist = plist->next;
	}
	return NULL;
}

test

void testSListFind() {
	SListNode* pplist = NULL;
	SListPushFront(&pplist, 1);
	SListPushFront(&pplist, 2);
	SListPushFront(&pplist, 3);
	SListPushFront(&pplist, 4);
	printf("%p\n", SListFind(pplist, 1));
	printf("%p\n", SListFind(pplist, 2));
	printf("%p\n", SListFind(pplist, 3));
	printf("%p\n", SListFind(pplist, 4));
}

image-20231211235634057

3.8单链表在pos位置之后插入x

通过find函数获得节点的地址,然后执行在指定节点的插入

void SListInsertAfter(SListNode* pos, SLTDateType x) {
	assert(pos);
	SListNode* p = BuySListNode(x);
	if (p == NULL) {
		perror("cant buy node");
		return;
	}
	p->next = pos->next;
	pos->next = p;
}  

test

void testSListInsertAfter() {
	SListNode* pplist = NULL;
	SListPushBack(&pplist, 1);
	SListPushBack(&pplist, 3);
	SListPushBack(&pplist, 5);
	SListPushBack(&pplist, 7);
	SListPrint(pplist);
	SListInsertAfter(SListFind(pplist,1), 2);
	SListPrint(pplist);
	SListInsertAfter(SListFind(pplist, 3), 4);
	SListPrint(pplist);
	SListInsertAfter(SListFind(pplist, 5), 6);
	SListPrint(pplist);
	SListInsertAfter(SListFind(pplist, 7), 8);
	SListPrint(pplist);
}

image-20231212001746687

3.9单链表删除pos位置之后的值

因为是删除pos之后的值,永远不可能删除第一个元素,所以不用二级指针。

void SListEraseAfter(SListNode* pos) {
	assert(pos);
	SListNode* p = pos->next;
	pos->next = pos->next->next;
	free(p);
}

test

void testSListEraseAfter() {
	SListNode* pplist = NULL;
	SListPushBack(&pplist, 1);
	SListPushBack(&pplist, 2);
	SListPushBack(&pplist, 3);
	SListPushBack(&pplist, 4);
	SListPushBack(&pplist, 5);
	SListPushBack(&pplist, 6);
	SListPushBack(&pplist, 7);
	SListPushBack(&pplist, 8);
	SListPrint(pplist);
	SListEraseAfter(SListFind(pplist, 7));
	SListPrint(pplist);
	SListEraseAfter(SListFind(pplist, 5));
	SListPrint(pplist);
	SListEraseAfter(SListFind(pplist, 3));
	SListPrint(pplist);
	SListEraseAfter(SListFind(pplist, 1));
	SListPrint(pplist);
}

image-20231212001852868

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值