数据结构基础(11) --循环链表的设计与实现

循环链表:最后一个结点的指针域的指针又指回第一个结点的链表;

循环单链表与单链表的区别在于:表中最有一个节点的指针不再是NULL, 而改为指向头结点(因此要对我们原来的MyList稍作修改), 从而整个链表形成一个环.

因此, 循环单链表的判空条件不再是头结点的指针是否为空, 而是他是否等于头结点;

其实如果只是单纯的实现循环链表对单链表的性能提升是不明显的, 反而增加了代码上实现的复杂度, 但是如果与下一篇中的双向链表相结合的话, 在速度上的提升是十分惊人的, 因此这篇博客权当做是一个过渡吧, 为上一篇博客和下一篇博客的结合起着承上启下的作用.

下面是MyList.h的完整代码与解析, 由于代码较多, 希望能够仔细阅读, 但其实下面的大部分代码都与前面类似只是对其中几处稍作修改, 遇到与单链表的不同之处, 我会与++符号作为注释指出:

#ifndef MYLIST_H_INCLUDED
#define MYLIST_H_INCLUDED

#include <iostream>
#include <stdexcept>
using namespace std;

//循环链表
//前向声明
template <typename Type>
class MyList;
template <typename Type>
class ListIterator;

//链表节点
template <typename Type>
class Node
{
    //可以将MyList类作为Node的友元
    //同时也可以将Node类做成MyList的嵌套类, 嵌套在MyList中, 也可以完成该功能
    friend class MyList<Type>;
    friend class ListIterator<Type>;

    template <typename T>
    friend ostream &operator<<(ostream &os, const MyList<T> &list);
private:
    //constructor说明:
    //next = NULL;    //因为这是一个新生成的节点, 因此下一个节点为空
    Node(const Type &dataValue):data(dataValue), next(NULL) {}

    Type data;  //数据域:节点数据
    Node *next; //指针域:下一个节点
};

//链表
template <typename Type>
class MyList
{
    template <typename T>
    friend ostream &operator<<(ostream &os, const MyList<T> &list);

    friend class ListIterator<Type>;
public:
    MyList();
    ~MyList();

    //将元素插入表头
    void insertFront(const Type &data);
    //将元素插入到位置index上(index从1开始)
    void insert(const Type &data, int index);
    //删除表中所有值为data的节点
    void remove(const Type &data);
    bool isEmpty() const;

private:
    //指向第一个节点的指针
    Node<Type> *first;
};

template <typename Type>
MyList<Type>::MyList()
{
    //first指向一个空节点
    first = new Node<Type>(0);

    // ++ 这是一个关键点
    //first的下一个元素就指向first
    //此时, 代表链表是否已经到达结尾处的判断已经不再是(是否等于NULL)
    //而改为(是否等于first)
    //因为此时first代表链表的最后一个元素
    //同时,first又是第一个元素的前一个元素
    first -> next = first;
}

template <typename Type>
MyList<Type>::~MyList()
{
    Node<Type> *deleteNode = NULL;
    // ++ 保存链表尾元素
    Node<Type> *tmp = first;

    // ++ first首先指向第一个真实的元素
    first = first->next;
    //一路到达链表结尾
    while (first != tmp)
    {
        deleteNode = first;
        first = first -> next;
        delete deleteNode;
    }
    // ++ 释放到链表的空节点
    delete tmp;
}

//这一步与前一版链表相同
template <typename Type>
void MyList<Type>::insertFront(const Type &data)
{
    Node<Type> *newNode = new Node<Type>(data);
    newNode -> next = first -> next;
    first -> next = newNode;
}

template <typename Type>
void MyList<Type>::insert(const Type &data, int index)
{
    //由于我们在表头添加了一个空节点
    //因此如果链表为空, 或者在链表为1的位置添加元素
    //其操作与在其他位置添加元素相同

    int count = 1;
    //此时searchNode肯定不为first
    Node<Type> *searchNode = first;

    //++ 注意:此处将NULL修改为first
    // 找到要插入的位置
    // 如果所给index过大(超过了链表的长度)
    // 则将该元素插入到链表表尾
    // 原因是 searchNode->next != first 这个条件已经不满足了
    while (count < index && searchNode->next != first)
    {
        ++ count;
        searchNode = searchNode->next;
    }

    // 插入链表
    Node<Type> *newNode = new Node<Type>(data);
    newNode->next = searchNode->next;
    searchNode->next = newNode;
}

template <typename Type>
void MyList<Type>::remove(const Type &data)
{
    if (isEmpty())
        return ;

    Node<Type> *previous = first;   //保存要删除节点的前一个节点
    for (Node<Type> *searchNode = first->next;
            //searchNode != NULL; // ++ 注意此处不再是判断是否为NULL
            searchNode != first;     // ++ 而是不能等于first, first代表链表的末尾
            searchNode = searchNode->next)
    {
        if (searchNode->data == data)
        {
            previous->next = searchNode->next;
            delete searchNode;
            //重新调整searchNode指针
            //继续遍历链表查看是否还有相等元素

            // ++ 注意
            //如果当前searchNode已经到达了最后一个节点
            //也就是searchNode->next已经等于first了, 则下面这条语句不能执行
            if (previous->next == first)
                break;

            searchNode = previous->next;
        }
        previous = searchNode;
    }
}
//注意判空条件
template <typename Type>
bool MyList<Type>::isEmpty() const
{
    return first->next == first;
}

