c语言 单链表的增删查改(附:c++版本实现)

  • Post author:
  • Post category:其他




前言

本篇博客用C语言分模块的实现单链表的各个接口,先介绍其结构,后根据结构设计合适的算法(

c++版本在最后,若你的基础不错可以直接跳到最后

)。



链表基本结构

链表由一个个的节点连接成,这里的节点是一个结构体,由数据域与指针域组成。

typedef int SListDateType

typedef struct SListNode
{
	SListDataType data;    //data变量用来存储数据
	struct SListNode* next;//next指向下一个节点;
}SListNode;
//用SListNode重新声明节点类型

不同与顺序表,链表中节点的储存不是连续的,每个节点都是分散的,通过next指针来寻找下一个节点。

链表中:

尾节点

的next指向NULL,并且还有一个头指针phead指向链表中第一个节点(也就是不带哨兵位节点)。头指针为空,说明链表为空。



链表的尾插

分两种情况

  • 链表为空:创建一个新的节点,使其指针域指向NULL,数据域放入要放的数据。最后让头指针指向该节点
  • 链表不为空:同样也要创建一个节点。接着找到尾指针(指针域为空的节点),使其指向新的节点

先封装创建新节点的函数

SListNode* CreatSListNode(SListDataType x)
{
	SListNode* newNode = (SListNode*)malloc(sizeof(SListNode));
	if (newNode == NULL)
	{
		printf("申请空间失败\n:);
		return 0;
	}
	else
	{
		newNode->next = NULL;
		newNode->data = x;
	}
	return newNode;
}

创建新节点并填入数据到其数据域中,之后要找到尾节点(创建一个指针cur,cur最初指向链表的首节点,如果cur的next所指向的指针不为空,cur要继续往后走,直到cur的next指向了NULL,说明cur当前指向的是尾节点),将尾节点的指针指向新节点。

当链表为空时,将头指针指向新节点,需要把头指针的值从NULL改成新节点的地址(phead为指向链表第一个节点的指针,链表为空phead为空,链表不为空phead不为空,要通过函数修改一个变量的值,需要传递其地址,那么要修改phead指针的值就要传递指针的地址,也就是二级指针),所以这里要传址操作。

分成两种情况

void SListPushBack(SListNode** pphead, SListData x)
{
	SListNode* newNode = CreatSListNode(x);
	if (*pphead == NULL)
	{
		*phead = newNode;
		return;
	}
	else
	{
		SListNode* cur = *phead;
		while (cur->next != NULL)
			cur = cur->next;
		cur->next = newNode;
	}
}



链表的尾删

删除链表的尾节点,同样也要找到尾节点的位置,释放尾节点,将尾节点前一个节点的next指针指向空。用两个指针,一个tail最后指向尾节点,prev最后指向尾节点前一个节点。分为三种情况

  • 链表为空不删除
  • 只有一个节点,此时删除后头指针需要指向空,因此需要传递二级指针
  • 有一个以上的节点
void SListPopBack(SListNode** pphead)
{
	if (*pphead == NULL)
		return;
	else if ((*pphead)->next == NULL)//只有一个节点
	{
		free((*pphead)->next);
		*pphead = NULL;
	}
	else
	{
		SListNode* prev = NULL;
		SListNode* tail = *pphead;
		while (tail->next)
		{
			prev = tail;
			tail = tail->next;
		}
		free(tail);
		prev->next = NULL;
	}	
}	



链表的头插与头删



头插

创建新节点,将新节点的指针指向phead。

如果链表为空,指针指向phead,也就指向了NULL,此时链表有一个节点,但需要改变头指针,所以需要传递二级指针。

如果链表有一个及以上的节点,其操作与链表为空时一样。

void SListPushFront(SListNode** pphead, SListDara x)
{
	SListNode* newNode = CreatSListNode(x);
	newNode->next = *pphead;
	*pphead = newNode;
}



头删

同样头删也要改变头指针,所以需要传递二级指针。将头指针实现第二个节点,释放第一个节点。

如果链表为空不删除

void SListPopFront(SListNode** pphead)
{
	if (*pphead == NULL)
		return;
	SListNode* first = *pphead;
	*pphead = (*ppehad)->next;
	free(first);
}



链表的查找与修改



查找

查找函数返回一个指针。指向要查找的节点,通过数据域查找节点,定义一个指针cur如果cur的data等于要查找的数据,函数返回,否则cur指向下一个节点。找不到返回NULL。

SListNode* SListNodeFind(SListNode* phead,SListData x)
{
	if (phead == NULL)
		return NULL; 
	SListNode* cur = phead;
	while (cur)
	{
		if (cur->data == x)
			return cur;
		else
			cur = cur->next;
	}
	return NULL}	



修改

假设要修改链表中的数据域为2的节点,通过调用

查找

函数,接收查找函数的返回值:对应节点的指针,对该指针解引用就能修改里面的数据。

SListNode* pList;
SListPushBack(pList, 3);
SListPushBack(pList, 4);
SListPushBack(pList, 2);
SListNode* pos = SListNodeFind(pList, 2);
if (pos != NULL)
	pos->data = 20; // 查找到2对应的节点后进行修改



任意位置的插入与删除



插入

单链表的插入是将节点插入另一个节点之后。先创建一个新的节点,将该节点插入pos指针指向节点的后面,使pos指向的next指向新的节点,新节点的next指向pos之前指向的next。所以先将newNode的next指向pos的next,再将pos的next指向newNode。

viod SListInsertAfter(SListNode* pos, SListData x)
{
	assert(pos);
	SListNode* newNode = CreatSListNode(x);
	newNode->next = pos->next;
	pos->next = newNode;
}



删除

删除一个数删除的不是内存而是指向该内存的指针,也就是释放该指针。

在这里插入图片描述

删除的是

pos后面的节点next

,先将该节点保存为next指针,需要使pos的指针指向nextnext,然后释放next节点。

有两种特殊情况:

  • 如果pos是尾节点,pos后面没有节点,说明要删除的数不存在,不需要删除
  • 如果pos后面只有一个节点,删除该节点后pos的指针将指向空指针,pos成为链表的尾节点
void SListEraseAfter(SListNode* pos)
{
	assert(pos);
	// 下面的代码能同时针对两种特殊情况,不用分别做判断
	// next为尾节点时,if条件不会进入,不会进行删除操作
	// next后只有一个节点时,nextnext为NULL,pos的next会指向NULL
	if (pos->next) 
	{
		SListNode* next = pos->next;
		SListNode* nextnext = next->next;
		free(next);
		pos->next = nextnext;
	}
}



C++版本实现

以上是大一写的内容,现在博主大二复习数据结构时又手撕了一遍单链表,不过与C语言不同,这次是用C++实现的单链表。所以这里将forward_list.hpp的内容放出,其中,对于与之前不同的但又很关键的点会用注释的形式提醒,如果你理解了C语言版本的单链表,C++版本对你来说也不是难事

#include <iostream>

template <class T>
struct flist_node
{
	typedef T value_type;
	value_type _data;
	flist_node *_next;
	flist_node(const value_type& x)
	{
		_data = x;
		_next = nullptr;
	}
};

template <class T>
class forward_list
{
	typedef T value_type;
	typedef flist_node<T> node;
public:
	~forward_list();
	// 在第pos个节点后插入新节点
	bool insert(int pos, const value_type& x);
	bool push_front(const value_type& x) { return insert(0, x); }
	// 删除第pos个节点
	bool erase(int pos);
	bool pop_front() { return erase(1); }
	// 返回链表中第一个值为x的节点序号
	size_t find(const value_type& x);
	void print();
private:
	size_t _size = 0;
	node *_head = nullptr;
};

template <class T>
forward_list<T>::~forward_list()
{
	node *cur = _head;
	node *next = nullptr;
	while (cur)
	{
		next = cur->_next;
		delete cur;
		cur = next;
	}
}

// 注意,此版本只实现insert和erase,即任意位置的插入与删除
// 其他位置的插入与删除是对insert和erase的复用

// 在第pos个节点后插入节点,需要判断pos是否为0
// 要注意的是,链表没有第0个节点,节点下标从1开始
// 如果为0,即表示头插,需要修改_head
// 如果不为0,需要根据pos的值进行遍历,找到第pos个节点

// 函数最开始需要对pos的合法性进行判断
template <class T>
bool forward_list<T>::insert(int pos, const value_type& x)
{
	if (pos > _size || pos < 0)
		return false;
	
	node *new_node = new node(x);
	if (pos == 0)
	{
		new_node->_next = _head;
		_head = new_node;
	}
	else
	{
		node *cur = _head;
		for (int i = 1; i < pos; ++i)
		{
			cur = cur->_next;
		}
		
		node *next = cur->_next;
		cur->_next = new_node;
		new_node->_next = next;
	}

	++_size;
	return true;
}

// 删除第pos个节点,同样两种情况
// pos为1表示头删除,需要修改_head的值
// pos不为1,根据pos进行遍历,主要需要保存上一个节点的地址

// 同样的要对pos进行合法性的判断
template <class T>
bool forward_list<T>::erase(int pos)
{
	if (pos > _size || pos == 0 || pos < 0)
		return false;
	
	if (pos == 1)
	{
		node *second = _head->_next;
		delete _head;
		_head = second;
	}
	// 至少删除的是第二个节点 
	else
	{
		node *prev = _head;
		node *cur = _head->_next;

		for (int i = 2; i < pos; ++i)
		{
			prev = cur;
			cur = cur->_next;
		}

		prev->_next = cur->_next;
		delete cur;
	}
	--_size;
	return true;
}

template <class T>
size_t forward_list<T>::find(const value_type& x)
{
	node *cur = _head;
	int count = 1;
	while (cur)
	{
		if (cur->_data == x)
			return count;
		cur = cur->_next;
		++count;
	}

	return 0;
}

// for debug
template <class T>
void forward_list<T>::print()
{
	node *cur = _head;
	while (cur)
	{
		std::cout << cur->_data << ' ';
		cur = cur->_next;
	}
	std::cout << std::endl;
}



版权声明:本文为weixin_61432764原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。