链表(二)——常见的进阶操作

引言

这篇文章主要是讲单链表的一些常用的进阶操作,主要方法就是双指针,本文篇幅较长,在这里给出问题的索引,可以根据问题索引迅速定位自己需要看的内容(引申问题之后会找机会补充)

  1. 有序链表的合并——>划分链表

  2. 单链表反转——>链表相加求和

  3. 寻找链表的倒数第k个节点——>旋转链表

  4. 寻找链表的中间节点——>回文串

  5. 判断链表中是否有环——>求环的长度

双指针

普通双指针(1、2、3)
有序链表的合并
过程思路

有序链表的合并做法跟归并排序的的时候操作是一个思路,开辟两个指针,分别对应一个链表,指向小元素的指针移动。

在这里插入图片描述

代码实现
/*
有序链表的合并,两条链表是上图的两条链表
*/
#include <stdio.h>
#include <malloc.h>
#include <stdbool.h>
#include <windows.h>
typedef struct NODE
{
    int data;
    struct NODE *pNext;
} node; //定义链表节点的类型
node *mergeinto_node(node *pHead1, node *pHead2);
node *create_node(int size);
void print_node(node *);
int main()
{
    node *pHead1;
    node* pHead2;
    pHead1 = create_node(4);
    pHead2 = create_node(3);
    pHead2 = mergeinto_node(pHead1,pHead2);
    print_node(pHead2);

}
node *create_node(int size)
{
    int i, val;
    node *phead = (node *)malloc(sizeof(node)); //创建头节点的“分身”,利用“分身”创建一个链表之后返回分身地址,pHead就构成了一个链表的头节点
    phead->pNext = NULL;
    node *p = phead;
    for (i = 0; i < size; i++) //根据输入的length创建新节点
    {
        node *pNew = (node *)malloc(sizeof(node));
        scanf("%d", &val);
        pNew->data = val;
        p->pNext = pNew; //先让新节点指针指向头节点后继节点
        p = pNew;        //再让头节点指向新节点
    }
    p->pNext = NULL; //尾节点要置NULL否则野指针循环无法结束
    return phead;
}
void print_node(node *pHead)
{
    printf("It's the time to print node.\n");
    node *q = pHead->pNext;
    while (q != NULL)
    {
        printf("%d\t", q->data);
        q = q->pNext;
    }
    q = NULL;
}
node* mergeinto_node(node* pHead1,node* pHead2)
{
    node *p = pHead1->pNext;            //指向第一个链表的第一个节点
    node *q = pHead2->pNext;            //指向第二个链表的第一个节点
    node *pNewhead=(node*)malloc(sizeof(node)); //用于储存合并的链表.
    node *t=pNewhead;               //合并链表的分身

    while(p!=NULL&&q!=NULL)        //当两个链表都没有遍历完成的时候循环插入链表元素
    {
        if(p->data <= q->data)      //第一个链表元素更小插入第一个链表元素到合并链表中
        {
            t->pNext = p;
            t = p;
            p = p->pNext;
        }
        if (p->data > q->data)      //第二个链表元素更小,插入第二个链表元素到合并链表中
        {
            t->pNext = q;
            t = q;
            q = q->pNext;
        }
        if(p==NULL)                 //如果第一个链表遍历完成了,但是第二个链表还没有完成(解决链表不等长的问题)
        {                           //循环把第二个链表的剩下的元素全部加入合并链表中
            for (; q != NULL;)
            {
                t->pNext = q;
                t = q;
                q = q->pNext;
            }
        }
        if (q==NULL)
        {
            for (; p != NULL;)
            {
                t->pNext = p;
                t = p;
                p = p->pNext;
            }
        }
    }
    t->pNext = NULL;                //合并链表尾节点置NULL
    return pNewhead;                //返回合并链表头节点
}

在这里插入图片描述

链表的反转(三指针)
过程思路

需要将链表反转,对于第一个和第二个节点,我们可以直接得到的信息是把第二个节点->pNext = 第一个节点的地址,我们改变了第二个节点的pNext,让原本储存第三个节点的地址,变成了第一个节点的地址,此时问题出现了——指针丢失,我们丢失了第三个节点的地址,后续所有节点也找不到了。所以我们要用一个临时节点储存下一个节点的pNext域。这样问题得到解决,而同时我们也需要一个指针储存前一个当前节点的前一个指针域,否则下一次运行的时候就找不到前一个结点了,也就无法把当前节点的指针域指向前一个结点。

