数据结构学习_01_单链表_相交链表_链表的环

1 链表的概念和定义

链表(Linked List)是一种常见的基础数据结构,是一种线性表,但是并不会按线性的顺序存储数据,而是在每一个节点里存到下一个节点的指针(Pointer)。
如下图:

在这里插入图片描述
由于不必须按顺序存储,链表在插入的时候可以达到 O(1)的复杂度,比另一种线性表 —— 顺序表快得多,但是查找一个节点或者访问特定编号的节点则需要 O(n) 的时间,而顺序表相应的时间复杂度分别是 O(log n) 和 O(1)。
使用链表结构可以克服数组链表需要预先知道数据大小的缺点,链表结构可以充分利用计算机内存空间,实现灵活的内存动态管理。但是链表失去了数组随机读取的优点,同时链表由于增加了结点的指针域,空间开销比较大。
在计算机科学中,链表作为一种基础的数据结构可以用来生成其它类型的数据结构。链表通常由一连串节点组成,每个节点包含任意的实例数据(data fields)和一或两个用来指向上一个/或下一个节点的位置的链接(links)。链表最明显的好处就是,常规数组排列关联项目的方式可能不同于这些数据项目在记忆体或磁盘上顺序,数据的访问往往要在不同的排列顺序中转换。而链表是一种自我指示数据类型,因为它包含指向另一个相同类型的数据的指针(链接)。
链表允许插入和移除表上任意位置上的节点,但是不允许随机存取。链表有很多种不同的类型:单向链表,双向链表以及循环链表。
链表通常可以衍生出循环链表,静态链表,双链表等。对于链表使用,需要注意头结点的使用。

2 相交链表

相交链表时Y形状的,没有X形状的,如下图
在这里插入图片描述
在这里插入图片描述
那么是什么原因呢,因为一个节点可以被多个节点的next指针同时指向,但是一个节点的next指针不可以同时指向多个多个节点,简单的说可以多对一,但是不可以一对多。
代码和分析思路如下:

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
 //明确相交链表只能是Y形状,绝对不能是X形状,因为一个节点可以被多个节点指向,但是一个节点不能指向多个节点。
typedef struct ListNode Node;
struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB) {
    
    
    int lenA = 0;
    int lenB = 0;
    Node* curA = headA;
    Node* curB = headB;
    //计算出链表A和B的长度
    while(curA)
    {
    
    
        lenA++;
        curA=curA->next;
    }
    while(curB)
    {
    
    
        lenB++;
        curB = curB->next;
    }
    //让长链表先走,先走的距离为两个链表的长度的差,达到二者在同一位置的目的,然后两个同时走并相互比较直到找到相交节点
    int gap = abs(lenA-lenB);

    Node *longList = headA,*shortList = headB;
    if(lenA<lenB)
    {
    
    
        longList = headB;
        shortList = headA;
    }
    while(gap--)
    {
    
    
        longList = longList->next;
    }
    while(longList && shortList)//此时longList和shortList是同样长链表的头节点,所以只放一个就可以了,当然&&上另一个也没撒问题
    {
    
    
        if(longList==shortList)
        {
    
    
            return longList;//return shortList->next也可以
        }
        else
        {
    
    
            longList  = longList->next;
            shortList = shortList->next;
        }
    }

    return NULL;
}


3 带环的链表

3.1 如何判断链表是否带环

如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。
如下图的链表的是带环的,如果你尝试去遍历它,你会发现会死循环
在这里插入图片描述
在这里插入图片描述
那么如何判断给的链表是否是带环的呢?
其实我们可以定义两个指针,一个慢,一个快,分别为slow,fast,slow一次走一步,fast一次走两步,分析过程和证明如下:
在这里插入图片描述
在这里插入图片描述
代码如下:

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
 typedef struct ListNode Node;
bool hasCycle(struct ListNode *head) {
    
    
    Node* slow = head;
    Node* fast = head;

    while(fast && fast->next){
    
    
        slow = slow->next;
        fast = fast->next->next;

        if(slow == fast){
    
    
            return true;
        }
    }
    return false;
}

//如何证明呢?
//画图说明slow一次走一步,fast一次走2步,fast和slow进入环后,二者
//之间的距离为X,则快慢指针之间的距离会一直缩小,指导二者相遇
//证明了只有链表有环才存在这种情况。

3.2 求链表入环的第一个节点

方法一见下图:
在这里插入图片描述
在环内的相遇节点断开,问题就转换成了蓝色链表和棕色链表的求相交节点的问题了。
在这里插入图片描述
代码实现如下:

//第一种方法
//快慢指针进入环后,二者终会相遇,再相遇节点处断开链表然后就是两个
//链表,此时问题就转换成了求两个链表的相交节点
typedef struct ListNode Node;
class Solution {
    
    
public:
    ListNode *detectCycle(ListNode *head) {
    
    
        //先找到在环中的相遇节点
        Node* slow = head;
        Node* fast = head;
        while(fast && fast->next){
    
    
            slow = slow->next;
            fast = fast->next->next;
            if(slow==fast){
    
    
                break;
            }
        }
        //如果此时程序进入到if中证明链表无环
        if(fast==NULL || fast->next==NULL){
    
    
            return NULL;
        }
        Node* head1 = head;
        //断开链表
        Node* head2 = slow->next;
        slow->next = NULL;
      
       return getIntersectionNode(head1,head2);


    }
    //求两个链表的相交节点的方法
    struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB) {
    
    
    int lenA = 0;
    int lenB = 0;
    Node* curA = headA;
    Node* curB = headB;
    //计算出链表A和B的长度
    while(curA)
    {
    
    
        lenA++;
        curA=curA->next;
    }
    while(curB)
    {
    
    
        lenB++;
        curB = curB->next;
    }
    //让长链表先走,先走的距离为两个链表的长度的差,达到二者在同一位置的目的,然后两个同时走并相互比较直到找到相交节点
    int gap = abs(lenA-lenB);

    Node *longList = headA,*shortList = headB;
    if(lenA<lenB)
    {
    
    
        longList = headB;
        shortList = headA;
    }
    while(gap--)
    {
    
    
        longList = longList->next;
    }
    while(longList && shortList)//此时longList和shortList是同样长链表的头节点,所以只放一个就可以了,当然&&上另一个也没撒问题
    {
    
    
            if(longList==shortList)
            {
    
    
                return longList;//return shortList->next也可以
            }
            else
            {
    
    
                longList  = longList->next;
                shortList = shortList->next;
            }
    }

    return NULL;
 }
};
 


 //第一种方法的变形
 //快慢指针进入环后,二者终会相遇,再相遇节点处逻辑上断开链表其实没有断开,slow->next作为新的头,在计算长度的时候,lenA= lenB
 //要初始化为1
 //链表,此时问题就转换成了求两个链表的相交节点
typedef struct ListNode Node;
struct ListNode *detectCycle(struct ListNode *head) {
    
    
    if(head == NULL){
    
    
        return NULL;
    }
    Node* slow = head;
    Node* fast = head;
    while(fast && fast->next){
    
    
        slow = slow->next;
        fast = fast->next->next;
        if(slow == fast){
    
    
            break;
        }
    }
    //此时找到二者相遇的节点slow == fast
    if(fast==NULL || fast->next == NULL){
    
    
        return NULL;
    }

    //定义逻辑上两个链表的头节点
    Node* curA = head;

    Node* newHead = slow->next;
    Node* curB = newHead;
    
    int lenA = 1;
    int lenB = 1;

    while(curA != slow){
    
    
        lenA++;
        curA= curA->next;
    }
    while(curB != slow){
    
    
        lenB++;
        curB = curB->next;
    }

    int gap = abs(lenA - lenB);

    Node* longerList = head;
    Node* shortList = newHead;

    if(lenA < lenB){
    
    
        longerList = newHead;
        shortList = head;
    }

    while(gap--){
    
    
        longerList = longerList->next;
    }
   //此时long和short链表在同一起跑线上
    while(longerList){
    
    //while(shortList)
         if(longerList == shortList){
    
    
             return longerList;
         }else{
    
    
             longerList = longerList->next;
             shortList = shortList->next;
         }
    }

    return NULL;

}

方法2:比较难理解,需要推理和证明
过程如下图:
在这里插入图片描述
代码实现:

//方法2:需要推导和证明
//快慢指针加高中物理的相遇问题
typedef struct ListNode Node;
class Solution {
    
    
public:
    ListNode *detectCycle(ListNode *head) {
    
    
        //先找到在环中的相遇节点
        Node* slow = head;
        Node* fast = head;
        while(fast && fast->next){
    
    
            slow = slow->next;
            fast = fast->next->next;
            if(slow==fast){
    
    
                break;
            }
        }
        //如果此时程序进入到if中证明链表无环
        if(fast==NULL || fast->next==NULL){
    
    
            return NULL;
        }
        
        Node* meet  = slow;

        while(head!=meet){
    
    
            head=head->next;
            meet = meet->next;
        }
       return meet;


    }

};

到这里就结束了,你明白了嘛。

猜你喜欢

转载自blog.csdn.net/CZHLNN/article/details/111768567