C语言 | 单链表 | 代码实现

1.链表的基本介绍

链表的实现又很多种方式,通常来说常用的有两种,无头单向非循环链表和带头双向循环链表。无头单链表通常是作为其他数据结构的子结构,如哈希桶、图的邻接表等等。而带头双向循环链表,通常作为独立的结构出现,C++中的list就是使用这个结构实现。

2.链表的设计

在这里插入图片描述
C语言中怎么对一个结点进行描述呢?通过结构体来描述,一个结点。那么创建一个SingleList.h来声明Node结构体,typedef 一个 DataType数据类型,可以更加灵活处理数据。接着就是正常定义结构体。

#include <stdio.h>

typedef int DataType;

typedef struct SingleList
{
	DataType data;
	struct SingleList* next;
}Node;

接下来创建一个main.c文件来定义一个单链表。定义一个单链表,只需要定义一个结构体Node的指针指向单链表的头结点的地址,就能处理这个链表。一开始,链表为NULL。

#include "SingleList.h"
int main()
{
	Node* plist = NULL;	
	return 0;
}
3.链表的增删改查

1)链表的尾插法: 在SingleList.h中经行函数声明,然后创建一个SingleList.c完成函数实现。尾插法最朴素的想法是找到尾部tail 然后让tail->next = newNode(新建结点)。
如何找到尾部呢?tail->next == NULL就说明了这是最后一个结点,是尾部。单链表找尾部只能通过循环遍历的方式查找。

void PushBack(Node** pphead,DataType data);

函数的实现,当链表为NULL时需要单独处理一下。

void PushBack(Node** pphead,DataType data)
{
	assert(pphead != NULL);
	Node* newNode = BuyNode(data);

	// 链表为空时需要单独去判断,不然tail = *pphead 为NULL,tail->next崩溃了
	if(*pphead == NULL) 
	{
		*pphead = newNode;
	}
	else 
	{
		Node* tail = *pphead;	// 让tail从头开始
		while(tail->next != NULL /*这里是循环的条件,当taile->next == NULL 循环结束找到尾*/)
		{
			tail = tail->next;
		}
		// 链接结点
		tail->next = newNode;	
	}
}

链表的增加需要向系统申请内存结点,这部分的代码相对固定所以封装到BuyNode函数中。另外为了测试方便编写一个PrintList函数打印链表。

Node* BuyNode(DataType data)
{
	Node* node = (Node*)malloc(sizeof(Node));
	if(node == NULL)
	{
		perror("malloc create node fail\n");
		return NULL;
	}

	// 创建结点成功!初始化结点
	node->data = data;
	node->next = NULL;

	return node;
}

如果细心可以发现,PrintfList传入的是一级指针,而PushBack需要传入二级指针。PrintList只是访问Node的内容,所以定义Node 接收Node的地址,就能访问到内容。而PushBack需要改变Node结点中的next,改变Node* 的内容就需要二级指针 Node**。

void PrintList(Node* phead)
{
	Node* cur = phead;
	while(cur != NULL)
	{
		printf("%d->",cur->data);
		cur = cur->next;	
	}
	printf("NULL\n");
}

2)链表的头插法: 头插法直接让newNode指向原头结点,然后更新头结点即可。

void PushFront(Node** pphead,DataType data)
{
	assert(pphead != NULL);
	Node* newNode = BuyNode(data);

	// 直接让newNode指向头,*pphead为NULL也不怕,然后更新头节点
	newNode->next = *pphead;	
	*pphead = newNode;
}

3)链表的尾删: 删除操作要保证链表不为NULL。另外尾删要注意,为尾部的前一个next不要指向free的空间。而是让它指向NULL。所以要找到尾的前一个prev。根据经验,链表、树中NULL应该看成一个结点。

void PopBack(Node** pphead)
{
	assert(pphead);
	assert(*pphead != NULL); // 链表为NULL,那就不能删了

	// 尾删的时候是找到为尾部的前一个,让perv->next = NULL.所以只有一个结点的时候单独处理.不然下面的语句prev->next->next就出错了
	if((*pphead)->next == NULL)
	{
		free(*pphead);
	}
	else
	{
		Node* prev = *pphead;

		while(prev->next->next != NULL) {prev = prev->next;}

		free(prev->next);
		prev->next = NULL;
	}
}

