反转链表

来源:互联网 发布:淘宝网优衣库旗舰店 编辑:程序博客网 时间:2024/06/04 18:22

问题:

给定一个单向链表,设计一个时间优化并且空间优化的算法,找出该链表的倒数第m个元素。实现您的算法,注意处理相关的出错情况。m定义为当m=0时,返回链表最后一个元素

 

解答:

这是一个难题,因为单向链表只能正向遍历,这个问题需要根据元素与链表尾的相对位置来找出该元素,但是当发现链表尾时,没有简单的办法回溯到倒数第m个元素。

 

我们需要的是倒数第m个元素,所以,如果我们从某个元素开始,遍历了m个元素之后刚好到达链表末尾,那这个元素就是要找的元素。一种方法是简单的以这种方式检查每个元素,直到找到要找的元素为止。但这样同样的元素会被遍历多次,针对链表中大部分元素我们都会遍历m个元素,如果链表的长度是n的话,那么这个算法的时间复杂度就是Omn)。

 

也许从链表的尾部倒推回去不是最好的办法,那么我们可以从链表头开始计数。

思路一:想要的元素是倒数第m个元素,而我们知道m值。它肯定是从链表头算起的第x个元素,且x+m=n,即链表的长度。而计算出链表的所有元素个数是容易的。之后就可以算出x=n-m,并从链表头开始遍历x个元素。尽管这个算法需要2次遍历链表,但它的时间复杂度仍是On)。同时,如果我们可以修改链表的函数,在添加元素时对一个计数遍历加1,在删除元素时对这个计数变量减1,我们就可以省掉链表长度计算的过程,使它更有效率。

上面思路的缺点是:如果这是一个很长的链表而计算机的内存又有限,那么这个链表的大部分很可能都存放在切换出物理内存的虚拟内存上(即存放在磁盘上)。如果真是这样,那么链表的每次遍历都将需要做大量的磁盘读写操作才能把链表的有关部分读到物理内存里来进行处理。在这种情况下,如果算法只需对链表做一次完整的遍历,那么它将比必须做两次遍历的算法快很多---虽然它们都是O(n)级的算法。

 

但是,如果不能修改链表的数据结构呢?在上面的分析中,当我们到达链表的末尾时,实际上只对保存下来的m个元素中的一个感兴趣,即当前位置的前面第m个元素。我们之所以记下m个元素只是因为当前位置的前面第m个元素在每次移动位置时都会改变。保持一个m个元素的队列,每次在移动当前位置时,将当前元素添加在头部,在尾部删除一个元素,这样做只是为了确保队列中最后一个元素总是当前位置的前面第m个元素。

 

思路二:透过想象看本质,上面我们使用这个m个元素的数据结构来隐式地移动一个前m个元素的指针,保持它与当前位置的指针同步移动。但是,这个数据结构是不必要的,我们可以显式地移动前面第m个元素的指针,就像移动当前位置的指针一样,这与通过一个队列隐式地移动同样简单,这样一来,就无需保存当前位置和前面第m个元素之间的所有元素。这个算法的优点是:线性时间复杂度、一次遍历、可以忽略的存储要求。

 

此时,我们需要使用两个指针:一个当前位置指针和一个前面第m个元素的指针。需要确保两个指针之间相差m个元素,然后以同样的速度移动它们。如果当前指针到达链表的末尾,前面第m个元素的指针就是指向倒数第m个元素。同时要注意边界条件,如果链表的长度小于m,那么就没有倒数第m个元素,因此,在移动当前指针时要检查是否到达链表的末尾。

 

实现代码如下:

#include <iostream>

#include <assert.h>

 

struct ListNode

{

    int m_nKey;

    ListNode*m_pNext;

};

 

void InitList(ListNode**pList)

{

    *pList= (ListNode*)malloc(sizeof(ListNode));

    (*pList)->m_pNext= NULL;

}

 

void InsertList(ListNode*pList, int data)

{

    assert(pList!= NULL);

    ListNode*pNewNode = (ListNode*)malloc(sizeof(ListNode));

    pNewNode->m_nKey= data;

    pNewNode->m_pNext= pList->m_pNext;

    pList->m_pNext= pNewNode;

}

 

//思路一的核心算法

//注意:链表最后一个元素是倒数第0

ListNode*findMLastElement1(ListNode*head, unsigned int m)

