数据结构:链表 C++

这篇博客详细介绍了C++实现链表的各种操作,包括找到链表中倒数第k个节点、翻转链表、从尾到头输出链表、在O(1)时间内删除链表节点、找到两个链表的第一个公共节点,以及复制复杂链表。每种操作都提供了详细的分析和C++代码实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目来自于

http://zhedahht.blog.163.com/blog/#m=0&t=1&c=fks_081075092084086069092074084070080087080066083082

非常感谢July大神,何海涛和众网友。


本博文初衷是为了方便自己准备面试,有一篇文章可以看所有的东西,省的翻来翻去。

代码是手打的Qt C++,解法中不考虑实现最简单的毫无难度的暴力解法了。

因为很短,不区分*.cc和*.hh文件了,都写在一起; 注释初衷也是为了自己理解。

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

1.1 链表中倒数第k个结点

题目描述

输入一个单向链表,输出该链表中倒数第k个结点。链表的倒数第0个结点为链表的尾指针。链表结点定义如下:

struct ListNode
{
      int       m_nKey;
      ListNode* m_pNext;
};


分析与C++代码

// 微软100题:09 链表中倒数第k个结点[数据结构]
// 题目:输入一个单向链表,输出该链表中倒数第k个结点。链表的倒数第0个结点为链表的尾指针。
// 链表结点定义如下:
// struct ListNode
// {
//      int       m_nKey;
//      ListNode* m_pNext;
// };
#include <iostream>
#include <string>
 
using namespace std;
 
//define node
struct ListNode
{
    int       m_nKey;
    ListNode* m_pNext;
};
 
void printList(ListNode* head, string name)
{
    cout << name;
    while (head != NULL)
    {
        cout << head->m_nKey << "-> ";
        head = head->m_pNext;
    }
    cout << "NULL." << endl;
}
 
ListNode* FindKthToEnd( ListNode* myList, int k);
 
int main()
{
    //create a list
    ListNode* myList = NULL;
    ListNode* head = NULL;
    int listLength = 10;
    for (int ii = 0; ii < listLength; ++ii)
    {
        if (head == NULL)
        {
            head = new ListNode;
            head->m_nKey = ii;
            head->m_pNext = NULL;
            myList = head;
        }else
        {
            head->m_pNext = new ListNode;
            head = head->m_pNext;
            head->m_nKey = ii;
            head->m_pNext = NULL;
        }
    }
 
    //print a list
    printList(myList, "myList: ");
 
    // run
    int k = 4;
    ListNode* kthNode = FindKthToEnd(myList, k);
    if (kthNode != NULL)
    {
        cout << "The "<< k <<"-th value from the end is: " << kthNode->m_nKey << endl;
    }
 
 
    //delete a list
    while(myList != NULL)
    {
        ListNode* tmp = myList;
        myList = myList->m_pNext;
        delete tmp;
    }
}
//%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
// 推荐解法: 由于是单链表,我们不知道链表长度n,不然我们完全可以只走
// (n-k)步。最简单的获取链表长度的方法就是遍历一遍链表,然后再从头开
// 始走(n-k)个。如果链表的结点数不多,这是一种很好的方法。但如果输入
// 的链表的结点个数很多,有可能不能一次性把整个链表都从硬盘读入物理
// 内存,那么遍历两遍意味着一个结点需要两次从硬盘读入到物理内存。我们
// 知道把数据从硬盘读入到内存是非常耗时间的操作。我们能不能把链表遍历
// 的次数减少到1?如果可以,将能有效地提高代码执行的时间效率。
// 如果我们在遍历时维持两个指针,第一个指针从链表的头指针开始遍历,在
// 第k-1步之前,第二个指针保持不动;在第k-1步开始,第二个指针也开始从
// 链表的头指针开始遍历。由于两个指针的距离保持在k-1,当第一个(走在前
// 面的)指针到达链表的尾结点时,第二个指针(走在后面的)指针正好是倒数
// 第k个结点。这种思路只需要遍历链表一次。对于很长的链表,只需要把每个
// 结点从硬盘导入到内存一次。因此这一方法的时间效率前面的方法要高。
ListNode* FindKthToEnd( ListNode* myList, int k)
{
    // k 必须至少是1 因为链表的最后一个结点是NULL
    if ((myList == NULL) || (k <= 0))
    {
        cout << "myList is NULL or k <= 0." << endl;
        return NULL;
    }
    // 快慢指针的变种,先后指针
    ListNode* first = myList;
    ListNode* second = myList;
 
    // 让first指针先走k步
    while(k > 0)
    {
        if (first == NULL)
        {
            break;
        }
        k--;
        first = first->m_pNext;
    }
    if (k > 0)
    {
        cout << "k is longer than than the total length of the list!" << endl;
        return NULL;
    }
    printList(first, "first: ");
 
    // first and second指针都走,当second指针到链表尾部
    // 时,first指针指的就是倒数第k个结点
    while(first != NULL)
    {
        first = first->m_pNext;
        second = second->m_pNext;
    }
    return second;
}
 
 


