算法2-链表

数组

单链表基本操作

基础工具

cpp 复制代码
class ListNode {
public:
    int val;
    ListNode *next;
    ListNode(int x) : val(x), next(NULL) {}
};

// 输入一个数组,转换为一条单链表
ListNode* createLinkedList(std::vector<int> arr) {
    if (arr.empty()) {
        return nullptr;
    }
    ListNode* head = new ListNode(arr[0]);
    ListNode* cur = head;
    for (int i = 1; i < arr.size(); i++) {
        cur->next = new ListNode(arr[i]);
        cur = cur->next;
    }
    return head;
}

查/改

cpp 复制代码
// 创建一条单链表
ListNode* head = createLinkedList({1, 2, 3, 4, 5});

// 遍历单链表
for (ListNode* p = head; p != nullptr; p = p->next) {
    std::cout << p->val << std::endl;
}

改就是利用for找到位置然后替换数字就可以

头插:

现在持有现有链表的头结点。在头部插入只需要,将新插入的节点作为头结点即可

cpp 复制代码
// 创建一条单链表
ListNode* head = createLinkedList({1, 2, 3, 4, 5});

// 在单链表头部插入一个新节点 0
ListNode* newNode = new ListNode(0);
newNode->next = head;
head = newNode;

// 现在链表变成了 0 -> 1 -> 2 -> 3 -> 4 -> 5
尾插

找到尾部节点,然后把心插入的位置接入到原来的尾部即可

cpp 复制代码
// 创建一条单链表
ListNode* head = createLinkedList({1, 2, 3, 4, 5});

// 在单链表尾部插入一个新节点 6
ListNode* p = head;
// 先走到链表的最后一个节点
while (p->next != nullptr) {
    p = p->next;
}
// 现在 p 就是链表的最后一个节点
// 在 p 后面插入新节点
p->next = new ListNode(6);

// 现在链表变成了 1 -> 2 -> 3 -> 4 -> 5 -> 6
中间插入

相对复杂,先找到前驱结点,然后插入新节点

cpp 复制代码
// 创建一条单链表
ListNode* head = createLinkedList({1, 2, 3, 4, 5});

// 在第 3 个节点后面插入一个新节点 66
// 先要找到前驱节点,即第 3 个节点
ListNode* p = head;
for (int i = 0; i < 2; i++) {
    p = p->next;
}
// 此时 p 指向第 3 个节点
// 组装新节点的后驱指针
ListNode* newNode = new ListNode(66);
newNode->next = p->next;

// 插入新节点
p->next = newNode;

// 现在链表变成了 1 -> 2 -> 3 -> 66 -> 4 -> 5

删除一个节点需要,先找到你要删除的前驱节点然后,然后将前驱结点next指针指向被删除节点的下一个节点即可

cpp 复制代码
// 创建一条单链表
ListNode* head = createLinkedList({1, 2, 3, 4, 5});
// 删除第 4 个节点,要操作前驱节点
ListNode* p = head;
for (int i = 0; i < 2; i++) {
    p = p->next;
}

// 此时 p 指向第 3 个节点,即要删除节点的前驱节点
// 把第 4 个节点从链表中摘除
p->next = p->next->next;

// 现在链表变成了 1 -> 2 -> 3 -> 5
尾删

将数第二个节点的尾部置为null即可

cpp 复制代码
// 创建一条单链表
ListNode* head = createLinkedList({1, 2, 3, 4, 5});

// 删除尾节点
ListNode* p = head;
// 找到倒数第二个节点
while (p->next->next != nullptr) {
    p = p->next;
}

// 此时 p 指向倒数第二个节点
// 把尾节点从链表中摘除
p->next = nullptr;

// 现在链表变成了 1 -> 2 -> 3 -> 4
头删
cpp 复制代码
// 创建一条单链表
ListNode* head = createLinkedList(vector<int>{1, 2, 3, 4, 5});

// 删除头结点
ListNode* old = head;
head = head->next;
old->next = null;