代码实现
#include <stdio.h>
#include <malloc.h>
#include <stdbool.h>
#include <windows.h>
typedef struct NODE
{
    int data;
    struct NODE *pNext;
} node; //定义链表节点的类型
node* reverse_node(node *pHead);
node *create_node(int size);
void print_node(node *);
int main()
{
    node *pHead;
    pHead = create_node(7);
    pHead=reverse_node(pHead);
    print_node(pHead);
}
node *create_node(int size)
{
    int i, val;
    node *phead = (node *)malloc(sizeof(node)); //创建头节点的“分身”,利用“分身”创建一个链表之后返回分身地址,pHead就构成了一个链表的头节点
    phead->pNext = NULL;
    node *p = phead;
    for (i = 0; i < size; i++) //根据输入的length创建新节点
    {
        node *pNew = (node *)malloc(sizeof(node));
        scanf("%d", &val);
        pNew->data = val;
        p->pNext = pNew; //先让新节点指针指向头节点后继节点
        p = pNew;        //再让头节点指向新节点
    }
    p->pNext = NULL; //尾节点要置NULL否则野指针循环无法结束
    return phead;
}
node* reverse_node(node* pHead)
{
    node *pre = NULL;           //首节点没有前一个结点
    node *cur = pHead->pNext;   //当前节点为首节点
    node *next;                 //储存后继节点
    while (cur != NULL)
    {
        next = cur->pNext;      //先储存好会指针丢失的后继节点
        cur->pNext = pre;       //改变当前节点和前驱结点的指向关系
        pre = cur;              //更新前驱结点
        cur = next;             //更新当前节点
    }
    return pre;                 //返回前驱结点,这里容易错因为cur已经指向NULL了,说明前驱结点才是有意义的
}
void print_node(node *pHead)
{
    printf("It's the time to print node.\n");
    node *q = pHead;
    while (q != NULL)
    {
        printf("%d\t", q->data);
        q = q->pNext;
    }
    q = NULL;
}

在这里插入图片描述

寻找倒数第n个节点(只用一次遍历)
过程思路

中间节点和倒数第n个节点,都不同于我们直接顺序遍历那么方便,如果是顺序遍历要找第k个节点循环k次就好了,既然顺序遍历这么简单,那我们就思考如何把这个问题转化成顺序遍历的第m个节点。譬如倒数第n个节点,那就是需要顺序查找len-k个节点,如果不是限定了一次遍历,那我们就可以暴力解决,求出len,再循环。

那如果要求只循环一次,怎么控制置顺序查找len-k个节点呢。这时候双指针就出现了。我们需要控制两个指针的距离是m那么,当第一个先走的指针走到表尾的时候,第二个指针走过的路程就是len-m,就符合我们的要求
在这里插入图片描述

代码实现
#include <stdio.h>
#include <malloc.h>
#include <stdbool.h>
#include <windows.h>
typedef struct NODE
{
    int data;
    struct NODE *pNext;
} node; //定义链表节点的类型
int find_node(node *pHead,int position);
node *create_node(int size);
int main()
{
    node *pHead;
    pHead = create_node(7);
    printf("the val of position is %d", find_node(pHead, 3));
}
node *create_node(int size)	//创建一个链表
{
    int i, val;
    node *phead = (node *)malloc(sizeof(node)); //创建头节点的“分身”,利用“分身”创建一个链表之后返回分身地址,pHead就构成了一个链表的头节点
    phead->pNext = NULL;
    node *p = phead;
    for (i = 0; i < size; i++) //根据输入的length创建新节点
    {
        node *pNew = (node *)malloc(sizeof(node));
        scanf("%d", &val);
        pNew->data = val;
        p->pNext = pNew; //先让新节点指针指向头节点后继节点
        p = pNew;        //再让头节点指向新节点
    }
    p->pNext = NULL; //尾节点要置NULL否则野指针循环无法结束
    return phead;
}
int find_node(node *pHead,int position)
{
    node *first = pHead;
    node *second = pHead;
    int cnt = 0;
    while(first->pNext!=NULL)
    {
        cnt++;					//cnt控制着first与second距离,值一旦到达positon第二个指针出发
        first = first->pNext;
        if(cnt>=position)
        {
            second = second->pNext;
        }
    }
    return second->data;
}