4)链表的头删: 需要提前记录一个头结点的下一个。

void PopFront(Node** pphead)
{
	assert(pphead != NULL);
	assert(*pphead != NULL);

	Node* next = (*pphead)->next;
	free(*pphead);
	*pphead = next;
}

5)链表的查找: 查找的是第一个data相同的结点。遍历寻找即可。

Node* Find(Node* phead,DataType data)
{
	Node* cur = phead;

	while(cur != NULL)
	{
		if(cur->data == data) return cur;
		cur = cur->next;
	}
	return NULL;	
}

6)链表从pos位置前插入:

void Insert(Node** pphead,Node* pos,DataType data)
{
	assert(pphead);
	assert(pos);

	if(pos == *pphead){PushFront(pphead,data);}
	else
	{
		Node* prev = *pphead;
		// 一般来说: Insert接口是配合Find接口使用的,传入进来的pos要么是pos要么是链表中的结点
		// 如果pos不是链表中的元素,查找的时候就会出错.
		while(prev->next != pos){ prev = prev->next; }

		Node* newNode = BuyNode(data);
		prev->next = newNode;
		newNode->next = pos;
	}
}

7)链表删除pos位置结点:

void Erase(Node** pphead,Node* pos /*删除pos位置*/)
{
	assert(*pphead != NULL);
	assert(pos);

	if(*pphead == pos){PopFront(pphead);}
	else
	{
		Node* prev = *pphead;
		// 一般来说: Erase接口也是配合Find接口使用的,传入进来的pos要么是pos要么是链表中的结点
		while(prev->next != pos){ prev = prev->next; }

		prev->next = pos->next;
		free(pos);
		pos = NULL;
	}
}
4.完整代码

这里要注意的一点是编译的时候记得两个 .c 文件都要一起编译。

gcc -o test Main.c SingleList.c

SingleList.h 文件

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

typedef int DataType;

typedef struct SingleList
{
	DataType data;
	struct SingleList* next;
}Node;


void PushBack(Node** pphead,DataType data);
void PrintList(Node* phead);
void PushFront(Node** pphead,DataType data);
void PopBack(Node** pphead);
void PopFront(Node** pphead);

Node* Find(Node* phead,DataType data);

void Insert(Node** pphead,Node* pos,DataType data);
void Erase(Node** pphead,Node* pos);

SingleList.c 文件

#include "SingleList.h"

Node* BuyNode(DataType data)
{
	Node* node = (Node*)malloc(sizeof(Node));
	if(node == NULL)
	{
		perror("malloc create node fail\n");
		return NULL;
	}

	// 创建结点成功!初始化结点
	node->data = data;
	node->next = NULL;

	return node;
}

void PushBack(Node** pphead,DataType data)
{
	assert(pphead != NULL);
	Node* newNode = BuyNode(data);

	// 链表为空时需要单独去判断,不然tail = *pphead 为NULL,tail->next崩溃了
	if(*pphead == NULL) 
	{
		*pphead = newNode;
	}
	else 
	{
		Node* tail = *pphead;	// 让tail从头开始
		while(tail->next != NULL /*这里是循环的条件,当taile->next == NULL 循环结束找到尾*/)
		{
			tail = tail->next;
		}
		// 链接结点
		tail->next = newNode;	
	}
}

void PrintList(Node* phead)
{
	Node* cur = phead;
	while(cur != NULL)
	{
		printf("%d->",cur->data);
		cur = cur->next;	
	}
	printf("NULL\n");
}

void PushFront(Node** pphead,DataType data)
{
	assert(pphead != NULL);
	Node* newNode = BuyNode(data);

	// 直接让newNode指向头,*pphead为NULL也不怕,然后更新头节点
	newNode->next = *pphead;	
	*pphead = newNode;
}

void PopBack(Node** pphead)
{
	assert(pphead);
	assert(*pphead != NULL); // 链表为NULL,那就不能删了

	// 尾删的时候是找到为尾部的前一个,让perv->next = NULL.所以只有一个结点的时候单独处理.
	if((*pphead)->next == NULL)
	{
		free(*pphead);
	}
	else
	{
		Node* prev = *pphead;

		while(prev->next->next != NULL) {prev = prev->next;}

		free(prev->next);
		prev->next = NULL;
	}
}