1.2 翻转链表

题目描述:

输入一个链表的头结点,反转该链表,并返回反转后链表的头结点。链表结点定义如下: 

struct ListNode
{
      int       m_nKey;
      ListNode* m_pNext;
};

分析与C++代码:

// 微软100题:19翻转链表

// 题目:输入一个链表的头结点,反转该链表,并返回反转后链表的头结点。
// 链表结点定义如下:
// struct ListNode
// {
//      int       m_nKey;
//      ListNode* m_pNext;
// };
#include <iostream>
#include <string>
using namespace std;
//define node
struct ListNode
{
    int       m_nKey;
    ListNode* m_pNext;
};
void printList(ListNode* head, string name)
{
    cout << name;
    while (head != NULL)
    {
        cout << head->m_nKey << "-> ";
        head = head->m_pNext;
    }
    cout << "NULL." << endl;
}
ListNode* reverseList(ListNode* myList);
ListNode* reverseList2(ListNode* head, ListNode*& new_head);
int main()
{
    //create a list
    ListNode* myList = NULL;
    ListNode* head = NULL;
    int listLength = 10;
    for (int ii = 0; ii < listLength; ++ii)
    {
        if (head == NULL)
        {
            head = new ListNode;
            head->m_nKey = ii;
            head->m_pNext = NULL;
            myList = head;
        }else
        {
            head->m_pNext = new ListNode;
            head = head->m_pNext;
            head->m_nKey = ii;
            head->m_pNext = NULL;
        }
    }
    //print a list
    printList(myList, "myList: ");
    // run
    ListNode* myList_reversed = reverseList(myList);
    printList(myList_reversed, "reversed myList: ");
    reverseList2(myList_reversed, myList);
    printList(myList, "reversed_reversed_myList: ");
    //delete a list
    while(myList != NULL)
    {
        ListNode* tmp = myList;
        myList = myList->m_pNext;
        delete tmp;
    }
}
//%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
// 推荐解法: 三步翻转法
// 为了使空间复杂度为O(1),我们原位翻转链表。
// 整个算法需要两个辅助指针,和head指针一起遍历链表
// 两个辅助指针一个tmp1指向当前结点,另一个tmp2指向当前结点的下一个,
// 然后画个图就明白了
//(1) tmp1->next = tmp2->next;
//(2) tmp2->next = head;
//(3) head = tmp2
ListNode* reverseList(ListNode* head)
{
    if(head == NULL)
    {
        return NULL;
    }
    //辅助指针两个
    ListNode* tmp1 = head;
    ListNode* tmp2 = head->m_pNext;
    while(tmp2 != NULL)
    {
        //三步翻转
        tmp1->m_pNext = tmp2->m_pNext;
        tmp2->m_pNext = head;
        head = tmp2;
        // 准备处理下一个
        tmp2 = tmp1->m_pNext;
    }
    return head;
}
//%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
// 其他解法: 递归处理
// 现在需要把A->B->C->D进行反转, 可以先假设B->C->D已经反转好,
// 已经成为了D->C->B,那么接下来要做的事情就是将D->C->B看成一个整体,
// 让这个整体的next指向A,所以问题转化了反转B->C->D。
// 那么,可以先假设C->D已经反转好,已经成为了D->C,那么接下来要做的事情
// 就是将D->C看成一个整体,让这个整体的next指向B,所以问题转化了反转C->D。
// 那么,可以先假设D(其实是D->NULL)已经反转好,已经成为了D(其实是head->D),
// 那么接下来要做的事情就是将D(其实是head->D)看成一个整体,
// 让这个整体的next指向C,所以问题转化了反转D。
// 上面这个过程就是递归的过程。
// 难点在于题目要求返回头指针,不过,原链表的最后一个结点是新链表的头结点。
// 理解了这一点,在递归到最后的时候处理一下还是可以的
// http://blog.youkuaiyun.com/feliciafay/article/details/6841115
ListNode* reverseList2(ListNode* head, ListNode*& new_head)
{
    if(head == NULL)
    {
        return NULL;
    }
    // 就一个结点,直接返回
    if (head->m_pNext == NULL)
    {
        // 原链表的最后一个结点是新链表的头结点
        new_head = head;
        return head;
    }
    // 递归: 处理右边的子链表,把当前结点加到最后
    ListNode* tail = reverseList2(head->m_pNext, new_head);
    tail->m_pNext = head;
    head->m_pNext = NULL;
    return head;
}

