链表中几个较重要的问题

发布者:luanzgc最新更新时间:2015-05-05 来源: 51hei关键字:链表  指针  比较操作 手机看文章 扫描二维码
随时随地手机看文章
文章还是关于链表,本次主要涉及几个比较深入的问题:循环链表的判定、倒数第m个节点的数据获取、多层次链表的设计、平铺和取消平铺。

 

    /*单链表*/
    typedef struct list
    {
        struct list *next;
        int data;
    } List_t, *List_handle_t;

    /*双向链表*/
    typedef struct Dblist
    {
        struct Dblist *next;
        struct Dblist *prev;

        int data;
    }DList_t, *DList_handle_t;

    /*多层次链表*/
    typedef struct mullevel_list
    {
        struct mullevel_list *prev;
        struct mullevel_list *next;
        struct mullevel_list *child;

        int data;
    }MList_t, *MList_handle_t;

关于链表主要还是搞清楚指针的相关问题。链表头的更新操作也是指针操作的一部分,如何实现链表头的自动更新也是需要注意的,如果每次都采用返回值的形式实现链表的头的更新,这在实际的操作中非常的不放便,采用指向指针的指针实现链表头的更新将是非常不错的选择。其实这也是内存分配中经常使用的技巧。
 

 

    /*内存分配*/
    bool GetMemory(char ** str, int n)
    {
        *str = (char *) malloc(n);
        if(*str)
          return true;
        return false;
    }

    /*链表头的更新*/
    bool insert_listnode(List_handle_t *head, int a)
    {
       List_handle_t newlistnode = (List_handle_t)malloc(sizeof(List_t)/sizeof(char));

       if(*head == NULL && newlistnode != NULL)
       {
         *head = newlistnode;
         newlistnode->data = a;
         newlistnode->next = NULL;

         return true;
       }
       else if(*head != NULL ** newlistnode != NULL)
       {
           newlistnode->data = a;
           newlistnode->next = *head;
           *head = newlistnode;

           return true;
       }
       return false;
    }

其中这种采用指向指针的指针的方式就能够保证链表的自动更新,这种特性主要是C/C++中函数值传递的特性,形参不改变实参的值,但是当传递的是指针时,这时指针实质上就是地址,作为参数时,地址并不会改变,但是地址中的内容是可改变的,这是内存分配问题中经常使用的技巧,如上面的代码所示。这种代码的形式还有一些优点,可以判断判断问题是否完成,通过判断是否需要再次分配。
 
单链表的逆序问题:
逆序问题实质上主要是完成每一个链表节点的操作,然后更新链表头,这时需要三个指针,其中一个表示逆序链表的表头,一个表示需要操作的节点,最后一个表示下一个即将操作的节点,也就是逆序操作需要保存三个节点才能保证一个逆序的完成。首先保证下一个即将操作的节点存在,然后实现逆序链表表头与实际操作的节点进行指向操作,更新表头。

 

    bool reversed_list(List_handle_t *head)
    {
            List_handle_t mid ;
            List_handle_t fir ;
            List_handle_t last;

            if(*head != NULL)
            {
                    mid = last = head;
                    /*save the node next to be operated*/
                    fir = mid->next;
                    /*tail of the list*/
                    last->next = NULL;

                    while(fir != NULL)
                    {
                            /*get the node to be operated*/
                            mid = fir;
                            /*save the node next to be operated*/
                            fir = fir->next;
                            /*link to the head of list*/
                            mid->next = last;
                            /*update the head of list*/
                            last = mid;
                    }
                    /*return the actual list head*/
                    *head = last;
                    return true;
            }
            return false;
    }


关于链表是否为循环链表的问题,这种问题是一个经典的问题,因为链表操作实质上就是指针的比较高级的操作。所以一般都需要依仗指针进行操作。如何判断是否为循环呢?如果是像数组那种连续的内存空间可以通过指针的值进行判断,连续性就能使得指针的比较存在意义,但是链表是一个非连续的内存空间,对指针进行比较就没有任何的意义。通常采用快慢指针的形式进行判断。
 
