1 条题解

  • 1
    @ 2024-7-25 13:38:10
    单链表单链表

    单链表是一种基本的线性数据结构,它通过链式存储方式而不是像数组那样是一块连续的内存位置来存储元素。类比数组在内存中是一段连续的空间,但是链表的每个节点可能是在随机的位置,(逻辑上连续,物理上不一定连续),在单链表中,每个元素(或称为节点)包含两部分:数据域和指针域。

    数据域用于存储实际的数据,而指针域则存储指向下一个节点的地址。

    这样,每个节点都链接到列表中的下一个节点,形成一个链条。

    如下图:

    作为链表初学者我们一般这样定义节点:

    struct Node//构建一个节点
    {
    	int data;//数据域
    	struct Node * next;//指针域
    };
    

    创建链表

    int main() 
    {
        int n;
        scanf("%d", &n); // 输入链表的节点数
    
        Node* phead = NULL; // 头指针
        Node* ptail = NULL; // 尾指针
        phead = (Node*)malloc(sizeof(Node)); // 头指针指向不存储数据的头节点
        ptail = phead; // 尾指针也指向头节点
    
        for (int i = 1; i <= n; ++i) 
        { // i == 1是第一个存储数据的节点即首节点
            Node* p = (Node*)malloc(sizeof(Node)); // 动态申请内存空间
            int data;
            scanf("%d", &data); // 输入数据
            p -> data = data; // 存储数据
    
            ptail -> pnext = p; // 把新节点加到链表的尾部
            ptail = p; // 更新尾指针,指向新节点
            ptail -> pnext = NULL; // 新节点的 pnext 是 NULL
        }
        return 0;
    }
    

    遍历链表

        Node* current = phead -> pnext; // 从第一个有效节点开始
        while (current != NULL) 
        {
            printf("%d ", current -> data); // 输出当前节点的数据
            current = current -> pnext; // 移动到下一个节点
        }
    

    头插法

        int data;
        Node* newNode = (Node*)malloc(sizeof(Node));
        scanf("%d", &data);
        newNode -> data = data;
        newNode -> pnext = phead -> pnext; // 新节点的 pnext 指向原来的头节点
        phead -> pnext = newNode; // 更新头指针
    

    尾插法

        // 如果链表为空
        if (phead -> pnext == NULL) 
        {
            phead -> pnext = newNode;
        } else 
        {
            Node* current = phead -> pnext;
            while (current -> pnext != NULL) 
            { // 找到链表的尾部
                current = current -> pnext;
            }
            current -> pnext = newNode; // 将尾节点的 pnext 指向新节点
        }
    

    释放内存

        Node* current = phead;
        while (current != NULL) 
        {
            Node* temp = current;
            current = current -> pnext;
            free(temp); // 释放当前节点
        }
    

    可以发现代码实在太多了,我们学习用数组模拟链表,headhead 表示头指针,idxidx 表示为链表中的节点编号,用 e[idx]e[idx],表示链表节点 idxidx 的数据域,ne[idx]ne[idx],表示链表节点 idxidx 的指针域。

    #include <iostream>
    
    using namespace std;
    
    const int N = 100010;
    
    
    // head 指向首节点,此时我们忽略头节点,直接存储数据
    // e[i] 表示节点i的值
    // ne[i] 表示节点i的next指针是多少
    // idx 存储当前已经用到了哪个点
    // idx 为零时为首节点.
    int head = -1, e[N], ne[N], idx;
    
    // 将x插到第一个有效结点前面
    void add_to_head(int x)
    {
        e[idx] = x, ne[idx] = head, head = idx++;
    }
    
    // 将x插到下标是k的点后面
    void add(int k, int x)
    {
        e[idx] = x, ne[idx] = ne[k], ne[k] = idx++;
    }
    
    // 将下标是k的点后面的点删掉
    void remove(int k)
    {
        ne[k] = ne[ne[k]];
    }
    
    int main()
    {
        int m;
        cin >> m;
        while (m--)
        {
            int k, x;
            char op;
    
            cin >> op;
            if (op == 'H')
            {
                cin >> x;
                add_to_head(x);
            }
            else if (op == 'D')
            {
                cin >> k;
                if (!k) head = ne[head];
                else remove(k - 1);
            }
            else
            {
                cin >> k >> x;
                add(k - 1, x);
            }
        }
    
        for (int i = head; i != -1; i = ne[i]) cout << e[i] << ' ';
        cout << endl;
    
        return 0;
    }
    
    • @ 2024-12-22 21:48:22

      你好,你的题解让我学到了一些新思路!特别是数组模拟链表的部分,我之前完全没想到可以这么做。不过有一个地方我卡住了:比如头插法的 e[idx] = x, ne[idx] = head, head = idx++,我感觉自己有点理解不到位,不知道能不能麻烦你再稍微解释一下?

    • @ 2024-12-22 21:49:29

      @ 如果要遍历链表打印所有数据,代码中从 for (int i = head; i != -1; i = ne[i]) 开始,这个循环为什么能确保所有节点都被访问到呢?

    • @ 2025-1-3 18:08:32

      @ 开创一个新的节点,数据域 e[idx]=xe[idx] = x, 指针域指向 headhead 指向的节点 ( 即首节点也就是第一个存储数据的节点 ),这也是头插法名字的含义,然后 headhead 指向新的节点 idxidx。

    • @ 2025-1-3 18:26:02

      @ 我们在用数组模拟时无视了头节点,直接存储数据从首节点开始,headhead 指向首节点,所以我们从他开始循环 intint i=headi = headii 为我们当前遍历到的节点,我们可以发现,无论如何添加节点,链表尾节点的指针域都为 1-1,这恰好是遍历链表的终止条件 i!=1i != -1 ,而 i=ne[i]i = ne[i] 表示的是当前遍历到的节点 ii 为当前节点 ii 指向的下一个节点,也就是在不断地向后走直到 i==1i == -1 终止,也就是链表的尾节点。这样我们就遍历了整个链表。

    • @ 2025-1-3 18:26:22

      @ 我们在用数组模拟链表时忽略了头节点,直接从首节点开始存储数据。head 指向链表的首节点,所以我们从 head 开始遍历链表。遍历时,i 代表当前遍历到的节点,初始时为 head。每次遍历时,i = ne[i] 表示我们移动到当前节点指向的下一个节点。由于链表尾节点的 ne[i]-1,我们通过 i != -1 作为遍历终止的条件,一旦 i-1,表示已遍历至链表尾部。这样,我们就完成了链表的遍历。

  • 1

信息

ID
99
时间
1000ms
内存
256MiB
难度
10
标签
递交数
4
已通过
3
上传者