1.3 从尾到头输出链表

题目描述:

输入一个链表的头结点,从尾到头反过来输出每个结点的值。链表结点定义如下:

struct ListNode

{

      int       m_nKey;

      ListNode* m_pNext;

};


分析与C++代码:

// 微软100题:31从尾到头输出链表[数据结构]

// 题目:输入一个链表的头结点,从尾到头反过来输出每个结点的值。
// 链表结点定义如下:
// struct ListNode
// {
//      int       m_nKey;
//      ListNode* m_pNext;
// };
#include <iostream>
#include <string>
using namespace std;
//define node
struct ListNode
{
    int       m_nKey;
    ListNode* m_pNext;
};
void printList(ListNode* head, string name)
{
    cout << name;
    while (head != NULL)
    {
        cout << head->m_nKey << "-> ";
        head = head->m_pNext;
    }
    cout << "NULL." << endl;
}
void printListInReversedOrder(ListNode* head);
int main()
{
    //create a list
    ListNode* myList = NULL;
    ListNode* head = NULL;
    int listLength = 10;
    for (int ii = 0; ii < listLength; ++ii)
    {
        if (head == NULL)
        {
            head = new ListNode;
            head->m_nKey = ii;
            head->m_pNext = NULL;
            myList = head;
        }else
        {
            head->m_pNext = new ListNode;
            head = head->m_pNext;
            head->m_nKey = ii;
            head->m_pNext = NULL;
        }
    }
    //print a list
    printList(myList, "myList: ");
    // run
    printListInReversedOrder(myList);
    //delete a list
    while(myList != NULL)
    {
        ListNode* tmp = myList;
        myList = myList->m_pNext;
        delete tmp;
    }
}
//%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
// 推荐解法: 递归
// 很直接的方法是反转链表,然后顺序输出,这个方法上一题已经写过了
// 反转链表的一个坏处是他改变了链表,我们要输出没必要改动链表
// 递归这打印即可,不过链表很大的话,递归会耗尽栈空间,所以最好还是
// 使用堆里的内存自己维护一个栈。
// 下面的是实现还是递归的实现,练练手
void printListInReversedOrder(ListNode* head)
{
    if (head == NULL)
    {
        return;
    }
    if (head->m_pNext == NULL)
    {
        cout << head->m_nKey << "->";
        return;
    }
    // 先打印后面的,再打印当前结点
    printListInReversedOrder(head->m_pNext);
    cout << head->m_nKey << "->";
    return;
}


1.4 在O(1)时间删除链表结点[数据结构] 

题目描述:

给定链表的头指针和一个结点指针,在O(1)时间删除该结点。链表结点的定义如下:

struct ListNode

{

      int        m_nKey;

      ListNode*  m_pNext;

};

函数的声明如下:

void DeleteNode(ListNode*pListHead,ListNode*pToBeDeleted);

分析和C++代码:

// 微软100题:33在O(1)时间删除链表结点[数据结构]

// 题目:给定链表的头指针和一个结点指针,在O(1)时间删除该结点。
// 链表结点定义如下:
// struct ListNode
// {
//      int       m_nKey;
//      ListNode* m_pNext;
// };
// 函数的声明如下:
// void DeleteNode(ListNode* pListHead, ListNode* pToBeDeleted);
#include <iostream>
#include <string>
 
using namespace std;
 
//define node
struct ListNode
{
    int       m_nKey;
    ListNode* m_pNext;
};
 