两个指针,其中一个指针以每次一个对象的形式遍历链表,另一个链表以每次多个对象的形式遍历,如果是非循环的链表,那么快的指针会首先到达链表的尾部。但是如果是循环链表,这时快指针的遍历速度快,因为存在循环,就会存在快指针指向慢指针后面对象的时刻,如果快指针指向的对象就是慢指针指向的对象或者快指针的下一个对象就是慢指针指向的对象(这两种情况都合适,这需要一句循环链表中的对象进行确定),就说明了链表是一个循环链表。快指针的访问速度可以设置为每次两个对象,这样就能实现判断。如下所示:

 

    bool isTermination(List_handle_t list)
    {
        List_handle_t slow , fast;
        slow = fast = list;

        while(1)
        {
            if(!fast || !fast->next)
                return false;
            else
            {
                /*快指针以2倍速度循环*/
                fast = fast->next->next;
                /*慢指针以1倍速度循环*/
                slow = slow->next;

                if(fast == slow || fast->next == slow)
                    return false;
            }
        }
    }

 [page]
链表倒数m个节点的对象
这种问题的解决方式很多,但是如何保证复杂度上最小却是一个重要的问题,最好是只遍历一次链表就能找到对应的节点,实质上采用类似于哨兵指针的形式就能实现。设置两个指针,分别执行链表头和链表的第m个对象,然后两个指针分别遍历,当执行第m个节点对象的指针指向了最后一个节点对象时,这时指向表头的那个链表实质上就指向了倒数第m个节点的对象。这个指向第m个节点的指针就起到了类似哨兵指针的作用。

 

    List_handle_t findMlastnode(List_handle_t list, int m)
    {
        int n = 0;
        List_handle_t temp = list;
        List_handle_t mtemp = NULL;

        if(temp != NULL)
        {
            /*find the mth node*/
            while( temp != NULL && n != m)
            {
                temp = temp->next;
                ++ n;
            }

            if(n == m && temp != NULL)
            {
                /*point to the mth node*/
                mtemp = temp;
                /*point to the head*/
                temp = list;
                
                /*pass the list*/
                while(mtemp->next != NULL)
                {
                    mtemp = mtemp->next;
                    temp = temp->next;
                }
                
                return temp;
            }
        }
        return NULL;
    }

关于多层次链表的平铺操作,因为多层次链表是类似于树的结构,当然可以采用类似树的遍历形式进行平铺,但是这种方式对节点的访问形式往往都是多次遍历。由于多层次的链表平铺还需要取消平铺操作,因此最好不要破坏每一个层次中的链接关系,如果破坏了每一层中的链接关系,就会使得每一层的还原操作非常复杂,我们可以按照层次逐层逐层的访问。
 
多层次链表的平铺最好的方式是充分利用尾节点,也就是将每一层的对象都接到平铺链表的尾部,而且随着平铺链表长度的增长,下一层次的节点也能够访问到,这时候通过判断节点是否存在子层,如果有就继续添加到尾节点,这样就能实现不同层次节点的平铺。这种平铺操作的优点在于只遍历了一次第一层的节点完成平铺操作,而且没有破坏每一层对象的链接关系,便于后期的还原。这种方法的关键在于如何控制链表的尾节点。

 

    /*add sublevel listnode to the tail of first level*/
    void appendtail(MList_handle_t head, MList_handle_t *tail)
    {
        MList_handle_t list = head;
        
        /*update the list tail*/
        (*tail)->next = head;
        head->next = (*tail);

        /*pass the node in this list*/
        for(list; list->next != NULL; list= list->next);
        
        /*updata the list tail*/
        *tail = list;
    }

    void flattenList(MList_handle_t head, MList_handle_t *tail)
    {
        MList_handle_t list = head;

        /*list will be growing*/
        while(list)
        {
            if(list->child)
            {
                appendtail(list->child,tail);
            }

            list = list->next;
        }
    }

