使用二级指针完成链表操作

本文介绍了一种链表操作算法,该算法能够根据给定的阈值x,将链表中所有节点按值分为两部分:一部分节点值小于x,另一部分节点值大于等于x,并保持原有的相对顺序。通过使用二级指针技巧,该算法能优雅地解决这一问题,适用于大多数链表结构问题。

//=============================QUESTION =================================
// Rearrange a list in such a way:  given x, all nodes with val >= x should come after those with val < x.
// for example, given x = 5, a list {0 , 3 , 6 , 2 , 5} should be transformed into : { 0 , 3,  2,  6 , 5}. Of course, {0 , 3 , 2 , 5 ,6 } also meets the requirement.
// =====================================================================

#include <iostream>

struct ListNode {
    int val;
    struct ListNode *next;
    ListNode(int x) : val(x), next(NULL) {}
};

//***** using second-order pointer can solve this question beautifully.  ****
//in fact, second-order pointer can handle almost all list structure problems neatly.
ListNode* rearrange(ListNode* pHead, int x)
{
    ListNode* smaller = NULL, **smaller_to_fill = &smaller;
    ListNode* bigger = NULL, **bigger_to_fill = &bigger;
    while(pHead)
    {
        if (pHead->val >= x)
        {
            *bigger_to_fill = pHead;
            bigger_to_fill = &(pHead->next);
        }
        else
        {
            *smaller_to_fill = pHead;
            smaller_to_fill = &(pHead->next);
        }
        pHead = pHead->next;
    }
    *smaller_to_fill = bigger;
    *bigger_to_fill = NULL;
    return smaller;
}





////TEST 

using namespace std;

int main(int argc, const char * argv[]) {

    ListNode* head, **p_to_fill = &head;

    //create a linklist
    for (int i = 0; i < 30; i++) {
        ListNode* new_node = new ListNode( (i * 101) % 31);
        *p_to_fill = new_node;
        p_to_fill = &(new_node->next);
    }
    
    //before rearrangement
    ListNode* p = head;
    while(p)
    {
        cout << p->val << "  ";
        p = p->next;
    }
    
    
    cout << "\n\n\n\n";
    
    ListNode* p_rearranged = rearrange(head, 15);
    
    //after rearrangement
    p = p_rearranged;
    while(p)
    {
        cout << p->val << "  ";
        p = p->next;
    }

    //delete the list
    ListNode** p_to_delete = &head;
    while(*p_to_delete)
    {
        ListNode* node = *p_to_delete;
        p_to_delete = &(node->next);
        delete node;
    }

    return 0;
}
<think>嗯,用户想知道链表什么时候需要使用二级指针。这个问题看起来和C或C++中的指针操作有关。首先,我得回忆一下链表的基本结构,链表节点通常包含数据和指向下一个节点的指针。操作链表时,经常需要修改头指针,比如插入或删除节点的时候。 那什么时候会用到二级指针呢?二级指针是指向指针的指针,也就是说,如果我们要修改头指针本身,比如在链表头部插入节点,这时候如果头指针是一个普通指针,函数参数传递的是它的值,无法直接修改原指针的值。这时候就需要传递头指针的地址,也就是二级指针,这样才能在函数内部修改头指针指向的位置。 举个例子,比如在C语言中,如果有一个函数要添加节点到链表头部,如果头指针是Node* head,那么函数参数应该是Node** head_ref,这样在函数内部通过*head_ref来修改head的值。否则,如果只是传递Node* head,函数内部修改的只是副本,原来的head指针不会改变。 另外,删除链表节点的时候,如果删除的是头节点,同样需要修改头指针,这时候也需要二级指针。或者当链表为空时,添加第一个节点也需要改变头指针,这时候也需要通过二级指针操作。 不过,有时候也可以用返回头指针的方式,比如函数返回新的头指针,然后让调用者自己更新。这时候可能不需要二级指针。所以二级指针主要是在需要直接修改头指针本身的时候使用,而不仅仅是修改链表的内容。 可能用户在学习链表操作时遇到了问题,比如函数内修改头指针无效,这时候需要理解指针传递的原理。需要确保他们明白为什么有时候必须用二级指针,而有时候不需要。可能用户对指针的概念还不够清晰,需要进一步解释指针和二级指针的区别,以及何时传递指针的地址。 总结一下,使用二级指针的情况包括:修改链表头指针的时候,如在头部插入或删除节点,初始化空链表时添加第一个节点,或者需要让函数永久改变头指针的指向的情况。这些都是需要直接修改原指针的值,而不是修改指针指向的内容。</think>在链表操作中,使用二级指针的核心场景是:**当需要直接修改链表头指针本身时**。以下是具体场景和原理的逐步分析: --- ### 一、为什么需要二级指针链表头指针 `head` 本身是一个指针变量,存储链表首节点的地址。若要通过函数修改 `head` 的值(例如插入/删除首节点),需传递 `head` 的地址,即: - **一级指针** `Node* head`:存储节点的地址 - **二级指针** `Node** head_ref`:存储 `head` 的地址 若直接传递一级指针,函数内修改的是其副本,原指针不变。 --- ### 二、必须用二级指针的典型场景 #### 1. 在链表头部插入节点 ```c void insert_at_head(Node** head_ref, int data) { Node* new_node = (Node*)malloc(sizeof(Node)); new_node->data = data; new_node->next = *head_ref; // 新节点指向原头节点 *head_ref = new_node; // 修改头指针指向新节点 } ``` 调用方式: ```c Node* head = NULL; insert_at_head(&head, 10); // 传递 head 的地址 ``` #### 2. 删除链表头节点 ```c void delete_head(Node** head_ref) { if (*head_ref == NULL) return; Node* temp = *head_ref; *head_ref = (*head_ref)->next; // 修改头指针指向下一个节点 free(temp); } ``` #### 3. 初始化空链表时添加第一个节点 若链表初始为空(`head = NULL`),通过函数添加第一个节点时,必须修改 `head` 的指向。 --- ### 三、不需要二级指针的场景 #### 当仅修改链表内容(不改变头指针本身时): ```c void append(Node* head, int data) { Node* new_node = (Node*)malloc(sizeof(Node)); new_node->data = data; new_node->next = NULL; // 遍历到链表末尾 Node* current = head; while (current->next != NULL) { current = current->next; } current->next = new_node; } ``` 此处仅修改链表尾部,无需改变 `head`,故直接传递一级指针即可。 --- ### 四、替代方案:返回新头指针 若不使用二级指针,可通过函数返回值更新头指针: ```c Node* insert_at_head(Node* head, int data) { Node* new_node = (Node*)malloc(sizeof(Node)); new_node->data = data; new_node->next = head; return new_node; // 返回新的头指针 } // 调用方式 head = insert_at_head(head, 10); ``` 此方法依赖调用者主动更新 `head`,但逻辑更直观。 --- ### 总结表格 | 场景 | 是否需要二级指针 | 原因 | |-----------------------|------------------|--------------------------| | 修改头指针(插入/删除) | 需要 | 需直接修改 `head` 的地址 | | 仅修改链表中间/尾部 | 不需要 | 不涉及 `head` 的修改 | | 通过返回值更新头指针 | 不需要 | 隐式更新 `head` | 理解二级指针的关键:**当函数需要永久改变头指针的指向时,必须传递头指针的地址(即二级指针)**。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值