void PopFront(Node** pphead)
{
	assert(pphead != NULL);
	assert(*pphead != NULL);

	Node* next = (*pphead)->next;
	free(*pphead);
	*pphead = next;
}


Node* Find(Node* phead,DataType data)
{
	Node* cur = phead;

	while(cur != NULL)
	{
		if(cur->data == data) return cur;
		cur = cur->next;
	}
	return NULL;	
}

void Insert(Node** pphead,Node* pos,DataType data)
{
	assert(pphead);
	assert(pos);

	if(pos == *pphead){PushFront(pphead,data);}
	else
	{
		Node* prev = *pphead;
		// 一般来说: Insert接口是配合Find接口使用的,传入进来的pos要么是pos要么是链表中的结点
		// 如果pos不是链表中的元素,查找的时候就会出错.
		while(prev->next != pos){ prev = prev->next; }

		Node* newNode = BuyNode(data);
		prev->next = newNode;
		newNode->next = pos;
	}
}

void Erase(Node** pphead,Node* pos /*删除pos位置*/)
{
	assert(*pphead != NULL);
	assert(pos);

	if(*pphead == pos){PopFront(pphead);}
	else
	{
		Node* prev = *pphead;
		// 一般来说: Erase接口也是配合Find接口使用的,传入进来的pos要么是pos要么是链表中的结点
		while(prev->next != pos){ prev = prev->next; }

		prev->next = pos->next;
		free(pos);
		pos = NULL;
	}
}

Main.c 文件

#include "SingleList.h"

int TestPushBack()
{
	Node* plist = NULL;	

	PushBack(&plist,1);
	PushBack(&plist,2);
	PushBack(&plist,3);
	PushBack(&plist,4);
	PushBack(&plist,5);

	PrintList(plist);
	return 0;
}

int TestPushFront()
{
	Node* plist = NULL;	

	PushFront(&plist,1);
	PushFront(&plist,2);
	PushFront(&plist,3);
	PushFront(&plist,4);
	PushFront(&plist,5);

	PrintList(plist);
	return 0;
}

int TestPopBack()
{
	Node* plist = NULL;	

	PushBack(&plist,1);
	PushBack(&plist,2);
	PushBack(&plist,3);
	PushBack(&plist,4);
	PushBack(&plist,5);

	PrintList(plist);
	PopBack(&plist);
	PrintList(plist);
	PopBack(&plist);
	PopBack(&plist);

	PrintList(plist);

	return 0;
}
int TestPopFront()
{
	Node* plist = NULL;	

	PushFront(&plist,1);
	PushFront(&plist,2);
	PushFront(&plist,3);
	PushFront(&plist,4);
	PushFront(&plist,5);

	PrintList(plist);
	PopFront(&plist);
	PrintList(plist);
	PopFront(&plist);
	PopFront(&plist);
	PopFront(&plist);
	PrintList(plist);
	return 0;
}

int TestInsert()
{
	Node* plist = NULL;	

	PushFront(&plist,1);
	Insert(&plist,plist,2);
	Insert(&plist,plist,3);
	Insert(&plist,plist,4);

	Node* pos = Find(plist,3);

	Insert(&plist,pos,5);	
	Insert(&plist,pos,6);	
	Insert(&plist,pos,7);	

	PrintList(plist);
}


int TestErase()
{
	Node* plist = NULL;	

	PushFront(&plist,1);
	Insert(&plist,plist,2);
	Insert(&plist,plist,3);
	Insert(&plist,plist,4);

	PrintList(plist);
	Node* pos = Find(plist,3);
	Erase(&plist,pos);

	PrintList(plist);
}

extern Node* BuyNode(DataType);

void TestOtherNode()
{	
	Node* node = BuyNode(5);
	Node* plist = NULL;	

	PushBack(&plist,1);
	PushBack(&plist,2);
	PushBack(&plist,3);
	PushBack(&plist,4);
	PushBack(&plist,5);

	// 测试Insert不和find使用,就出错了.
	Insert(&plist,node,6);
}

int main()
{
	TestOtherNode();
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值