初阶数据结构【TOP】3. 单链表


前言

之前笔者介绍了初阶数据结构的基础知识 , 那么链表也是占比很重要的一个知识,让我们一起领略吧 !


一、什么是链表 ?

概念:链表是一种物理存储结构上非连续 、非顺序的存储结构 ,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。

:简单来说就是 链表在物理结构上不一定连续 , 但逻辑结构是连续的 。这里的物理结构呢就是指数据存储的方式 。例如:顺序表数据存储一定是连续的

图示:
链表展示图
以上便是物理结构不连续 , 逻辑结构连续 。


二、链表的分析

节点:链表是由一个一个节点构成 , 上图每一个圆圈代表一个节点 。节点中包含:存储的数据 、指向下一个节点的指针 。

以上的节点是我们申请出来的空间 , 因为不涉及扩容所以用到 malloc 函数。点击可前往官网学习 malloc 函数!

综上,要写出一个链表就 必须有节点 , 一个一个节点就构成了一个完整的链表。

图例:
链表展示图
以上就是一个完整的单向链表 , Next 指针会指向下一个存储数据的节点 , 这样就让乱序的数据顺利的连接。


三、链表的实现

链表实现相关接口

1. 单链表的头插   
2. 单链表的头删
3. 单链表的尾插
4. 单链表的尾删
5. 单链表的查找 
6. 单链表的打印
7. 单链表在 pos 之后插入 (pos 前一个节点后插入数据)
8. 单链表删除 pos 之后的值
9. 单链表的销毁

:思考 : 1. 为什么不在 pos 之前插入数据 ? 2. 为什么不删除 pos 位置的值 ?

为什么不在 pos 之前插入数据 ?
首先,在链表中的插入很简单 , 不需要移动数据 , 只需改变指针的指向即可完成。 pos 之后插入数据也就意味这在 pos 前一个节点后插入数据 , 若要在 pos 之前插入数据 , 那么就要知道 pos 之前的前一个节点 , 这样就得遍历链表 , 时间复杂度为 O(N). 这样就会增加代码得复杂性和效率 。 然而 , 在 pos 之后呢 , 我们直接改指针的指向就可以达到目的 , 不需要再次遍历链表了 。

为什么不删除 pos 位置的值 ?
这个与上述同理 , 我们不需要再次遍历链表增加代码的复杂度和效率 。


单链表的实现

申请节点

分析

首先链表的第一步就是申请相关节点 ,还是三个相关文件 : SList.c 、SList.h 、test.c

单链表接口的声明

#pragma once

#include <stdio.h>
#include <assert.h>
#include <stdlib.h>

typedef int SNodeDateType;

typedef struct SListNode
{
	SNodeDateType data;     // 存储的数据
	struct SListNode* Next; // 指向下一个节点的指针
}SLNode;

//申请节点
SLNode* ByNode(SNodeDateType x);
// 单链表打印
void SListPrint(SLNode* plist);
// 单链表尾插
void SListPushBack(SLNode** pplist, SNodeDateType x);
// 单链表的头插
void SListPushFront(SLNode** pplist, SNodeDateType x);
// 单链表的尾删
void SListPopBack(SLNode** pplist);
// 单链表头删
void SListPopFront(SLNode** pplist);
// 单链表查找
SLNode* SListFind(SLNode* plist, SNodeDateType x);
// 单链表在pos位置之后插入x
void SListInsertAfter(SLNode* pos, SNodeDateType x);
// 单链表删除pos位置之后的值
void SListEraseAfter(SLNode* pos);


因为之后的接口使用需要申请节点 , 所以申请节点函数封装一个函数 , 后续使用只需调用即可。

申请节点

#include "SList.h"

//申请节点
SLNode* ByNode(SNodeDateType x)
{
	SLNode* newnode = (SLNode*)malloc(sizeof(SLNode));
	
	if (newnode == NULL)
	{
		perror("malloc fail!");
		exit(1);
	}
	//申请成功
	newnode->data = x;
	newnode->Next = NULL;

	return newnode;

}

