单链表实现一元多项式相加_作业-实现-单链表(难度1/10)含全部源码(必读)

本文详细介绍了C++如何实现单链表,包括链表节点的定义、添加元素、查找元素、删除元素以及链表大小的获取。通过实例代码展示了如何封装这些基本操作,并提供了完整的链表类`List`,该类支持插入、查找、删除和输出链表等功能。文章还提出了带有头结点的链表设计,以简化代码并提高效率。
摘要由CSDN通过智能技术生成

2020-11-08 安装严蔚敏的教材全部重新提供接口

后山coder:C++中级程序员教程 全目录(必读)

本作业主要考察:C++复制控制/C++动态内存管理/基础线性容器实现/测试驱动开发/接口完备性

写在前面

第一章 背景介绍

一、单链表有什么用?

链表主要用来存放数据,比如一个班级的所有学生对象。

二、与原始的数组相比,我们为何需要链表?

因为,两个方面:

1 链表可以动态增加元素,而不像C语言的数组那样一开始固定长度。比如:

int a[10];//创建固定长度的数组
Student s[10];//创建固定长度的数组

链表就可以不断的往里面放数据,而不需要考虑能不能存放的下。比如:

List a;//创建一个链表对象
a.push_back(1);//将整数1放入链表

2 将数据及其操作看成一个整体(是一种数据结构)

C语言的数组也好,动态的内存也好,都是需要程序员自己去管理的。繁琐而且容易出错,但是链表就可以统一管理,代码的可读性也很好。

class List
{
public:
    bool empty(void) const;//判断链表是不是空的
    size_t size(void) const;//得到链表里有多少个元素
    void push_back(int i);//往链表末尾追加一个新元素
    void pop_back(void);//删除链表的最后一个元素
private:
    //一些必要的成员变量
};

int main()
{
   List l;
   l.push_back(1);
   if(l.size()>0)
   {
        //做一些事情
    }
}

第二章 设计与分析

一、链表的样子

下面我们就一步一步设计链表这种结构

既然是链表,那就要前后链起来,变成一串。大概是这个样子

48cba5825f5e0d35d42f94f849364c3b.png

上面的链表只是示意图,链表总是要能够盛放数据的,不然也没有用处。

二、链表的元素

这里假设它盛放的一个一个的数字,干脆就是整数吧。像下面这样

ad428f482a130e320a68359aafceb11b.png

让我们关注组成链表的一个一个的链表节点,不妨叫做Node。

Node里面放了一个整数。大致像下面这样:

//链表的节点
struct Node
{
	int m_data;
};

这时候Node虽然没有链起来,但是却有存放整数的功能,我们不妨往里面存一个整数看看。例如,

#include <iostream>

//链表的节点
struct Node
{
	int m_data;
};

int main(void)
{
	Node node1;
	Node node2;
	node1.m_data = 1;
	node2.m_data = 2;

	std::cout << node1.m_data << ", " << node2.m_data << std::endl;

	return 0;
}

bcf7fe206e4ef71ede77df1ffd979fd7.png

如果我们愿意,我们可以创建很多node,然后逐个输出,这样好像看起来也是一个链表。

然而实际上它们并不是,因为它们散落在各个地方无法被统一管理(一盘散沙,这可不是我们想要的)。

三、将元素链起来

要想把各个节点Node链起来,还需要有一个链的功能,让一个Node具有可以找到下一个Node的能力。这时候我们可以把下一个Node的地址存在当前这个Node里。

这样以来,我们从第一个Node开始查看链表的时候,第一个Node除了能告诉我们它存的整数是多少,还能告诉我们下一个Node在哪里。

这个时候的链表应该更具体了,成了下面的样子。

注意观察,每一个节点被分成两部分:一部分用来存整数;另一部分用来存下一个节点在哪里。

5786012f422a0b7f765a4ecca091797d.png

这时候的Node结构也就变成了下面的样子,

//链表的节点
struct Node
{
	int m_data;
	Node* m_next;
};

现在,我们就让Node链起来看看,

#include <iostream>

//链表的节点
struct Node
{
	int m_data;
	Node* m_next;
};

