《数据结构、算法与应用 —— C++语言描述》学习笔记 — 线性表 —— 链式描述

这篇博客详细介绍了C++中链表数据结构的实现,包括链表节点的结构、线性表的链表描述,如构造、拷贝、析构、查找、删除、插入和输出操作。此外,还探讨了迭代器接口、线性表的扩展,如循环链表和双向链表的实现,以及相关操作的时间复杂度分析。
摘要由CSDN通过智能技术生成

一、链表的节点

链表中存储数据的节点至少包含链域(指向相邻节点)和数据域(包含数据本身),其结构定义为:

template<class T>
struct chainNode
{
	T element;
	chainNode<T>* next;

	chainNode() {};
	chainNode(const chainNode& other) :
		element(other.element),
		next(other.next) {};
	chainNode(const T& element, chainNode<T>* next = nullptr)
	{
		this->element = element;
		this->next = next;
	}
};

二、线性表链表描述

#include "linearList.h"
#include "chainNode.h"
using namespace std;

template<class T>
class chain : public linearList<T>
{
public:
	chain(int capacity = 10);
	chain(const chain<T>& other);
	~chain();

	bool empty();
	int size() const;
	T& get(int index) const;
	int indexOf(const T& element) const;
	void erase(int index);
	void insert(int index, const T& val);
	void output(std::ostream& out) const;

protected:
	void swap(chain<T>& other);
	void checkIndex(int index) const;

	chainNode<T>* first;
	int listSize;
};

1、构造和拷贝

template<class T>
inline chain<T>::chain(int capacity)
{
	if (capacity < 1)
	{
		throw new invalid_argument(string("Initial Capacity = %d must be > 0", capacity));
	}

	listSize = 0;
	first = nullptr;
}

template<class T>
inline chain<T>::chain(const chain<T>& other):
	first(nullptr),
	listSize(other.listSize)
{
	if (other.listSize == 0)
	{
		return;
	}

	//copy and swap
	chain<T> tempChain;
	tempChain.first = new chainNode<T>(*(other.first));
	tempChain.listSize = other.listSize;

	chainNode<T>* currentNode = tempChain.first;
	while (currentNode->next != nullptr)
	{
		currentNode->next = new chainNode<T>(*(currentNode->next));
		currentNode = currentNode->next;
	}

	swap(tempChain);
}

template<class T>
inline void chain<T>::swap(chain<T>& other)
{
	using std::swap;
	swap(first, other.first);
	swap(listSize, other.listSize);
}

这里我们使用copy and swap策略实现拷贝。构造函数的复杂度为O(1);拷贝构造函数的复杂度为O(n)

2、析构

template<class T>
inline chain<T>::~chain()
{
	listSize = 0;
	while (first != nullptr)
	{
		chainNode<T>* next = first->next;
		delete first;
		first = next;
	}
}

链表节点需要被逐个释放。该操作的时间复杂度为O(n)

3、容量

template<class T>
inline bool chain<T>::empty()
{
	return listSize == 0;
}

template<class T>
inline int chain<T>::size() const
{
	return listSize;
}

这几个方法的时间复杂度为O(1)

4、查找

template<class T>
inline T& chain<T>::get(int index) const
{
	checkIndex(index);

	// useless to check currentNode != nullptr after checkindex
	chainNode<T>* currentNode = first;
	while (index != 0)
	{
		currentNode = currentNode->next;
		index--;
	}

	return currentNode->element;
}

template<class T>
inline int chain<T>::indexOf(const T& element) const
{
	int index = 0;
	chainNode<T>* currentNode = first;
	while (currentNode != nullptr && currentNode->element != element)
	{
		currentNode = currentNode->next;
		index++;
	}

	if (currentNode == nullptr)
	{
		return -1;
	}

	return index;
}

template<class T>
inline void chain<T>::checkIndex(int index) const
{
	if (index >= listSize || index < 0)
	{
		throw new out_of_range(string("index = %d out of range with size = %d", index, listSize));
	}
}

这里由于元素存储不再是物理空间连续,而只能通过当前节点访问下一个几点。因此,这两个函数的时间复杂度为O(n)

5、删除

template<class T>
inline void chain<T>::erase(int index)
{
	checkIndex(index);

	chainNode<T>* prevNode = nullptr;
	chainNode<T>* currentNode = first;
	while (index != 0)
	{
		prevNode = currentNode;
		currentNode = currentNode->next;
		index--;
	}

	if (prevNode != nullptr)
	{
		prevNode->next = currentNode->next;
	}
	else
	{
		first = currentNode->next;
	}

	listSize--;
	delete currentNode;
}

这个方法的时间复杂度为O(n)。但是与数组相比,使用链表存储的数据在删除头尾节点时,有良好的性能。

6、插入

template<class T>
inline void chain<T>::insert(int index, const T& val)
{
	if (index > listSize || index < 0)
	{
		throw new out_of_range(string("index = %d out of range with size = %d", index, listSize));
	}

	chainNode<T>* prevNode = nullptr;
	chainNode<T>* currentNode = first;
	while (index != 0)
	{
		prevNode = currentNode;
		currentNode = currentNode->next;
		index--;
	}

	if (prevNode != nullptr)
	{
		prevNode->next = new chainNode<T>(val, currentNode);
	}
	else
	{
		first = new chainNode<T>(val, currentNode);
	}

	listSize++;
}