// 现在链表变成了 2 -> 3 -> 4 -> 5

双链表

基础工具函数

cpp 复制代码
class DoublyListNode {
public:
    int val;
    DoublyListNode *next, *prev;
    DoublyListNode(int x) : val(x), next(NULL), prev(NULL) {}
};

DoublyListNode* createDoublyLinkedList(const vector<int>& arr) {
    if (arr.empty()) {
        return NULL;
    }
    DoublyListNode* head = new DoublyListNode(arr[0]);
    DoublyListNode* cur = head;
    // for 循环迭代创建双链表
    for (int i = 1; i < arr.size(); i++) {
        DoublyListNode* newNode = new DoublyListNode(arr[i]);
        cur->next = newNode;
        newNode->prev = cur;
        cur = cur->next;
    }
    return head;
}

查/改

双链表的节点因为有前后指针,所以既可以前序遍历也可以后续遍历

cpp 复制代码
// 创建一条双链表
DoublyListNode* head = createDoublyLinkedList({1, 2, 3, 4, 5});
DoublyListNode* tail = nullptr;

// 从头节点向后遍历双链表
for (DoublyListNode* p = head; p != nullptr; p = p->next) {
    cout << p->val << endl;
    tail = p;
}

// 从尾节点向前遍历双链表
for (DoublyListNode* p = tail; p != nullptr; p = p->prev) {
    cout << p->val << endl;
}

头插
cpp 复制代码
// 创建一条双链表
DoublyListNode* head = createDoublyLinkedList({1, 2, 3, 4, 5});

// 在双链表头部插入新节点 0
DoublyListNode* newHead = new DoublyListNode(0);
newHead->next = head;
head->prev = newHead;
head = newHead;

// 现在链表变成了 0 -> 1 -> 2 -> 3 -> 4 -> 5
尾插

先找到尾部,然后对尾部节点的后续指针进行插入

cpp 复制代码
// 创建一条双链表
DoublyListNode* head = createDoublyLinkedList({1, 2, 3, 4, 5});

DoublyListNode* tail = head;
// 先走到链表的最后一个节点
while (tail->next != nullptr) {
    tail = tail->next;
}

// 在双链表尾部插入新节点 6
DoublyListNode* newNode = new DoublyListNode(6);
tail->next = newNode;
newNode->prev = tail;
// 更新尾节点引用
tail = newNode;

// 现在链表变成了 1 -> 2 -> 3 -> 4 -> 5 -> 6
在双链表中间插入元素
cpp 复制代码
// 创建一条双链表
DoublyListNode* head = createDoublyLinkedList({1, 2, 3, 4, 5});

// 想要插入到索引 3(第 4 个节点)
// 需要操作索引 2(第 3 个节点)的指针
DoublyListNode* p = head;
for (int i = 0; i < 2; i++) {
    p = p->next;
}

// 组装新节点
DoublyListNode* newNode = new DoublyListNode(66);
newNode->next = p->next;
newNode->prev = p;

// 插入新节点
p->next->prev = newNode;
p->next = newNode;

// 现在链表变成了 1 -> 2 -> 3 -> 66 -> 4 -> 5

删除节点

需要调整前驱结点和后驱结点

cpp 复制代码
// 创建一个双链表
DoublyListNode* head = createDoublyLinkedList({1, 2, 3, 4, 5});

// 删除第 4 个节点
// 先找到第 3 个节点
DoublyListNode* p = head;
for (int i = 0; i < 2; ++i) {
    p = p->next;
}

// 现在 p 指向第 3 个节点,我们将它后面那个节点摘除出去
DoublyListNode* toDelete = p->next;

// 把 toDelete 从链表中摘除
p->next = toDelete->next;
toDelete->next->prev = p;

// 把 toDelete 的前后指针都置为 null 是个好习惯(可选)
toDelete->next = nullptr;
toDelete->prev = nullptr;

// 现在链表变成了 1 -> 2 -> 3 -> 5
头删
cpp 复制代码
// 创建一条双链表
DoublyListNode* head = createDoublyLinkedList({1, 2, 3, 4, 5});