int main(void)
{
	std::cout << "Hello List!" << std::endl;

	Node node1;
	Node node2;
	node1.m_data = 1;
	node2.m_data = 2;
	node1.m_next = &node2;//node1的下一个节点是node2,我们可以从node1找到node2
	node2.m_next = nullptr;//下一个节点是空,表示这个节点是最后一个
	//我们不再直接访问node2了,因为有node1这个领头的节点就可以了
	std::cout << node1.m_data << ", " << node1.m_next->m_data << std::endl;

	return 0;
}

我们做到了,

701756700b6a68dce043ca4f3a47b598.png

上面我们已经有了一个粗糙的链表。这个链表的头儿是node1,尾巴是node2。

我们也确实打印出了这个链表。但是这个链表的所有内容都暴露了出来。其实,我们只想拿到node1这个节点,也不想让链表的其他部分暴露出来。这样一个链表就像一个整体一样可以被传递,可以增加节点,可以删除节点。一个链表就是一个链表,而不是很多节点。

这样以来,我们就要有一个List结构来专门做这件事情,也就是上图中的那个L。

那么最简单的L就是一个存储着一个node节点地址的变量就可以了。比如,

//链表
struct List
{
	Node* m_head;//存储链表的第一个节点来做到管理整个链表
};

有了List,我们就可以把上图中的那个L创造出来了。就像这样,

1ade3fdb4debd893cb454fb76b1b70a4.png

对应的代码,

#include <iostream>

//链表的节点
struct Node
{
	int m_data;
	Node* m_next;
};
//链表
struct List
{
	Node* m_head;//存储链表的第一个节点来做到管理整个链表
};

int main(void)
{
	Node node1;
	Node node2;
	node1.m_data = 1;
	node2.m_data = 2;
	node1.m_next = &node2;//node1的下一个节点是node2,我们可以从node1找到node2
	node2.m_next = nullptr;//下一个节点是空,表示这个节点是最后一个
	List L;
	L.m_head = &node1;
        //我们也不再直接访问node1了,因为有L了
	std::cout << L.m_head->m_data << ", " << L.m_head->m_next->m_data << std::endl;

	return 0;
}

输出当然还是一样,不会变化

add821096ff1135547345e2a56e89405.png

四、将元素链起来封装成函数

上面有两个地方需要简化:

我们希望向链表里添加一个节点变的简单不需要关注跟Node相关的细节。为此,我们封装一个函数,传进来一个整数,并将这个整数添加到链表里。这个函数不妨就叫PushBack吧。

五、输出一个链表

我们可以输出整个链表,我们希望有一个变量可以不断的往链表末尾走,从而每次都打印这个变量,来遍历整个链表。而不是像这样:L.mhead->mnext->m_next->......来得到后面的节点。所以我们有一个PrintList函数来做这件事情。

下面就是PushBack和PrintList加入进来之后的代码,

#include <iostream>

//链表的节点
struct Node
{
	int m_data;
	Node* m_next;
};
//链表
struct List
{
	void PushBack(int value);
	Node* m_head;//存储链表的第一个节点来做到管理整个链表
};
//打印整个链表
void PrintList(List l);//实现在后面

int main(void)
{
	//纯粹的链表出现了,我们的思考方式再也不用关心Node是啥玩意儿了。
	List list;
	list.m_head = nullptr;//这是约定,要遵守,表示当前链表还没有一个数据
	list.PushBack(1);
	list.PushBack(2);
	PrintList(list);

	return 0;
}

void List::PushBack(int value)
{
	//这时候我们并不知道链表目前有没有被插入过节点
	auto fast = m_head;//fast沿着头节点往后跑,找到最后一个节点
	auto slow = fast;//slow向跟屁虫一样,紧跟fast之后,用来对fast实施悬崖勒马,后面你会看到slow的妙处
	while (fast != nullptr)
	{
		slow = fast;//跟屁虫跟上
		fast = fast->m_next;//fast接着跑
	}
	//上面的循环不管如何,fast都会到达最后一个节点的下一个位置,也就是悬崖那里。而slow正好在前面一个节点
	if (slow != nullptr)//链表至少有一个节点,slow指向链表的最后一个节点
	{
		//必须使用动态的堆内存上的对象,不然栈对象会在这个函数执行结束的时候释放掉
		//那样我们的节点就被收回了
		auto node = new Node;
		slow->m_next = node;//由slow负责把新节点放到最后接上
		node->m_data = value;//保存传进来的要被保存的整数数据
		node->m_next = nullptr;//这是一种表示后面没有节点的约定
	}
	else//向空链表中增加第一个节点
	{
		m_head = new Node;
		m_head->m_data = value;
		m_head->m_next = nullptr;
	}
}
void PrintList(List l)
{
	auto p = l.m_head;
	while (p != nullptr)
	{
		std::cout << p->m_data << ", ";
		p = p->m_next;
	}
}

