双向链表详解及C++实现

一、引言

链表是一种常见的数据结构,它允许动态地分配内存空间,并通过指针(或引用)将数据元素连接在一起。双向链表作为链表的一种,除了拥有普通链表的特性外,每个节点还包含两个指针:一个指向前一个节点(prev),另一个指向后一个节点(next)。这种结构使得双向链表在插入、删除以及遍历等操作上都具有独特的优势。

二、双向链表的基本结构

双向链表的基本结构通常包括一个节点(Node)类和一个双向链表(DoublyLinkedList)类。节点类用于存储数据元素以及指向前后节点的指针,而双向链表类则用于管理这些节点,提供插入、删除、遍历等操作的方法。

1. 节点类(Node)

节点类通常包含两个指针(prev和next)和一个数据元素(data)。在C++中,节点类可以定义如下:

template <typename T>
class Node {
public:
    T data;
    Node* prev;
    Node* next;

    Node(T data) : data(data), prev(nullptr), next(nullptr) {}
};

这里使用了模板类(template class),使得节点类可以存储任意类型的数据。

2. 双向链表类(DoublyLinkedList)

双向链表类需要包含一些基本操作,如插入节点、删除节点、遍历链表等。以下是一个简单的双向链表类的实现:

template <typename T>
class DoublyLinkedList {
private:
    Node<T>* head;  // 指向头节点的指针
    Node<T>* tail;  // 指向尾节点的指针
    int size;       // 链表的大小

    // 辅助函数,用于在指定位置插入节点
    void insertAt(int position, T data) {
        // ... 实现细节将在后文给出 ...
    }

    // 辅助函数,用于删除指定位置的节点
    void deleteAt(int position) {
        // ... 实现细节将在后文给出 ...
    }

public:
    DoublyLinkedList() : head(nullptr), tail(nullptr), size(0) {}

    // 插入节点到链表末尾
    void append(T data) {
        insertAt(size, data);
    }

    // 插入节点到链表开头
    void prepend(T data) {
        insertAt(0, data);
    }

    // 删除指定位置的节点
    void removeAt(int position) {
        if (position < 0 || position >= size) {
            throw std::out_of_range("Invalid position");
        }
        deleteAt(position);
    }

    // 删除指定值的节点(如果存在多个,只删除第一个)
    void remove(T data) {
        Node<T>* current = head;
        while (current != nullptr) {
            if (current->data == data) {
                removeAt(currentIndex(current));
                return;
            }
            current = current->next;
        }
    }

    // 遍历链表并打印节点数据
    void printList() {
        Node<T>* current = head;
        while (current != nullptr) {
            std::cout << current->data << " ";
            current = current->next;
        }
        std::cout << std::endl;
    }

    // 获取链表大小
    int getSize() const {
        return size;
    }

    // 检查链表是否为空
    bool isEmpty() const {
        return size == 0;
    }

    // ... 其他可能的方法,如查找、反转等 ...
};

三、双向链表的操作实现

接下来,我们将详细实现双向链表类中的一些关键操作。

1. 插入节点(insertAt)

在指定位置插入节点时,我们需要考虑几种情况:插入到链表开头、插入到链表末尾以及插入到链表中间。以下是insertAt方法的实现:

template <typename T>
void DoublyLinkedList<T>::insertAt(int position, T data) {
    if (position < 0 || position > size) {
        throw std::out_of_range("Invalid position");
    }

    Node<T>* newNode = new Node<T>(data);

    if (size == 0) {  // 链表为空,新节点既是头节点也是尾节点
        head = tail = newNode;
    } else if (position == 0) {  // 插入到链表开头
        newNode->next = head;
        head->prev = newNode;
        head = newNode;
    } else if (position == size) {  // 插入到链表末尾
        newNode->prev = tail;
        tail->next = newNode;
        tail = newNode;
    } else {  // 插入到链表中间
        Node<T>* current = head;
        for (int i = 0; i < position - 1; ++i) {
            current = current->next;
        }
        newNode->prev = current;
        newNode->next = current->next;
        current->next->prev = newNode;
        current->next = newNode;
    }
    ++size;
}

2. 删除节点(deleteAt)

删除指定位置的节点时,我们需要考虑删除头节点、删除尾节点以及删除中间节点的情况。以下是deleteAt方法的实现:

template <typename T>
void DoublyLinkedList<T>::deleteAt(int position) {
    if (position < 0 || position >= size) {
        throw std::out_of_range("Invalid position");
    }

    Node<T>* target = head;
    if (position == 0) {  // 删除头节点
        head = head->next;
        if (head != nullptr) {
            head->prev = nullptr;
        } else {
            tail = nullptr;  // 如果链表为空,则尾节点也为空
        }
    } else if (position == size - 1) {  // 删除尾节点
        target = tail;
        tail = tail->prev;
        if (tail != nullptr) {
            tail->next = nullptr;
        } else {
            head = nullptr;  // 如果链表为空,则头节点也为空
        }
    } else {  // 删除中间节点
        for (int i = 0; i < position; ++i) {
            target = target->next;
        }
        target->prev->next = target->next;
        target->next->prev = target->prev;
    }
    delete target;
    --size;
}

四、测试代码

下面是一个简单的测试程序,用于演示双向链表的用法:

#include <iostream>

int main() {
    DoublyLinkedList<int> list;
    list.append(1);
    list.append(2);
    list.prepend(0);
    list.insertAt(1, 100);
    list.printList();  // 输出: 0 100 1 2

    std::cout << "Size: " << list.getSize() << std::endl;  // 输出: 4
    std::cout << "Is empty? " << (list.isEmpty() ? "Yes" : "No") << std::endl;  // 输出: No

    list.removeAt(1);  // 删除索引为1的节点(值为100)
    list.printList();  // 输出: 0 1 2

    list.remove(1);  // 删除值为1的节点
    list.printList();  // 输出: 0 2

    return 0;
}

五、总结

本文详细介绍了双向链表的基本结构、操作实现以及C++代码示例。双向链表通过引入两个指针(prev和next)来实现对前后节点的访问,从而在插入、删除和遍历等操作上提供了更高的灵活性。在实际应用中,双向链表常用于需要频繁进行节点插入和删除的场景,如LRU(最近最少使用)缓存淘汰算法等。通过掌握双向链表的基本原理和实现方法,读者可以更好地理解和应用这种数据结构。

  • 8
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Weirdo丨

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

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

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

打赏作者

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

抵扣说明:

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

余额充值