双向链表增删改查的c++完整实现

 
Category: DSA

写在前面

写一下双向链表的增删改查, 用C++实现.

完整代码可以看我的GitHub;

节点类-链表类

节点

class ListNode {
public:
    int val;
    ListNode* prev; // 前驱结点
    ListNode* next; // 后继结点
    ListNode() : ListNode(0, nullptr, nullptr) {}
    ListNode(int x) : ListNode(x, nullptr, nullptr) {}
    ListNode(int x, ListNode* _next) : ListNode(x, nullptr, _next) {}

    // 委托构造
    ListNode(int x, ListNode* _prev, ListNode* _next)
        : val(x), prev(_prev), next(_next) {}
};

链表

class DoubleLinkedList {
public:
    DoubleLinkedList() : head(nullptr) {}
    DoubleLinkedList(int head_val) : head(new ListNode(head_val)) {}
    DoubleLinkedList(int head_val, vector<int> rest);
    DoubleLinkedList(vector<int> nodes);
    ~DoubleLinkedList();

    void print();
    ListNode* first();
    ListNode* last();
    int len();

    // visit by pos
    /* int& operator[](int pos); */
    ListNode* operator[](int pos);
    ListNode* visit(int pos);
    ListNode* rvisit(int pos);

    // insert by pos
    void insert(int pos, int val);
    void append(int val);
    void add2head(int val);

    // delete by pos
    void pop(int pos);
    void pop_back();
    void pop_front();
    // delete by value
    void remove(int val, int cnt = 1);           // delete val cnt times
    void modify(int val, int node, int cnt = 1); // modify val->node cnt times
    int find(int node);                          // not found:-1
    int rfind(int node);                         // not found:-1


private:
    ListNode* head;
};

这里实现与之前写的单链表类似, 基本的增删改查肯定要有, 然后还有一个打印函数, (不然用重载的operator<<访问私有成员头结点不太好)

然后对于删除给出了基于位置的和基于值的两种实现, 直接调用已有的成员函数即可, 减少代码量.

然后是一个从右向左查找的rfind函数(事实上比较鸡肋, 需要先遍历到最后一个节点然后开始找).

还有一个重载的operator[], 以及visit函数, 用来基于位置找节点(指针).

一些辅助函数

双向链表格式化输出

ostream& operator<<(ostream& os, ListNode* lp) {
    if (lp == nullptr) return os << "∅" << endl;
    os << "∅ <== ";
    ListNode* cur = lp;
    while (cur != nullptr) {
        os << cur->val << (cur->next ? " <=> " : " ==> ");
        cur = cur->next;
    }
    os << "∅";
    return os << endl;
}

采用连字字体看起来就很舒服.

void DoubleLinkedList::print() { cout << head; }

双向链表长度

int DoubleLinkedList::len() {
    int size = 0;
    auto cur = head;
    while (cur) ++size, cur = cur->next;
    return size;
}

头尾结点

ListNode* DoubleLinkedList::first() { return head; }

ListNode* DoubleLinkedList::last() {
    auto cur = head;
    while (cur->next) cur = cur->next;
    return cur;
}

遍历(找节点指针)


ListNode* DoubleLinkedList::operator[](int pos) {
    if (head == nullptr) {
        cerr << "Attempt to get value from a NULL DoubleLinkedList\n";
        exit(1);
    }
    if (pos > len() - 1 || pos < 0) {
        cerr << "Wrong pos!\n";
        exit(1);
    }
    auto cur = head;
    while (pos--) cur = cur->next;
    return cur;
}

ListNode* DoubleLinkedList::visit(int pos) { return this->operator[](pos); }

ListNode* DoubleLinkedList::rvisit(int rpos) {
    // 从右往左遍历找目标位置
    if (head == nullptr) {
        cerr << "Attempt to get value from a NULL DoubleLinkedList\n";
        exit(1);
    }
    if (rpos > len() - 1 || rpos < 0) {
        cerr << "Wrong rpos!\n";
        exit(1);
    }
    auto cur = visit(len() - 1);
    while (rpos--) cur = cur->prev;
    return cur;
}

构造函数/析构函数

这里和单链表实现一样, 给出了三个构造函数, 一个通过初值列实现, 另外两个直接读取vector, 还是比较方便的.