在这里插入图片描述

快慢双指针(4、5)
寻找链表的中间节点
过程思路

从找倒数第k个节点同样来思考,我可以通过一个指针先走一半节点长,再出发一个指针,当第一个节点到末尾的时候,第二个节点所在的地方就是中间节点。可是面对了一个问题,那就是并不知道链表长度,如果直接用上面的程序无法控制position变量。

此时注意到,指针一半一半的关系,想到快慢指针,第一个指针——》快指针,每次移动两个节点。第二个指针——》慢指针,每次移动一个节点。当快指针到表尾的时候,慢指针自然到中间节点。

代码实现
#include <stdio.h>
#include <malloc.h>
#include <stdbool.h>
#include <windows.h>
typedef struct NODE
{
    int data;
    struct NODE *pNext;
} node; //定义链表节点的类型
int find_midnode(node *pHead);
node *create_node(int size);
int main()
{
    node *pHead;
    pHead = create_node(7);
    find_midnode(pHead);
}
node *create_node(int size)
{
    int i, val;
    node *phead = (node *)malloc(sizeof(node)); //创建头节点的“分身”,利用“分身”创建一个链表之后返回分身地址,pHead就构成了一个链表的头节点
    phead->pNext = NULL;
    node *p = phead;
    for (i = 0; i < size; i++) //根据输入的length创建新节点
    {
        node *pNew = (node *)malloc(sizeof(node));
        scanf("%d", &val);
        pNew->data = val;
        p->pNext = pNew; //先让新节点指针指向头节点后继节点
        p = pNew;        //再让头节点指向新节点
    }
    p->pNext = NULL; //尾节点要置NULL否则野指针循环无法结束
    return phead;
}
int find_midnode(node *pHead)
{
    node *fast = pHead;         //如果是pHead->pNext的话,当它长度为偶数,就会指向中间两个的后面那个.
    node *slow = pHead;
    if(pHead->pNext==NULL)      //链表为空
    {
        return -1;
    }
    while (fast != NULL && fast->pNext !=NULL)  //只要有一个不成立就退出循环所以应该是&&不是||   													否则slow指向尾节点的话退不出来
    {

        fast = fast->pNext->pNext;
        slow = slow->pNext;
    }
    printf("the mid of nodelist is %d ",slow->data);
    if (fast->pNext == NULL)            //说明到达链表长度为偶数,慢指针后一个节点也需要输出
    {
        printf("%d", slow->pNext->data);
    }
    return 0;
}

在这里插入图片描述

判断是否有环
过程思路

同样是快慢指针,如果有环,那么快指针必然会与慢指针相遇,因为两个指针最后都会进入环,而快指针每次又比慢指针多走一步,快指针总会追上慢指针。但是如果快指针先指向NULL,说明那就没有环。

#include <stdio.h>
#include <malloc.h>
#include <stdbool.h>
#include <windows.h>
typedef struct NODE
{
    int data;
    struct NODE *pNext;
} node; //定义链表节点的类型
void guess_circle(node *pHead);
node *create_node(int size);
int main()
{
    node *pHead;
    pHead = create_node(7);
    guess_circle(pHead);
}
node *create_node(int size)
{
    int i, val;
    node *phead = (node *)malloc(sizeof(node)); //创建头节点的“分身”,利用“分身”创建一个链表之后返回分身地址,pHead就构成了一个链表的头节点
    phead->pNext = NULL;
    node *p = phead;
    for (i = 0; i < size; i++) //根据输入的length创建新节点
    {
        node *pNew = (node *)malloc(sizeof(node));
        scanf("%d", &val);
        pNew->data = val;
        p->pNext = pNew; //先让新节点指针指向头节点后继节点
        p = pNew;        //再让头节点指向新节点
    }
    p->pNext = NULL; //尾节点要置NULL否则野指针循环无法结束
    return phead;
}
void guess_circle(node* pHead)
{
    node *fast=pHead;
    node *slow=pHead;
    while(fast!=NULL)
    {
        fast = fast->pNext->pNext;
        slow = slow->pNext;
        if(fast==slow)
        {
            printf("there exits a circle");
        }
    }
    printf("there don't have circle");
}

这个是上述代码执行结果
在这里插入图片描述将上述代码建立单链表改为循环链表在这里插入图片描述
结果如下判断正确在这里插入图片描述

  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值