链表:二级指针的使用

博文的记录源自阅读著名的酷壳主页:http://coolshell.cn/articles/8990.html

  Coolshell图文并茂说明了这个问题——我们在删除链表的时候,常常需要记录该结点在链表中是否有前趋prev。如果有,那么需要把prev->next指向该结点的next域,然后再删除该结点,这样才能保证链表不会因为删除结点而“断开”。像这样:

void remove(list_node **ref_head, list_node *del)
{
    if ((*ref_head) == NULL || del == NULL)
        return;
    list_node *cur = *ref_head, *prev = NULL;
    while (cur) {
        if (cur == del) {
            if (prev) {
                prev->next = cur->next;
            } else {
                *ref_head = cur->next;
            }
            delete cur;
            cur = NULL;
            break;
        }
        prev = cur;
        cur = cur->next;
    }
}

  那么,在遍历链表的时候,这种方法需要对prev is null or not进行判断,这在Linus Torvalds看来,是不聪明的做法,是不理解指针的表现。稍微想一下就知道,其实这样做的根本原因也很明了,第一,需要知道被删除的结点是否是链表的头结点,第二,需要保证链表在删除后不会“断开”。

  Linus的做法是用一个二级指针,去引用prev的next域。假设链表的结构名为list_node,那么这时要记录的不是list_node *prev,而是list_node **ref_prev。首先,实现代码是这样的:

void remove(list_node **ref_head, list_node *del)
{
    if ((*ref_head) == NULL || del == NULL)
        return;
    list_node *cur = *ref_head, **ref_prev = ref_head;
    while (cur) {
        if (cur == del) {
            *ref_prev = cur->next;
            delete cur;
            cur = NULL;
            break;
        }
        ref_prev = &(cur->next);
        cur = cur->next;
    }
}

  由代码可以看出,在执行cur = cur->next之前,首先用一个二级指针去引用当前cur的next域,其实也就是引用了方法一中的prev->next,这个原理和函数中的“传值”是一致的,就像swap函数一样,如果函数原型如void swap(int a, int b),即pass-by-value,这时函数执行时的变量a,b只是传入参数的一个副本,函数执行完毕对传入的变量值没有影响,要让值改变,函数应该是传入地址void swap(int &a, int &b);**ref_prev的道理也是如此,它不是记录prev,而是引用了prev的next域,在必要的时候改变prev->next的指向,这保证了链表不“断开”。其次,ref_prev的初始指向为链表的头结点,如果删除的结点是头结点,*ref_prev = cur->next也只是简单地把头结点改为原先头结点的next结点,不需要再判断prev是否为NULL了。

 今天看到了一道题目,大体意思是链表是排好序的,要你插入一个结点后,仍保持排序。稍微想想,似乎也是需要记录prev,如果插入的结点在链表内(非头结点也非尾结点),则要有这样的操作:prev->next = new_node; new_node->next = cur; (这里假定prev->value <= new_node->value <= cur->value, 即链表是升序排序的)很明显,这里的操作也可以用二级指针。

void insert_to_sorted_list(list_node **ref_head, int value, bool (*func)(int, int))
{
    if ((*ref_head) == NULL)
        return;
    list_node **ref_next = ref_head;
    list_node *cur = *ref_head;
    list_node *new_node = new list_node;
    new_node->value = value;
    new_node->next = NULL;
    while (cur) {
        if (func(value, cur->value)) {
            new_node->next = cur;
            *ref_next = new_node;
            return;
        }
        ref_next = &(cur->next);
        cur = cur->next;
    }
    *ref_next = new_node;
}

  这里考虑到题目未说明链表的排序方式(升序/降序),函数原型在参数里加入了一个函数指针。如果是在面试,这应该是个“考虑周全”的加分点吧?

时间: 2024-10-10 08:27:34

链表:二级指针的使用的相关文章

对于链表的链式存储的初始化操作,遇到的有关“二级指针”的知识小结

首先解释一下“二级指针”: 一级指针所关联的是其值(一个地址)名下空间里的数据,这个数据可以是任意类型并做任意用途,但二级指针所关联的数据只有一个类型一个用途,就是地址. 指针就是两个用途:提供目标的 读取 或 改写, 那么二级指针就是为了提供对于内存地址的读取或改写. 指针的表现形式是地址,核心是指向关系指针,运算符“*”的作用是按照指向关系访问所指向的对象. 如果存在A指向B的指向关系,则A是B的地址,“*A”表示通过这个指向关系间接访问B. 如果B的值也是一个指针,它指向C,则B是C的地址

