【C语言数据结构】单链表

C语言数据结构:线性表之单链表的实现讲解(带头结点)



前言

链表是线性表的一种,与顺序表不同的是,其虽然在逻辑上相邻,但在实际的物理内存中不相邻。

链表是一种物理储存单元上非连续、非顺序的储存结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。链表由一系列结点(链表中每一个元素称为结点)组成,结点可以在运行时动态生成。每个结点包括两个部分:一个是存储数据元素的数据域,另一个是存储下一个结点地址的指针域。 相比于线性表顺序结构,操作复杂。由于不必须按顺序存储,链表在插入的时候可以达到O(1)的复杂度,比另一种线性表顺序表快得多,但是查找一个节点或者访问特定编号的节点则需要O(n)的时间,而线性表和顺序表相应的时间复杂度分别是O(logn)和O(1)。

​ 

链表最明显的好处就是,常规 数组 排列关联项目的方式可能不同于这些数据项目在 记忆体 或 磁盘 上顺序,数据的存取往往要在不同的排列顺序中转换。 链表允许插入和移除表上任意位置上的节点 ,但是不允许随机存取 。

本篇文章将针对一般的单向链表进行讲解及代码实现,其他种类链表将在后续文章实现


一、带头结点的单链表

如图所示,带头结点的单链表由两部分构成:

1.不存储数据的头结点(表头)

2.保存数据的链表体 

这种结构使链表的操作更加简单,对数据的调用更加方便快捷,我们在调用链表时只需要传递表头信息即可

1.带头结点单链表头文件及函数声明

新建头文件"list.h"对链表函数声明进行保存,方便我们后期查看及使用

#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>

typedef int ELEM_TYPE;

//有效数据节点结构体设计:
typedef struct Node
{
	ELEM_TYPE data;//数据域
	struct Node* next;//指针域
}Node, *PNode;

//购买结点
PNode BuyNode();

//初始化
void Init_list(PNode p);

//头插
bool Insert_head(PNode p, ELEM_TYPE val);

//尾插
bool Insert_tail(PNode p, ELEM_TYPE val);

//按位置插
bool Insert_pos(PNode p, int pos, ELEM_TYPE val);

//头删
bool Del_head(PNode p);

//尾删
bool Del_tail(PNode p);

//按位置删//pos==0(删除第一个有效值)
bool Del_pos(PNode p, int pos);

//按值删
bool Del_val(PNode p, ELEM_TYPE val);

//查找
struct Node* Search(PNode p, ELEM_TYPE val);

//查找 前置结点
struct Node* Search_Prev(PNode p, ELEM_TYPE val);

//判空
bool IsEmpty(PNode p);
//判满 因为是单链表  节点都是用一个申请一个  所以不需要判满

//获取单链表有效值个数
int Get_length(PNode p);

//打印链表
void Show(PNode p);

//清空  节点清空时,直接释放内存,用的时候再申请
void Clear(PNode p);

//销毁
void Destroy(PNode p);

2.初始化

在对使用链表进行数据的储存之前,我们需要进行链表的初始化,首先需要对链表的基础结构进行构建。

链表结点由两个部分构成:数据域指针域

数据域进行数据保存,指针域指向下一个结点的地址,若不存在下一个结点,那么指针域指向NULL即可。

结点结构的设计如下:

typedef int ELEM_TYPE;//如此对数据类型进行改名操作,若要进行修改数据类型操作会更加方便

//有效数据节点结构体设计:
typedef struct Node
{
	ELEM_TYPE data;//数据域
	struct Node* next;//指针域
}Node, *PNode;

接着对链表初始化函数进行实现,这里的实现较为简单,直接将表头的指针域置为NULL即可,因为表中尚无元素。

//初始化
void Init_list(PNode p)
{
	assert(p != NULL);
	p->next = NULL;
}

3.结点申请

因为链表的结构特性,我们需要对结点进行多次申请,为了方便进行其他函数操作,并且为了优化代码,我们将结点的申请封装于一个函数

