【数据结构与算法】线性表——单链表的实现

本文提供了一个使用C++实现的单链表类模板,包括插入、删除、查询等核心功能,并附带测试代码及结果。

单链表的实现 C++代码

使用了模板。使用的时候直接导入头文件即可。代码实现相关细节、解释都在注释里了。
那么就直接上代码了。

#ifndef __MYLINKLIST_H__
#define __MYLINKLIST_H__
#include <iostream>

// 单链表中每个节点的定义
template<typename T>													// T代表数据元素的类型
struct Node
{
	T data;																// 数据域,存放数据元素
	Node<T>* next;														// 指针域,指向下一个同类型(和本类型相同)节点
};

// 单链表的定义
template<typename T>
class LinkList
{
public:
	LinkList();															// 构造函数
	~LinkList();														// 析构函数

public:
	bool ListInsert(int i, const T& e);									// 在第i个位置插入指定元素e
	bool InsertPriorNode(Node<T>* p_curr, const T& e);
	bool ListDelete(int i);												// 删除第i个位置的元素
	// 以更快捷高效的方式实现删除某个已知节点
	bool DeletePriorNode(Node<T>* p_del);

	bool GetElem(int i, T& e);											// 获得第i个位置的元素值
	int LocateElem(const T& e);											// 按元素值查找其在单链表中第一次出现的位置

	void DispList();													// 输出单链表中的所有元素
	int ListLength();													// 获取单链表的长度
	bool Empty();														// 判断单链表是否为空
	void ReverseList();													// 翻转单链表

	Node<T>* GetHead()
	{
		return m_head;
	}

private:
	Node<T>* m_head;													// 头指针(指向链表第一个节点的指针,如果链表有头结点,则指向头结点)
	int m_length;														// 单链表当前长度(当前有几个元素),
																		// 为了编写程序更方便和提高程序运行效率而引入,但不是必须引入。
};

// 通过构造函数对单链表进行初始化
template<typename T>
LinkList<T>::LinkList()
{
	m_head = new Node<T>;												// 先创建一个头结点
	m_head->next = nullptr;												// 头结点的指针域指向空
	m_length = 0;														// 头结点不计入单链表的长度

	/* 不带头结点的单链表 
	m_head = mullptr;
	m_length = 0;  */
}

// 带头节点的单链表插入操作
// 在第i个位置(位置编号从1开始)插入指定元素e
template<typename T>
bool LinkList<T>::ListInsert(int i, const T& e)
{
	// 判断插入位置是否合法,i的合法值应该时从 1 到 m_length + 1 之间
	if (i < 1 || i >(m_length + 1))
	{
		std::cout << "元素:" << e << "插入位置:" << i << "不合法,合法的位置是:[1~" << m_length + 1 << "]!" << std::endl;
		return false;
	}
	Node<T>* p_curr = m_head;
	// 整个for循环用于找到第i-1个节点
	for (int j = 0; j < (i - 1); ++j)									// j从0开始,表示p_curr更开始指向的是第0个节点(头结点)
	{
		p_curr = p_curr->next;											// p_curr会找到当前要插入的位置,
																		// 比如要在第2个位置插入,p_curr最终会指向第1个位置
	}
	Node<T>* node = new Node<T>;										// ①   创建新节点
	node->data = e;														// ②
	node->next = p_curr->next;											// ③   让新节点链上后续链表,因为p_curr->next指向的是后续链表的节点
	p_curr->next = node;												// ④   让当前位置链上新节点,因为node指向新节点
	std::cout << "成功在位置为:" << i << "处插入元素:" << e << "!" << std::endl;
	++m_length;															// 实际表长+1
	return true;
}

// 以更快捷高效的方式实现向某个已知节点之前插入新节点
template<typename T>
bool LinkList<T>::InsertPriorNode(Node<T>* p_curr, const T& e)
{
	// 在节点p_curr之前插入新节点,新节点数据域元素值为e
	Node<T>* node = new Node<T>;
	node->data = e;
	node->next = p_curr->next;
	p_curr->next = node;
	node->data = p_curr->data;
	p_curr->data = e;
	++m_length;
	return true;
}