DoubleLinkedList::DoubleLinkedList(int head_val, vector<int> rest) {
    head = new ListNode(head_val);
    ListNode *cur = head, *nxt = nullptr;
    for (auto i : rest) {
        nxt = new ListNode(i);
        cur->next = nxt;
        nxt->prev = cur;
        cur = cur->next;
    }
}

DoubleLinkedList::DoubleLinkedList(vector<int> nodes) {
    if (nodes.empty()) {
        cout << "Empty Data Source!\n";
        return;
    }
    head = new ListNode(nodes[0]);
    auto cur = head;
    for (auto i = nodes.begin() + 1; i < nodes.end(); i++) {
        auto nxt = new ListNode(*i);
        cur->next = nxt;
        nxt->prev = cur;
        cur = cur->next;
    }
}

DoubleLinkedList::~DoubleLinkedList() {
    ListNode* cur;
    while (head->next) {
        cur = head->next;
        cout << head->val << " ";
        delete head;
        head = cur;
    }
    cout << head->val << " ";
    delete head;
    cout << "deleted...\n";
}

对于析构函数, 没什么好说的, 跟单链表一样, 主要说一下构造函数, 由于是双向链表, 那么就一定要注意prev指针.

一般来说, 每次添加一个新节点, 都要链接四根指针, 分别是前驱节点的next, 当前节点的prev和next, 后继结点的prev, 缺一不可.

添加节点

void DoubleLinkedList::insert(int pos, int val) {
    if (pos >= len()) {
        auto cur = last(), nxt = new ListNode(val);
        cur->next = nxt;
        nxt->prev = cur;
    } else if (pos <= 0) {
        auto cur = head;
        head = new ListNode(val);
        head->next = cur;
        cur->prev = head;
    } else { // 中间位置
        auto pre = head;
        while (--pos) pre = pre->next;

        // pre为待插入位置的前一个节点
        auto cur = pre->next;
        pre->next = new ListNode(val, pre, cur);
    }
}

void DoubleLinkedList::add2head(int val) { insert(0, val); }
void DoubleLinkedList::append(int val) { insert(len(), val); }

删除节点

void DoubleLinkedList::pop(int pos) {
    if (head == nullptr) {
        cout << "Attempt to delete a NULL DoubleLinkedList!" << endl;
    } else if (head->next == nullptr) {
        delete head;
        head = nullptr; // 指针置为空, 避免空悬指针, 下同
    } else {
        if (pos >= len() - 1) {
            // 使用内置的visit函数
            auto cur = rvisit(1), tmp = cur->next;
            delete tmp;
            cur->next = nullptr; // 必须加, 否则析构时候会出错
        } else if (pos <= 0) {
            auto cur = head->next;
            delete head;
            head = cur;
        } else {
            auto cur = visit(pos - 1);
            auto nxt = cur->next->next, tmp = cur->next;
            cur->next = nxt;
            delete tmp;
        }
    }
}

void DoubleLinkedList::pop_front() { pop(0); }
void DoubleLinkedList::pop_back() { pop(len()); }

void DoubleLinkedList::remove(int val, int cnt) {
    int idx;
    do {
        if ((idx = find(val)) == -1) {
            cout << "can not find val " << val << " to delete\n";
            break;
        } else
            pop(idx);
    } while (--cnt);
}

修改节点

// modify val->node cnt times
void DoubleLinkedList::modify(int val, int node, int cnt) {
    int idx;
    do {
        if ((idx = find(val)) == -1) {
            cout << "can not find val " << val << " to modify\n";
            break;
        } else {
            /* visit(idx)->val = node; */
            (*this)[idx]->val = node;
        }
    } while (--cnt);
}

查找节点

int DoubleLinkedList::find(int node) {
    if (head == nullptr) return -1;
    auto cur = head;
    int pos{};
    while (cur) {
        if (cur->val == node) return pos;
        ++pos;
        cur = cur->next;
    }
    return -1;
}

int DoubleLinkedList::rfind(int node) {
    // 从右往左找
    auto cur = last();
    if (cur == nullptr) return -1;
    int pos{};
    while (cur) {
        if (cur->val == node) return pos;
        ++pos;
        cur = cur->prev;
    }
    return -1;
}