PNode BuyNode()
{
	PNode newnode = (PNode)malloc(sizeof(Node));
	if (newnode == NULL)//判断申请的结点是否有效
	{
		exit(0);
	}
	memset(newnode, 0, sizeof(Node));//对申请内存置空
	return newnode;
}

4.数据插入

为了减少代码的重复和冗余,我们先进行按位置插入函数的实现,其他的插入就可直接调用

1.按位置插入

//按位置插
bool Insert_pos(PNode p, int pos, ELEM_TYPE val)
{
	assert(p != NULL);
	if (pos < 0 || pos > Get_length(p))//判断插入位置是否合法
	{
		return false;
	}
	PNode node = p;
	for (int i = 0; i < pos; i++)//将指针置于需要插入的位置
	{
		node = node->next;
	}
	PNode newnode = BuyNode();
	newnode->data = val;
	newnode->next = node->next;
	node->next = newnode;
	return true;
}

2.头插

直接调用按位置插入,插入位置为0

//头插
bool Insert_head(PNode p, ELEM_TYPE val)
{
	assert(p != NULL);
	return Insert_pos(p, 0, val);
}

3.尾插

调用链表长度获取函数,再将链表元素数量信息,作为插入位置传给按位插入函数

//尾插
bool Insert_tail(PNode p, ELEM_TYPE val)
{
	assert(p != NULL);
	return Insert_pos(p, Get_length(p), val);
}

5.查找

我们先进行查找某值前置结点的函数实现,如此可方便其他函数调用,减少代码冗余。

1.查找前置结点

在为未查找到值的情况下,返回尾结点指针,这样可以防止其余函数调用时出现空指向问题

//查找 前置结点
struct Node* Search_Prev(PNode p, ELEM_TYPE val)
{
	assert(p != NULL);
	PNode node = p;
	while (node->next != NULL && node->next->data != val)
	{
		node = node->next;
	}
	return node;
}

2.查找值结点

直接调用查找1即可,且不用担心出现空指向问题

//查找
struct Node* Search(PNode p, ELEM_TYPE val)
{
	assert(p != NULL);
	return Search_Prev(p, val)->next;
}

6.数据删除

与前面插入函数相同,我们先实现按位置删除函数

1.按位置删除

//按位置删//pos==0(删除第一个有效值)
bool Del_pos(PNode p, int pos)
{
	assert(p != NULL);
	if (pos < 0 || pos >= Get_length(p))//判断位置是否合法
	{
		return false;
	}
	PNode node = p;
	for (int i = 0; i < pos; i++)
	{
		node = node->next;
	}
	PNode delnode = node->next;//将要删除的结点独立出来
	node->next = delnode->next;
	free(delnode);//释放要删除的结点
	delnode = NULL;
	return true;
}

2.头删

在调用按位置删除函数前进行判空操作,可能减少函数调用

//头删
bool Del_head(PNode p)
{
	assert(p != NULL);
	if (IsEmpty(p))
	{
		return false;
	}
	return Del_pos(p, 0);
}

3.尾删

//尾删
bool Del_tail(PNode p)
{
	assert(p != NULL);
	if (IsEmpty(p))
	{
		return false;
	}
	return Del_pos(p, Get_length(p) - 1);
}

4.按值删除

调用了查找前置函数

//按值删
bool Del_val(PNode p, ELEM_TYPE val)
{
	assert(p != NULL);
	if (IsEmpty(p))
	{
		return false;
	}
	PNode node = Search_Prev(p, val);
	PNode delnode = node->next;
	if (delnode != NULL)//判断需要删除的结点是否为空
	{
		node->next = delnode->next;
		free(delnode);
		delnode = NULL;
	}
	return true;
}

7.清空与销毁

1.清空

这里我们使用比较简单的方法,一直头删即可

//清空        节点清空时,直接释放内存,用的时候再申请
void Clear(PNode p)
{
	assert(p != NULL);
	while (!IsEmpty(p))
	{
		Del_head(p);
	}
}