//显示链表中的所有数据(测试用)
template <typename Type>
ostream &operator<<(ostream &os, const MyList<Type> &list)
{
    for (Node<Type> *searchNode = list.first -> next;
            searchNode != list.first;   //++ 注意
            searchNode = searchNode -> next)
    {
        os << searchNode -> data;
        if (searchNode -> next != list.first) // ++ 注意(尚未达到链表的结尾)
            cout << " -> ";
    }

    return os;
}

//ListIterator 除了判空函数的判空条件之外, 没有任何改变
template <typename Type>
class ListIterator
{
public:
    ListIterator(const MyList<Type> &_list):
        list(_list),
        currentNode((_list.first)->next) {}

    //重载 *operator
    const Type &operator*() const throw (std::out_of_range);
    Type &operator*() throw (std::out_of_range);

    //重载 ->operator
    const Node<Type> *operator->() const throw (std::out_of_range);
    Node<Type> *operator->() throw (std::out_of_range);

    //重载 ++operator
    ListIterator &operator++() throw (std::out_of_range);
    //注意:此处返回的是值,而不是reference
    ListIterator operator++(int) throw (std::out_of_range);

    bool isEmpty() const;

private:
    const MyList<Type> &list;
    Node<Type> *currentNode;
};

template <typename Type>
bool ListIterator<Type>::isEmpty() const
{
    // ++ 注意:判空条件改为list.first
    if (currentNode == list.first)
        return true;
    return false;
}
template <typename Type>
const Type &ListIterator<Type>::operator*() const
throw (std::out_of_range)
{
    if (isEmpty())
        throw std::out_of_range("iterator is out of range");
    // 返回当前指针指向的内容
    return currentNode->data;
}
template <typename Type>
Type &ListIterator<Type>::operator*()
throw (std::out_of_range)
{
    //首先为*this添加const属性,
    //以调用该函数的const版本,
    //然后再使用const_case,
    //将该函数调用所带有的const属性转除
    //operator->()的non-const版本与此类同
    return
        const_cast<Type &>(
            static_cast<const ListIterator<Type> &>(*this).operator*()
        );
}

template <typename Type>
const Node<Type> *ListIterator<Type>::operator->() const
throw (std::out_of_range)
{
    if (isEmpty())
        throw std::out_of_range("iterator is out of range");
    //直接返回指针
    return currentNode;
}

template <typename Type>
Node<Type> *ListIterator<Type>::operator->()
throw (std::out_of_range)
{
    // 见上
    return
        const_cast<Node<Type> *> (
            static_cast<const ListIterator<Type> >(*this).operator->()
        );
}

template <typename Type>
ListIterator<Type> &ListIterator<Type>::operator++()
throw (std::out_of_range)
{
    if (isEmpty())
        throw std::out_of_range("iterator is out of range");
    //指针前移
    currentNode = currentNode->next;
    return *this;
}
template <typename Type>
ListIterator<Type> ListIterator<Type>::operator++(int)
throw (std::out_of_range)
{
    ListIterator tmp(*this);
    ++ (*this); //调用前向++版本

    return tmp;
}

#endif // MYLIST_H_INCLUDED

附-测试代码:

int main()
{
    MyList<int> iMyList;
    for (int i = 0; i < 10; ++i)    //1 2 3 4 5 6 7 8 9 10
        iMyList.insert(i+1, i+1);

    for (int i = 0; i < 5; ++i)     //40 30 20 10 0 1 2 3 4 5 6 7 8 9 10
        iMyList.insertFront(i*10);

    iMyList.insertFront(100);//100 40 30 20 10 0 1 2 3 4 5 6 7 8 9 10
    iMyList.remove(10);      //100 40 30 20 0 1 2 3 4 5 6 7 8 9
    iMyList.remove(8);       //100 40 30 20 0 1 2 3 4 5 6 7 9

    cout << "------------ MyList ------------" << endl;
    for (ListIterator<int> iter(iMyList);
            !(iter.isEmpty());
            ++ iter)
        cout << *iter << ‘ ‘;
    cout << endl;
    cout << "Test: \n\t" << iMyList << endl;

    return 0;
}

时间: 2024-10-06 13:29:21

数据结构基础(11) --循环链表的设计与实现的相关文章

算法与数据结构基础11:C++实现——二拆搜索树节点删除

基于我的另一篇文章<算法与数据结构基础4:C++二叉树实现及遍历方法大全> ,二叉树的结构用的这篇文章里的. 二查找叉树的删除可以细分为三种情况: 1 被删除的是叶子节点,直接删除: 2 被删除只有一个子节点,指针下移: 3 有两个子节点,为了不破坏树的结构,需要找出一个节点来替换当前节点. 根据二叉树的特点,当前节点大于所有左子树,小于所有右子树, 可以用左子树中最大的节点,或者右子树最小的节点来替换当前节点,然后删除替换节点. // BSTree.h #include <cstdio

