C和C++程序员面试秘笈:31---如何判断一个链表有回环?环的连接点在哪?环的长度为多少?带环链表的长度为多少?

一、什么是回环链表

  • 如下图所示,链表从1开始,到4的时候进入回环状态

C和C++程序员面试秘笈:31---如何判断一个链表有回环?环的连接点在哪?环的长度为多少?带环链表的长度为多少?_第1张图片

二、如何判断一个链表有回环?

  • 方法有:
    • 定义两个指针p1和p2,指向于头结点
    • 两个指针循环遍历链表,假设p1每次向前走1步,p2每次向前走2步
    • 如果p2遇到了NULL指针,或者p1与p2指针相等时循环结束
      • 如果是p2遇到了NULL指针结束,则链表没有环
      • 如果是p1与p2指针相等导致的循环结束,则链表存在环

实现代码如下

/**
 * @description: 判断链表是否有回环
 * @param:
       _head: 链表的头结点
       start: 保存回环点相遇点
 * @return: true/false
 * @author: Dongshao
 */
bool isLoop(Node* _head, Node**start)
{
    if(_head == NULL || _head->_pNext == NULL)
        return false;
    
    Node *temp1 = _head;
    Node *temp2 = _head;

    // 向后遍历
    do {
       
        // temp1每次走1步, temp2每次走2步
        temp1 = temp1->_pNext;
        temp2 = temp2->_pNext->_pNext;

        // 因为temp2每次向后走2步, 因此如果其下一步为NULL, 则下下步就会报错, 因此需要判断temp2->_pNext != NULL
    } while((temp2 != NULL) && (temp2->_pNext != NULL) && (temp1 != temp2));

    // 如果指针相等, 则有回环
    if(temp1 == temp2)
    {
        // 保存temp1和temp2相遇的那个节点指针, 并返回出去
        *start = temp1;
        return true;
    } 

    return false;
}

测试代码如下

#include 
#include 

using namespace std;

typedef struct node
{
    struct node* _pNext;
    int _data;
}Node;

/**
 * @description: 判断链表是否有回环
 * @param:
       _head: 链表的头结点
       start: 保存回环开始节点指针
 * @return: true/false
 * @author: Dongshao
 */
bool isLoop(Node* _head, Node**start)
{
    if(_head == NULL || _head->_pNext == NULL)
        return false;
    
    Node *temp1 = _head;
    Node *temp2 = _head;

    // 向后遍历
    do {
       
        // temp1每次走1步, temp2每次走2步
        temp1 = temp1->_pNext;
        temp2 = temp2->_pNext->_pNext;

        // 因为temp2每次向后走2步, 因此如果其下一步为NULL, 则下下步就会报错, 因此需要判断temp2->_pNext != NULL
    } while((temp2 != NULL) && (temp2->_pNext != NULL) && (temp1 != temp2));

    // 如果指针相等, 则有回环
    if(temp1 == temp2)
    {
        // *start保存回环开始节点指针并返回
        *start = temp1;
        return true;
    } 

    return false;
}

/**
 * @description: 创建链表
 * @param:
       num: 链表节点数量
 * @return: 创建新链表的头指针
 * @author: Dongshao
 */
Node* createList(int num)
{
    if(num < 0)
        return NULL;

    Node *_head = (Node*)malloc(sizeof(Node));
    _head->_pNext = NULL;

    Node *_tempHead = _head;
    Node *_newNode;
    for(int i = 0; i < num; ++i)
    {
        // 创建新节点
        _newNode = (Node*)malloc(sizeof(Node));
        _newNode->_data = (i+1);
        _newNode->_pNext = NULL;

        // 向后偏移
        _tempHead->_pNext = _newNode;
        _tempHead = _newNode;
        _newNode = NULL;
    }

    return _head;
}

/**
 * @description: 打印链表
 * @param:
       _head: 链表的头结点
 * @return: 无
 * @author: Dongshao
 */
void showList(Node *_head)
{
    if(_head == NULL || _head->_pNext == NULL)
        return;

    Node *_tempNode = _head;

    // 循环打印
    while(_tempNode->_pNext != NULL)
    {
        if(_tempNode->_pNext->_pNext == NULL)
            break;
        else
            printf("%d->", _tempNode->_pNext->_data);

        _tempNode = _tempNode->_pNext;
    }

    // 打印最后一个节点的
    printf("%d\n", _tempNode->_pNext->_data);
}

/**
 * @description: 销毁链表
 * @param:
       _head: 链表的头结点
 * @return: 无
 * @author: Dongshao
 */
void freeList(Node* _head)
{
     if(_head == NULL || _head->_pNext == NULL)
        return;
    
    Node *temp1 = _head->_pNext;
    Noode *temp2
    while(temp1 != NULL)
    {
        temp2 = temp1->_pNext;
        free(temp1);
        temp1 = temp2;
    }
}

int main()
{
    Node *headList = NULL;

    // 创建链表, 并打印链表
    headList = createList(7);
    showList(headList);

    // temp1指向值为4的那个节点, temp2指向尾节点
    Node *temp1, *temp2;
    temp1 = headList->_pNext->_pNext->_pNext->_pNext;
    temp2 = headList->_pNext;
    while(temp2->_pNext != NULL)
        temp2 = temp2->_pNext;

    // 此处我们改变链表, 让temp2指向于temp1造成回环
    temp2->_pNext = temp1;

    // 判断是否有回环, 并打印回环开始节点的指针(为4)
    Node *start;
    std::cout << boolalpha << "isLoop: " << isLoop(headList, &start) << std::endl;
    std::cout << "loopNode data: " << start->_data << std::endl;

    // 释放链表
    freeList(headList);

    return 0;
}
  • 我们在main中构造了这样一个回环链表 

C和C++程序员面试秘笈:31---如何判断一个链表有回环?环的连接点在哪?环的长度为多少?带环链表的长度为多少?_第2张图片 

  • 结果如下所示:
    • 判断结果有回环
    • 回环的起始节点为4

三、环的连接点在哪?

  • 从上面我们知道,p1和p2第一次的碰撞点就是环的连接点

四、如何判断环的长度?

  • 从上面我们知道,p1和p2第一次的碰撞点就是环的连接点
  • 因此当第一次碰撞之后,从碰撞点继续遍历,当再次碰撞时判断走过的操作数就是该环的长度

五、带环链表的长度为多少?

  • “头结点到回环节点的长度 + 环的长度”即为带环链表的长度

你可能感兴趣的:(C和C++程序员面试秘笈)