// 删除头结点
DoublyListNode* toDelete = head;
head = head->next;
head->prev = nullptr;

// 清理已删除节点的指针
toDelete->next = nullptr;

// 现在链表变成了 2 -> 3 -> 4 -> 5
尾删
cpp 复制代码
// 创建一条双链表
DoublyListNode* head = createDoublyLinkedList({1, 2, 3, 4, 5});

// 删除尾节点
DoublyListNode* p = head;
// 找到尾结点
while (p->next != nullptr) {
    p = p->next;
}

// 现在 p 指向尾节点
// 把尾节点从链表中摘除
p->prev->next = nullptr;

// 把被删结点的指针都断开是个好习惯(可选)
p->prev = nullptr;

// 现在链表变成了 1 -> 2 -> 3 -> 4

链表代码实现

力扣707设计链表

cpp 复制代码
#include <iostream>
#include <stdexcept>

template<typename T>
class MyLinkedList {
    // 内部定义链表节点结构体 Node
    struct Node {
        T val;        // 节点中存放的数据,类型为 E(这里 E 应该是模板类型或者某种数据类型)
        Node* next;   // 指向下一个节点的指针
        Node* prev;   // 指向上一个节点的指针

        // 构造函数,初始化节点,val初始化为传入的value,next和prev初始化为空指针
        Node(T value) : val(value), next(nullptr), prev(nullptr) {}
    };

    Node* head;   // 指向链表的头节点(实际是虚拟头节点)
    Node* tail;   // 指向链表的尾节点(实际是虚拟尾节点)
    int size;     // 记录链表中的元素个数
public:
    //构造函数
    MyLinkedList() {
        head = new Node(T());
        tail = new Node(T());
        head->next = tail;
        tail->prev = head;
        size = 0;
    }
    //析构函数
    ~MyLinkedList(){
        while (size > 0) {
            removeFirst();
        }
        delete head;
        delete tail;
    }
    //增加
    void addLast(T t) //尾部增加
    {
        Node* x = new Node(t);
        Node* temp = tail->prev;
        
        temp->next = x;
        x->prev = temp;

        x->next = tail;
        tail->prev = x;
        size++;
    }
    void addFirst(T t)//头部增加
    {
        Node* x = new Node(t);
        Node* temp = head->next;
        //处理x后面的
        temp->prev = x;
        x->next = temp;
        //处理x前面的
        head->next = x;
        x->prev = head;

        size++;
    }
    void add(int index, T element) {
        checkPositionIndex(index);//先检查索引是否合规
        if (index == size) {
            addLast(element);
            return;
        }

        // 找到 index 对应的 Node
        Node* p = getNode(index);
        Node* temp = p->prev;
        // temp <-> p

        // 新要插入的 Node
        Node* x = new Node(element);

        p->prev = x;
        temp->next = x;

        x->prev = temp;
        x->next = p;

        // temp <-> x <-> p

        size++;
    }
    //删除
    T removeFirst() {
        if (size < 0) {
            throw std::out_of_range("No elements to remove");
        }
        Node* x = head->next;//把虚拟节点的后一个节点取出来(第一个节点取出)
        Node* temp = x->next;//把虚拟节点后第二个节取出(删除完后的第一个节点)

        head->next = temp;
        temp->prev = head;//断开x

        T value = x->val;//取出删除的值
        delete x;
        
        size--;
        return value;
    }
    T removeLast() {
        if (size < 1) {
            throw std::out_of_range("No elements to remove");
        }
        Node* x = tail->prev;
        Node* temp = tail->prev->prev;
        // temp <-> x <-> tail

        tail->prev = temp;
        temp->next = tail;

        T val = x->val;
        x->prev = nullptr;
        x->next = nullptr;
        delete x;
        // temp <-> tail

        size--;
        return val;
    }
    T remove(int index) {
        checkElementIndex(index);//先检查索引是否合规
        // 找到 index 对应的 Node
        Node* x = getNode(index);
        Node* prev = x->prev;
        Node* next = x->next;
        // prev <-> x <-> next
        prev->next = next;
        next->prev = prev;

        T val = x->val;
        x->prev = nullptr;
        x->next = nullptr;
        delete x;
        // prev <-> next

        size--;
        return val;
    }
    //查
    T get(int index) {
        checkElementIndex(index);
        // 找到 index 对应的 Node
        Node* p = getNode(index);

        return p->val;
    }