void printList(ListNode* head, string name)
{
    cout << name;
    while (head != NULL)
    {
        cout << head->m_nKey << "-> ";
        head = head->m_pNext;
    }
    cout << "NULL." << endl;
}
 
void DeleteNode(ListNode* pListHead, ListNode* pToBeDeleted);
 
int main()
{
    //create a list
    ListNode* myList = NULL;
    ListNode* head = NULL;
    ListNode* pToBeDeleted = NULL;
    int listLength = 10;
    for (int ii = 0; ii < listLength; ++ii)
    {
        if (head == NULL)
        {
            head = new ListNode;
            head->m_nKey = ii;
            head->m_pNext = NULL;
            myList = head;
        }else
        {
            head->m_pNext = new ListNode;
            head = head->m_pNext;
            head->m_nKey = ii;
            head->m_pNext = NULL;
 
        }
        if(ii == 9)
        {
            pToBeDeleted = head;
        }
    }
 
    //print a list
    printList(myList, "myList: ");
    cout << "To be deleted: " << pToBeDeleted->m_nKey << endl;
 
    // run
    // 注意:这是使用者的责任来保证pToBeDeleted指向的
    DeleteNode(myList, pToBeDeleted);
    printList(myList, "After deeation: ");
 
    //delete a list
    while(myList != NULL)
    {
        ListNode* tmp = myList;
        myList = myList->m_pNext;
        delete tmp;
    }
}
//%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
// 推荐解法: 乾坤大挪移
// 这是一道广为流传的Google面试题,能有效考察我们的编程基本功,还能考察我们的反应速度,更重要的是,还能考察我们对时间复杂度的理解。
// 在链表中删除一个结点,最常规的做法是从链表的头结点开始,顺序查找要删除的结点,找到之后再删除。由于需要顺序查找,时间复杂度自然就是O(n)了。
// 我们之所以需要从头结点开始查找要删除的结点,是因为我们需要得到要删除的结点的前面一个结点。我们试着换一种思路。
// 我们可以从给定的结点得到它的下一个结点。这个时候我们实际能够删除的是它的下一个结点,由于我们已经得到实际删除的结点的前面一个结点,
// 因此完全是可以实现的。当然,在删除之前,我们需要需要把给定的结点的下一个结点的数据拷贝到给定的结点中。此时,时间复杂度为O(1)。
// 上面的思路还有一个问题:如果删除的结点位于链表的尾部,没有下一个结点,怎么办?
// 我们仍然从链表的头结点开始,顺便遍历得到给定结点的前序结点,并完成删除操作。这个时候时间复杂度是O(n)。
// 那题目要求我们需要在O(1)时间完成删除操作,我们的算法是不是不符合要求?实际上,假设链表总共有n个结点,我们的算法在n-1总情况下时间复杂度是O(1),
// 只有当给定的结点处于链表末尾的时候,时间复杂度为O(n)。那么平均时间复杂度[(n-1)*O(1)+O(n)]/n,仍然为O(1)。
void DeleteNode(ListNode* pListHead, ListNode* pToBeDeleted)
{
    // 注意:这是使用者的责任来保证pToBeDeleted指向的内容
    // 属于pListHead指向的链表
    if ((pListHead == NULL)||(pToBeDeleted == NULL))
    {
        return;
    }
 
    // 特殊情况,pToBeDeleted指向的是最后一个结点,
    // 即删除尾结点,只能遍历整个链表了。
    // 如果直接delete置NULL,那么倒数第二个结点的next就是未定义的状态
    // 所以必须遍历
    if(pToBeDeleted->m_pNext == NULL)
    {
        // 找到倒数第二个,即要被删除的结点的前一个
        while(pListHead->m_pNext->m_pNext != NULL)
        {
            pListHead = pListHead->m_pNext;
        }
        pListHead->m_pNext = NULL;
        delete pToBeDeleted;
        pToBeDeleted = NULL;
    }else
    {
        ListNode* tmp = pToBeDeleted->m_pNext;
        // copy下一个结点的内容到当前结点,然后删除下一个结点
        // 即, 取而代之
        pToBeDeleted->m_nKey = tmp->m_nKey;
        pToBeDeleted->m_pNext = tmp->m_pNext;
        delete tmp;
        tmp = NULL;
    }
    return;
}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