// 删除第i个位置的元素
template<typename T>
bool LinkList<T>::ListDelete(int i)
{
	if (m_length < 1)
	{
		std::cout << "当前单链表为空,无法删除任何数据!" << std::endl;
		return false;
	}
	if (i < 1 || i > m_length)
	{
		std::cout << "删除位置:" << i << "不合法,合法位置是:[1~" << m_length << "]!" << std::endl;
		return false;
	}
	Node<T>* p_curr = m_head;  
	// 整个for循环用于找到第i-1个节点
	for (int j = 0; j < (i - 1); ++j)									// j从0开始,表示p_curr刚开始指向的是第0个节点(头结点)
	{
		p_curr = p_curr->next;
																		// p_curr会找到当前要删除的位置所代表的节点的前一个节点的位置
	}
	Node<T>* p_willdel = p_curr->next;									// p_willdel指向待删除的节点
	p_curr->next = p_willdel->next;										// 第i-1个节点的next指针指向了第i+1个节点
	std::cout << "成功删除位置为:" << i << "的元素,该元素的值为:" << p_willdel->data << "!" << std::endl;
	--m_length;															// 实际表长-1
	delete p_willdel;
	return true;
}
// 以更快捷高效的方式实现删除某个已知节点
template<typename T>
bool LinkList<T>::DeletePriorNode(Node<T>* p_del)
{ 
	// 思想:把要删除的节点的下一个节点数据域拷贝到当前要删除的节点,
	// 当前要删除的节点的指针域指向下一个节点的指针域指向的节点。
	// 这样就相当于把待删除的节点的下一个节点数据拷贝到了需要待删除的节点上。
	// 然后真正删除的是待删除节点的下一个节点,从而实现时间复杂度为O(1)
	// 删除p_del所指向的节点
	if (p_del->next == nullptr)
	{
		// 待删除的节点正好是最后一个节点,不能用这种方法删除,只能常规删除
		return ListDelete(m_length);
	}
	p_del->data = p_del->next->data;									// 把待删除的下一个节点的数据保存到当前的需要删除节点的数据域
	Node<T>* p_willdel = p_del->next;									// 待删除的节点的下一个节点(真正要删除的节点)
	p_del->next = p_willdel->next;
	--m_length;
	std::cout << "成功删除节点元素值为:" << p_willdel->data << "的节点!" << std::endl;
	delete p_willdel;
	return true;
}

// 获得第i个位置的元素值
template<typename T>
bool LinkList<T>::GetElem(int i, T& e)
{
	if (m_length < 1)
	{
		std::cout << "当前单链表为空,无法获取任何数据!" << std::endl;
		return false;
	}
	if (i < 1 || i > m_length)
	{
		std::cout << "当前元素的位置:" << i << "不合法,合法位置是:[1~" << m_length << "]!" << std::endl;
		return false;
	}
	Node<T>* p_curr = m_head;
	// for循环
	for (int j = 0; j < i; ++j)
	{
		p_curr = p_curr->next;
	}
	e = p_curr->data;
	std::cout << "成功获取位置为:" << i << "的元素,该元素的值为:" << e << "!" << std::endl;
	return true;
}

// 按元素值查找其在单链表中第一次出现的位置
template<typename T>
int LinkList<T>::LocateElem(const T& e)
{
	Node<T>* p_curr = m_head;
	for (int i = 1; i <= m_length; ++i)
	{
		p_curr = p_curr->next;
		if (p_curr->data == e)
		{
			std::cout << "值为:" << e << "的元素在单链表中第一次出现的位置为:" << i << "!" << std::endl;
			return i;
		}
	}
	std::cout << "值为:" << e << "的元素在单链表中不存在!" << std::endl;
	return -1;
}


// 输出单链表中的所有元素
template<typename T>  // 时间复杂度O(n)
void LinkList<T>::DispList()
{
	std::cout << "开始打印单链表的元素" << std::endl;
	Node<T>* p_curr = m_head->next;										// 直接略过头结点
	while (p_curr!=nullptr)
	{
		std::cout << p_curr->data << "  ";
		p_curr = p_curr->next;
	}
	std::cout << std::endl;
	std::cout << "单链表元素打印结束!" << std::endl;
	return;
}

// 获取单链表的长度
template<typename T>													// O(1)
int LinkList<T>::ListLength()
{
	return m_length;
}
// 判断单链表是否为空
template<typename T>													// O(1)
bool LinkList<T>::Empty()
{
	/*  带头节点的还可以这样判断
	if (m_head->next == nullptr)
		return true;
	return false;
	*/
	if (m_length < 1)
		return true;
	return false;
}