当然输出就是我们期望的那样:

54389fa704657ea5df5ce2341ed20bec.png

那么到目前为止,我们的单向链表就初步实现了。只不过这样的链表用处很受局限。

下面是作业,请你仿照上面的代码和思路实现下面的功能:

1 单链表的元素查找:

Node* FindInList(int value);//函数返回查找到值为value的第一个元素,没有就返回nullptr

2 实现单链表元素查找: (注意利用1)

bool ExistInList(int value);//函数返回value这个值是否在链表中,找到一个就算有。

3 实现单链表的节点删除:(注意利用1)

void DeleteValueInList(int value);//删除链表中的值为value的第一个元素,不存在什么也不做

4 实现链表当前元素数量:

size_t Size(void) const;//返回链表当前的容量

5 实现链表的默认构造函数

List();//实现链表成员变量的初始化
~List();//超出作用域的时候释放对象,同时负责释放自己开辟的动态内存

6 实现链表的复制构造函数

List(const List& listFrom);//拷贝一个新的链表,从listFrom拿数据
List& operator=(const List& listFrom);//将以对象赋值给另一个对象的时候

第三章 完整实现

一、带有头结点单链表

从第二章 四、PushBack函数的实现来看,我们的代码不够简洁,因为总是要在做一件事情之前先判断一下链表是否为空。这样的代码写起来容易出错,而且也不直观。

如果链表增加一个头结点,代码写起来就会舒服很多,虽然多了一个节点,但是好处远大于坏处(代码简洁)。

比如,插入元素就不用考虑链表是否为空:

void SLList::insert(int i, const ElementType & e)
{
	if (i < 0 || i > m_size)
	{
		throw std::runtime_error("invalid index to insert.");
	}
	SLNode* p = &m_head;
	int step = 0;
	while (step < i && p != nullptr)
	{
		p = p->m_next;
		++step;
	}
	SLNode* q = new SLNode(e);
	q->m_next = p->m_next;
	p->m_next = q;
	++m_size;
}

二、带有头结点单链表的完整实现

6154aac24591a2c56c3acad557671917.png

代码如下(已包含大部分必要的注释):

#include <iostream>
#include <string>

typedef int ElementType;

class SLList
{
public:
	SLList();//默认构造函数
	SLList(const SLList& from);//复制构造函数
	SLList& operator = (const SLList& from);//赋值操作符重载
	~SLList();//析构函数
public:
	class SLNode
	{
		friend class SLList;
	private:
		SLNode();
		SLNode(const ElementType& t);
		~SLNode();
	public:
		ElementType m_data;
		SLNode* m_next;
	};
public://成员函数
	bool empty(void) const { return m_size == 0; }//判断链表是否为空
	int size(void) const { return m_size; }//返回链表元素的数量
	const ElementType& get_element(int i) const;//获取链表的第i个元素(可能会抛异常)
	ElementType& get_element(int i);//获取链表的第i个元素(可能会抛异常)
	/* 在第i个元素之前插入e(可能会抛异常)
	   拥有n个元素的链表可以插入的位置有n+1个:0, 1, ... , n
	   插入位置为n表示插入在最后一个元素之后
	*/
	void insert(int i, const ElementType& e);
	/* 查找某个元素是否存在,当发现第一个时就返回,所以返回SLNode*;
	   这个函数有3个作用:
	   1 查找元素是否存在; 
	   2 读写元素;
	   3 相当于返回的是迭代器
	*/
	SLNode* find(const ElementType& e);
	void delete_element(int i);//删除第i个元素(可能会抛异常)
	void clear(void);//删除所有元素
private:
	void copy(const SLList & from);
private:
	/* 指向头节点,不存储元素;
	   next为空指针表示没有最后一个元素,也就是空链表;
	   方便代码编写
	*/
	SLNode m_head;
	int m_size;//链表当前有多少个元素
};

