单链表的成环、相交、逆置和从尾到头打印

如果链表中含有圆环,那么对链表的遍历会有什么影响呢?

  • 毋庸置疑,影响是很大的。

这里写图片描述

单判断单链表的成环的方法有两种:

  • 首先介绍一种常用方法:试想有一个圆形跑道,甲乙两人同时在跑道的起点,如果甲的速度是乙的两倍,那么当乙跑完半圈时,甲跑完一圈回到起点,乙跑完一圈回到起点时,甲跑完两圈也回到起点,这样的话,甲乙重新在起点相遇。于是乎我们可以定义两个指针p1、p2均指向链表的第一个节点,指针p1后移一次遍历一个节点,指针p2后移一次遍历两个节点,现将两指针同时前进,如果两指针相遇就说明该单链表成环。

  • 另外一种方法就是在时间复杂度为O(n)的前提下判断链表是否有环。首先我们为每一个链表设置一个标识flag = 0,用指针p来遍历该单链表,每遍历一个节点将标识flag重新置为1,当遍历到标识为1的节点时表示该单链表成环且该节点为环的起始节点,
    这里写图片描述

计算环的长度:

  • 当两指针相遇时,保持指针p1不变,移动指针p2一次遍历一个节点,用计数器记录移动次数,当两指针再次相遇时即可得出环的长度。

寻找环的起点位置:

  • 入环起始点在头结点K跳的位置,环的长度为N也就是N跳;K可以写成:K=aN+b其中a=K/N;b=K%N;p1和p2相交时是处于环中从起始点开始第N-b的位置,也就是还需要b跳到入环起始结点;现在p3和p1同时起步,一起走,p1第一次和p3相遇的地方即为入环起始点;
    这里写图片描述

代码实现:

//节点形式:
/*
typedef struct Node
{
    int data;
    struct Node *next;
}Node;
*/
//两指针法:
bool Is_RingLink1(Node *phead)
{
    Node *p1 = phead;
    Node *p2 = phead;
    while(p1 != NULL && p2 != NULL)
    {
        p1 = p1->next;
        p2 = p2->next->next; 
        if(p1 == p2)
        {
            return true;
        }
    }
    return false;
}

//标识法
//节点形式:
/*
typedef struct Node
{
    int data;
    struct Node *next;
    int flag = 0;
}Node;
*/
bool IS_RingLink2(Node *phead)
{
    Node *p = phead;
    while(p != NULL && p->flag != 1)
    {
        p->flag = 1;
        p = p->next;
        if(p != NULL && p->flag == 1)
        {
            return true;
        }
    }
    return false;
}

//计算成环链表环的长度
int LengthLink(Node *phead)
{
    int count = 1;
    Node *p1 = phead;
    Node *p2 = phead;
    wile(p1 != NULL && p2 != NULL & p1 != p2)
    {
        p1 = p1->next;
        p2 = p2->next;
        if(p2 != NULL)
        {
            p2 = p2->next;
        }
    }
    p2 = p2->next;
    while(p1 != p2)
    {
        count++;
        p2 = p2->next;
    } 
    return count + 1;
}

//寻找成环链表的环的起始位置
//用标识法的直接返回第一个标识flag = 1的几点即为起始位置。由上可知,不用标识法的环起始位置的找寻代码如下:
Node *FindFistNode(Node *phead)
{
    Node *p1 = phead;
    Node *p2 = phead;
    Node *p3 = phead;
    while(p1 != p2)
    {
        p1 = p1->next;
        p2 = p2->next->next;
    }
    int count = 1;
    while(p1 != p3)
    {
        p1 = p1->next;
        p3 = p3->next;
        count++;
    }
    printf("为第%d个节点\n", count);
    return p3;
}

单链表相交
这里写图片描述

有关于链表相交的问题我们可以通过以下问题来展现:

  • 判断两链表是否相交

  • 两单链表如果相交于上图两种形式,则可知第一个节点或最后一个节点一定相同

  • 代码实现

//节点形式:
/*
typedef struct Node
{
    int data;
    struct Node *next;
}Node;
*/

