C语言实现无头单链表详解

目录
  • 链表的结构体描述(节点)
  • 再定义一个结构体(链表)
  • 断言处理 & 判空处理
  • 创建链表
  • 创建节点
  • 头插法
  • 打印链表
  • 尾插法
  • 指定位置插入
  • 头删法
  • 尾删法
  • 指定位置删除
  • 查找链表
  • 删除所有指定相同的元素
  • 总结

再封装的方式,用 c++ 的思想做无头链表

链表的结构体描述(节点)

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
typedef int DataType;
//节点
typedef struct Node
{
	DataType data;
	struct Node* next;
}NODE,*LPNODE;

再定义一个结构体(链表)

通过记录头节点和尾节点的方式去描述链表

typedef struct list
{
	LPNODE frontNode;        //头节点
	LPNODE backNode;         //尾节点
	int curSize;	         //当前节点个数
}LIST,*LPLIST;

断言处理 & 判空处理

如果 list 为空,会引发中断(申请内存可能失败)

防御性编程,如果申请内存失败,操作无效,NULL 里面没有 frontNode,backNode,curSize,存在安全隐患C6011:取消对 NULL 指针"list"的引用

如果申请内存失败,assert()直接让程序直接中断,并且告诉你程序断在哪一行

#include<assert>    //断言处理宏
   #define assert(expression) (void)(  \
            (!!(expression)) ||        \