1.5 两链表的第一个公共结点[数据结构] 

题目描述:

两个单向链表,判断是否相交,如果相交找出它们的第一个公共结点。

链表的结点定义为:

struct ListNode

{

      int         m_nKey;

      ListNode*   m_pNext;

};

分析与C++代码:

// 微软100题:35两链表的第一个公共结点[数据结构]

// 题目:两个单向链表,判断是否相交,如果相交找出它们的第一个公共结点。
// 链表结点定义如下:
// struct ListNode
// {
//      int       m_nKey;
//      ListNode* m_pNext;
// };
#include <iostream>
#include <string>
#define LENGTH1 10
#define LENGTH2 20
using namespace std;
 
//define node
struct ListNode
{
    int       m_nKey;
    ListNode* m_pNext;
};
 
void printList(ListNode* head, string name)
{
    cout << name;
    while (head != NULL)
    {
        cout << head->m_nKey << "-> ";
        head = head->m_pNext;
    }
    cout << "NULL." << endl;
}
 
//create a list
void createList(ListNode*& myList, int listLength)
{
    ListNode* head = NULL;
    for (int ii = 0; ii < listLength; ++ii)
    {
        if (head == NULL)
        {
            head = new ListNode;
            head->m_nKey = ii;
            head->m_pNext = NULL;
            myList = head;
        }else
        {
            head->m_pNext = new ListNode;
            head = head->m_pNext;
            head->m_nKey = ii;
            head->m_pNext = NULL;
        }
    }
}
 
void deleteList(ListNode* head)
{
    while(head != NULL)
    {
        ListNode* tmp = head;
        head = head->m_pNext;
        delete tmp;
        tmp = NULL;
    }
}
 
void createLoop(ListNode* head, int position)
{
    ListNode* tail = NULL, *pos = NULL;
    while(head != NULL)
    {
        position--;
        if(position == 0)
        {
            pos = head;
        }
        if (head->m_pNext == NULL)
        {
            tail = head;
            break;
        }
        head = head->m_pNext;
    }
    tail->m_pNext = pos;
    cout << "pos: " << pos->m_nKey << endl;
}
 
void ifHasLoop(ListNode* head, ListNode*& enterOrtail, int& listLength);
bool isInList(ListNode* enter1, ListNode* enter2, ListNode*& firstCommon);
bool findFirstCommon(ListNode* head1, ListNode* head2, int ListLength1, int ListLength2, ListNode* &firstCommon);
bool ifIntersect(ListNode* head1, ListNode* head2, ListNode*& firstCommon );
 
int main()
{
    //create lists
    ListNode* myList1 = NULL;
    ListNode* myList2 = NULL;
    createList(myList1, LENGTH1);
    createList(myList2, LENGTH2);
 
    //print lists
    printList(myList1, "myList1: ");
    printList(myList2, "myList2: ");
 
    //make a loop
    createLoop(myList1, LENGTH1/2);
    createLoop(myList2, LENGTH2/2);
 
    //make an intersection
//    myList2 = myList1->m_pNext->m_pNext->m_pNext->m_pNext;
 
    // run
//    //测试 ifHasLoop函数的功能
//    int myListLength1 = 0;
//    int myListLength2 = 0;
//    ListNode* enterOrtail1 = NULL;
//    ListNode* enterOrtail2 = NULL;
//    ifHasLoop(myList1, enterOrtail1, myListLength1);
//    ifHasLoop(myList2, enterOrtail2, myListLength2);
//    cout << "MyList1: " << enterOrtail1->m_nKey << " length: " << myListLength1 << endl;
//    cout << "MyList2: " << enterOrtail2->m_nKey << " length: " << myListLength2 << endl;
    ListNode* firstCommon = NULL;
    if (ifIntersect(myList1, myList2, firstCommon))
    {
        cout << "intersection in " << firstCommon->m_nKey << endl;
    }else
    {
        cout << "no intersection found!" << endl;
    }
 
 
    //delete a list
    //防止重复删除结点的删除日后加上
//    deleteList(myList1);
//    deleteList(myList2);
 
}
//%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
// 推荐解法: 具体情况具体分析
// 简单说有三种情况
// 1) 两个链表都是无环的单链表;
// 2) 一个链表有环,另一个链表无环;
// 3) 两个链表都有环
 