数据结构基础(13) --链式栈的设计与实现

采用链式存储的栈成为链式栈(或简称链栈), 链栈的优点是便于多个栈共享存储空间和提高其效率, 且不存在栈满上溢的情况(因为链栈是靠指针链接到一起,只要内存够大, 则链栈理论上可以存储的元素是没有上限的); 与顺序栈相比, 由于顺序栈是采用的数组实现, 因此一旦数组填满, 则必须重新申请内存, 并将所有元素"搬家", 而链栈则省略了这一"耗时耗力"的工作, 但却需要付出附加一个指针的代价; 链栈通常采用单链表实现, 并规定所有的操作都必须实在单链表的表头进行, 而且w

数据结构基础(17) --二叉查找树的设计与实现

二叉排序树的特征 二叉排序树或者是一棵空树,或者是具有如下特性的二叉树: 1.每一元素都有一个键值, 而且不允许重复; 2.若它的左子树不空,则左子树上所有结点的值均小于根结点的值: 3.若它的右子树不空,则右子树上所有结点的值均大于根结点的值: 4.它的左.右子树也都分别是二叉排序树. 二叉排序树保存的元素构造 template <typename Type> class Element { public: Element(const Type& _key): key(_key) {

数据结构开发(11):双向循环链表的实现

0.目录 1.双向循环链表的实现 2.小结 1.双向循环链表的实现 本节目标: 使用 Linux 内核链表实现 StLib 中的双向循环链表 template <typename T> class DualCircleList; StLib 中双向循环链表的设计思路: 数据结点之间在逻辑上构成双向循环链表,头结点仅用于结点的定位. 实现思路: 通过模板定义 DualCircleList 类,继承自 DualLinkList 类 在 DualCircleList 内部使用Linux内核链表进行实

2.35 Java基础总结①抽象②接口③设计抽象类和接口的原则④接口和抽象类的区别

java基础总结①抽象②接口③设计抽象类和接口的原则④接口和抽象类的区别 一.抽象 abstract作用:不能产生对象,充当父类,强制子类正确实现重写方法和类相比仅有的改变是不能产生对象,其他的都有,包括构造.属性等等任何一个类只要有一个抽象的方法就成了抽象类 抽象方法 public abstract A();①方法是抽象的,这个类也是抽象的:②子类必须重写抽象方法,除非子类也是抽象类 抽象类可以没有抽象方法,但一般不这么设计 二.接口 interface 接口也是Java的一种引用数据类型(J

《计算机科学导论》之数据结构基础知识

<计算机科学导论(第二版)>  11章   数据结构 11.1  引言  1.为什么要使用数据结构? 尽管单变量在程序设计语言中被大量使用,但是它们不能有效地解决复杂问题.此时考虑使用数据结构. 2.数据结构是什么? 数据结构是相互之间存在一种或多种特定关系的数据元素的集合. 3.三种数据结构 数组: 记录; 链表: 大多的编程语言都隐式实现了前两种,而第三种则通过指针和记录来模拟. 11.2  数组 1.为什么使用数组? 为了处理大量的数据,需要一个数据结构,如数组.当然还有其他的数据结构.

Java基础11 对象引用(转载)

对象引用 我们沿用之前定义的Human类,并有一个Test类: public class Test{    public static void main(String[] args){        Human aPerson = new Human(160);    }  class Human{    public Human(int h){        this.height = h;    }    public int getHeight(){        return this

数据结构和算法 (二)数据结构基础、线性表、栈和队列、数组和字符串

Java面试宝典之数据结构基础 —— 线性表篇 一.数据结构概念 用我的理解,数据结构包含数据和结构,通俗一点就是将数据按照一定的结构组合起来,不同的组合方式会有不同的效率,使用不同的场景,如此而已.比 如我们最常用的数组,就是一种数据结构,有独特的承载数据的方式,按顺序排列,其特点就是你可以根据下标快速查找元素,但是因为在数组中插入和删除元素会 有其它元素较大幅度的便宜,所以会带来较多的消耗,所以因为这种特点,使得数组适合:查询比较频繁,增.删比较少的情况,这就是数据结构的概念.数据结构 包括

CSI.SAFE.v14.1.0.1052混凝土预制板和基础系统的终极设计工具

CSI.SAFE.v14.1.0.1052混凝土预制板和基础系统的终极设计工具CSI SAFE 是混凝土预制板和基础系统的终极设计工具.从框架布局到细部图生产,CSI SAFE在一个简单而直观的环境中集成了各个方面的工 程设计过程.CSI SAFE 工程师提供了无与伦比的好处与它的真正独一无二的结合能力,综合能力和易用性. 布局模型是快速.高效与复杂的绘图工具,或者使用某个进口选项将在数据从CAD.电子表格或数据库程序.板或基金会可以是任何形状,可以 包括边缘形状和圆形和样条曲线. 后张拉板和梁