单链表(LinkedList)

与数组相似,链表也是一种线性数据结构。这里有一个例子:
在这里插入图片描述
  正如你所看到的,链表中的每个元素实际上是一个单独的对象,而所有对象都通过每个元素中的引用字段链接在一起。

  链表有两种类型:单链表和双链表。上面给出的例子是一个单链表,这里有一个双链表的例子:双向链表(DoubleLinkList)在这里插入图片描述
  单链表中的每个结点不仅包含值,还包含链接到下一个结点的引用字段。通过这种方式,单链表将所有结点按顺序组织起来。

下面是一个单链表的例子:
在这里插入图片描述
  蓝色箭头显示单个链接列表中的结点是如何组合在一起的。

结点结构

以下是单链表中结点的典型定义:

template<typename T>
class Node
{
public:
    T e;
    Node<T>*next;
    Node():e(0),next(nullptr){}
    Node(T& E):e(E),next(nullptr){}
    Node(T& E,Node<T>*Next):e(E),next(Next){}
};

  在大多数情况下,我们将使用头结点(第一个结点)来表示整个列表。

操作

  与数组不同,我们无法在常量时间内访问单链表中的随机元素。 如果我们想要获得第 i 个元素,我们必须从头结点逐个遍历。 我们按索引来访问元素平均要花费 O(N) 时间,其中 N 是链表的长度。

  例如,在上面的示例中,头结点是 23。访问第 3 个结点的唯一方法是使用头结点中的“next”字段到达第 2 个结点(结点 6); 然后使用结点 6 的“next”字段,我们能够访问第 3 个结点。

添加操作 - 单链表

如果我们想在给定的结点 prev 之后添加新值,我们应该:

  1. 使用给定值初始化新结点 cur
    在这里插入图片描述
  2. 将 cur 的“next”字段链接到 prev 的下一个结点 next
    在这里插入图片描述
  3. 将 prev 中的“next”字段链接到 cur
    在这里插入图片描述

  与数组不同,我们不需要将所有元素移动到插入元素之后。因此,您可以在 O(1) 时间复杂度中将新结点插入到链表中,这非常高效。

示例

在这里插入图片描述
  让我们在第二个结点 6 之后插入一个新的值 9。

  我们将首先初始化一个值为 9 的新结点。然后将结点 9 链接到结点 15。最后,将结点 6 链接到结点 9。

插入之后,我们的链表将如下所示:
在这里插入图片描述

代码:

template<typename T>
void LinkedList<T>::add(int index, T e) {
    if(index >= 0 && index <= size)
    {
        Node<T>*prev = dummyHead;
        for(int i = 0;i<index;++i){
            prev = prev->next;  //遍历到node为要插入节点的前一节点
        }
        //第一种写法
//    Node<T>*newNode = Node<T>(e);   //创建新节点传入直e
//    newNode->next = node->next; //新节点的next指向要插入节点
//    node->next = newNode;   //要插入节点的前一节点的next指向新节点
        //第二种写法
        prev->next = new Node<T>(e,prev->next); //创建一个节点传入直和让新节点的next指向插入节点,然后要插入节点的前一节点的next指向新节点
        ++size;
    }
}

在开头添加结点

  众所周知,我们使用头结点来代表整个列表。

因此,在列表开头添加新节点时更新头结点 head 至关重要。

  1. 初始化一个新结点 cur
  2. 将新结点链接到我们的原始头结点 head
  3. cur 指定为 head

  例如,让我们在列表的开头添加一个新结点 9。

  我们初始化一个新结点 9 并将其链接到当前头结点 23。
在这里插入图片描述
  指定结点 9 为新的头结点。
在这里插入图片描述

代码:

template<typename T>
void LinkedList<T>::addFirst(T e) {
    //第一种写法
//    Node<T>*node = new Node<T>(e);    //创建一个节点,把直放入节点
//    node->next = head;    //让创建的节点的下next指向当前头
//    head = node;  //头指向新创建的节点
    //第二种写法
//    head = new Node<T>(e,head); //新创建一个节点传入数据和头让新节点的next指向head,然后head在指向新节点
//    ++size;
    add(0,e);
}

在末尾添加节点

代码:

template<typename T>
void LinkedList<T>::addLast(int e) {
    add(size,e);
}