取消平铺操作,主要是切断每一层之间的前后链接关系,而保持子层链接关系,实质上这可以采用递归的形式实现,因为如果当前节点存在子节点,那么就将子节点的链接关系切断,如果子节点也仍然存在子节点,那么先切断子层的链接关系,因为平铺没有破坏每一层的链接关系,这样只访问第一层就能完成取消平铺操作。实质完成的操作就是讲当前子节点的前一个节点的后一个节点设置为NULL,而将当前子节点的前一个对象设置为NULL,这样就切断了各层之间的关系。因为每一次切断都会导致平铺链表的缩短,当平铺链表只有原始第一层的长度时,这时候就完成了链表的取消平铺操作,当然仍然需要注意尾节点的管理问题。但是我们不能将取消平铺操作直接设置成一个递归操作,平铺操作最后肯定会管理链表尾,而子层与母层的链表断裂关系并不需要设置链表尾。

 

    void unflattensearch(MList_handle_t head)
    {
        MList_handle_t list = head;

        while(list)
        {
            if(list->child)
            {
                /*break the link between two levels*/
                list->child->prev->next = NULL;
                list->child->prev = NULL;
                
                /*break the link between other levels*/
                unflattensearch(list->child);
            }
            /*next listnode*/
            list = list->next;
        }
    }

    /*************************************************************
            this function can not be reserve
            because the function must update tail
            actual there is only one time to operate.
    **************************************************************/
    void unflattenList(MList_handle_t head, MList_handle_t * tail)
    {
        MList_handle_t list = head;

        unflattensearch(list);
        
        /*pass to the last of list*/
        for(list; list->next; list = list->next);
        
        /*update the tail of list*/
        *tail = list;
    }

 
总结
 
关于链表的操作我认为主要还是要设置恰当的指针,链表的操作就是指针的操作,但是因为链表的特殊性,使得指针的比较操作变得无效,但是可以通过指针的相等和不相等进行操作,设置哨兵指针等指针的重要操作。
 
同时需要注意链表是一个可能动态增长的对象,只要时刻理解这种动态特性就能比较好的理解链表中的多层次问题,平铺过程就是利用了链表的动态增长过程,通过链表尾实现动态操作。而取消平铺操作只是完成了切断各层之间的连接关系而已,并不会更新链表尾,但是链表的长度也发生了动态变化。
 
把握链表的动态增长特性和指针的相关操作就能很好的完成链表的相关操作。

关键字:链表  指针  比较操作 引用地址:链表中几个较重要的问题

上一篇:栈的经典运用
下一篇:C/C++中关于局部函数中更新实参指针的方法

推荐阅读最新更新时间:2024-03-16 14:00

采用指针式万用表判断铭牌丢失三相交流电动机的极对数
对于长时间使用后的电动机,如其上的铭牌丢失而需要了解其极对数时,可以采用指针式万用表进行剩磁检测的方法来进行判断,具体方法如下。 (1)判断方法。具体方法与要求简述如下。 1)将指针式万用表置于毫安挡,两表笔连接到三相交流电动机定子绕组任一相的引出线上,如图9-6所示。 2)在电动机转轴的初始位置做一个明显的便于观察标记。 3)用手缓慢地盘动电动机的转子一周。由于电动机铁心中剩磁的作用,当转子转动时,定子绕组中就会有交流电流产生,连接在定子绕组两端的万用表的指针就会发生偏转,指针偏离零位的次数就是电动机的极对数。 (2)需要说明的问题。有时也可以根据电动机相间绝缘的极相组数来判断电动机的极数。这主要是由于电动机绕组的极相组数
[测试测量]
采用<font color='red'>指针</font>式万用表判断铭牌丢失三相交流电动机的极对数
指针式万用表应用须知
  指针式万用表的使用方法   (1)熟悉表盘上各符号的意义及各个旋钮和选择开关的主要作用。   (2)进行机械调零。   (3)根据被测量的种类及大小,选择转换开关的挡位及量程,找出对应的刻度线。   (4)选择表笔插孔的位置。   (5)测量电压:测量电压(或电流)时要选择好量程,如果用小量程去测量大电压,则会有烧表的危险;如果用大量程去测量小电压,那么指针偏转太小,无法读数。量程的选择应尽量使指针偏转到满刻度的2/3左右。如果事先不清楚被测电压的大小时,应先选择最高量程挡,然后逐渐减小到合适的量程。   a交流电压的测量:将万用表的一个转换开关置于交、直流电压挡,另一个转换开关置于交流电压的合适量程上,万用表两表笔和被测电路