与删除类似,这个方法的时间复杂度为O(n)

7、输出

template<class T>
std::ostream& operator<<(std::ostream& out, const chain<T>& ch)
{
	ch.output(out);
	return out;
}

template<class T>
inline void chain<T>::output(std::ostream& out) const
{
	for (auto node = first; node != nullptr; node = node->next)
	{
		out << node->element << " ";
	}
}

二者的时间复杂度都是O(n)

三、迭代器

1、接口

#include "chainNode.h"
#include <xmemory>

template<class T>
class chainIterator
{
public:
	using iterator_category = std::forward_iterator_tag;
	using value_type = T;
	using difference_type = ptrdiff_t;
	using pointer = T*;
	using reference = const value_type&;

	chainIterator(chainNode<T>* node;);
	T& operator*() const;
	T* operator->() const;
	chainIterator<T> operator++();
	chainIterator<T> operator++(int);
	bool operator==(const chainIterator<T>& _right) const;
	bool operator!=(const chainIterator<T>& _right) const;

protected:
	chainNode<T>* node;
};

2、实现

template<class T>
inline chainIterator<T>::chainIterator(chainNode<T>* node)
{
	this->node = node;
}

template<class T>
inline T& chainIterator<T>::operator*() const
{
	return node->element;
}

template<class T>
inline T* chainIterator<T>::operator->() const
{
	return &node->element;
}

template<class T>
inline chainIterator<T> chainIterator<T>::operator++()
{
	node = node->next;
	return *this;
}

template<class T>
inline chainIterator<T> chainIterator<T>::operator++(int)
{
	auto old = *this;
	this->operator++();
	return old;
}

template<class T>
inline bool chainIterator<T>::operator==(const chainIterator<T>& right) const
{
	return node == right.node;
}

template<class T>
inline bool chainIterator<T>::operator!=(const chainIterator<T>& right) const
{
	return !this->operator==(right);
}

3、begin 和 end

template<class T>
inline chainIterator<T> chain<T>::begin()
{
	return chainIterator<T>(first);
}

template<class T>
inline chainIterator<T> chain<T>::end()
{
	return nullptr;
}

四、线性表扩展

1、接口

#pragma once
#include "linearList.h"

template<class T>
class extendedLinearList : linearList<T>
{
public:
	virtual ~extendedLinearList() {};
	virtual void clear() = 0;
	virtual void push_back(const T& element) = 0;
};

2、链表接口

#include "extendedLinearList.h"
#include "chain.h"

template<class T>
class extendedChain : public chain<T>, public extendedLinearList<T>
{
public:
	using iterator = typename chainIterator<T>;

	extendedChain(int capcity = 10);
	~extendedChain();

	bool empty();
	int size() const;
	T& get(int index) const;
	int indexOf(const T& element) const;
	void erase(int index);
	void insert(int index, const T& val);
	void output(std::ostream& out) const;
	iterator begin();
	iterator end();

	void clear() override;
	void push_back(const T& element) override;

protected:
	chainNode<T>* last;
};

3、链表实现

扩展后的的链表接口很多可以用原chain的接口直接实现,这里主要列举几个需要改造的接口。

(1)增加和删除

template<class T>
inline void extendedChain<T>::erase(int index)
{
	this->checkIndex(index);

	chainNode<T>* prevNode = nullptr;
	chainNode<T>* currentNode = this->first;
	while (index != 0)
	{
		prevNode = currentNode;
		currentNode = currentNode->next;
		index--;
	}

	if (prevNode != nullptr)
	{
		if (currentNode == last)
		{
			last = prevNode;
		}
		prevNode->next = currentNode->next;
	}
	else
	{
		this->first = currentNode->next;
	}

	this->listSize--;
	delete currentNode;
}

template<class T>
inline void extendedChain<T>::insert(int index, const T& val)
{
	if (index > this->listSize || index < 0)
	{
		throw new out_of_range(string("index = %d out of range with size = %d", index, this->listSize));
	}

	chainNode<T>* prevNode = nullptr;
	chainNode<T>* currentNode = this->first;
	while (index != 0)
	{
		prevNode = currentNode;
		currentNode = currentNode->next;
		index--;
	}

	if (prevNode != nullptr)
	{
		prevNode->next = new chainNode<T>(val, currentNode);
		if (this->listSize == index)
		{
			last = prevNode->next;
		}
	}
	else
	{
		this->first = new chainNode<T>(val, currentNode);
		if (this->listSize == 0)
		{
			last = this->first;
		}
	}

	this->listSize++;
}

这里的接口主要代码和chain中保持一致。需要注意的是,last的值可能会随着元素的增加和删除而变化。

(2)clear

template<class T>
inline void extendedChain<T>::clear()
{
	this->listSize = 0;
	while (this->first != nullptr)
	{
		auto next = this->first->next;
		delete this->first;
		this->first = next;
	}
}

这个方法的时间复杂度为O(n)