尾插

传参分析

:链表的声明中传参用的是二级指针 , 为什么呢?

给出测试代码:

#include "SList.h"

int main()
{
	SLNode* list = NULL;

	//头插
	SLTPushFront(list, 2);
	SListPrint(list);
	return 0;

}

头插代码

//单链表的头插
void SLTPushFront(SLNode* phead, SNodeDateType x)
{
	//assert(phead);

	SLNode* newnode = ByNode(x);

	newnode->Next = phead;
	phead = newnode; //新节点成为头节点

}

调试图以及运行结果

解释图
解释图
以上调试后便可知道结果了 , 头插代码传参是一级指针 , phead 进行头插不会影响list 这个指针 ,因为: 形参是实参的一份临时拷贝 ,需要改变实参形参部分就必须传地址 , 那么一级指针的地址就是二级指针 。

尾插

// 单链表尾插
void SListPushBack(SLNode** pplist, SNodeDateType x)
{
	assert(pplist);
	SLNode* ptail = *pplist;
	
	//申请节点
	 SLNode* node = ByNode(x);
	 //链表为空
	 if(ptail == NULL)
	 {
		 *pplist = node;
	 }
	 else
	 {
		 //找尾
		 while(ptail->Next)
		 {
			 ptail = ptail->Next;
		 }
		 //ptail 就是尾
		 ptail->Next = node;
	 }
}

头插

头插

// 单链表的头插
void SListPushFront(SLNode** pplist, SNodeDateType x)
{
	assert(pplist);
	//申请节点
	SLNode* node = ByNode(x);
	
	node->Next = *pplist;
	*pplist = node;
}

尾删

// 单链表的尾删
void SListPopBack(SLNode** pplist)
{
	//链表不能为空
	assert(pplist && (*pplist));
	//链表只有一个节点
	if ((*pplist)->Next == NULL) //-> 优先级高于*
	{
		free(*pplist);
		*pplist = NULL;
	}
	else {
		//链表有多个节点

		SLNode* prev = *pplist;
		SLNode* ptail = *pplist;
		while (ptail->Next)
		{
			prev = ptail;
			ptail = ptail->Next;
		}
		//prev ptail
		free(ptail);
		ptail = NULL;
		prev->Next = NULL;
	}
}

头删

//单链表头删
void SListPopFront(SLNode** pplist)
{
	//链表不能为空
	assert(pplist && *pplist);

	SLNode* Next = (*pplist)->Next; //-> 优先级高于*
	free(*pplist);
	*pplist = Next;
}

打印

// 单链表打印
void SListPrint(SLNode* plist)
{
	SLNode* pur = plist;
	//遍历链表逐一打印
	while(pur)
	{
		printf("%d->", pur->data);
		pur = pur->Next;
	}
	printf(" NULL\n");
}

查找

// 单链表查找
SLNode* SListFind(SLNode* plist, SNodeDateType x)
{
	SLNode* pur = plist;
	while (pur)//等价于pcur != NULL
	{
		if (pur->data == x)
		{
			return pur;
		}
		pur = pur->Next;
	}
	//pur == NULL
	return NULL;
}

单链表在pos位置之后插入

void SListInsertAfter(SLNode* pos, SNodeDateType x)
{
	assert(pos);

	SLNode* node = SLTBuyNode(x);
	//pos -> newnode -> pos->next
	node->Next = pos->Next;
	pos->Next = node;
}

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

void SListEraseAfter(SLNode* pos)
{
	assert(pos && pos->Next);
	SLNode* del = pos->Next;
	//pos del del->next
	pos->Next = del->Next;
	free(del);
	del = NULL;
}

销毁链表

void SListDesTroy(SLNode** pplist)
{
	assert(pplist && *pplist);

	SLNode* pur = *pplist;
	while (pur)
	{
		SLNode* next = pur->Next;
		free(pur);
		pur = next;
	}
	
	*pplist = NULL;
}

四、整体代码

#define  _CRT_SECURE_NO_WARNINGS