主函数: 测试

#include "Double_Linked_List.hpp"

// === test func === ===//
void test_ctor() {
    DoubleLinkedList ll1(12);
    ll1.print();

    DoubleLinkedList ll2(1, {2, 3, 4});
    ll2.print();

    DoubleLinkedList ll3({1, 2, 3, 4});
    ll3.print();
    /* ∅ <== 12 ==> ∅ */
    /* ∅ <== 1 <=> 2 <=> 3 <=> 4 ==> ∅ */
    /* ∅ <== 1 <=> 2 <=> 3 <=> 4 ==> ∅ */
    /* 1 2 3 4 deleted... */
    /* 1 2 3 4 deleted... */
    /* 12 deleted... */
}

void test_fundamental() {
    DoubleLinkedList ll1({1, 2, 3, 4});
    cout << "ll1=";
    ll1.print();
    cout << "len(ll1)=" << ll1.len() << endl;
    cout << "first: " << ll1.first()->val << endl;
    cout << "last: " << ll1.last()->val << endl;
    cout << "visit(1) : " << ll1.visit(1)->val << endl;
    cout << "rvisit(1) : " << ll1.rvisit(1)->val << endl;
    /* ll1=∅ <- 1 <-> 2 <-> 3 <-> 4 -> ∅ */
    /* len(ll1)=4 */
    /* first: 1 */
    /* last: 4 */
    /* visit(1) : 2 */
    /* rvisit(1) : 3 */
    /* 1 2 3 4 deleted... */
}

void test_insert() {
    DoubleLinkedList ll1({1, 2, 3, 4});
    ll1.print();
    ll1.insert(0, 3);
    ll1.insert(5, 3);
    ll1.insert(2, 3);
    ll1.print();
    /* ∅ <- 1 <-> 2 <-> 3 <-> 4 -> ∅ */
    /* ∅ <- 3 <-> 1 <-> 3 <-> 2 <-> 3 <-> 4 <-> 3 -> ∅ */
    /* 3 1 3 2 3 4 3 deleted... */
}

void test_pop() {
    DoubleLinkedList ll1({0, 1, 2, 3, 4});
    ll1.pop(12);
    ll1.pop(-1);
    ll1.pop(1);
    ll1.print(); // ∅ <- 1 <-> 3 -> ∅
    /* 1 3  deleted... */
}

void test_find_pop() {
    DoubleLinkedList ll1({1, 2, 3, 4});
    ll1.append(5);
    ll1.pop(0);
    ll1.pop_back();
    ll1.insert(4, 12);
    ll1.add2head(9);
    cout << "ll1.find(2):" << ll1.find(2) << endl;
    cout << "ll1.rfind(12):" << ll1.rfind(12) << endl;
    ll1.print();
    /* ll1.find(2):1 */
    /* ll1.rfind(12):0 */
    /* ∅ <- 9 <-> 2 <-> 3 <-> 4 <-> 12 -> ∅ */
    /* 9 2 3 4 12 deleted... */
}

void test_operator_at_modify() {
    DoubleLinkedList ll1({1, 2, 3, 4});
    ll1.append(1);
    ll1.remove(1, 3);
    ll1.print();
    cout << "ll1[0]=" << ll1[0]->val << endl;
    /* cout << "ll1[9]=" << ll1[9] << endl; */
    ll1.add2head(1);
    ll1.modify(1, 11, 3);
    ll1.print();
    /* can not find val 1 to delete */
    /* ∅ <- 2 <-> 3 <-> 4 -> ∅ */
    /* ll1[0]=2 */
    /* can not find val 1 to modify */
    /* ∅ <- 11 <-> 2 <-> 3 <-> 4 -> ∅ */
    /* 11 2 3 4 deleted... */
}

int main(int argc, char* argv[]) {
    // test_ctor();
    test_fundamental();
    /* test_insert(); */
    /* test_pop(); */
    /* test_find_pop(); */
    /* test_operator_at_modify(); */
    return 0;
}

小结

会了单链表, 双向链表也就会了, 注意指针的删除释放内存等操作即可, 都是细节.

如果有什么问题欢迎评论区留言.