(3)push_back

template<class T>
inline void extendedChain<T>::push_back(const T& element)
{
	auto newNode = new chainNode<T>(element);
	if (this->listSize == 0)
	{
		this->first = last = newNode;
	}
	else
	{
		last->next = newNode;
		last = newNode;
	}

	this->listSize++;
}

由于增加了last节点,这里push_back方法,即在末尾追加元素的时间复杂度为O(1)

五、循环链表和头结点

循环表在链表的基础上增加一个头结点。将单向链表的尾结点与头结点相连,链表就成为循环链表。头结点起到哨兵节点的作用,其并不真正保存数据。相比普通链表,循环列表的各项操作的时间复杂度并没有变化。但是其代码更为简单。我们简单实现其indexof方法:

template<class T>
inline int circularChainWithHeader<T>::indexOf(const T& element) const
{
	header->element = element;

	int index = 0;
	auto current = header->next;
	while (current != header && current->element != element)
	{
		current = current->next;
		index++;
	}

	return current == header ? index : -1;
}

六、双向链表

双向链表支持双向访问。其每个结点都维护其前驱结点和后继结点。在链表中,既保存了第一个元素节点,又保存了最后一个结点。双向链表可以让我们可为快速地执行一些操作。例如查找,我们可以根据index决定从左向右找还是从右向左找。

1、结点

#pragma once

template<class T>
struct bidirectionalChainNode
{
	T element;
	bidirectionalChainNode<T>* next;
	bidirectionalChainNode<T>* prev;

	bidirectionalChainNode() {};
	bidirectionalChainNode(const bidirectionalChainNode& other) :
		element(other.element),
		next(other.next),
		prev(other.prev) {};
	bidirectionalChainNode(const T& element, bidirectionalChainNode<T>* next = nullptr, bidirectionalChainNode<T>* prev = nullptr)
	{
		this->element = element;
		this->next = next;
		this->prev = prev;
	}
};

2、查找

template<class T>
inline T& bidirectionalChain<T>::get(int index) const
{
	checkIndex(index);

	if (index < listSize / 2)
	{
		auto currentNode = first;
		for (int i = 0; i < index; i++)
		{
			currentNode = currentNode->next;
		}
		return currentNode->element;
	}
	else
	{
		auto currentNode = last;
		for (int i = listSize - 1; i > index; i--)
		{
			currentNode = currentNode->prev;
		}
		return currentNode->element;
	}
}

3、删除

template<class T>
inline void bidirectionalChain<T>::erase(int index)
{
	checkIndex(index);

	bidirectionalChainNode<T>* currentNode = nullptr;
	if (index < listSize / 2)
	{
		currentNode = first;
		for (int i = 0; i < index; i++)
		{
			currentNode = currentNode->next;
		}
		currentNode == first ? first = currentNode->next : nullptr;
	}
	else
	{
		currentNode = last;
		for (int i = listSize - 1; i > index; i--)
		{
			currentNode = currentNode->prev;
		}
		currentNode == last ? last = currentNode->prev : nullptr;
	}

	currentNode->next == nullptr ? nullptr : currentNode->next->prev = currentNode->prev;
	currentNode->prev == nullptr ? nullptr : currentNode->prev->next = currentNode->next;
	delete currentNode;
	listSize--;
}

4、插入

template<class T>
inline void bidirectionalChain<T>::insert(int index, const T& val)
{
	if (index > listSize || index < 0)
	{
		throw new out_of_range(string("index = %d out of range with size = %d", index, listSize));
	}

	if (listSize == 0)
	{
		first = last = new bidirectionalChainNode<T>(val, nullptr, nullptr);
	}
	else if (index == 0)
	{
		first->prev = new bidirectionalChainNode<T>(val, first, nullptr);
		first = first->prev;
	}
	else if (listSize == index)
	{
		last->next = new bidirectionalChainNode<T>(val, nullptr, last);
		last = last->next;
	}
	else
	{
		bidirectionalChainNode<T>* currentNode = nullptr;
		if (index < listSize / 2)
		{
			currentNode = first;
			for (int i = 0; i < index; i++)
			{
				currentNode = currentNode->next;
			}
		}
		else
		{
			currentNode = last;
			for (int i = listSize - 1; i > index; i--)
			{
				currentNode = currentNode->prev;
			}
		}

		currentNode->prev = new bidirectionalChainNode<T>(val, currentNode, currentNode->prev);
		currentNode->prev->prev->next = currentNode->prev;
	}
	
	listSize++;
}

5、迭代器

与普通链表的迭代器不同,双向链表的迭代器为双向迭代器。也就是说该迭代器需要支持递减操作:

template<class T>
inline bidirectionalChainIterator<T> bidirectionalChainIterator<T>::operator--()
{
	node = node->prev;
	return *this;
}

template<class T>
inline bidirectionalChainIterator<T> bidirectionalChainIterator<T>::operator--(int)
{
	auto old = *this;
	this->operator--();
	return old;
}

除此之外,我们还可以为bidirectionalChain提供反向迭代器用于反向遍历链表。相应地,在链表类中,我们可以提供rbeginrend方法。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值