数据结构----单链表

一,链表的概念及结构

1,线性表的概念

线性表是具有相同类型的n个数据元素的有限序列。

线性表有两个特点:物理结构和逻辑结构。

物理结构:指的是数据在内存中的存储结构。有线性非线性两种情况。

逻辑结构:指的是数据之间的逻辑关系,是抽象的。比如一些数据是没有任何关系,是孤立的,就是非线性的,如果这些数据可以想象成用一条“线’连接起来,那就是线性性的。而对于线性表来说,它在逻辑结构上一定是线性的。如下图:

 

2,链表的概念

链表是线性表的一种,他在逻辑结构上是线性的,在物理结构上是非线性的。它的数据元素的逻辑顺序是通过指针连接实现的

3,链表的结构

我们可以将链表看成是一个火车,火车是由一节一节的车厢组成的,链表是由一个一个节点组成的。

由上图可看出,节点是由两部分组成的,一部分存储数据,一部分存储指向下一个节点的指针,通过指针将一个个的节点连接起来。

也叫做数据域和指针域。这里就需要用到自定义类型-----结构体类型

struct  SListNode

{
    int data;

    struct SListNode* next;   //指向下一个节点的指针

};

二,链表的实现

1,首先我们需要定义三个文件

头文件SListNode.h,用来声明各种函数和包含所需的头文件。

源文件SListNode.c,用来实现所声明的函数。

源文件test.c,作为测试文件,测试所写的方法。

2,写代码

2.1,结构设计,设计节点

//定义节点的结构 
//  数据+指向下一个节点的指针
typedef int SLTDataType;
typedef struct SListNode
{
	SLTDataType data;
	struct SListNode* next;
}SLNode;

对结构体类型进行重命名,方便使用。 

2.2,链表的功能(声明函数)

//创建节点
SLNode* SLBuyNode(SLTDataType x);

//打印链表
void SLPrint(SLNode* phead);

//尾插
void SLPushBack(SLNode** pphead, SLTDataType x);

//头插
void SLPushFront(SLNode** pphead, SLTDataType x);

//尾删
void SLPopBack(SLNode** pphead);

//头删
void SLPopFront(SLNode** ppheead);

//查找
SLNode* SLFind(SLNode** pphead, SLTDataType x);

//在指定位置之前插入数据
void SLInsert(SLNode** pphead, SLNode* pos, SLTDataType x);

//在指定位置之后插入数据
void SLInserAfter(SLNode* pos, SLTDataType x);

//删除pos节点
void SLErase(SLNode** pphead, SLNode* pos);

//删除pos之后的节点
void SLEraseAfter(SLNode* pos);

2.3,链表功能的实现

1,创建节点

//创建新节点
SLNode* SLBuyNode(SLTDataType x)
{
	//先动态开辟申请一块内存
	SLNode* newnode = (SLNode*)malloc(sizeof(SLNode));
	if (newnode == NULL)  //判断申请是否成功
	{
		perror("malloc fail");
		exit(1);         //失败直接结束程序
	}
	newnode->data = x;   //将x存入新节点的data
	newnode->next = NULL; //next置位空
	return newnode;
}

 2,打印链表

我们需要再定义一个指针来遍历整个链表,指针每向后走,打印该节点处的数据。

//打印链表
void SLPrint(SLNode* phead)
{
	SLNode* pcur = phead; //pcur指向第一个节点
	while (pcur)  //开始遍历整个链表
	{
		printf("%d->", pcur->data);
		pcur = pcur->next;    //打印完一个后,pcur向后走,走到下一个节点
	//打印完最后一个数据后,pcur->next=NULL,pcur=NULL,循环停止
    }
	printf("NULL\n");
}

3,尾插

注意:在创建新节点时,已经让next=NULL,所以在尾插后不需要再置位空。

而且这里在传参的时候,需要传地址,形参用二级指针接收,因为这里所传的链表可能是个空链表,这时候需要改变链表的指向,而用一级指针接收的话,只会改变形参,实参是不会改变的。

 

//尾插
void SLPushBack(SLNode** pphead, SLTDataType x)
{
	//创建新节点,调用SLBuyNode函数,将x存入
	SLNode* newnode = SLBuyNode(x);
	//链表为空和不为空
	//为空
	if (*pphead == NULL)
	{
		*pphead = newnode;  //链表为空,直接让新节点成为头节点
	}
    //不为空
	else
	{
		//ptail找尾节点
		SLNode* ptail = *pphead;
		//找尾
		while (ptail->next != NULL)
		{
			ptail = ptail->next;
		}
		//ptail就是尾节点
		ptail->next = newnode;
	}
}

4,头插

//头插
void SLPushFront(SLNode** pphead, SLTDataType x)
{
	assert(pphead);  //pphead不能为空
	//创建新节点
	SLNode* newnode = SLBuyNode(x);
	//新节点的next指向头节点*pphead
	newnode->next = *pphead;
	*pphead = newnode;   //*pphead指向newnode,新的头节点
}

5,尾删

