链表操作(链表面试题)

这次需要实现的操作有
list.h

#ifndef __LIST_H__
#define __LIST_H__
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
typedef int DataType;
typedef struct Node
{
    DataType data;
    struct Node* next;
}Node, *pNode, *pList;
void InitLinkList(pList* pplist);    //二级指针  初始化
void PushFront(pList* pplist, DataType d);   //头部插入
void Display(pList pl);
int GetLength(pList plist);    //求一个无环链表长度
void PushBack(pList* pplist, DataType d); //尾部插入
pNode Find(pList pl, DataType d);   //查询
void Show(pList pl);    //逆序打印
void DelNotTail(pNode pos);  //删除一个无头节点
void InsertFrontNode(pNode pos, DataType d);  //  无头节点前插入
void JosephCycle(pList pl, int k);  //   约瑟夫环
void ReverseList(pList* pplist);//   逆序
void BubbleSortList(pList plist);  //   排序
pList Merge(pList *ppl1, pList *ppl2); //对两个有序链表进行合并  合并为一个有序链表
Node * FindMidNode(pList plist);//   查找一个中间数  要求只遍历一遍
void DelKNode(pList plist, int k);  //删除倒数第K个元素
pNode CheckCircle(pList plist);  //判断是否有环 并返回相遇点
int GetCircleLength(pNode meet);   //求环的长度
pNode GetCircleEntry(pNode meet,pList plist);   //求带环链表的链入点
void CheckCross(pList plist1, pList plist2);    //分情况讨论    求两条链的链入点
int Check(Node* meet, pList plist);   //判断meet是否在plist上
void print(pNode meet);    //打印环上的所有数据
#endif

只实现部分稍微复杂的函数。

void DelNotTail(pNode pos);  //删除一个无头节点

这里写图片描述
假设数据存储方式是这样的,如果要删除第2个节点,则可以将第二个节点和第三个节点的数值就行交换,然后让第二个字节指向第四个字节,并销毁第三个字节就可以了。
这里写图片描述
实现:

void DelNotTail(pNode pos)  
{
    Node *cur = pos;
    Node *next = cur->next;
    //值交换
    int tmp = cur->data;
    cur->data = next->data;
    next->data = tmp;
    //指向下一节点,销毁
    cur->next = next->next;
    free(next);
}

void InsertFrontNode(pNode pos, DataType d); // 无头节点前插入
这里写图片描述
同样是使用值交换,然后进行链接;
这里写图片描述
实现:

void InsertFrontNode(pNode pos, DataType d)
{
    //值创建
    Node*cur = NULL;
    cur = (Node*)malloc(sizeof(Node));  
    if (cur == NULL)
    {
        perror("malloc");
        exit(EXIT_FAILURE);
    }
    cur->data = d;
    cur->next = NULL;
    //值交换
    int tmp = pos->data;
    pos->data = cur->data;
    cur->data = tmp;
    //链接
    cur->next = pos->next;
    pos->next = cur;
}

void ReverseList(pList* pplist);// 逆序
这里写图片描述
从前向后依次遍历,最终E的next指向D,结束逆序;
实现:

void ReverseList(pList* pplist)  //     逆序
{
    Node *head = *pplist;    //维护头部
    Node *cur = *pplist;    
    Node *tmp = *pplist;
    tmp = tmp->next;
    head->next = NULL;   //先进行维护
    while (tmp->next)  //交换次序
    {
        cur = tmp;
        tmp = tmp->next;
        cur->next = head;
        head = cur;
    }
    *pplist = head;
}

void JosephCycle(pList pl, int k) ; //约瑟夫环
一个环链表,从初始位置开始报数,每数到3,链接到下一个节点,并销毁数到3的节点。因为第一个人已经报数,所以计数器应从1开始;
实现:

void JosephCycle(pList pl, int k)   //约瑟夫环
{
    int count = 1;
    while (pl != NULL)
    {
        pl = pl->next;
        count++;
        if (count == k)
        {
            printf("%d ", pl->data);
            DelNotTail(pl);  //删除当前节点
            count = 1;
            if (pl->data == pl->next->data)  //当剩下最后一人的时候跳出
            {
                return ;
            }
        }
    }
}

void BubbleSortList(pList plist); // 排序
排序可以当作冒泡排序,设定一个tail节点,指向NULL;每次遍历后tail向前走一次,当tail走到第一个节点的next时,停止比较;
实现:

void BubbleSortList(pList plist)    //排序
{
    assert(plist);
    Node *head = plist;
    Node *cur = plist;
    Node *til = NULL;
    while (til!=head->next)         
    {
        while (cur->next != til)
        {
            if (cur->data > cur->next->data)     //交换数据
            {
                int tmp = cur->data;
                cur->data = cur->next->data;
                cur->next->data = tmp;
            }
            cur = cur->next;     //后移
        }
        til = cur;        //til向前挪一位  每轮减少一次筛选
        cur = head;         //cur重新指向头部 重新开始比较
    }
}

pList Merge(pList *ppl1, pList *ppl2); //对两个有序链表进行合并 合并为一个有序链表
创建一个head节点,让head节点先指向两个链表中第一个元素的最小值;
这时候需要一个新的new_head节点提前记录head最开始的位置;
随后每次进行一次比较,让head->next指向该最小值,该链表向后移一位,head也向后移一位直到其中一个为空,因为都是有序链表,让head->next指向另一个非空链表;
实现:

pList Merge(pList *ppl1,pList *ppl2)  //对两个有序链表进行合并  合并为一个有序链表
{
    assert(*ppl1);
    assert(*ppl2);
    Node *p1 = *ppl1;
    Node *p2 = *ppl2;
    Node *head = NULL;
    Node *new_head =NULL;
    if (ppl1 == NULL)    //ppl1  如果l1为空  直接将head链接上l1
    {
        return p2;
    }
    else if (ppl2 == NULL)
    {
        return p1;
    }
    //先对head进行处理,让head指向第一个最小元素
    else if (p1->data > p2->data)
    {
        head = p2;
        p2 = p2->next;
    }
    else
    {
        head = p1;
        p1 = p1->next;
    }
    new_head = head;     //new_head用来保存一个起始位置
    while ((p1 != NULL) && (p2 != NULL))    //跳出循环后必有一个为空 一个不为空
    {
        //比较之后链接到head上
        if (p1->data > p2->data)    //如果p1>p2  将p2插入到p1的后面
        {
            head->next = p2;
            p2 = p2->next;
        }
        else                           //如果p1<p2,则将p1插入到p2的后面
        {
            head->next = p1;
            p1 = p1->next;

        }
        head = head->next;
    }
    if (p1 == NULL)
    {
        head->next = p2;
        return new_head;
    }
    else if (p2 == NULL)
    {
        head->next = p1;
        return new_head;
    }
}

Node * FindMidNode(pList plist);// 查找一个中间数 要求只遍历一遍
用快慢指针的方法,快指针每次走两步,慢指针每次走一步,当快指针为NULL时,慢指针指向中间数的地址
实现:

Node * FindMidNode(pList plist)   //  查找一个中间数  要求只遍历一遍
{
    assert(plist);
    //快慢指针
    Node *fast = plist;    //快指针每次走2步
    Node *slow = plist;    //慢指针每次走1步
    while((fast != NULL)&&(fast->next != NULL))
    {
        fast = fast->next->next;
        slow = slow->next;
    }
    return slow;
}

void DelKNode(pList plist, int k); //删除倒数第K个元素
同样使用快慢指针的方法,让快指针先走k步,当快指针为NULL时,慢指针指向倒数第K个元素;
实现:

void DelKNode(pList plist,int k)  //删除倒数第K个元素
{
    void DelKNode(pList plist,int k)  //删除倒数第K个元素
{
    //快慢指针
    if (plist == NULL)
    {
        return;
    }
    Node * fast = plist;
    Node *slow = plist;
    Node *del = plist;
    int count = k;
    while (count >1 && fast != NULL)    //快指针先走k步
    {
        fast = fast->next;
        count--;
    }
    if (count>1||fast==NULL)  //越界
    {
        printf("无法删除\n");
        return;
    }
    while (fast->next!=NULL)   //移动
    {
        fast = fast->next;
        slow = slow->next;
    }
    if (count <= 1 && slow->next != NULL)
    {
        slow->data = slow->next->data;
        del = slow->next;
        slow->next = slow->next->next;
    }
    free(del);
}

pNode CheckCircle(pList plist); //判断是否有环 并返回相遇点
用快慢指针的方法,快指针每次走两步,慢指针每次走一步,当快指针为空则表示不带环,否则快指针必然和慢指针在环上相遇,返回相遇点;
实现:

pNode CheckCircle(pList plist)  //判断是否有环 并返回相遇点
{
    //快慢指针
    Node *fast = plist;
    Node *slow = plist;
    while (fast!=NULL&&fast->next!=NULL)
    {
        fast = fast->next->next;
        slow = slow->next;
        if (fast==slow)
        {
            return slow;     //相遇点
        }
    }
    return NULL;   //当fast为空时,不带环,返回NULL
}

int GetCircleLength(pNode meet); //求环的长度
让相遇点的位置一直向后偏移,当偏移的位置和相遇点的位置相同时,则表明已经走了一圈,这时返回长度;
实现:

int GetCircleLength(pNode meet)   //求环的长度
{
    Node *cur = meet;
    Node *next = meet->next;
    int count = 1;    //next已经提前走了一步,所以从1开始
    while (next != cur)
    {
        count++;
        next = next->next;
    }
    return count;
}

pNode GetCircleEntry(pNode meet,pList plist); //求带环链表的链入点
设环长为L,链入点到相遇点的长度为y,起始位置到链入点的长度为x;
则当快节点(快节点走了m圈)和慢节点(慢节点走了n圈)都走到相遇点meet时,存在一个公式,2(x+y+nl)=x+y+ml;消元后,x+y=ml-nl;
设m-n为常数k,则x+y=kl >> x=kl-y;所以当一个节点从起始位置出发,另一个节点从相遇点出发,每次向后移一位,必定在链入点处相遇;

pNode GetCircleEntry(pNode meet, pList plist)   //求带环链表的链入点
{
    int len = GetCircleLength(meet);
    Node *fast = plist;   //fast从起始点开始出发
    Node *cur = meet;      //cur从相遇点出发
    while (fast!=cur)
    {
        fast = fast->next;
        cur = cur->next;
    }
    return cur;
}

void CheckCross(pList plist1, pList plist2); // 求两条链的接入点
要求两条链的接入点,首先要判断两条链的类型;
1.如果两条链都无环的话,对末尾的位置进行判断,如果位置相同,则开始求两条链的接入点,如果末尾位置不同,则表明没有接入点;
2.如果一条链有环,另一条无环,则必然没有接入点;
3如果两条链都有环,则对两条链的相遇点进行判断,若第一条链的相遇点在第二条链上,则表明两条链有链入点;否则是两条无接入点的有环链;
当两条链有接入点时,需要对链的类型进行判断,存在两种情况
这里写图片描述
分别求出两条链的接入点,如果接入点相同,则是第一种情况,如果接入点不同则是第二种情况;
第一种情况,可以将任意一条链的相遇点断开,然后求两条无环链的接入点;
第二种情况,整条环都是接入点,所以可以用任意一条链的接入点或相遇点打印环上的所有数字;
总共应该有以下6种情况
这里写图片描述
实现:
在这里面封装了三个新的函数:
int Check(Node* meet, pList plist); //判断相遇点meet是否在另一条链plist上
int GetLength(pList plist); //求一个无环链表长度
void print(pNode meet); //打印环上的所有数据 利用相遇点打印

void CheckCross(pList plist1,pList plist2)    //    返回两条链表是否有接入点,有就输出该接入点 分情况讨论
{
    Node *p1= CheckCircle(plist1);  //p1为plist1的相遇点
    Node *p2 = CheckCircle(plist2);  //p2为plist2的相遇点
    if (p1 == NULL && p2 == NULL)   //如果p1,p2都为NULL,则表明是两条无环链
    {
        //1. 无环    -- 不相交  相交
        Node *cur1 = plist1;
        Node *cur2 = plist2;
        while (cur1->next != NULL)    //cur1,cur2分别指向末位置
        {
            cur1 = cur1->next;
        }
        while (cur2->next != NULL)
        {
            cur2 = cur2->next;
        }
        if (cur1 != cur2)   //终点不同则表明无交点并退出
        {
            printf("两条无环链表无交点\n");
            return;
        }
        else
        {
            printf("两条无环链表有交点\n");
        }
        cur1 = plist1;  //重新指向头部
        cur2 = plist2;
        int len1 = GetLength(plist1);   //求两条无环链的接入点
        int len2 = GetLength(plist2);
        if (len1 > len2)      //让长的先走
        {
            int count1 = len1 - len2;
            while (count1 > 1)
            {
                cur1 = cur1->next;
                count1--;
            }
        }
        else
        {
            int count2 = len2 - len1;
            while (count2 > 0)
            {
                cur2 = cur2->next;
                count2--;
            }

        }
        while (cur1 != cur2)
        {
            cur1 = cur1->next;
            cur2 = cur2->next;
        }
        printf("%d-->\n",cur2->data);
        return;
        //return cur2;
    }

    //2. 一个有环 一个无环     相交 不相交
    if ((p1 == NULL && p2 != NULL)||(p2 == NULL && p1 != NULL))//如果一条是无环,一条有环,则必定无接入点
    {
        printf("一条有环链,一条无环链,无交点\n");
        return;
    }
    else    //先判断是否有两个不相交的有环链
    {
        Node*txt=CheckCircle(plist1);
        int ret=Check(txt,plist2);   //判断plist1的相遇点在不在plist2上,在则返回1
        if (ret==1)
        {
            printf("两条相交的有环链\n");
            Node *cur1 = plist1;
            Node *cur2 = plist2;
            Node *net1=CheckCircle(plist1);    //p1的相遇点
            Node *net2 = CheckCircle(plist2); // p2的相遇点
            //如果链入点相同,则第一种情况,否则第二种情况
            Node * zcc = GetCircleEntry(net1, plist1);    //plist1的接入点
            Node * xcc = GetCircleEntry(net2, plist2);    //plist2的接入点
            if (zcc == xcc)        //接入点相同    则是第一种情况
            {
                net1->next = NULL;   //断开环上的任意一个相遇点
                int len1 = GetLength(cur1);
                int len2 = GetLength(cur2);
                if (len1 > len2)      //让长的先走
                {
                    int count1 = len1 - len2;
                    while (count1 > 1)
                    {
                        cur1 = cur1->next;
                        count1--;
                    }
                }
                else
                {
                    int count2 = len2 - len1;
                    while (count2 > 0)
                    {
                        cur2 = cur2->next;
                        count2--;
                    }

                }
                while (cur1 != cur2)  //当cur1==cur2时,就是接入点的位置
                {
                    cur1 = cur1->next;
                    cur2 = cur2->next;
                }
                printf("%d-->\n",cur2->data);
                //return cur2;
            }
            else if (zcc != xcc)    //链入点不相同 则接入点是整条环 所以需要打印任意一个环上的所有数据
            {
                print(net1);
            }
        }
        else
        {
            printf("两条不相交的有环链\n");
            return;
        }
    }
    //两个都有环     不相交   相交--》相遇点在环上 --》 相遇点不在环上
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值