彻底理解链表中为何使用二级指针或者一级指针的引用

 彻底理解链表中为何使用二级指针或者一级指针的引用 http://blog.csdn.net/u012434102/article/details/44886339 struct _node  {  void*data;  struct_node *prior;  struct_node *next;  } typedef_node Node;   //给这个_node结构体定义一个别名,任何使用_node的地方都可以用Node来替换 typedef_node* PNode;   //给这个指向

【转】Linus:利用二级指针删除单向链表

原文作者:陈皓 原文链接:http://coolshell.cn/articles/8990.html 感谢网友full_of_bull投递此文(注:此文最初发表在这个这里,我对原文后半段修改了许多,并加入了插图) Linus大婶在slashdot上回答一些编程爱好者的提问,其中一个人问他什么样的代码是他所喜好的,大婶表述了自己一些观点之后,举了一个指针的例子,解释了什么才是core low-level coding. 下面是Linus的教学原文及翻译—— “At the opposite en

二级指针删除链表元素

利用二级指针删除链表内一个元素,传统的做法是:找到将要删除元素的前一个指针,然后再删除当前元素.代码示例: void delete_node( elem_type x, struct node* l ){????struct node* p = find_prev ( x, l ); ????if ( !p->next ) {????????printf ( "element %d not in the struct node*\n", x );????????return;??

关于二级指针的认识

在链表操作中经常会遇到链表头的问题,如下图: 在这个链表插入函数中,如果链表中第一个元素就小于newValue,则需要更新头指针.当然可以把头指针定义为全局变量,但这并不是最优解,所以我们需要在函数中更新头指针的值,如下图: 当然Link *head  这里的head就是struct ** Node二级指针,在这里只是实现了更新头指针的功能,其实二级指针还有更妙的用法:链表中每一个节点都有指向它的指针,指向头节点的是头指针,指向其他节点的是它前一个节点的链,这样只需要修改指向每个节点的链即可:

关于一级指针和二级指针的简单见解

一级指针形式如: int *p二级指针形式如: int **p 可能很多初学者会疑惑在函数参数里面到底应该用一级指针还是二级指针. 下面用几个例子说明一下: 第一个是大家都很熟悉的链表 下面是一个链表结构:   typedef struct Node{ int value; struct Node *next;} 对于添加数据到链表尾部这个函数,传入的参数必须是一个二级指针. 在传参的时候发生的赋值动作是: Node *hNode;Node **pNode = &hNode; pNode指向了h

(C++)函数参数传递中的一级指针和二级指针

(C++)函数参数传递中的一级指针和二级指针 主要内容: 1.一级指针和二级指针 2.函数指针传递的例子 3.什么时候需要传递二级指针? 4.二级指针在链表中的使用 1.一级指针和二级指针 一级指针:即我们一般说的指针,就是内存地址: 二级指针:指向指针的指针,就是地址的地址: 如: int a=1; int *p=&a; // p为a变量的地址,通过*p可以得到a的值 int **q=&p; // q为p指针的地址,通过**q可以得到a的值 2.函数指针传递的例子 程序1: 1 2 3

单链表-快慢指针

快慢指针: 定义两个指针,一个快,一个慢,可以有多种用途.例如:快速找到位置长度单链表中的中间结点:对于循环链表中利用快慢指针也可以判断是否存在环. 快速找到位置长度单链表中的中间结点 1)使用一个指针,先索引一遍获取总长度,再取长度一半去循环获取到中间值:O(3L/2). 2)使用两个指针,快指针和慢指针,快指针一次向前走2格,慢指针一次走一格,当快指针走完全程,慢指针正好走在中间:O(L/2). 代码实现: //快速查找中间元素 int FindMidEle(LinkList L, Elem

黑马程序员---C基础9【字符串的输入输出】【字符串相关函数】【指针】【指针变量初始】【二级指针】

------Java培训.Android培训.iOS培训..Net培训.期待与您交流! ------- [字符串的输入输出] 1.字符串输出: %s-------从给定的地址开始输出字符直到遇到\0停止: printf("%s\n",&a[3]);  printf("%s\n",a); char a[]="hellowo\0rd!"; 2.字符串输入: 从键盘上接收一个字符串,保存在以a为首地址的字符数组中 scanf("%s&