#include "SList.h"

//申请节点
SLNode* ByNode(SNodeDateType x)
{
	SLNode* newnode = (SLNode*)malloc(sizeof(SLNode));
	
	if (newnode == NULL)
	{
		perror("malloc fail!");
		exit(1);
	}
	//申请成功
	newnode->data = x;
	newnode->Next = NULL;

	return newnode;

}

//
// 单链表打印
void SListPrint(SLNode* plist)
{
	SLNode* pur = plist;
	//遍历链表逐一打印
	while(pur)
	{
		printf("%d->", pur->data);
		pur = pur->Next;
	}
	printf(" NULL\n");
}
// 单链表尾插
void SListPushBack(SLNode** pplist, SNodeDateType x)
{
	assert(pplist);
	SLNode* ptail = *pplist;
	
	//申请节点
	 SLNode* node = ByNode(x);
	 //链表为空
	 if(ptail == NULL)
	 {
		 *pplist = node;
	 }
	 else
	 {
		 //找尾
		 while(ptail->Next)
		 {
			 ptail = ptail->Next;
		 }
		 //ptail 就是尾
		 ptail->Next = node;
	 }
}


// 单链表的头插
void SListPushFront(SLNode** pplist, SNodeDateType x)
{
	assert(pplist);
	//申请节点
	SLNode* node = ByNode(x);
	
	node->Next = *pplist;
	*pplist = node;
}
// 单链表的尾删
void SListPopBack(SLNode** pplist)
{
	//链表不能为空
	assert(pplist && (*pplist));
	//链表只有一个节点
	if ((*pplist)->Next == NULL) //-> 优先级高于*
	{
		free(*pplist);
		*pplist = NULL;
	}
	else {
		//链表有多个节点

		SLNode* prev = *pplist;
		SLNode* ptail = *pplist;
		while (ptail->Next)
		{
			prev = ptail;
			ptail = ptail->Next;
		}
		//prev ptail
		free(ptail);
		ptail = NULL;
		prev->Next = NULL;
	}
}
//单链表头删
void SListPopFront(SLNode** pplist)
{
	//链表不能为空
	assert(pplist && *pplist);

	SLNode* Next = (*pplist)->Next; //-> 优先级高于*
	free(*pplist);
	*pplist = Next;
}
// 单链表查找
SLNode* SListFind(SLNode* plist, SNodeDateType x)
{
	SLNode* pur = plist;
	while (pur)//等价于pcur != NULL
	{
		if (pur->data == x)
		{
			return pur;
		}
		pur = pur->Next;
	}
	//pur == NULL
	return NULL;
}
// 单链表在pos位置之后插入x
void SListInsertAfter(SLNode* pos, SNodeDateType x)
{
	assert(pos);

	SLNode* node = SLTBuyNode(x);
	//pos -> newnode -> pos->next
	node->Next = pos->Next;
	pos->Next = node;
}
// 单链表删除pos位置之后的值
void SListEraseAfter(SLNode* pos)
{
	assert(pos && pos->Next);
	SLNode* del = pos->Next;
	//pos del del->next
	pos->Next = del->Next;
	free(del);
	del = NULL;
}
//销毁链表
void SListDesTroy(SLNode** pplist)
{
	assert(pplist && *pplist);

	SLNode* pur = *pplist;
	while (pur)
	{
		SLNode* next = pur->Next;
		free(pur);
		pur = next;
	}
	
	*pplist = NULL;
}



测试代码

#include "SList.h"

int main()
{
	SLNode* list = NULL;

	SListPushBack(&list, 1);
	SListPushBack(&list, 2);
	SListPushBack(&list, 3);
	SListPrint(list);

	SListPushFront(&list, 4);
	SListPrint(list);
	SListPopBack(&list);
	SListPrint(list);
	SListPopFront(&list);
	SListPrint(list);

	return 0;

}

运行结果
运行结果


总结

以上是笔者对单链表的相关介绍,后续还会更多关于数据结构的知识 持续关注哦 !

  • 10
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值