2.销毁

调用清空函数

//销毁
void Destroy(PNode p)
{
	Clear(p);
	p->next = NULL;
}

8.带头结点单链表源文件及整体函数实现

#include "list.h"

//初始化
void Init_list(PNode p)
{
	assert(p != NULL);
	p->next = NULL;
}

PNode BuyNode()
{
	PNode newnode = (PNode)malloc(sizeof(Node));
	if (newnode == NULL)
	{
		exit(0);
	}
	memset(newnode, 0, sizeof(Node));
	return newnode;
}

//头插
bool Insert_head(PNode p, ELEM_TYPE val)
{
	assert(p != NULL);
	return Insert_pos(p, 0, val);
}

//尾插
bool Insert_tail(PNode p, ELEM_TYPE val)
{
	assert(p != NULL);
	return Insert_pos(p, Get_length(p), val);
}

//按位置插
bool Insert_pos(PNode p, int pos, ELEM_TYPE val)
{
	assert(p != NULL);
	if (pos < 0 || pos > Get_length(p))
	{
		return false;
	}
	PNode node = p;
	for (int i = 0; i < pos; i++)
	{
		node = node->next;
	}
	PNode newnode = BuyNode();
	newnode->data = val;
	newnode->next = node->next;
	node->next = newnode;
	return true;
}

//头删
bool Del_head(PNode p)
{
	assert(p != NULL);
	if (IsEmpty(p))
	{
		return false;
	}
	return Del_pos(p, 0);
}

//尾删
bool Del_tail(PNode p)
{
	assert(p != NULL);
	if (IsEmpty(p))
	{
		return false;
	}
	return Del_pos(p, Get_length(p) - 1);
}

//按位置删//pos==0(删除第一个有效值)
bool Del_pos(PNode p, int pos)
{
	assert(p != NULL);
	if (pos < 0 || pos >= Get_length(p))
	{
		return false;
	}
	PNode node = p;
	for (int i = 0; i < pos; i++)
	{
		node = node->next;
	}
	PNode delnode = node->next;
	node->next = delnode->next;
	free(delnode);
	delnode = NULL;
	return true;
}

//按值删
bool Del_val(PNode p, ELEM_TYPE val)
{
	assert(p != NULL);
	if (IsEmpty(p))
	{
		return false;
	}
	PNode node = Search_Prev(p, val);
	PNode delnode = node->next;
	if (delnode != NULL)
	{
		node->next = delnode->next;
		free(delnode);
		delnode = NULL;
	}
	return true;
}

//查找
struct Node* Search(PNode p, ELEM_TYPE val)
{
	assert(p != NULL);
	return Search_Prev(p, val)->next;
}

//查找 前置结点
struct Node* Search_Prev(PNode p, ELEM_TYPE val)
{
	assert(p != NULL);
	PNode node = p;
	while (node->next != NULL && node->next->data != val)
	{
		node = node->next;
	}
	return node;
}

//判空
bool IsEmpty(PNode p)
{
	assert(p != NULL);
	return p->next == NULL;
}
//判满 因为是单链表  节点都是用一个申请一个  所以不需要判满

//获取单链表有效值个数
int Get_length(PNode p)
{
	assert(p != NULL);
	int count = 0;
	PNode node = p;
	while (node->next != NULL)
	{
		node = node->next;
		count++;
	}
	return count;
}

//打印链表
void Show(PNode p)
{
	assert(p != NULL);
	PNode node = p->next;
	while (node != NULL)
	{
		printf("%d ", node->data);
		node = node->next;
	}
	printf("\n");
}

//清空        节点清空时,直接释放内存,用的时候再申请
void Clear(PNode p)
{
	assert(p != NULL);
	while (!IsEmpty(p))
	{
		Del_head(p);
	}
}

//销毁
void Destroy(PNode p)
{
	Clear(p);
	p->next = NULL;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值