(_wassert(_CRT_WIDE(#expression), _CRT_WIDE(__FILE__), (unsigned)(__LINE__)), 0) \
        )

LPLIST  createList()
{
	LPLIST list = (LPLIST)malloc(sizeof(LIST));
    list = NULL;                        //list为空 引发中断
	assert(list);
    //if(list == NULL)
    //return NULL;                      //可以替换
	list->frontNode = NULL;
	list->backNode = NULL;
	list->curSize = 0;
	return list;
}

//abort() has been called line 25

创建链表

描述链表最初的状态

LPLIST  createList()
{
	LPLIST list = (LPLIST)malloc(sizeof(LIST)); //用结构体变量去描述 做动态内存申请
	assert(list);
	list->frontNode = NULL;                     //链表刚开始是空的 头尾节点指向空
	list->backNode = NULL;
	list->curSize = 0;                          //当前元素个数为0
	return list;
}

创建节点

LPNODE createNode(int data)
{
	LPNODE newNode = (LPNODE)malloc(sizeof(NODE));
	assert(newNode);
    //初始化数据
	newNode->data = data;
	newNode->next = NULL;
	return newNode;
}

头插法

插入一个节点,节点插在原表头前面,表头会改变,在1(表头)前面插入666(数据)

把新节点的 next 指针指向原来的表头

把原来表头指针从原来的位置挪到新节点的位置

//插入的链表 插入的数据
void push_front(LPLIST list,int data)
{
	LPNODE newNode = createNode(data);    //创建新节点
	newNode->next = list->frontNode;
	list->frontNode = newNode;
	//护头也要护尾
	if (list->curSize == 0)               //只有一个节点 链表为空尾节点也是指向新节点
		list->backNode = newNode;         //把尾节点置为新节点
	list->curSize++;
}
//测试代码
	LPLIST list = createList();
	push_front(list, 1);
	push_front(list, 2);                  //2 1
	printList(list);                      

打印链表

从第1个节点开始打印

void printList(LPLIST list)
{
	LPNODE pmove = list->frontNode;
	while (pmove != NULL)             //不为空打印数据
	{
		printf("%d\t", pmove->data);
		pmove = pmove->next;
	}
	printf("\n");
}

尾插法

不需要找尾巴,因为记录了尾巴的位置

//插入的链表 插入的数据
void push_back(LPLIST list, int data)
{
	LPNODE newNode = createNode(data);     //创建新节点
	if (list->curSize == 0)
	{
		list->frontNode = newNode;         //只有1个节点的情况 表头也是指向新节点
		//list->backNode = newNode;        //表尾指向新节点
	}
	else
	{
		list->backNode->next = newNode;    //把原来链表的尾节点的next指针置为新节点
		//list->backNode = newNode;        //原来的表尾挪到新节点的位置
	}
	list->backNode = newNode;              //相同代码
	list->curSize++;
}
//测试代码
    push_back(list, 666);
	push_back(list, 999);                  //2 1 666 999
	printList(list);

指定位置插入

根据指定数据做插入,插在指定位置(数据)前面,不需要考虑表尾(尾插)

分改变表头、不改变表头两种情况:指定数据,数据可能插在表头前面(头节点的数据==指定数据)

前驱节点的 next 指针指向新节点,新节点的 next 指针指向当前节点

void push_appoin(LPLIST list, int posData, int data)
{
	if (list == NULL || list->curSize == 0)  //为空无法做插入
	{
		printf("无法插入链表为空!\n");
		return;
	}
    //其他情况可以插入
	if (list->frontNode->data == posData)    //头节点的数据==指定数据
	{
		push_front(list, data);              //会改变表头 用头插法插入
		return;
	}
    //正常插入的情况
	LPNODE preNode = list->frontNode;        //定义一个前驱节点指向第1个节点
	LPNODE curNode = list->frontNode->next;  //定义一个当前节点指向第1个节点的下一个节点
    //当前节点!=NULL && 当前节点的数据!=指定数据
	while (curNode != NULL && curNode->data != posData) //并排往下走
	{
		preNode = curNode;                   //前1个节点走到当前节点的位置
		curNode = preNode->next;             //当前节点走到原来位置的下一个
	}
    //分析查找结果做插入
	if (curNode == NULL)                     //没找到无法做插入
	{
		printf("没有找到指定位置,无法插入!\n");
	}
	else
	{
		LPNODE newNode = createNode(data);   //创建新节点
		preNode->next = newNode;             //连接
		newNode->next = curNode;
		list->curSize++;
	}
}
//测试代码
    push_appoin(list, 666, 888);
	printList(list);                         //2 1 888 666 999

头删法

只有一个节点的情况:表尾也要改变(表尾指向了一段删除的内存),表尾也要置为空

void pop_front(LPLIST list)
{
	if (list == NULL || list->curSize == 0)     //判空处理
	{
		printf("链表为空,无法删除!\n");
		return;
	}
	LPNODE nextNode = list->frontNode->next;    //头节点的下一个--->第2个节点
	free(list->frontNode);                      //直接删除表头
	list->frontNode = nextNode;                 //把原来的表头节点置为下一个节点
	if (list->curSize == 1)                     //只有1个节点的情况
	{
		list->backNode = NULL;                  //表尾也要置为空
	}
	list->curSize--;
}
//测试代码
    pop_front(list);
	pop_front(list);
	pop_front(list);
	pop_front(list);
	printList(list);                             //999

尾删法

要找到表尾的上一个节点

//要删除的链表
void pop_back(LPLIST list)
{
	if (list == NULL || list->curSize == 0)
	{
		printf("链表为空,无法删除!\n");
		return;
	}
    //从第1个节点开始找 做移动
	LPNODE preNode = list->frontNode;    //头节点
	LPNODE backNode = list->frontNode;   //头节点的下一个
	while (backNode->next != NULL)
	{
		preNode = backNode;              //并排往下走
		backNode = preNode->next;
	}
	free(backNode);
	if (list->curSize == 1)              //只有一个节点的情况
	{
		backNode = NULL;                 //释放后置空
		list->frontNode = NULL;          //表头也要置为空
		list->backNode = NULL;           //表尾置为空
		list->curSize--;
	}
	else
	{
		backNode = NULL;
		preNode->next = NULL;
		list->backNode = preNode;
		list->curSize--;
	}
}
//测试代码
    pop_back(list);
	printList(list);

指定位置删除

void pop_appoin(LPLIST list,int posData)
{
	if (list == NULL || list->curSize == 0)
	{
		printf("链表为空,无法删除!\n");
		return;
	}
	if (list->frontNode->data == posData) //头节点的数据==指定数据
	{
		pop_front(list);                  //头删法
		return;
	}
	if (list->backNode->data == posData) //尾节点的数据==指定数据
	{
		pop_back(list);                  //尾删法
		return;
	}
    //中间的某个情况
	LPNODE preNode = list->frontNode;    //原来的头部
	LPNODE curNode = list->frontNode->next;
	while (curNode != NULL && curNode->data != posData)
	{
		preNode = curNode;               //并排往下走
		curNode = preNode->next;
	}
	if (curNode == NULL)
	{
		printf("未找到指定位置,无法删除!\n");
	}
	else
	{
		preNode->next = curNode->next;   //先连后断
		free(curNode);
		curNode = NULL;
		list->curSize--;
	}
}
//测试代码
    pop_appoin(list, 2);
	pop_appoin(list, 999);
	pop_appoin(list, 888);               //2 1 888 666 999
	printList(list);                     //1 666

查找链表

//要查找的链表 要查找的数据
LPNODE searchlist(LPLIST list, int posData)
{
	if (list == NULL)                              //list为空 返回NULL无法做删除
		return NULL;
	LPNODE pmove = list->frontNode;                //定义一个移动的节点
	while (pmove != NULL && pmove->data != posData)
	{
		pmove = pmove->next;                       //数据!=指定数据一直往下走
	}
	return pmove;                                  //退出循环直接返回
}

删除所有指定相同的元素

void pop_all(LPLIST list, int posData)
{
	while (searchlist(list, posData) != NULL)   //!=NULL说明里面有指定数据
	{
		pop_appoin(list, posData);              //持续做删除
	}
}
//测试代码
	LPLIST test = createList();
	push_back(test, 1);
	push_back(test, 1);
	push_back(test, 1);
	push_back(test, 2);
	pop_all(test, 1);
	printList(test);                            //2

总结

到此这篇关于C语言实现无头单链表详解的文章就介绍到这了,更多相关C语言无头单链表内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • C语言链表与单链表详解

    链表是什么及链表的优势 链表是一种介于数组的另外一种数据结构: 我们知道数组可以存放很多的元素,这些元素都是呈线性排列,也就是一个挨着一个连续存放 但是当元素足够多时,还能继续正常的存放吗? 事实上的不可以的,虽然系统的内存足够大,但是这些内存不都是连续的,这就导致会出现没有足够的空间去存储这些元素. 其次就是数组的大小需要你去申请,如果你申请的空间足够大,就会导致内存的浪费 而链表就很好的解决了这两个问题 链表的组成 链表的作用就是相当与数组一样,储存你数据的 但又不同于数组,链表把每一个游离

  • 一文弄懂C语言如何实现单链表

    目录 一.单链表与顺序表的区别: 一.顺序表: 二.链表 二.关于链表中的一些函数接口的作用及实现 1.头文件里的结构体和函数声明等等 2.创建接口空间 3.尾插尾删 4.头插头删 5.单链表查找 6.中间插入(在pos后面进行插入) 7.中间删除(在pos后面进行删除) 8.单独打印链表和从头到尾打印链表 9.test.c 总结 一.单链表与顺序表的区别: 一.顺序表: 1.内存中地址连续 2.长度可以实时变化 3.不支持随机查找 4.适用于访问大量元素的,而少量需要增添/删除的元素的程序 5

  • 详解C语言之单链表

    目录 一.思路步骤 1. 定义结构体 2.初始化 3.求当前数据元素的个数 4.插入 5.删除 6.释放内存空间 二.代码 总结 一.思路步骤 1. 定义结构体 a.数据域:用来存放数据 b.指针域:用来存放下一个数据的位置 2.初始化 申请头结点,并将其初始化为空 3.求当前数据元素的个数 a.设置一个指针变量p指向头结点和计数变量size等于0 b.循环判断p->next是否为空,如果不为空,就让指针p指向它的直接后继结点,并让size自增 c.返回size 4.插入 a.设置两个指针,一个

  • C语言数据结构与算法之单链表

    目录 基本概念 读取数据元素 获取第i个结点的数据元素 插入数据元素  初始化链表 打印链表 顺序表查空 顺序表的删除  删除第i个结点及其数据元素 情况1:当删除的是第一个元素 情况2:除第一个结点外 完整代码 删除单链表整表 单链表VS顺序表 基本概念 链表的每一个结点中只包含一个指针域 优点 : 储存空间利用高效 举例来说: typedef struct student{ int id; //学生编号 char* name; //学生名称 //指向下一结点的指针 struct Studen

  • C语言实现单链表的快速排序算法

    目录 背景 设计思路 算法主要步骤 快速排序算法实现 整个程序源代码 测试案例 总结 背景 传统QuickSort算法最大不足之处在于,由于其基于可索引存储结构设计(一般为数组或索引表),因而无法用于链式存储结构,而链式存储结构的实际应用非常广泛,例如动态存储管理.动态优先级调度等等,故本文针对于单向链表,以QuickSort的分治策略为基础,提出一种可用于单向链表的快速排序算法. 设计思路 将单向链表的首节点作为枢轴节点,然后从单向链表首部的第二个节点开始,逐一遍历所有后续节点,并将这些已遍历

  • C语言实现无头单链表详解

    目录 链表的结构体描述(节点) 再定义一个结构体(链表) 断言处理 & 判空处理 创建链表 创建节点 头插法 打印链表 尾插法 指定位置插入 头删法 尾删法 指定位置删除 查找链表 删除所有指定相同的元素 总结 再封装的方式,用 c++ 的思想做无头链表 链表的结构体描述(节点) #include <stdio.h> #include <stdlib.h> #include <assert.h> typedef int DataType; //节点 typede

  • Java数据结构之单链表详解

    一.图示 二.链表的概念及结构 链表是一种物理存储结构上非连续存储结构,数据元素的逻辑顺序是通过链表中的引用链接次序实现的 . 实际中链表的结构非常多样,以下情况组合起来就有8种链表结构: 单向.双向 带头.不带头 循环.非循环 今天,我们实现的是一个 单向 无头 非循环的链表. 下面是此链表的结构组成. 三.单链表的实现 (1)定义一个节点类型 我们创建一个 ListNode 的类作为节点类型,那么我们如何定义成员属性呢? 通过上面的结构分析,我们需要定义两个成员变量 val --作为该节点的

  • Python线性表种的单链表详解

    目录 1. 线性表简介 2. 数组 3. 单向链表 设计链表的实现 链表与顺序表的对比 1. 线性表简介 线性表是一种线性结构,它是由零个或多个数据元素构成的有限序列.线性表的特征是在一个序列中,除了头尾元素,每个元素都有且只有一个直接前驱,有且只有一个直接后继,而序列头元素没有直接前驱,序列尾元素没有直接后继. 数据结构中常见的线性结构有数组.单链表.双链表.循环链表等.线性表中的元素为某种相同的抽象数据类型.可以是C语言的内置类型或结构体,也可以是C++自定义类型. 2. 数组 数组在实际的

  • C语言数据结构之单向链表详解分析

    链表的概念:链表是一种动态存储分布的数据结构,由若干个同一结构类型的结点依次串连而成. 链表分为单向链表和双向链表. 链表变量一般用指针head表示,用来存放链表首结点的地址. 每个结点由数据部分和下一个结点的地址部分组成,即每个结点都指向下一个结点.最后一个结点称为表尾,其下一个结点的地址部分的值为NULL(表示为空地址). 特别注意:链表中的各个结点在内存中是可以不连续存放的,具体存放位置由系统分配. 例如:int *ptr ; 因此不可以用ptr++的方式来寻找下一个结点. 使用链表的优点

  • Python数据结构之单链表详解

    本文实例为大家分享了Python数据结构之单链表的具体代码,供大家参考,具体内容如下 # 节点类 class Node(): __slots__=['_item','_next'] # 限定Node实例的属性 def __init__(self,item): self._item = item self._next = None # Node的指针部分默认指向None def getItem(self): return self._item def getNext(self): return s

  • 如何用JavaScript实现功能齐全的单链表详解

    前言 前端也要搞好数据结构哦! 用JavaScript实现了个单链表,通过LinkedList构造函数可实例化一个单链表数据结构的对象,所有的方法放到LinkedList构造函数的原型对象上,写了暂时能想到的所有方法 GitHub源码地址,下载可运行 实现 通过LinkedList的类创建链表实例,链表下有添加,查找,删除,显示节点等方法 链表初始默认有一个"_head"头部节点,使用时隐藏 按元素/索引 添加.删除,未找到时返回错误,查找未找到时返回null或-1 let obj =

  • C语言数据结构之单链表存储详解

    目录 1.定义一个链表结点 2.初始化单链表 3.输出链表数据 4.完整代码 如果说,顺序表的所占用的内存空间是连续的,那么链表则是随机分配的不连续的,那么为了使随机分散的内存空间串联在一起形成一种前后相连的关系,指针则起到了关键性作用. 单链表的基本结构: 头指针:永远指向链表第一个节点的位置. 头结点:不存任何数据的空节点,通常作为链表的第一个节点.对于链表来说,头节点不是必须的,它的作用只是为了方便解决某些实际问题. 首元结点:首个带有元素的结点. 其他结点:链表中其他的节点. 1.定义一

  • C语言数据结构之单链表操作详解

    目录 1.插入操作 2.删除操作 3.查找操作 4.修改操作 5.完整代码 1.插入操作 (1)创建一个新的要插入的结点 (2)将新结点的 next 指针指向插入位置后的结点 (3)将插入位置前的节点指针 next 指向新的结点 注意:步骤(2)(3)的顺序不能颠倒,否则会导致插入位置后的部分链表丢失. 插入位置一共分三种,分别是头部插入.中间插入和尾部插入. 如图: 代码: link* insertElem(link* p,int elem,int pos){ link* temp = p;/

  • Go语言数据结构之单链表的实例详解

    目录 任意类型的数据域 实例01 快慢指针 实例02 反转链表 实例03 实例04 交换节点 实例05 任意类型的数据域 之前的链表定义数据域都是整型int,如果需要不同类型的数据就要用到 interface{}. 空接口 interface{} 对于描述起不到任何的作用(因为它不包含任何的method),但interface{}在需要存储任意类型的数值的时候相当有用,因为它可以存储任意类型的数值. 一个函数把interface{}作为参数,那么它可以接受任意类型的值作为参数:如果一个函数返回i

随机推荐