[测试测量]
数据指针DPTR的应用特性是什么?
它用于存放即将发送或者已经接收的数据,它在SFR块中,只有一个字节地址,但实际上是由发送缓冲器和接收缓冲器组成。这两个缓冲器都是独立的寄存器,当即将发送的数据传送到SBUF时,进的是发送缓冲器。 当要从SBUF取出数据时,则取自接收缓冲器,取走的是刚刚接收的数据。
[单片机]
如何用指针式万用表测量交流电容
作为入门者,正确使用是最基本的技能,本文主要讲解如何用指针式万用表测量交流。 测电容用电阻挡,根据电容容量选择适当的量程,并注意测量时对于电解电容黑表笔要接电容正极。 1、估测微法级电容容量的大小:可凭经验或参照相同容量的标准电容,根据指针摆动的最大幅度来判定。所参照的电容耐压值可以不同,只要容量相同即可,例如估测一个100μf/250v的电容可用一个100μf/25v的电容来参照,只要它们指针摆动最大幅度一样,即可断定容量一样。 2、估测皮法级电容容量大小:要用r×10kω挡,但只能测到1000pf以上的电容。对1000pf或稍大一点的电容,只要万用表针稍有摆动,即可认为容量够了。 3、测电容是否漏电:对1000微法以上
[测试测量]
如何用<font color='red'>指针</font>式万用表测量交流电容
mf-47指针万用表技术指标
mf-47指针技术指标,如表1所示: 表1 mf-47指针万用表技术指标
[测试测量]
mf-47<font color='red'>指针</font>万用表技术指标
初始化ARM处理器各模式下的堆栈指针SP(R13)
程序设计思路:通过状态寄存器与通用寄存器之间数据传输指令MRS/MSR实现,修改时应采用“读取-修改-写回”三个步骤来实现。每次只需修改相应的域即可,如本次程序只修改C控制域。同时应注意系统模式与用户模式共用SP,只需初始化其一即可。 程序代码如下: (1)在GNU ARM开发环境下编程: .equ _ISR_STARTADDRESS, 0xC7FF000 @设置栈的内存基地址 .equ UserStack, _ISR_STARTADDRESS @用户模式堆栈地址0x7FF000 .equ SVCStack, _ISR_STARTADDRESS+256 @管理模式堆栈地址0x7FF100 .equ UndefStac
[单片机]
怎样使用指针式万用表采用换挡法检测高内阻电源电压?
在采用指针式万用表检测高内阻电源电压(可以是等效电源的电 压)时,相当于在被检测的电路上并联了一只电阻,该电阻即为万用表的内 阻,由此就会引起较大的误差。 (1)检测方法与计算公式。为了消除上述检测误差,在采用指针式万用 表检查高内阻电源电压时,可以利用两个不同的电压挡分别测量高内阻电源 的电压,以消除检测误差。在进行实际检测时,可以先采用万用表的某一低 压量程挡UMI,测得一个电压值U1,然后再将万用表换为其相邻的高压量 程挡UM2,采用同样的方法又测得一个电压值U2,然后代入以下公式,就 可以求得高内阻电源的电压值 (2)检测计算举例。如使用MF500型指针式万用表的10V直流电压挡 UMl,测得某高内阻电源
[测试测量]
怎样使用<font color='red'>指针</font>式万用表采用换挡法检测高内阻电源电压?
PLC指针类型与间接寻址如何使用
在西门子S7-300和S7-400的编程中经常需要调用一些系统功能或功能块,在输入参数时经常碰到有指针类型的参数,那么你对指针类型了解吗?我第一次接触指针一词是在学习C语言的时候,指针和链表是C语言中的一个重点难点。在C语言中,指针即存储器地址,在西门子PLC中的指针也是指地址。 下面看看西门子POINTER类型的结构: 参数类型POINTER存储下列信息: · DB编号(或0,如果数据没有存储在DB中) · CPU中的存储区域(下表给出了参数类型POINTER存储器区的十六进制代码) 十六进制代码 存储区 描述 b#16#81 I 输入区域 b#16#82 Q 输出区域 b#16#83 M 位存储区域 b#
[嵌入式]
小广播
添点儿料...
无论热点新闻、行业分析、技术干货……
设计资源 培训 开发板 精华推荐

最新单片机文章
何立民专栏 单片机及嵌入式宝典

北京航空航天大学教授,20余年来致力于单片机与嵌入式系统推广工作。

换一换 更多 相关热搜器件
随便看看
电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2024 EEWORLD.com.cn, Inc. All rights reserved