//尾删
void SLPopBack(SLNode** pphead)
{
	SLNode* ptail = *pphead;
	SLNode* pcur = NULL;
	//找尾节点
	while (ptail->next)
	{
		pcur = ptail;       
		ptail = ptail->next;
	}//pcur记录尾节点的上一个节点
	pcur->next = NULL;
	free(ptail);
	ptail = NULL;  //释放尾节点ptaail
	//ptail为尾节点
}

6,头删


//头删
void SLPopFront(SLNode** pphead)
{
	assert(pphead && *pphead);//链表不能为空,pphead也不能为空
	SLNode* next = (*pphead)->next;//定义next指针指向第二个节点
	// ->优先级比*高,需要加()
	free(*pphead);   //释放第一个节点
	*pphead = next;   //next成为新的头节点
}

7,查找指定元素

//查找
SLNode* SLFind(SLNode** pphead, SLTDataType x)
{
	SLNode* pre = *pphead;//用pre来遍历链表
	while (pre)
	{
		if (pre->data == x)
		{
			return pre;   //找到了,返回该节点的地址
		}
		pre = pre->next;
	}
	//循环结束还没找到,返回NIULL
	return NULL;
}

8,在指定位置之前插入数据

//在指定位置之前插入数据
void SLInsert(SLNode** pphead, SLNode* pos, SLTDataType x)
{
	assert(pphead && *pphead);
	assert(pos);  //给定位置不能为空
	SLNode* newnode = SLBuyNode(x);  //要插入的节点
	//pos是不是头节点
	// 
	//是头节点,直接调用头插函数
	if (pos == *pphead)
	{
		SLPushFront(pphead, x);
	}
	//不是头节点
	else
	{
		SLNode* prv = *pphead;
		//找到pos之前的节点
		while (prv->next != pos)
		{
			prv = prv->next;
		}
        //prv就是pos之前的节点
		//prv  newnode  pos 连接
		prv->next = newnode;
		newnode->next = pos;
	}
}

9,在指定位置之后插入数据

//在指定位置之后插入数据
void SLInserAfter(SLNode* pos, SLTDataType x)
{
	SLNode* newnode = SLBuyNode(x);
	newnode->next = pos->next;
	pos->next = newnode;
}

10,删除pos节点

//删除pos节点
void SLErase(SLNode** pphead, SLNode* pos)
{
	assert(pphead && *pphead);
	assert(pos);  //删除的节点不能为空
	SLNode* prv = *pphead;
	//pos是头节点
	if (pos == *pphead)
	{
		SLPopFront(pphead);//调用头删函数
	}
	//找出pos之前的节点
	while (prv->next != pos)
	{
		prv = prv->next;
	}
	prv->next = pos->next;
	free(pos); //释放pos
	pos = NULL;
}

 三,代码总体

头文件

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

//定义节点的结构 
//  数据+指向下一个节点的指针
typedef int SLTDataType;
typedef struct SListNode
{
	SLTDataType data;
	struct SListNode* next;
}SLNode;



//创建节点
SLNode* SLBuyNode(SLTDataType x);

//打印链表
void SLPrint(SLNode* phead);

//尾插
void SLPushBack(SLNode** pphead, SLTDataType x);

//头插
void SLPushFront(SLNode** pphead, SLTDataType x);

//尾删
void SLPopBack(SLNode** pphead);

//头删
void SLPopFront(SLNode** ppheead);

//查找
SLNode* SLFind(SLNode** pphead, SLTDataType x);

//在指定位置之前插入数据
void SLInsert(SLNode** pphead, SLNode* pos, SLTDataType x);

//在指定位置之后插入数据
void SLInserAfter(SLNode* pos, SLTDataType x);

//删除pos节点
void SLErase(SLNode** pphead, SLNode* pos);

源文件

//实现方法
#include "SlistDode.h"


//创建新节点
SLNode* SLBuyNode(SLTDataType x)
{
	//先动态开辟申请一块内存
	SLNode* newnode = (SLNode*)malloc(sizeof(SLNode));
	if (newnode == NULL)  //判断申请是否成功
	{
		perror("malloc fail");
		exit(1);
	}
	newnode->data = x;   //将x存入新节点的data
	newnode->next = NULL; //next置位空
	return newnode;
}

//打印链表
void SLPrint(SLNode* phead)
{
	SLNode* pcur = phead; //pcur指向第一个节点
	while (pcur)  //开始遍历整个链表
	{
		printf("%d->", pcur->data);
		pcur = pcur->next;    //打印完一个后,pcur向后走,走到下一个节点
	}
	printf("NULL\n");
}

//尾插
void SLPushBack(SLNode** pphead, SLTDataType x)
{
	//创建新节点,调用SLBuyNode函数,将x存入
	SLNode* newnode = SLBuyNode(x);
	//链表为空和不为空
	//为空
	if (*pphead == NULL)
	{
		*pphead = newnode;  //链表为空,直接让新节点成为头节点
	}
    //不为空
	else
	{
		//ptail找尾节点
		SLNode* ptail = *pphead;
		//找尾
		while (ptail->next != NULL)
		{
			ptail = ptail->next;
		}
		//ptail就是尾节点
		ptail->next = newnode;
	}
}
//头插
void SLPushFront(SLNode** pphead, SLTDataType x)
{
	assert(pphead);  //pphead不能为空
	//创建新节点
	SLNode* newnode = SLBuyNode(x);
	//新节点的next指向头节点*pphead
	newnode->next = *pphead;
	*pphead = newnode;   //*pphead指向newnode,新的头节点
}