删除操作 - 单链表

如果我们想从单链表中删除现有结点 cur,可以分两步完成:

  1. 找到 cur 的上一个结点 prev 及其下一个结点 next
    在这里插入图片描述
  2. 接下来链接 prevcur 的下一个节点 next
    在这里插入图片描述

  在我们的第一步中,我们需要找出 prevnext。使用 cur 的参考字段很容易找出 next,但是,我们必须从头结点遍历链表,以找出 prev,它的平均时间是 O(N),其中 N 是链表的长度。因此,删除结点的时间复杂度将是 O(N)

  空间复杂度为 O(1),因为我们只需要常量空间来存储指针。

示例在这里插入图片描述

让我们尝试把结点 6从上面的单链表中删除。

  1. 从头遍历链表,直到我们找到前一个结点 prev,即结点 23

  2. 将 prev(结点 23)与 next(结点 15)链接
    在这里插入图片描述
    结点 6 现在不在我们的单链表中。

代码:

template<typename T>
T LinkedList<T>::remove(const int index) {
    if(index>=0 && index<=size)
    {
        Node<T>*prev = dummyHead;
        for(int i = 0;i<index;++i)  //找到要删除节点的前一个节点
        {
            prev = prev->next;
        }
        Node<T>*retNode = prev->next;   //保存要删除的节点
        prev->next = retNode->next; //让前一节点next指向要删除节点的后一节点
        retNode->next = nullptr;    //要删除节点next指向空
        --size;
        return retNode->e;
    }
}

删除第一个结点

如果我们想删除第一个结点,策略会有所不同。

  正如之前所提到的,我们使用头结点 head 来表示链表。我们的头是下面示例中的黑色结点 23。在这里插入图片描述
  如果想要删除第一个结点,我们可以简单地将下一个结点分配给head。也就是说,删除之后我们的头将会是结点 6。
在这里插入图片描述
  链表从头结点开始,因此结点 23 不再在我们的链表中。

代码:

template<typename T>
T LinkedList<T>::removeFirst() {
    return remove(0);
}

删除最后一个结点

代码:

template<typename T>
T LinkedList<T>::removeLast() {
    return remove(size-1);
}

时间复杂度

在这里插入图片描述

代码清单

LinkedList.h

#ifndef C___LINKEDLIST_H
#define C___LINKEDLIST_H
#include <iostream>
template<typename T>
class Node
{
public:
    T e;
    Node<T>*next;
    Node():e(0),next(nullptr){}
    Node(T& E):e(E),next(nullptr){}
    Node(T& E,Node<T>*Next):e(E),next(Next){}
};
template<typename T>
class LinkedList {

public:
    LinkedList();
    //返回连表大小
    int getSize()const;
    //判断是否为空
    bool isEmpty()const;
    //头插入
    void addFirst(T e);
    //为插入
    void addLast(T e);
    //插入
    void add(int index, T e);
    //练习:获取链表第👈index个位置的元素
    T get(const int index);
    //获取链表第一个元素
    T getFirst();
    //获取链表最后一个元素
    T getLast();
    //练习:修改链表第👈index个位置的元素
    void set(const int index,const T&e);
    //查找链表是否有元素e
    bool contains(const T&e)const;
    //删除元素
    T remove(const int index);
    //删除头
    T removeFirst();
    //删除尾
    T removeLast();
    //打印链表
    void print()const;
private:
    Node<T>*dummyHead;  //虚拟头节点,不存数据
    int size;   //记录大小
};

template<typename T>
int LinkedList<T>::getSize() const {
    return size;
}

template<typename T>
bool LinkedList<T>::isEmpty() const {
    return size == 0;
}

template<typename T>
void LinkedList<T>::addFirst(T e) {
    //第一种写法
//    Node<T>*node = new Node<T>(e);    //创建一个节点,把直放入节点
//    node->next = head;    //让创建的节点的下next指向当前头
//    head = node;  //头指向新创建的节点
    //第二种写法
//    head = new Node<T>(e,head); //新创建一个节点传入数据和头让新节点的next指向head,然后head在指向新节点
//    ++size;
    add(0,e);
}