SLList::SLList()
	: m_size(0)
{
	std::cout << "SLList()n";
}

SLList::~SLList()
{
	clear();
	std::cout << "~SLList()n";
}

void SLList::copy(const SLList & from)
{
	clear();
	auto pThis = &m_head;
	auto pFrom = &from.m_head;
	for (int i = 0; i < from.m_size; i++)
	{
		auto q = new SLNode(pFrom->m_next->m_data);
		pThis->m_next = q;
		pThis = q;
		pFrom = pFrom->m_next;
	}
	m_size = from.m_size;
}
SLList::SLList(const SLList & from)
{
	if (!from.empty())
	{
		copy(from);
	}
	std::cout << "SLList(const SLList & from)n";
}

SLList & SLList::operator=(const SLList & from)
{
	if (this == &from)
	{
		std::cout << "SLList & SLList::operator=(const SLList & from)n";
		return *this;
	}
	else
	{
		copy(from);
		std::cout << "SLList & SLList::operator=(const SLList & from)n";
		return *this;
	}
}

const ElementType& SLList::get_element(int i) const
{
	if (i < 0 || i >= m_size)
	{
		throw std::runtime_error("invalid index of get_element.");
	}
	auto p = &m_head;
	int j = 0;
	while (p->m_next && j < i)
	{
		p = p->m_next;
		++j;
	}
	return p->m_next->m_data;
}

ElementType & SLList::get_element(int i)
{
	if (i < 0 || i >= m_size)
	{
		throw std::runtime_error("invalid index of get_element.");
	}
	auto p = &m_head;
	int j = 0;
	while (p->m_next && j < i)
	{
		p = p->m_next;
		++j;
	}
	return p->m_next->m_data;
}

SLList::SLNode * SLList::find(const ElementType & e)
{
	auto p = &m_head;
	while (p->m_next)
	{
		if (p->m_next->m_data == e)
		{
			return p->m_next;
		}
		p = p->m_next;
	}
	return nullptr;
}

void SLList::insert(int i, const ElementType & e)
{
	if (i < 0 || i > m_size)
	{
		throw std::runtime_error("invalid index to insert.");
	}
	SLNode* p = &m_head;
	int step = 0;
	while (step < i && p != nullptr)
	{
		p = p->m_next;
		++step;
	}
	SLNode* q = new SLNode(e);
	q->m_next = p->m_next;
	p->m_next = q;
	++m_size;
}

void SLList::delete_element(int i)
{
	if (i < 0 || i >= m_size)
	{
		throw std::runtime_error("invalid index of delete_element.");
	}
	auto p = &m_head;
	int j = 0;
	while (p->m_next && j < i)
	{
		p = p->m_next;
		++j;
	}
	auto q = p->m_next;
	p->m_next = q->m_next;
	delete q;
	--m_size;
}

void SLList::clear(void)
{
	for (auto p = &m_head; p->m_next != nullptr; )
	{
		auto q = p->m_next;
		p->m_next = q->m_next;
		delete q;
	}
	m_head.m_next = nullptr;
	m_size = 0;
}

SLList::SLNode::SLNode()
	:m_next(nullptr)
{
}
SLList::SLNode::SLNode(const int& t) : m_data(t), m_next(nullptr)
{
}

