头&尾插法原理和单链表的创建及常见操作-C++模版类实现

写在最前:
之前也写过一些关于链表操作的文章,但是都是基于C语言的,最近在试着用C++刷题,所以记录一下有关C++的数据结构实现。本篇是链表的C++的模版类实现,顺便对链表的基本知识以及头插法和尾插法的原理加以介绍。

单链表:
单链表是一种线性存储结构,单的意思就是指链表中无环,可以想像成一条铁链,上面的每一个小铁环就是链表的一个节点。

如下图所示,单链表基本上由两大部分构成:数据域指针域

C++链表程序实现:

template<typename T>
struct MyListNode{
    T val;  // 数据域
    MyListNode *next; // 指针域
    MyListNode(): next(NULL) {} // 定义指针域的默认构造函数
    MyListNode(const T &v): val(v), next(NULL) {} // 定义指针域的构造函数
};

顾名思义,所谓数据域,是指链表的每个节点用来存储数据的内存空间,而指针与则是用来存储指针的内存空间。

其中各个val代表的就是数据域,其中存储的数据可以是任意形式,小到一个数,大到一个类。next代表的就是指针域,用来存放下一个节点的内存地址。一个链表的第一个节点就称为头(Head)节点,最后一个节点就称为尾(Tail)节点(也可用Rear表示)。

通常在创建链表的时候,如上图所示会在常规的头节点前维护一个节点当作后节点,这个头节点特殊的地方就是它的数据域用来存储链表的长度,及数据域存储的是当前链表中的节点个数。不过大家平时在leetcode上刷题时的输入链表基本上头节点都是直接存储的数据,因此是否需要维护一个特殊的节点作为头节点用来存储链表中节点的个数也没有很硬性的要求。

介绍完链表的基础知识,接下来介绍一下两种创建链表的方式。

头插法:
所谓头插法,就是在创建链表的时候,每次拿到一个新的节点就插在Head之后。即如果用3个整形数1,2,3来创建链表,如果按照数字升序输入数据,那么用头插法创建后得到的链表则为3->2->1

头插法的步骤如下图所示:
头&尾插法原理和单链表的创建及常见操作-C++模版类实现_第1张图片

尾插法:
所谓尾插法,就是在创建链表的时候,每次拿到一个新的节点就插在Tail之后,然后将新插入的节点更新为尾节点。即如果用3个整形数1,2,3来创建链表,如果按照数字升序输入数据,那么用头插法创建后得到的链表则为1->2->3

尾插法的步骤如下图所示:
头&尾插法原理和单链表的创建及常见操作-C++模版类实现_第2张图片
因为一开始都要先初始化列表并且读入第一个数据将其插在头节点后面,所以前4步与头插法相同,唯一有区别的就是第5步,因为是尾插法,所以新插入的节点不是插在头节点的后面,而是插在尾节点的后面,并且把新节点更新为尾节点。

链表模版类程序实现:
C++的模版类声明如下:

// 定义链表类
template<typename T>
class MyList{
public:
    MyList(){m_pHead = new MyListNode<T>(); m_pHead->val = 0;} // 当前链表中节点个数为0   默认构造函数
    MyList(const T &v); // 构造函数
    ~MyList(){clearList();} // 析构函数,清空链表释放内存
    void headInsert(T v); // 头插法插入元素
    void tailInsert(T v); // 尾插法插入元素
    void clearList(); // 清空链表
    MyListNode<T>* getHead(){return m_pHead;} // 查找头节点
    MyListNode<T>* getTail(){return m_pTail;} // 查找头节点
    bool insert(MyListNode<T> *p, const T &v); // 在p节点后插入元素
    bool removeNext(MyListNode<T> *p); // 移除p节点之后的元素
    void traverseList(); // 遍历链表

private:
    MyListNode<T> *m_pHead; // 定义头节点,数据域用来存储节点个数
    MyListNode<T> *m_pTail; // 定义尾节点,用来记录尾节点
};

本次的链表模版类主要包含以下常用的基本功能:

  • 头插法插入元素
  • 尾插法插入元素
  • 清空链表
  • 拿头节点
  • 拿尾节点
  • 在某节点后插入新的节点
  • 删除某节点后面的节点
  • 遍历并输出节点

因此除了构造函数和析构函数以外,一共有8个成员函数,另有2个私有数据成员指针,用来存储头节点和尾节点的地址。

完整的链表模版类实现如下:
C++版:

#include 
#include 

using namespace std;

// 定义链表的数据结构
template<typename T>
struct MyListNode{
    T val;  // 数据域
    MyListNode *next; // 指针域
    MyListNode(): next(NULL) {} // 定义指针域的默认构造函数
    MyListNode(const T &v): val(v), next(NULL) {} // 定义指针域的构造函数
};