template<typename T>
void LinkedList<T>::add(int index, T e) {
    if(index >= 0 && index <= size)
    {
        Node<T>*prev = dummyHead;
        for(int i = 0;i<index;++i){
            prev = prev->next;  //遍历到node为要插入节点的前一节点
        }
        //第一种写法
//    Node<T>*newNode = Node<T>(e);   //创建新节点传入直e
//    newNode->next = node->next; //新节点的next指向要插入节点
//    node->next = newNode;   //要插入节点的前一节点的next指向新节点
        //第二种写法
        prev->next = new Node<T>(e,prev->next); //创建一个节点传入直和让新节点的next指向插入节点,然后要插入节点的前一节点的next指向新节点
        ++size;
    }
}

template<typename T>
void LinkedList<T>::addLast(T e) {
    add(size,e);
}

template<typename T>
LinkedList<T>::LinkedList() {
    dummyHead = new Node<T>();
    size = 0;
}

template<typename T>
T LinkedList<T>::get(const int index) {
    if(index>=0 && index<=size)
    {
        Node<T>*cur = dummyHead->next;  //把第一个元素的位置给cur
        for(int i = 0;i<index;++i)
        {
            cur = cur->next;
        }
        return cur->e;//返回👈第index个节点的元素
    }
}

template<typename T>
T LinkedList<T>::getFirst() {
    return get(0);
}

template<typename T>
T LinkedList<T>::getLast() {
    return get(size-1);
}

template<typename T>
void LinkedList<T>::set(const int index, const T &e) {
    if(index>=0 && index<=size)
    {
        Node<T> *cur = dummyHead->next;
        for (int i = 0; i < index; ++i) {
            cur = cur->next;
        }
        cur->e = e;
    }
}

template<typename T>
bool LinkedList<T>::contains(const T &e) const {
    //第一种遍历
//    Node<T>*cur = dummyHead->next;
//    while(cur!= nullptr)
//    {
//        if(cur->e == e)
//        {
//            return true;
//        }
//        cur = cur->next;
//    }
//    return false;
    //第二种遍历
    for(Node<T>*cur = dummyHead->next;cur!= nullptr;cur = cur->next)
    {
        if(cur->e == e) //如果找到元素返回true
        {
            return true;
        }
    }
    return false;   //否则返回false
}

template<typename T>
void LinkedList<T>::print() const {
    std::cout << "LinkedList: size = " << size << std::endl;
    std::cout << "[";

    for(Node<T>*cur = dummyHead->next;cur!= nullptr;cur = cur->next)
    {
        std::cout<<cur->e<<"->";
    }
    std::cout<<"NULL"<<"]"<<std::endl;
}

template<typename T>
T LinkedList<T>::remove(const int index) {
    if(index>=0 && index<=size)
    {
        Node<T>*prev = dummyHead;
        for(int i = 0;i<index;++i)  //找到要删除节点的前一个节点
        {
            prev = prev->next;
        }
        Node<T>*retNode = prev->next;   //保存要删除的节点
        prev->next = retNode->next; //让前一节点next指向要删除节点的后一节点
        retNode->next = nullptr;    //要删除节点next指向空
        --size;
        return retNode->e;
    }
}

template<typename T>
T LinkedList<T>::removeLast() {
    return remove(size-1);
}

template<typename T>
T LinkedList<T>::removeFirst() {
    return remove(0);
}
#endif

main.cpp

int main()
{

    LinkedList<int> *ll;
    ll = new LinkedList<int>();
    for(int i = 0;i<10;++i)
    {
        ll->addFirst(i);
        ll->print();
    }
    ll->add(2,666);
    ll->print();
    cout<<endl;
    cout<<"get(2)"<<ll->get(2)<<endl;
    cout<<"getSize()"<<ll->getSize()<<endl;
    cout<<"getFirst()"<<ll->getFirst()<<endl;
    cout<<"getLast()"<<ll->getLast()<<endl;
    cout<<"isEmpty"<<ll->isEmpty()<<endl;
    cout<<"contains"<<ll->contains(666)<<endl;
    ll->set(3,999);
    ll->addLast(000);
    ll->print();
    cout<<endl;
    ll->removeLast();
    ll->removeFirst();
    ll->remove(1);
    ll->print();
    return 0;
}
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

吃米饭

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值