//尾删
void SLPopBack(SLNode** pphead)
{
	SLNode* ptail = *pphead;
	SLNode* pcur = NULL;
	//找尾节点
	while (ptail->next)
	{
		pcur = ptail;       
		ptail = ptail->next;
	}//pcur记录尾节点的上一个节点
	pcur->next = NULL;
	free(ptail);
	ptail = NULL;  //释放尾节点ptaail
	//ptail为尾节点
}

//头删
void SLPopFront(SLNode** pphead)
{
	assert(pphead && *pphead);//链表不能为空,pphead也不能为空
	SLNode* next = (*pphead)->next;//定义next指针指向第二个节点
	// ->优先级比*高,需要加()
	free(*pphead);   //释放第一个节点
	*pphead = next;   //next成为新的头节点
}
//查找
SLNode* SLFind(SLNode** pphead, SLTDataType x)
{
	SLNode* pre = *pphead;//用pre来遍历链表
	while (pre)
	{
		if (pre->data == x)
		{
			return pre;   //找到了,返回该节点的地址
		}
		pre = pre->next;
	}
	//循环结束还没找到,返回NIULL
	return NULL;
}


//在指定位置之前插入数据
void SLInsert(SLNode** pphead, SLNode* pos, SLTDataType x)
{
	assert(pphead && *pphead);
	assert(pos);  //给定位置不能为空
	SLNode* newnode = SLBuyNode(x);  //要插入的节点
	//pos是不是头节点
	// 
	//是头节点,直接调用头插函数
	if (pos == *pphead)
	{
		SLPushFront(pphead, x);
	}
	//不是头节点
	else
	{
		SLNode* prv = *pphead;
		//找到pos之前的节点
		while (prv->next != pos)
		{
			prv = prv->next;
		}
		//prv  newnode  pos 连接
		prv->next = newnode;
		newnode->next = pos;
	}
}

//在指定位置之后插入数据
void SLInserAfter(SLNode* pos, SLTDataType x)
{
	SLNode* newnode = SLBuyNode(x);
	newnode->next = pos->next;
	pos->next = newnode;
}

//删除pos节点
void SLErase(SLNode** pphead, SLNode* pos)
{
	assert(pphead && *pphead);
	assert(pos);  //删除的节点不能为空
	SLNode* prv = *pphead;
	//pos是头节点
	if (pos == *pphead)
	{
		SLPopFront(pphead);//调用头删函数
	}
	//找出pos之前的节点
	while (prv->next != pos)
	{
		prv = prv->next;
	}
	prv->next = pos->next;
	free(pos); //释放pos
	pos = NULL;
}

测试文件test.c

/测试
#include "SlistDode.h"


void test1()
{
	SLNode* node1 =(SLNode*) malloc(sizeof(SLNode));
	node1->data = 1;

	SLNode* node2 =(SLNode*) malloc(sizeof(SLNode));
	node2->data = 2;

	SLNode* node3 = (SLNode*)malloc(sizeof(SLNode));
	node3->data = 3;

	SLNode* node4 = (SLNode*)malloc(sizeof(SLNode));
	node4->data = 4;
	//连接4个节点
	node1->next = node2;
	node2->next = node3;
	node3->next = node4;
	node4->next = NULL;

	SLNode* PList = node1;
	SLPrint(PList);
}
void test2()
{
	SLNode* PList = NULL;
	//尾插
	SLPushBack(&PList, 1);
	SLPushBack(&PList, 2);
	SLPushBack(&PList, 3);
	SLPushBack(&PList, 4);
	//头插
	/*SLPushFront(&PList, 1);
	SLPushFront(&PList, 2);
	SLPushFront(&PList, 3);
	SLPushFront(&PList, 4);
	SLPrint(PList);*/

	//尾删
	/*SLPopBack(&PList);
	SLPrint(PList);
	SLPopBack(&PList);
	SLPrint(PList);
	SLPopBack(&PList);
	SLPrint(PList);*/

	//头删
	/*SLPopFront(&PList);
	SLPrint(PList);
	SLPopFront(&PList);
	SLPrint(PList);
	SLPopFront(&PList);
	SLPrint(PList);
	SLPopFront(&PList);
	SLPrint(PList);*/

	//查找
	SLNode* find = SLFind(&PList, 2);
	if (find == NULL)
		printf("找不到");
	else
		printf("找到了");


	//在指定位置之前插入数据
	SLInsert(&PList, find, 4);
	SLPrint(PList);
}
int main()
{
	//test1();
	test2();
	return 0;
}

感谢观看,有问题还请指教!

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值