// 情况1) 如果两个无环单向链表有公共的结点,两个链表将从某一结点开始,
// 它们的m_pNext都指向同一个结点。但由于是单向链表的结点,每个结点只有一个
// m_pNext,因此从第一个公共结点开始,之后它们所有结点都是重合的,不可能再
// 出现分叉。所以,两个有公共结点而部分重合的链表,拓扑形状看起来像一个Y,而不可能像X。
// 如何判断两个单向链表有没有公共结点?前面已经提到,如果两个链表有一个公共结点,
// 那么该公共结点之后的所有结点都是重合的。那么,它们的最后一个结点必然是重合的。
// 因此,我们判断两个链表是不是有重合的部分,只要分别遍历两个链表到最后一个结点。
// 如果两个尾结点是一样的,说明它们用重合;否则两个链表没有公共的结点。
 
// 在上面的思路中,顺序遍历两个链表到尾结点的时候,我们不能保证在两个链表上同时到达尾结点。
// 这是因为两个链表不一定长度一样。但如果假设一个链表比另一个长l个结点,我们先在长的链表上
// 遍历l个结点,之后再同步遍历,这个时候我们就能保证同时到达最后一个结点了。由于两个链表从
// 第一个公共结点考试到链表的尾结点,这一部分是重合的。因此,它们肯定也是同时到达第一公共结点的。
// 于是在遍历中,第一个相同的结点就是第一个公共的结点。
// 在这个思路中,我们先要分别遍历两个链表得到它们的长度,并求出两个长度之差。
// 在长的链表上先遍历若干次之后,再同步遍历两个链表,知道找到相同的结点,或者一直到链表结束。
// 此时,如果第一个链表的长度为m,第二个链表的长度为n,该方法的时间复杂度为O(m+n)。
 
// 上面只考虑了单链表无环的情况,单链表还可能有环。
// 如果有环且两个链表相交,则两个链表都有共同一个环,即环上的任意一个节点都存在于两个链表上。
// 因此,就可以判断一链表上俩指针相遇的那个节点,在不在另一条链表上。
 
// 综上: 整个算法应该包括下面四步
//1) 判断两个单链表是否有环: 有环 返回环入口指针; 无环 返回尾指针和链表长度;
//2) 一个链表有环, 一个无环: 不可能相交,直接返回不相交
//3) 两个链表都无环: 判断尾指针是否相同: 相同 先后指针找相交点; 不相同 直接返回不相交
//4) 两个链表都有环: 判断链表一的环入口是否在链表儿上。
// http://blog.youkuaiyun.com/wcyoot/article/details/6426436
// http://wuchong.me/blog/2014/03/25/interview-link-questions/

 
 
// 判断两个单链表是否有环: 有环 返回环入口指针; 无环 返回尾指针和链表长度;
// 快慢指针,按照 p2 每次两步,p1 每次一步的方式走,如果发现 p2  p1 重合,就确定了单向链表有环路了。
// 接下来,让p2回到链表的头部,重新走,每次步长不是走2了,而是走1,那么当 p1  p2 再次相遇的时候,就是环路的入口了。
// 为什么? 假定起点到环入口点的距离为 a,p1  p2 的相交点M与环入口点的距离为b,环路的周长为L,
//  p1  p2 第一次相遇的时候,假定 p1 走了 n 步。那么有:
// p1走的路径: a+b  n;
// p2走的路径: a+b+k*L = 2*n; p2  p1 多走了k圈环路,总路程是p1的2倍
// 根据上述公式可以得到 k*L=a+b=n 显然,如果从相遇点M开始,p1 再走 n 步的话,还可以再回到相遇点,
// 同时p2从头开始走的话,经过n步,也会达到相遇点M。
// 显然在这个步骤当中 p1  p2 只有前 a 步走的路径不同,所以当 p1  p2 再次重合的时候,必然是在链表的环路入口点上。
void ifHasLoop(ListNode* head, ListNode*& enterOrtail, int& listLength)
{
    // 快慢指针
    ListNode* fast = head;
    ListNode* slow = head;
 
    // 判断是否有环
    listLength = 1;
    while (fast != NULL && fast->m_pNext != NULL)
    {
        slow = slow->m_pNext;
        fast = fast->m_pNext;
        listLength++;
        if (fast->m_pNext !=NULL )
        {
            fast = fast->m_pNext;
            listLength++;
        }
        if (fast->m_pNext == NULL)
        {
            // 无环 fast现在指向最后一个结点
            enterOrtail = fast;
            return;
        }
 
        if (slow == fast)
        {
            // 有环
            listLength = -1;
            break;
        }
    }
 
    // 判断交点位置
    fast = head;
    while(fast != slow)
    {
        fast = fast->m_pNext;
        slow = slow->m_pNext;
    }
 
    enterOrtail = slow;
    return;
}
 