SLList::SLNode::~SLNode()
{
}
void print_list(const SLList& slist, const std::string& msg)
{
	std::cout << "print " << msg << ":";
	for (int i = 0; i < slist.size(); ++i)
	{
		std::cout << slist.get_element(i) << " ";
	}
	std::cout << "n";
}
void Check(bool b)
{
	if (b)
	{
		std::cout << "Pass" << std::endl;
	}
	else
	{
		std::cout << "NOT Pass" << std::endl;
	}
}
int main()
{
	{
		//test empty/size;
		std::cout << "test empty/size" << std::endl;
		SLList slist;
		Check(slist.size() == 0);
		Check(slist.empty());
		slist.insert(0, 1);
		Check(slist.size() == 1);
		Check(slist.empty() == false);
	}
	{
		//test insert/get_element/find;
		std::cout << "test insert/get_element/find" << std::endl;
		SLList slist;
		Check(slist.size() == 0);
		Check(slist.empty());
		try
		{
			slist.get_element(-1);
		}
		catch (const std::exception& e)
		{
			std::cout<<"Pass " << e.what() << std::endl;
		}
		Check(slist.find(123) == nullptr);
		slist.insert(0, 123);
		Check(slist.find(123) && slist.find(123)->m_data == 123);
		Check(slist.size() == 1);
		Check(slist.empty() == false);
		Check(slist.get_element(0) == 123);
		slist.insert(1, 456);
		Check(slist.find(456) && slist.find(456)->m_data == 456);
		Check(slist.get_element(0) == 123);
		Check(slist.get_element(1) == 456);
		Check(slist.size() == 2);
		try
		{
			slist.get_element(2);

		}
		catch (const std::exception& e)
		{
			std::cout << "Pass " << e.what() << std::endl;
		}
		slist.get_element(1) = 789;
		Check(slist.get_element(1) == 789);
		Check(slist.find(789) && slist.find(789)->m_data == 789);
	}
	{
		//test delete_element;
		std::cout << "test delete_element" << std::endl;
		SLList slist;
		try
		{
			slist.delete_element(0);
		}
		catch (const std::exception& e)
		{
			std::cout << "Pass " << e.what() << std::endl;
		}
		slist.insert(0, 123);
		Check(slist.find(123) && slist.find(123)->m_data == 123);
		
		slist.delete_element(0);
		Check(slist.size() == 0);
		Check(slist.empty());

		slist.insert(0, 123);
		slist.insert(1, 456);
		slist.insert(2, 789);
		slist.insert(3, 101112);
		slist.insert(4, 131415);
		print_list(slist, "5个整数");
		slist.delete_element(slist.size() - 1);
		print_list(slist, "删除最后一个,剩4个整数");

		slist.delete_element(0);
		print_list(slist, "删除第1个,剩3个整数");
		slist.delete_element(1);
		print_list(slist, "删除第2个,剩2个整数");
		slist.delete_element(0);
		slist.delete_element(0);
		print_list(slist, "全部删除后");
	}
	{
		//test clear;
		std::cout << "test clear" << std::endl;
		SLList slist;
		slist.insert(0, 123);
		slist.insert(1, 456);
		slist.insert(2, 789);
		slist.insert(3, 101112);
		slist.insert(4, 131415);
		print_list(slist, "5个整数");
		slist.clear();
		print_list(slist, "全部删除后");
	}
	{
		//test copy;
		std::cout << "test copy" << std::endl;
		SLList slist;
		slist.insert(0, 123);
		slist.insert(1, 456);
		slist.insert(2, 789);
		slist.insert(3, 101112);
		slist.insert(4, 131415);
		print_list(slist, "slist 5个整数");
		{
			SLList slist2(slist);
			print_list(slist2, "slist2 5个整数");
		}
		{
			SLList slist3;
			slist3 = slist;
			print_list(slist3, "slist3 5个整数");
		}
		slist.clear();
		print_list(slist, "全部删除后");
	}
}

测试与输出:

test empty/size
SLList()
Pass
Pass
Pass
Pass
~SLList()
test insert/get_element/find
SLList()
Pass
Pass
Pass invalid index of get_element.
Pass
Pass
Pass
Pass
Pass
Pass
Pass
Pass
Pass
Pass invalid index of get_element.
Pass
Pass
~SLList()
test delete_element
SLList()
Pass invalid index of delete_element.
Pass
Pass
Pass
print 5个整数:123 456 789 101112 131415
print 删除最后一个,剩4个整数:123 456 789 101112
print 删除第1个,剩3个整数:456 789 101112
print 删除第2个,剩2个整数:456 101112
print 全部删除后:
~SLList()
test clear
SLList()
print 5个整数:123 456 789 101112 131415
print 全部删除后:
~SLList()
test copy
SLList()
print slist 5个整数:123 456 789 101112 131415
SLList(const SLList & from)
print slist2 5个整数:123 456 789 101112 131415
~SLList()
SLList()
SLList & SLList::operator=(const SLList & from)
print slist3 5个整数:123 456 789 101112 131415
~SLList()
print 全部删除后:
~SLList()

试试吧,祝你好运!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值