《数据结构、算法与应用 —— C++语言描述》学习笔记 — 线性表 —— 链式描述
一、链表的节点
链表中存储数据的节点至少包含链域(指向相邻节点)和数据域(包含数据本身),其结构定义为:
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提供反向迭代器用于反向遍历链表。相应地,在链表类中,我们可以提供rbegin和rend方法。