// 翻转单链表
template<typename T>
void LinkList<T>::ReverseList()
{ // 这个不是针对数据域的翻转,而是整个节点的翻转
	if (m_length <= 1)
	{
		std::cout << "当前单链表中的元素不超过1个,不用做任何翻转操作!" << std::endl;
		return;
	}
	// 至少有2个节点才会走到这里
	Node<T>* pothersjd = m_head->next->next;							// 指向从第二个节点开始的后续节点
	m_head->next->next = nullptr;										// 把第一个节点的指针域先置空
	Node<T>* p_tmp;
	while (pothersjd != nullptr)
	{
		p_tmp = pothersjd;												// 当前需要移动的元素
		pothersjd = pothersjd->next;									// 指向下一个需要移动的元素

		// ListInsert(1, p_tmp);										// 这样也可以
		p_tmp->next = m_head->next;
		m_head->next = p_tmp;
	}
	return;
}


// 释放单链表-析构函数
template<typename T>
LinkList<T>::~LinkList()
{
	Node<T>* p_node = m_head->next;
	Node<T>* p_tmp;
	while (p_node != nullptr)											// 该循环负责释放数据节点
	{
		p_tmp = p_node;
		p_node = p_node->next;
		delete p_tmp;
	}
	delete m_head;														// 释放头结点
	m_head = nullptr;													//非必须,但是是个好习惯
	m_length = 0;														// 长度置0
}

#endif // !__MYLINKLIST_H__

不带头结点的单链表插入操作 [仅供参考]

// 不带头结点的单链表插入操作
// 在第i个位置(位置编号从1开始)插入指定元素e  【【仅供参考】】
template<typename T>
bool LinkList<T>::ListInsert(int i, const T& e)
{
	// 判断插入位置是否合法,i的合法值应该时从 1 到 m_length + 1 之间
	if (i < 1 || i >(m_length + 1))
	{
		std::cout << "元素:" << e << "插入位置:" << i << "不合法,合法的位置是:[1~" << m_length + 1 << "]!" << std::endl;
		return false;
	}

	if (i == 1)  // 插入到第一个位置与插入到其他位置不同,需要单独处理
	{
		Node<T>* node = new Node<T>;
		node->data = e;
		node->next = m_head;   // m_head原来指向的啥,就让新创建的 node(新的头结点)指向m_head
		m_head = node;   // 头指针指向新插入的第一个节点
		std::cout << "成功在位置为:" << i << "处插入元素:" << e << "!" << std::endl;
		++m_length;
		return true;
	}

	// 插入的不是第一个位置,测程序流程继续向下走
	Node<T>* p_curr = m_head;
	// 整个for循环用于找到第i-1个节点
	for (int j = 1; j < (i - 1); ++j)  // j从1开始,表示p_curr刚开始指向的是第1个节点
	{
		p_curr = p_curr->next;
		 // p_curr会找到当前要插入的位置,比如要在第2个位置插入,p_curr会指向第1个节点
	}
	Node<T>* node = new Node<T>;
	node->data = e;
	node->next = p_curr->next;  // 让新节点链上后续链表,因为p_curr->next指向的就是后续链表节点
	p_curr->next = node;  // 让当前位置链上新节点,因为node指向新节点
	std::cout << "成功在位置为:" << i << "处插入元素:" << e << "!" << std::endl;
	++m_length;	  // 实际长度+1

	return true;
}

测试Demo

// main 主函数
#include <iostream>
using namespace std;
#include "MyLinkList.hpp"

int main()
{
	LinkList<int> slinkobj;
	slinkobj.ListInsert(1, 12);
	slinkobj.ListInsert(1, 24);
	slinkobj.ListInsert(3, 48);
	slinkobj.ListInsert(2, 100);

	slinkobj.ListDelete(4);							// 删除第四个位置的节点
	int eval = 0;
	slinkobj.GetElem(3, eval);						// 如果GetElem返回true,则eval中保存着获取到的元素值
	int findvalue = 100;							// 在单链表中要找的元素值
	cout << "单链表的长度:" << slinkobj.ListLength() << endl;
	cout << "单链表是否为空:" << slinkobj.Empty() << endl;
	slinkobj.LocateElem(findvalue);
	slinkobj.DispList();
	slinkobj.ReverseList();
	slinkobj.DispList();
	return 0;
}

测试结果

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值