链表描述的基本知识和单向链表的描述见线性表–单向链表。
使用头节点的循环链表可以使得链表的一些操作程序更加简洁,运行速度更快。例如我们的index_of操作,就可以将要查找的元素放入头节点的数据域,在循环条件中仅仅需要判断while(currentNode->element!=_given_element)
一个表达式,不需要像前面我们实现的未带有头节点的单向链表一样进行两个表达式的判别while(currentNode!=nullptr && currentNode->element!=_given_element)
,因为在头节点链表中我们可以肯定即使链表中没有需要查找的元素 _given_element
我们也能停止循环(当遍历到头节点时循环终止)。
使用双向链表则可以降低链表基本操作的时间复杂度。比如insert/get/erase操作,当用户给出的索引小于size/2时,就可以直接从链表的左边进行遍历,反之则从右边进行遍历(需要注意的是,我们实现的链表的插入操作是在索引处前插,而删除操作是直接删除给定索引节点,也就是说要插入的节点的后继节点实际就是索引处的节点,而要删除的节点的后继节点实际是索引处节点的后一个节点,因此从右边开始遍历的情况下,为了找出给定索引的节点的后继节点,插入操作的循环步数要比删除操作多一步)。
也因此相较于单链表,我们需要更多地考虑对头节点以及增删节点的相邻节点的指针维护。
与单链表一样,在拷贝控制时尤其需要注意对原有链表元素的内存资源的释放,避免直接拷贝指针导致内存泄漏或者二次delete。
自定义的异常类
illegalParameterValue.h
#pragma once
//========================自定义的参数异常类==========================
#include <iostream>
#include <string>
using std::cout; using std::cin; using std::ends; using std::endl; using std::cerr;
using std::string;
class illegalParameterValue {
public:
illegalParameterValue(const string& _message="Illegal parameter value!") :message(_message) {
}
string what() const {
return message; }
void output()const {
cout << message << endl; }
private:
string message;
};
需要实现线性表抽象数据类(ADT)的接口
myLinearList.h
#pragma once
//========================线性表的抽象描述========================
template <typename T>
class myLinearList {
public:
virtual ~myLinearList() {
}
virtual bool empty()const = 0;//是否为空线性表
virtual size_t get_size()const = 0;//返回线性表的元素个数
virtual T& get_element(size_t index)const = 0;//返回索引处的元素值
virtual size_t index_of(const T& element)const = 0;//返回该元素第一次出现的索引
virtual void insert(const T&, size_t index) = 0;//在指定索引处位置插入元素
virtual void erase(size_t index) = 0;//删除指定索引位置的元素
virtual void output(std::ostream& os = cout) const = 0;//遍历输出线性表
};
对上述抽象类的扩展
myExtendedLinearList.h
#pragma once
//========================拓展的线性表的抽象类========================
#include "myLinearList.h"
//增加几个接口 实现这个抽象类的extendedChain类需要改进erase和insert代码
template <typename T>
class myExtendedLinearList : public myLinearList<T> {
public:
virtual ~myExtendedLinearList() {
}
virtual void clear() = 0;
virtual void push_back(const T& element) = 0;//将元素element插入到表尾
};
双向节点
#pragma once
template <typename T>
struct myBidirNode{
//同时具有前驱节点、后继节点
myBidirNode<T>* prev;
myBidirNode<T>* next;
T element;
myBidirNode() = default;
//构造节点时 要么传入一个数据域的元素构造一个悬空的节点(prev/next都为空)
//要么同时传入数据域的元素和指针域的两个节点指针
myBidirNode(const T& _element):element(_element),prev(nullptr),next(nullptr){
}
myBidirNode(const T& _element,myBidirNode<T>* _prev,myBidirNode<T>* _next)
:element(_element),prev(_prev),next(_next){
}
};
用于双向循环链表的自定义双向迭代器类
#pragma once
#include "myBidirNode.h"
template <typename T>
class myBidirIteraotr {
public:
//公有成员
typedef std::bidirectional_iterator_tag iterator_category;//迭代器类型标签
typedef T value_type;//值类型
typedef std::ptrdiff_t difference_type;//标识迭代器之间的距离 C++17 弃用
typedef T* pointer;//指向被迭代的类型(T)的指针
typedef T& reference;//被迭代类型(T)的引用
//构造函数
myBidirIteraotr(myBidirNode<T>* _ptr=nullptr) :node_ptr(_ptr) {
}
//解引用操作
T operator*() const {
return node_ptr->element; }
T* operator->() const {
return &(node_ptr->element); }
//递增递减
myBidirIteraotr& operator++() {
node_ptr=node_ptr->next; return *this; }
myBidirIteraotr& operator--() {
node_ptr=node_ptr->prev; return *this; }
myBidirIteraotr operator++(int) {
myBidirIteraotr<T> ret = *this; ++(*this); return ret; }
myBidirIteraotr operator--(int) {
myBidirIteraotr<T> ret = *this; --(*this); return ret; }
//相等
bool operator==(const myBidirIteraotr& rhs)const {
return node_ptr == rhs.node_ptr; }
bool operator!=(const myBidirIteraotr& rhs)const {
return node_ptr != rhs.node_ptr; }
protected:
myBidirNode<T>* node_ptr;//指向元素的指针
};
带头节点的双向循环链表的具体实现
一些操作注意事项及思考见注释
#pragma once
#include "illegalParameterValue.h"
#include "myExtendedLinearList.h"
#include "myBidirNode.h"
#include "myBidirIterator.h"
#include <sstream>
//带有头节点的双向循环链表
template <typename T>
class doubleCircularChain:public myExtendedLinearList<T>{
public:
doubleCircularChain();
doubleCircularChain(const doubleCircularChain<T>& copiedChain);
doubleCircularChain(doubleCircularChain<T>&& movedChain) noexcept;
~doubleCircularChain();
doubleCircularChain& operator=(doubleCircularChain<T>& rhs);//为了区分重载 必须将rhs声明为左值引用类型
doubleCircularChain& operator=