{

    if(head== NULL)

    return NULL;

 

    //计算链表中节点数º

    ListNode*pCurrent = head->m_pNext;

    unsigned int nCount= 0;

    while(pCurrent!= NULL)

    {

        pCurrent= pCurrent->m_pNext;

        nCount++;

    }

 

    //如果链表中节点数少于m,返回NULL

    if(nCount< m)

    return NULL;

 

    //链表倒数第m个节点就是从链表开头第nCount-m个节点

    //注意:m0开始

    pCurrent= head;

    for(unsigned int i=0;i<nCount-m; i++)

    {

        pCurrent= pCurrent->m_pNext;

    }

    return pCurrent;

}

 

//思路二的核心算法

//注意:链表最后一个元素是倒数第个

ListNode*findMLastElement2(ListNode*head, unsigned int m)

{

    if(head== NULL)

    return NULL;

 

    ListNode*current, *mBehind;

    current= head;

    for(int i=0;i<m; i++)

    {

    //current设置为当前指针,它前面必须存在第m个元素,否则出错

        if(current->m_pNext!= NULL)

        {

            current= current->m_pNext;

        }

        else

        {

            return NULL;

        }

    }

 

    mBehind= head; //当前位置current的前面第m个元素的指针,从链表头开始移动

    while(current->m_pNext!= NULL)

    {

        current= current->m_pNext;

        mBehind= mBehind->m_pNext;

    }

    return mBehind;

}

 

//打印链表元素

void PrintListNormally(ListNode*pListHead)

{

    ListNode*pTempNode =pListHead->m_pNext;

    while(pTempNode!= NULL)

    {

        std::cout<<pTempNode->m_nKey<<std::endl;

        pTempNode= pTempNode->m_pNext;

    }

}

 

int main()

{

    ListNode*pListHead = NULL;

    InitList(&pListHead);

    for(int i=9;i>=0; i--)

    {

        InsertList(pListHead,i);

    }

 

    PrintListNormally(pListHead);

 

    ListNode*findNode = NULL;

 

    findNode= findMLastElement1(pListHead, 3);

    std::cout<<"链表中倒数第3个元素是:"<<findNode->m_nKey<<std::endl;

 

    findNode= findMLastElement2(pListHead, 4);

    std::cout<<"链表中倒数第4个元素是:"<<findNode->m_nKey<<std::endl;

 

    system("pause");

    return 0;

}

 

扩展问题:

如何获得处于单链中间位置的节点(只能遍历链表一次!)

解答:

其实原理和上面的一样,就是设置两个指针,或游标(形象点):

Element*pSlow = head; //标识当前节点

Element*pFast = head; //标识当前节点的下一个节点

将慢游标前进步长设为1,快游标前进步长设为2

pSlow= pSlow->next; //前进一个节点

pFast=pFast->next->next; //前进两个节点因此,快游标是慢游标速度的两倍,

//当快游标到达链表尾节点或尾节点前一个节点时,慢游标正好处于链表的

//中间位置,即为所求。

原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 2018年服装店生意特别差怎么办 打印机拍的照片打出黑怎么办 租的汽车撞了怎么办 神州租车车坏了怎么办 深圳市公安局办保安员证怎么办? 老婆被车撞了后失忆了怎么办 生气引起的短暂失忆怎么办 win10系统忘记开机密码怎么办 戴尔win10密码忘了怎么办 戴尔电脑win10密码忘了怎么办 出门忘记带身份证了怎么办 一年染了7次头发怎么办 低头久了颈椎疼怎么办 一只眼睛磨得慌怎么办 没有睡好眼睛痛怎么办 好几天没休息好怎么办 血糖高睡不好觉怎么办 眼睛感觉磨的慌怎么办 痔疮手术后大便疼肛裂痛怎么办 肛裂排便困难痛怎么办 智齿导致的牙疼怎么办 肛裂一直不愈合怎么办 孕妇肛裂拉屎疼怎么办 产后50天小肚子突出怎么办 肚子像怀孕一样大怎么办 蹲厕所拉不出来怎么办 生气导致回奶了怎么办 老公每晚要吃奶才睡觉怎么办 分分钟想把老公杀掉怎么办 老公出轨闹的厉害离家出走怎么办? 儿子故意杀人一审判死刑怎么办 被家暴时妻子杀了丈夫该怎么办 丈夫挣钱不给妻子怎么办 白色皮鞋染了色怎么办 刺扎到手里拿不出来怎么办 军官证解锁片丢了怎么办 六安市人民医院药品停用了怎么办 信无法寄到该怎么办 5个月的宝宝光有屎沫怎么办 胸牌的别针坏了怎么办 工资表税金扣多了怎么办?