    T getFirst() {
        if (size < 1) {
            throw std::out_of_range("No elements in the list");
        }

        return head->next->val;
    }

    T getLast() {
        if (size < 1) {
            throw std::out_of_range("No elements in the list");
        }

        return tail->prev->val;
    }
    //改
    T set(int index, T val) {
        checkElementIndex(index);
        // 找到 index 对应的 Node
        Node* p = getNode(index);

        T oldVal = p->val;
        p->val = val;

        return oldVal;
    }
    int getSize() const {
        return size;
    }

    bool isEmpty() const {
        return size == 0;
    }

    void display() {
        std::cout << "size = " << size << std::endl;
        for (Node* p = head->next; p != tail; p = p->next) {
            std::cout << p->val << " <-> ";
        }
        std::cout << "nullptr" << std::endl;
        std::cout << std::endl;
    }

private:
    Node* getNode(int index) //获取节点
    {
        checkElementIndex(index);
        Node* p = head->next;
        //可以优化,通过 index 判断从 head 还是 tail 开始遍历
        //if (index < size / 2) {
        //    // 从 head->next 开始往后遍历
        //    p = head->next;
        //    for (int i = 0; i < index; i++) {
        //        p = p->next;
        //    }
        //}
        //else {
        //    // 从 tail 开始往前遍历
        //    p = tail;
        //    for (int i = size - 1; i > index; i--) {
        //        p = p->prev;
        //    }
        //}
        for (int i = 0; i < index; i++) {
            p = p->next;
        }
        return p;
    }
   //检查索引是否合规
    bool isElementIndex(int index) const {
        return index >= 0 && index < size;
    }
    bool isPositionIndex(int index) const {
        return index >= 0 && index <= size;
    }
    // 检查 index 索引位置是否可以存在元素
    void checkElementIndex(int index) const {
        if (!isElementIndex(index))
            throw std::out_of_range("Index: " + std::to_string(index) + ", Size: " + std::to_string(size));
    }
    // 检查 index 索引位置是否可以添加元素
    void checkPositionIndex(int index) const {
        if (!isPositionIndex(index))
            throw std::out_of_range("Index: " + std::to_string(index) + ", Size: " + std::to_string(size));
    }
};
int main() {
    MyLinkedList<int> list;
    list.addLast(1);
    list.addLast(2);
    list.addLast(3);
    list.addFirst(0);
    list.add(2, 100);

    list.display();
    // size = 5
    // 0 <-> 1 <-> 100 <-> 2 <-> 3 <-> null

    return 0;
}
相关推荐
yeshihouhou9 小时前
redis数据分片算法
redis·算法·哈希算法
李余博睿(新疆)9 小时前
c++经典练习题-分支练习(1)
数据结构·c++·算法
xu_yule9 小时前
算法基础-动态规划
算法·动态规划
自然常数e9 小时前
深入理解指针(7)
c语言·数据结构·算法·visual studio
张人玉9 小时前
西门子PLC地址知识点
算法·西门子plc
sheeta19989 小时前
LeetCode 每日一题笔记 日期:2025.12.17 题目:3573.买卖股票的最佳时机Ⅴ
笔记·算法·leetcode
榮十一9 小时前
10道SQL练习题及答案
数据库·sql·算法
l1t10 小时前
Javascript引擎node bun deno比较
开发语言·javascript·算法·ecmascript·bun·精确覆盖·teris
古城小栈10 小时前
Go 异步编程:无锁数据结构实现原理
java·数据结构·golang