bool isInList(ListNode* enter1, ListNode* enter2, ListNode*& firstCommon)
{
    bool intersection = false;
    ListNode* tmp = enter1;
 
    // 遍历整个环一次,如果有enter2说明相交,不然不相交
    do
    {
        if(tmp == enter2)
        {
            firstCommon = enter2;
            intersection = true;
            break;
        }
        tmp = tmp->m_pNext;
    }while (tmp != enter1);
 
    // 如果两个环相交, 问题变为第一个公共结点是在
    // 环上,还是在环前面的链表上。
    if (intersection)
    {
        //这段懒得写了,就是判断环之前的两个无环链表是否相交,交在哪里
        return true;
    }
 
    return false;
}
 
bool findFirstCommon(ListNode* head1, ListNode* head2, int ListLength1, int ListLength2, ListNode* &firstCommon)
{
    // 长的链表先移动diff个,把两个链表的差异去掉
    int diff = 0;
    if (ListLength1 >= ListLength2)
    {
        diff = ListLength1 - ListLength2;
        while(diff > 0)
        {
            diff--;
            head1 = head1->m_pNext;
        }
    }else
    {
        diff = ListLength2 - ListLength1;
        while(diff > 0)
        {
            diff--;
            head2 = head2->m_pNext;
        }
    }
    while(head1 != NULL)
    {
        if (head1 == head2)
        {
            firstCommon = head1;
            return true;
        }
        head1 = head1->m_pNext;
        head2 = head2->m_pNext;
    }
    return NULL;
}
 
bool ifIntersect(ListNode* head1, ListNode* head2, ListNode*& firstCommon )
{
    // 判断两个链表是否有环
    int myListLength1 = 0;
    int myListLength2 = 0;
    ListNode* enterOrtail1 = NULL;
    ListNode* enterOrtail2 = NULL;
    ifHasLoop(head1, enterOrtail1, myListLength1);
    ifHasLoop(head2, enterOrtail2, myListLength2);
    cout << "MyList1: " << enterOrtail1->m_nKey << " length: " << myListLength1 << endl;
    cout << "MyList2: " << enterOrtail2->m_nKey << " length: " << myListLength2 << endl;
 
    // 如果两个链表中只有一个有环,两个链表不会相交
    if(myListLength1*myListLength2 <0)
    {
        return false;
    }
 
    // 如果两个链表都有环, 判断enterOrtail1 是否在链表myList2上;
    // 或者反过来,判断enterOrtail2 是否在链表myList1上;
    // 如果在,就用这个enterOrtail当做相交的第一个结点
    if (myListLength1== -1 && myListLength2 == -1)
    {
        cout << "both lists have loop!" << endl;
        return isInList(enterOrtail1, enterOrtail2, firstCommon);
    }
 
    // 如果两个链表都没环, 判断两个链表是否相交;
    // 用前后指针找到相交的第一个结点
    if (myListLength1 > 0 && myListLength2 > 0)
    {
        cout << "both lists do not have loop!" << endl;
        return findFirstCommon(head1, head2, myListLength1, myListLength2, firstCommon);
    }
 
    return false;
 
}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 


1.6 复杂链表的复制[算法] 

题目描述:

有一个复杂链表,其结点除了有一个m_pNext指针指向下一个结点外,还有一个m_pSibling指向链表中的任一结点或者NULL。其结点的C++定义如下:

            struct ComplexNode

{

   int m_nValue;

   ComplexNode* m_pNext;

   ComplexNode* m_pSibling;

};


分析与C++代码:

// 微软100题:31从尾到头输出链表[数据结构]

