C语言数据结构——链表(1)

前言

这是一位即将大二的大学生(卷狗)在暑假预习数据结构时的一些学习笔记,仅供大家参考学习。水平有限,如有错误,还望多多指正。

在本篇博客中,主要讲解了在数据结构中的链表,并且给出代码讲解来实现其具体功能

链表

首先,我们要认识到这一点,链表是数据结构中最基础也是最重要的一部分,后序有很多的数据结构都是依靠链表来完成的。

定义

链表是一种离散的存储结构,所谓离散,就是指它的存储空间不连续(这一点与数组不同)
而在链表内部,存在数据域指针域两部分——数据域存放的是节点自身需要存储的内容,指针域是链表而各个节点之间可以相互访问的关键所在。
如下图,就是一个简单的链表节点
在这里插入图片描述
一些专业术语

  • 首节点 :第一个有效节点
  • 尾节点:最后一个有效节点
  • 头节点:一般会在首节点前面加一个head,叫做头节点。*头节点不存放有效数据(本文中存放的是链表元素个数),存在目的是方便于链表算法的操作
  • 头指针:指向头节点的指针变量,存放头节点地址
  • 尾指针:指向尾节点的指针变量

链表特点

  • 是离散存储的——n个节点存储空间离散分配
  • 彼此通过指针相连
  • 除了首节点和尾节点以外,每一个节点只有一个前驱节点和后继节点(针对非循环列表

分类

  • 单链表
    通俗来讲 单链表就是一个串,可以从头走到尾,各个节点之间通过指针单向连接,尾节点的指针指向NULL
    单链表结构如下图所示
    单链表示意图

  • 双链表
    与单链表不同,对于双链表而言,存在两个指针域 prenext ,既可以从头走到尾,也可以从尾走到头,节点之间相互指向,尾节点的指针指向NULL
    双链表结构如下图所示
    双链表示意图

  • 循环链表
    对于循环链表,其尾节点的指针不在指向NULL,而是指向头节点,因此构成了一个循环
    在循环链表中,可以通过任意一个节点来找到其他所有节点
    循环链表结构如下图所示
    循环单链表
    循环单链表示意图

    循环双链表
    循环单链表示意图

链表具体功能

对于链表的一些操作,我们主要实现以下四种功能

  • 初始化创建
  • 插入(头插法、尾插法)
  • 删除
  • 遍历

单链表代码实现

首先先利用结构体定义一个链表节点

typedef struct NODE
{
	int data;   //数据域
	struct NODE* next;  //指针域
}Node, *pNode;

初始化

所谓初始化,就是给头节点动态分配一块空间,并对其参数进行初始设置

pNode initList()
{
	pNode head = (pNode)malloc(sizeof(Node));
	head -> data = 0;   //头节点数据域存放链表长度
	head -> next = NULL;
	
	return head;
}

插入

头插法

所谓头插法,就是在链表的头节点与首节点之间插入一个元素,如下图所示:
头插法
其实操作也很简单,我觉得最重要的是操作的顺序不能颠倒

  1. 先给新节点数据域赋值
  2. 通过head -> next 找到首节点,让新节点指向首节点
    在这里插入图片描述
  3. 将头节点的指针域指向新节点,新节点即成为新的首节点,完成头插操作。
    在这里插入图片描述

注意第二步和第三步顺序不能调换
我们可以思考一下,由于链表的存储是不连续的,如果先让 head 的指针域指向了新节点,那么原首节点在内存中将不易找到,因此不能完成头插。
具体代码

void headInsert(pNode List, int data)
{
	pNode node = (pNode)malloc(sizeof(Node));  //开创节点
	node -> data = data;    //新节点数据处理
	node -> next = List -> next;   //头插法
	List -> next = node;
	List -> data ++;  //链表长度增加
}

尾插法

相对头插法而言,尾插法会相对简单一点,不用关注代码前后顺序的问题,重要的是如何找到尾节点,完成尾插
我的思路是定义一个计数指针变量cur,由于尾节点指向为NULL,所以当cur -> next不为空时,就一直指向下一个节点,最后循环结束时cur指向的就是尾节点,即:

// 寻找尾节点
	pNode cur = List;
	while(cur -> next)
	{
		cur = cur -> next;   //循环结束时 cur是尾节点
	}

然后我们只需要给新节点数据域赋值,改变原尾节点和新尾节点指向即可
具体代码

void tailInsert(pNode List, int data)
{
	// 寻找尾节点
	pNode cur = List;
	while(cur -> next)
	{
		cur = cur -> next;   //循环结束时 cur是尾节点
	}
	
	pNode node = (pNode)malloc(sizeof(Node));
	node -> data = data;
	node -> next = NULL;
	cur -> next = node;
	List -> data ++;
}

删除

如果想要删除一个节点,思路上也很简单:
首先我们要找到这个节点,然后将其上一个节点的指针指向下一个节点,最后再把这个节点free 掉即可,如下图所示
删除

而在代码上,问题的关键在于——我们该如何寻找目标节点的上一个节点???
其实我们可以定义两个指针变量—— nodepre,利用双指针思想,使 pre 始终 node 的上一个节点,所以当 node 找到目标节点时,pre 指向的就是目标节点的上一个节点
具体代码

#define TRUE 1
#define FALSE 0

int delet(pNode List, int data)
{
	pNode pre = List;
	pNode node = List -> next;  //node永远是pre的下一个节点
	while(node)
	{
		if(node -> data == data){
			pre -> next = node -> next;
			free(node);
			List -> data --;
			return TRUE;
		}
		
		pre = node;
		node = node -> next;
	}
	
	return FALSE;
}

遍历

我么可以创建一个临时指针变量,通过对地址的访问,依次遍历链表,直到最后走向NULL,结束遍历
具体代码

void printfList(pNode List)
{
	pNode node = List -> next;
	while(node)
	{
		printf("%d ", node -> data);
		node = node -> next;
	}
	printf("\n");
}

完整代码

单链表完整代码

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

#define TRUE 1
#define FALSE 0

typedef struct NODE
{
	int data;
	struct NODE* next;
}Node, *pNode;

pNode initList()
{
	pNode head = (pNode)malloc(sizeof(Node));
	head -> data = 0;   //头节点数据域存放链表长度
	head -> next = NULL;
	
	return head;
}
void headInsert(pNode List, int data)
{
	pNode node = (pNode)malloc(sizeof(Node));  //开创节点
	node -> data = data;    //新节点数据处理
	node -> next = List -> next;   //头插法
	List -> next = node;
	List -> data ++;  //链表长度增加
}
void tailInsert(pNode List, int data)
{
	// 寻找尾节点
	pNode cur = List;
	while(cur -> next)
	{
		cur = cur -> next;   //循环结束时 cur是尾节点
	}
	
	pNode node = (pNode)malloc(sizeof(Node));
	node -> data = data;
	node -> next = NULL;
	cur -> next = node;
	List -> data ++;
}
int delet(pNode List, int data)
{
	pNode pre = List;
	pNode node = List -> next;  //node永远是pre的下一个节点
	while(node)
	{
		if(node -> data == data){
			pre -> next = node -> next;
			free(node);
			List -> data --;
			return TRUE;
		}
		
		pre = node;
		node = node -> next;
	}
	
	return FALSE;
}
void printfList(pNode List)
{
	pNode node = List -> next;
	while(node)
	{
		printf("%d ", node -> data);
		node = node -> next;
	}
	printf("\n");
}

int main()
{
	pNode List = initList();
	headInsert(List, 1);
	headInsert(List, 2);
	headInsert(List, 3);
	tailInsert(List, 4);
	tailInsert(List, 5);
	tailInsert(List, 6);
	printfList(List);
	delet(List, 5);
	delet(List, 3);
	printfList(List);
	
	return 0;
}

最后(未完待续、、、

最后感谢大家的观看,后续还会更新关于双链表、循环链表和其他数据结构的博客,如有错误,还望多多指正
未完待续、、、

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值