// 定义链表类
template<typename T>
class MyList{
public:
    MyList(){m_pHead = new MyListNode<T>(); m_pHead->val = 0;} // 当前链表中节点个数为0   默认构造函数
    MyList(const T &v); // 构造函数
    ~MyList(){clearList();} // 析构函数,清空链表释放内存
    void headInsert(T v); // 头插法插入元素
    void tailInsert(T v); // 尾插法插入元素
    void clearList(); // 清空链表
    MyListNode<T>* getHead(){return m_pHead;} // 查找头节点
    MyListNode<T>* getTail(){return m_pTail;} // 查找头节点
    bool insert(MyListNode<T> *p, const T &v); // 在p节点后插入元素
    bool removeNext(MyListNode<T> *p); // 移除p节点之后的元素
    void traverseList(); // 遍历链表

private:
    MyListNode<T> *m_pHead; // 定义头节点,数据域用来存储节点个数
    MyListNode<T> *m_pTail; // 定义尾节点,用来记录尾节点
};
    
template<typename T>
MyList<T>::MyList(const T &v){
    m_pHead = new MyListNode<T>();
    auto tmp = new MyListNode<T>(v);
    m_pHead->next = tmp;
    m_pHead->val++;
    m_pTail = tmp;
}

template<typename T>
void MyList<T>::headInsert(T v){
    auto tmp = new MyListNode<T>(v);
    if(tmp == NULL){ // 判断内存是否分配成功
        cerr << "内存分配失败" << endl;
        exit(-1);
    }
    if(m_pHead->next != NULL){ // 判断链表是否为空
        tmp->next = m_pHead->next;
        m_pHead->next = tmp;
    }
    else{
        m_pHead->next = tmp;
        m_pTail = tmp;
    }
    m_pHead->val++;
}

template<typename T>
void MyList<T>::tailInsert(T v){
    auto tmp = new MyListNode<T>(v);
    if(tmp == NULL){ // 判断内存是否分配成功
        cerr << "内存分配失败" << endl;
        exit(-1);
    }
    if(m_pHead->next == NULL){ // 判断链表是否为空
        m_pHead->next = tmp;
    }
    else{
        m_pTail->next = tmp;
    }
    m_pTail = tmp;
    m_pHead->val++;
}

template<typename T>
void MyList<T>::clearList(){
    if(m_pHead->val == 0) // 判断链表是否为空
        return;
    MyListNode<T> *tmp1, *tmp2 = m_pHead->next;
    while(tmp2 != NULL){ // 逐个释放链表上每个节点的内存
        tmp1 = tmp2;
        delete tmp1;
        tmp2 = tmp2->next;
    }
    m_pHead->val = 0;
    m_pHead->next = NULL;
    tmp1 = NULL;
}

template<typename T>
bool MyList<T>::insert(MyListNode<T> *p, const T &v){
    if(p == NULL){ // 判断被插入的节点是否存在
        if(m_pHead->val == 0){ // 判断链表是否为空
            headInsert(v); // 如果链表为空则直接使用头插法
            return true;
        }
        else // 链表不能为空且节点不存在
            return false;
    }
    if(p == m_pTail) // 如果被插入的节点是尾节点则直接使用尾插法
        tailInsert(v);
    else{ // 不是尾节点则正常插入
        auto tmp = new MyListNode<T>(v);
        if(tmp == NULL){
            cerr << "内存分配失败" << endl;
            exit(-1);
        }
        tmp->next = p->next;
        p->next =tmp;
    }
    m_pHead->val++;
    return true;
}

template<typename T>
bool MyList<T>::removeNext(MyListNode<T> *p){
    if(p == NULL || p->next == NULL) // 判断删除操作是否合法
        return false;
    else{
        p->next = p->next->next;
        m_pHead->val--;
        return true;
    }
}

template<typename T>
void MyList<T>::traverseList(){
    if(m_pHead->val == 0){ // 判断链表是否为空
        cout << "链表为空" << endl;
        return;
    }
    MyListNode<T> *tmp = m_pHead->next;
    while(tmp != NULL)
    {
        cout << tmp->val << "—>";
        tmp = tmp->next;
    }
    cout << "over" << endl;
}

接下来对模版类进行插入,头插,尾插,删除,清空以及遍历操作。

测试程序如下:

int main(){
    auto *L = new MyList<int>(1); // 实例化链表对象,并创建一个数据域为1的节点
    L->insert(L->getHead()->next, 2); // 在数据域为1的节点后插入数据域为2的节点
    L->headInsert(0); // 头插法插入数据域为0的节点
    L->tailInsert(3); // 尾插法插入数据域为3的节点
    L->traverseList();
    L->removeNext(L->getHead());
    L->traverseList();
    L->clearList(); // 清空链表
    L->traverseList();
    L->insert(L->getHead(), 1); // 在头节点后插入数据域为1的节点
    L->traverseList();

    delete L;
    L = NULL;
    
    return 0;
}

测试结果如下:

0>1>2>3>over
1>2>3>over
链表为空
1>over
Program ended with exit code: 0

写在最后:

如果有哪里理解错的地方欢迎大家留言交流,如需转载请标明出处。

如果你没看懂一定是我讲的不好,欢迎留言,我继续努力。

手工码字码图码代码,如果有帮助到你的话留个赞吧,谢谢。

以上。

你可能感兴趣的:(数据结构,链表,单链表,c++)