// 题目:有一个复杂链表,其结点除了有一个m_pNext指针指向下一个结点外,还有一个m_pSibling指向链表中的任一结点或者NULL。
// 链表结点定义如下:
//    struct ComplexNode
//    {
//       int m_nValue;
//       ComplexNode* m_pNext;
//       ComplexNode* m_pSibling;
//    };
 
 
// http://www.geeksforgeeks.org/a-linked-list-with-next-and-arbit-pointer/
#include <iostream>
#include <string>
 
using namespace std;
 
class Node
{
    public:
    int data;
    Node *arbit, *next;
    Node(int data)
    {
        this->data = data;
        arbit = next = NULL;
    }
};
 
Node* copyList(Node *orig);
Node* createList(int n);
void printList(Node *head);
void printArbitList(Node *head);
 
Node* createList(int n)
{
    Node *head = new Node(1);
    Node *ptr, *ptr1;
    ptr = ptr1 = head;
    for (int i = 2; i <= n; i++)
    {
        ptr1 = new Node(i);
        ptr->next = ptr1;
        ptr = ptr1;
    }
    return head;
}
void printList(Node *head)
{
    if (head == NULL)
    {
        return;
    }
    while (head != NULL)
    {
        cout<<head->data<<" ";
        head = head->next;
    }
}
 
void printArbitList(Node *head)
{
    if (head == NULL)
    {
        return;
    }
    while (head != NULL)
    {
        cout<<head->data<<"->"<<head->arbit->data<<" . ";
        head = head->next;
    }
}
int main()
{
    Node *head = createList(5);
    // create arbit linking of original list
    Node *ptr = head;
    ptr->arbit = ptr->next->next; // 1 -> 3
    ptr->next->arbit = ptr; // 2 -> 1
    ptr->next->next->arbit = ptr->next->next->next->next; // 3 -> 5
    ptr->next->next->next->arbit = ptr->next; // 4 -> 2
    ptr->next->next->next->next->arbit = ptr; // 5 -> 1
    cout<<"original: ";
    printList(head);
    cout<<endl<<"arbit linking: ";
    printArbitList(head);
    Node *copy = copyList(head);
    cout<<endl<<endl<<"copy: ";
    printList(copy);
    cout<<endl<<"arbit linking: ";
    printArbitList(copy);
    cout<<endl;
    return 0;
}
//%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
// 推荐解法:
// 1) Create the copy of node 1 and insert it between node 1 & node 2 in original Linked List,
//    create the copy of 2 and insert it between 2 & 3.. Continue in this fashion, add the copy of N afte the Nth node
// 2) Now copy the arbitrary link in this fashion
//    original->next->arbitrary = original->arbitrary->next;  /*TRAVERSE TWO NODES*/
//    This works because original->next is nothing but copy of original and Original->arbitrary->next is nothing but copy of arbitrary.
// 3) Now restore the original and copy linked lists in this fashion in a single loop.
//    original->next = original->next->next;
//    copy->next = copy->next->next;
// 4) Make sure that last element of original->next is NULL.
// Time Complexity: O(n)
// Auxiliary Space: O(1)
// 图解见 http://zhedahht.blog.163.com/blog/static/254111742010819104710337/
// http://www.geeksforgeeks.org/a-linked-list-with-next-and-arbit-pointer/
Node* copyList(Node *orig)
{
    // create the copy of node 1 and insert it b/w node 1 & node 2 in original list and
    // repeat this for all the nodes in the original list
    Node *prev, *curr, *next;
    prev = orig;
    next = orig->next;
    int i = 1;
    while (next != NULL)
    {
        curr = new Node(i++);
        curr->next = next;
        prev->next = curr;
        prev = next;
        next = next->next;
    }
    prev->next = new Node(i);
    // copy the arbit linking of original list
    Node *temp_orig = orig;
    while (temp_orig != NULL)
    {
        temp_orig->next->arbit = temp_orig->arbit->next;
        temp_orig = temp_orig->next->next;
    }
    // separate the original and copy list
    Node* copy = orig->next;
    Node *temp_copy = copy;
    temp_orig = orig;
    while (temp_copy->next != NULL)
    {
        temp_orig->next = temp_orig->next->next;
        temp_orig = temp_orig->next;
        temp_copy->next = temp_copy->next->next;
        temp_copy = temp_copy->next;
    }
    temp_orig->next = NULL;
    // return the head pointer to copied list
    return copy;
}
 
 


评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值