bool IntersectLink(Node *phead1, Node *phead2)
{
    Node *p1 = phead1;
    Node *p2 = phead2;
    if(p1 == p2)
    {
        return true;
    }
    while(p1->next != NULL)
    {
        p1 = p1->next;
    }
     while(p2->next != NULL)
    {
        p2 = p2->next;
    }
    if(p1 == p2)
    {
        return true;
    }
    return false;
}
  • 如果相交输出第一个相交的节点

  • 首先遍历两条链表记录其长度,找出较长的一条链表的长度,然后两链表长度相减得到一个n值,使用两个指针指向两链表的第一个节点,较长链表的指针先遍历n个节点,然后两指针一起遍历节点,遇到的第一个相等的节点即为第一个公共点。

Node* FIndFirstNode(Node *phead1, Node *phead2)
{
    if(!IntersectLink(phead1, phead2))
    {
        return NULL;
    } 
    Node *p1 = phead1;
    Node *p2 = phead2;
    if(p1 == p2)
    {
        while(p1->next != p2->next)
        {
           p1 = p1->next;
           p2 = p2->next; 
        }
        return p1;
    }
    Node *s1 = phead1;
    Node *s2 = phead2;
    int count1 = 1;
    int count2 = 1;
    while(p1 != NULL)
    {
        count1++;
        p1 = p1->next;
    }
    while(p2 != NULL)
    {
        count2++;
        p2 = p2->next;
    }
    if(count1 > count2)
    {
        int n = count1 - count2;
        while(n > 0)
        {
            s1 = s1->next;
            n--;
        }
    }
    if(count2 > count1)
    {
        int n = count2 - count1;
        while(n > 0)
        {
            s2 = s2->next;
            n--;
        }
    }
    while(s1 != s2)
    {
        s1 = s1->next;
        s2 = s2->next;
    } 
    return s1; 
}

链表的逆置:
问题描述:输入一个链表指针,将该链表以相反的形式表示,并输出逆置后链表的指针。
这里写图片描述

问题分析:想要逆置一个链表需要重新调整指针的方向。由图可知,为了避免在 i 节点处断开,我们需要在调整节点 i 的next之前需要先将 j 节点保存下来。也就是说,我们在调整一个节点的next时除了要知道该节点本身外还需要知道该节点的前一个节点。因为逆置需要将该节点指向他的前一个节点,同时我们还要事先将该节点的下一个节点保存起来,防止链表断开。于是我们不难想出定义三个指针来分别指向该节点、该节点的前一个节点、该节点的下一个节点。

代码实现如下:

//节点形式
/*
typedef struct Node
{
    int data;
    struct Node *next;
}Node;
*/

Node *ReverseList(Node *phead)
{
    Node *reversehead = NULL;
    Node *pnode = phead;
    Node *prenode = NULL;
    while(pnode != NULL)
    {
        Node *pnext = pnode->next;
        if(pnext == NULL)
        {
            reversehead = pnode;
        }
        pnode->next = prenode;
        prenode = pnode;
        pnode = pnext;
    }
    return reversehead;
}

链表的从尾到头打印:
对于单链表我们都只能从第一个节点往后遍历,现在要求我们从最后一个节点开始将链表的各个节点的数据打印出来。这时我们可以用一个递归来实现。每次先后遍历时如果该节点的next为NULL则打印数据,当递归到最后一个节点时往前回退的时候就将各个节点的数据以相反的顺序打印了出来。

代码实现:

void ReversePrint(Node *phead)
{
    if(phead != NULL)
    {   
        Node *p = phead;
        if(p->next != NULL)
        {
            ReversePrint(p->next);
        }
        printf("%d ", p->data);
    }
}

另外,我们又想到栈的特性是先进后出,我们可以先将连表各个节点的数据依次入栈,入完之后再出栈,出来的数据就是和链表原来的数据顺序相反,也就实现了从尾到头的打印。
代码实现:

void ReversePrint(Node *phead)
{
    if(phead == NULL)
    {
        return;
    }
    int n = 1;
    Node *p = phead;
    while(p->next != NULL)
    {
        n++;
        p = p->next;
    } 
    int *stack = (int *)malloc(sizeof(int) * n);
    int top = 0;
    Node *q = phead;
    while(q->next != NULL)
    {
        stack[top++] = q->data;
        q = q->next;
    }
    while(top >= 0)
    {
       printf("%d  ", stack[top--]);
    }
    printf("\n");
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值