c++ 实现链表

129 阅读3分钟

类结构

在链表中用序号访问的范围是[1,n]

节点结构

单链表的节点结构应该由两个属性,分别存储着这个节点的数据和指向下一个节点的指针。

template<typename T>  class LNode{
private:
    //数据域
    T data;
    //指针域
    class LNode<T>* next;
}

指向链表的壳子

指向链表的壳子中有三个属性,两条指针分别指向了头节点,尾节点,此外还记录了链表的长度
头节点与首元节点是不同的,头节点不存储数据,只为了统一操作逻辑而存在,其指针域指向首元节点。而头指针会指向链表的第一个节点,若有头节点则指向头节点,若无头节点则指向首元节点。对于空表来说,指向头节点(含有头节点的链表)或者指向空。

template<typename T> class CLinkList{
//属性
private:
    //长度
    int length{};
    //头节点
    LNode<T>* data;
    //尾节点
    LNode<T>* tail;

构造和析构

template<typename T>
LNode<T>::~LNode() {
    if(this->next!= nullptr){
        delete this->next;
    }
}

节点析构时需要将后续的节点一并析构,否则后续的节点会泄露。

template<typename T>
CLinkList<T>::CLinkList() {
    this->length=0;
    this->data = new  LNode<T>();
    this->tail = data;
}

template<typename T>
CLinkList<T>::~CLinkList() {
    delete this->data;
}

此处是带有头节点的版本,在构造壳子时会同时将头节点一同构造出来。析构时需要将指针指向的下一个

支持的一些方法

//头插法
template<typename T>
bool CLinkList<T>::HeadInsert(T element) {

    LNode<T> *lNode = new LNode<T>();
    if(this->length==0){
        this->tail=lNode;
    }
    lNode->setData(element);
    lNode->setNext(this->data->getNext());
    this->data->setNext(lNode);
    this->length++;
    return true;
}

//尾插法
template<typename T>
bool CLinkList<T>::TailInsert(T element) {
    auto *lNode = new LNode<T>();
    lNode->setData(element);
    lNode->setNext(this->tail->getNext());
    this->tail->setNext(lNode);
    this->length++;
    this->tail = lNode;
    return true ;
}

//打印列表
template<typename T>
void CLinkList<T>::Print() {
    if(this->length == 0){
        std::cout<<"[]"<<std::endl;
        return;
    }
    cout<<"[";
    LNode<T>* p = this->data->getNext();
    while(p->getNext()!= nullptr){
        cout<<p->getData()<<",";
        p = p->getNext();
    }
    cout<<p->getData()<<"]"<<endl;
}

//按序号查找
template<typename T>
T& CLinkList<T>::operator[](int position) {
    if(position<1||position>this->length){
        cout<<"索引值无效"<<endl;
    }
    int pos = 1;
    auto p = this->data->getNext();
    while(pos<position){
        p = p->getNext();
        pos++;
    }
    return p->getData();
}

//按值查找
template<typename T>
LNode<T> *CLinkList<T>::locateElem(T e) {
    auto p = this->data->getNext();
    while(p!= nullptr){
        if(p->getData()==e){
            break;
        }
        p = p->getNext();
    }
    return p;
}

//在序号位置插入
template<typename T>
bool CLinkList<T>::ListInsert(int i, T e) {
    int pos = 1;
    if(i<1||i>this->length+1){
        std::cout<<"插入位置无效"<<std::endl;
        return false;
    }
    auto p = this->data->getNext();
    while(pos<i-1){
        p = p->getNext();
        pos++;
    }
    auto lNode = new LNode<T>();
    lNode->setData(e);
    lNode->setNext(p->getNext());
    p->setNext(lNode);
    this->length++;
    return true;
}

//删除序号位置的节点
template<typename T>
bool CLinkList<T>::ListDelete(int i, T &e) {
    if(i<1||i>this->length){
        std::cout<<"删除位置无效"<<std::endl;
        return false;
    }
    int pos = 1;
    auto p = this->data->getNext();
    while(pos<i-1){
        p = p->getNext();
        pos++;
    }
    auto d = p->getNext();
    p->setNext(p->getNext()->getNext());
    d->setNext(nullptr);
    e = d->getData();
    delete d;
    this->length--;
    return true;
}

总结

  1. 链表是顺序表的链式存储。它的每一个节点在逻辑上是相邻的,但是在物理位置上则毫无关系。
  2. 由于其物理位置没有关系,故不可以随机存取,其读取速度较顺序表来说要慢,同时空间利用率也不如顺序表。但是顺序表需要一大片连续的空间,当空间不够时需要重新分配并拷贝内容,在空间分配上链表要更加的灵活。
  3. 在插入和删除操作中,时间复杂度虽然相同,但顺序表的移动元素要比链表的比较操作更加耗时,故在插入和删除中,链表比顺序表更好一些。