链表相关

本文转自:https://blog.csdn.net/hansionz/article/details/81608860#1offer_66

链表面试题总结

一.定义数据结构

typedef int DataType;

typedef struct Node
{
    DataType data;
    struct Node * next;
}Node,*pNode,LinkList,*pLinkList;

二.各题目函数声明

//从尾到头打印链表
void PrintLinkTailToHead(pLinkList pl);

//删除一个无头单链表的非尾结点
void RmoveNodeNotTail(pLinkList *ppl, pNode pos);

//用链表实现约瑟夫环
void JosephCircle(pLinkList *ppl, int sz);

//在无头单链表的一个节点前插入一个节点(不能遍历链表)
void InsertNodeNotHead(pLinkList *ppl, DataType data, pNode pos)

//链表的逆置
void Reverselist(pLinkList *ppl);

//链表的冒泡排序
void BubbleSort(pLinkList *ppl);

//合并两个有序的链表,合并后依然有序
pNode Merge(pLinkList list1, pLinkList list2);

//合并两个有序的链表(递归版本)
pNode Merge_R(pLinkList list1, pLinkList list2);

//求单链表中间结点
pNode FindMidNode(pLinkList plist);

//求单链表倒数第K个结点
pNode FindLastKNode(pLinkList plist, int k);

//判断链表是否带环,带环返回相遇点,不带环返回NULL
pNode IsCircle(pLinkList plist);

//求环的长度
int GetCircleLength(pNode meet);

//求环的入口点
pNode GetCircleEntryNode(pLinkList plist, pNode meet);

//判断链表是否相交(假设链表不带环),相交返回1,不想交返回0
int IsCross(pLinkList plist1, pLinkList plist2);

//求两个链表的交点
pNode GetMeetNode(pLinkList plist1, pLinkList plist2);

//求两个链表中相同的数据(交集)
void UnionSet(pLinkList plist1, pLinkList plist2);

三.函数实现

(1).从尾到头打印链表(题目来源:剑指offer)

https://blog.csdn.net/hansionz/article/details/80905962

void PrintLinkTailToHead(pLinkList pl,stack * s)
{
//(1)
#if 0
    if(pl != NULL)
    {
        if(pl->next != NULL)
        {
            PrintLinkTailToHead(pl->next);
        }
        printf("%d--->",pl->data);
    }
    /*if (pl == NULL)
    {
        return NULL;
    }
    PrintLinkTailToHead(pl->next);
    printf("%d--->", pl->data);*/
#endif
//(2)
#if 0
    pNode tail = NULL;
    while (pl != tail)
    {
        pNode cur = pl;
        while (cur->next != tail)
        {
            cur = cur->next;
        }
        printf("%d--->", cur->data);
        tail = cur;
    }
#endif
//(3)
    Node* cur = pl;
    while (cur != NULL)
    {
        s->data[s->top++] = cur->data;
        cur = cur->next;
    }
    while (s->top >= 0)
    {
        printf("%d--->", s->data[s->top--]);
    }
    printf("over\n");
}

(2).删除一个无头单链表的非尾结点(不能遍历链表)(题目来源:剑指offer)

思路:要删除一个结点,就必须找到要删除结点的前驱。但是不能遍历链表,我们找不到前驱。我们试着改变一下思路,将要删除结点的下一个结点的数据复制给要删除的结点,而要删除的结点就变成了原来要删除结点的下一个结点。这样就很巧妙的删除了结点。
https://blog.csdn.net/hansionz/article/details/80934410

void RmoveNodeNotTail(pLinkList *ppl, pNode pos)
{
    pNode del = NULL;
    assert(ppl != NULL);
    assert(pos->next != NULL);
    assert(*ppl != NULL);
    pos->data = pos->next->data;//复制数据
    del = pos->next;//改变要删除的结点
    pos->next = del->next;//删除下一个结点
    free(del);
    del = NULL;
}

(3).用单链表实现约瑟夫环问题

https://blog.csdn.net/hansionz/article/details/81539863

void JosephCircle(pLinkList *ppl, int k)
{
    assert(ppl != NULL);
    pNode cur = *ppl;
    pNode tmp = *ppl;
    pNode del = NULL;
    //将单链表连成一个换
    while (tmp->next)
    {
        tmp = tmp->next;
    }
    tmp->next = *ppl;
    //如果还有大于1个结点则继续查找
    while (cur != cur->next)
    {
        int count = k;
        //走k-1步,清除一个
        while (--count)
        {
            cur = cur->next;
        }
        //删除当前结点
        del = cur->next;
        printf("删除:%d\n", cur->data);
        cur->data = cur->next->data;
        cur->next = del->next;
        free(del);
        del = NULL;
    }
    printf("剩余:%d\n", cur->data);
}

(4).在无头单链表的一个节点前插入一个节点(不能遍历链表)

思路:既然不能遍历链表,说明找不到前驱,那我们只能先将这个结点插入到它的后边,然后在交换数据。或者,在开辟结点的时候就已前面结点的数据开辟,插入之后再将前面结点的数据域设置为已知数据,从而达到交换的效果。

void InsertNodeNotHead(pLinkList *ppl, DataType data, pNode pos)
{
    pNode newNode = NULL;
    assert(ppl != NULL);
    //(1)
#if 0
    DataType tmp = 0;
    newNode = BuyNode(data);
    newNode->next = pos->next;
    pos->next = newNode;
    tmp = newNode->data;
    newNode->data = pos->data;
    pos->data = tmp;
#endif
    //(2)
    newNode = BuyNode(pos->data);
    newNode->next = pos->next;
    pos->next = newNode;
    pos->data = data;
}

(5).链表的逆置/反转

思路:重新开辟一条链表,然后依次取下原链表的每个结点头插入到新的链表中,新的链表就是逆置的结果

void Reverselist(pLinkList *ppl)
{
    pNode cur = *ppl;
    pNode tmp = NULL;
    pLinkList head = NULL;
    assert(ppl != NULL);
    if (*ppl == NULL || (*ppl)->next == NULL)
    {
        return;
    }
    while (cur)
    {
        tmp = cur->next;//保存下一个结点,否则会丢失链表后边的内容
        cur->next = head;
        head = cur;
        cur = tmp;
    }
    *ppl = head;
}

单独总结于另一边博客:
https://blog.csdn.net/hansionz/article/details/82285384

(6).链表的冒泡排序

void BubbleSort(pLinkList *ppl)
{
    assert(ppl != NULL);
    pNode cur = *ppl;
    pNode tail = NULL;
    while (cur != tail)//确定躺数
    {
        while (cur->next != tail) //确定比较次数
        {
            if (cur->data > cur->next->data)
            {
                DataType tmp = cur->data;
                cur->data = cur->next->data;
                cur->next->data = tmp;
            }
            cur = cur->next;
        }
        tail = cur;
        cur = *ppl;
    }
}

(7).合并两个有序的链表,合并后依然有序

pNode Merge(pLinkList list1, pLinkList list2)
{
    pLinkList newhead = NULL;
    //一个链表为空,另一个不为空
    if (list1 == NULL)
    {
        return list2;
    }
    if (list2 == NULL)
    {
        return list1;
    }
    //两个链表都为空
    if ((list1 == NULL) && (list2 == NULL))
    {
        return NULL;
    }
    //无头结点的单链表,必须先插入一个结点才能将以后的结点操作统一
    if (list1->data < list2->data)
    {
        newhead = list1;
        list1 = list1->next;
    }
    else
    {
        newhead = list2;
        list2 = list2->next;
    }
    //保存链表最后一个结点的位置
    pNode tail = newhead;
    //依次比较尾插
    while ((list1 != NULL) && (list2 != NULL))
    {
        if (list1->data < list2->data)
        {
            tail->next = list1;
            list1 = list1->next;
        }
        else
        {
            tail->next = list2;
            list2 = list2->next;
        }
        tail = tail->next;
    }
    //将剩余的结点插入到新链表中
    if (list1 == NULL)
    {
        tail->next = list2;
    }
    else if (list2 == NULL)
    {
        tail->next = list1;
    }
    return newhead;
}

(8).合并两个有序的链表,合并后依然有序(递归版本)

pNode Merge_R(pLinkList list1, pLinkList list2)
{
    pLinkList newhead = NULL;
    if (list1 == NULL)
    {
        return list2;
    }
    if (list2 == NULL)
    {
        return list1;
    }
    if ((list1 == NULL) && (list2 == NULL))
    {
        return NULL;
    }
    if (list1->data < list2->data)
    {
        newhead = list1;
        newhead->next = Merge_R(list1->next, list2);
    }
    else
    {
        newhead = list2;
        newhead->next = Merge_R(list1, list2->next);
    }
    return newhead;
}

(9).求单链表中间结点

思路:利用快慢指针思想,快指针fast每次走两步,满指针slow每次走一步。当快指针做到最后一个结点时,慢指针正好指向中间结点。

pNode FindMidNode(pLinkList plist)
{
    pNode slow = plist;
    pNode fast = plist;
    //链表没有结点或者只有一个结点直接返回
    if ((plist == NULL) || (plist->next == NULL))
    {
        return plist;
    }
    //节点数为偶数时判断条件为fast!=NULL,此时中间节点为后面的那个;
    //为奇数时判断条件为fast->next!=NULL.
    while ((fast != NULL) && (fast->next != NULL))
    {
        fast = fast->next->next;
        slow = slow->next;
    }
    return slow;
}

(10).求单链表倒数第K个结点

思路:依然是利用快慢指针的方法,让快指针先走k-1步,然后快指针和慢指针一起出发,当快指针走到最后一个结点时,慢指针指向倒数第k个结点。

pNode FindLastKNode(pLinkList plist, int k)
{
    pNode fast = plist;
    pNode slow = plist;
    if (plist == NULL)
    {
        return plist;
    }
    //快指针走k-1步
    while (--k)
    {
        //如果链表总的节点数小于k,则肯定不存在倒数第k个结点
        if (fast == NULL)
        {
            return NULL;
        }
        fast = fast->next;
    }
    //快慢指针一起走,快的到终点,慢指针则指向倒数第k个结点
    while (fast->next != NULL)
    {
        fast = fast->next;
        slow = slow->next;
    }
    return slow;
}

(11).判断单链表是否带环,带环返回相遇点,不带环返回NULL

思路:依旧是快慢指针的思想,快指针一次走两步,慢指针一次走一步,如果单链表带环,则快指针和慢指针必然相遇。

//带环返回相遇点,不带环返回NULL
pNode IsCircle(pLinkList plist)
{
    pNode fast = plist;
    pNode slow = plist;
    if (plist == NULL)
        return NULL;
    while ((fast != NULL) && (fast->next != NULL))
    {
        fast = fast->next->next;
        slow = slow->next;
        if (fast == slow)
        {
            return fast;
        }
    }
    return NULL;
}

(12).求单链表中环的长度

思路:从相遇点的下一个结点开始便利一遍环,就可以求出来环的长度了。但是len必须是从1开始,否则会少计算相遇点那个结点。

int GetCircleLength(pNode meet)
{
    assert(meet != NULL);
    pNode cur = meet->next;
    int len = 1;
    //len必须从1开始,否则没有算meet那个结点
    while (cur != meet)
    {
        len++;
        cur = cur->next;
    }
    return len;
}

(13).求单链表中求环的入口点

思路:


在这里插入图片描述
pNode GetCircleEntryNode(pLinkList plist, pNode meet)
{
    pNode cur = plist;
    if (plist == NULL)
    {
        return NULL;
    }
    assert(meet != NULL);
    while (cur != meet)
    {
        cur = cur->next;
        meet = meet->next;
    }
    return cur;
}

(14).判断链表是否相交(假设链表不带环),相交返回1,不想交返回0

这里写图片描述

思路:两条不带环的链表相交最后一个结点必然相同。

int IsCross(pLinkList plist1, pLinkList plist2)
{
    pNode end1 = plist1;
    pNode end2 = plist2;
    if ((plist1 == NULL) || (plist2 == NULL))
    {
        return 0;
    }
    //确定链表一的尾结点
    while (end1->next != NULL)
    {
        end1 = end1->next;
    }
    //确定链表二的尾结点
    while (end2->next != NULL)
    {
        end2 = end2->next;
    }
    return end1 == end2;
}

(15).求两个不带环单链表的交点

这里写图片描述
pNode GetMeetNode(pLinkList plist1, pLinkList plist2)
{
    int len1 = 0;
    int len2 = 0;
    int gap = 0;
    pNode cur1 = plist1;
    pNode cur2 = plist2;
    //求链表1长度
    while (cur1)
    {
        len1++;
        cur1 = cur1->next;
    }
    //求链表2长度
    while (cur2)
    {
        len2++;
        cur2 = cur2->next;
    }
    //求差值
    gap = abs(len1 - len2);

    cur1 = plist1;//长
    cur2 = plist2;//短
    if (len1 < len2)
    {
        cur1 = plist2;
        cur2 = plist1;
    }
    //长链表走gap步
    while (gap--)
    {
        cur1 = cur1->next;
    }
    //一起走
    while (cur1!=cur2)
    {
        cur1 = cur1->next;
        cur2 = cur2->next;
    }
    return cur1;
}

(16).求两个已排序链表中相同的数据(交集)

void UnionSet(pLinkList plist1, pLinkList plist2)
{
    if ((plist1 == NULL) || (plist2 == NULL))
    {
        return;
    }
    while ((plist1 != NULL) && (plist2 != NULL))
    {
        if (plist1->data < plist2->data)
        {
            plist1 = plist1->next;
        }
        else if (plist1->data > plist2->data)
        {
            plist2 = plist2->next;
        }
        else
        {
            printf("%d ", plist1->data);
            plist1 = plist1->next;
            plist2 = plist2->next;
        }
    }
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,332评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,508评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,812评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,607评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,728评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,919评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,071评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,802评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,256评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,576评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,712评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,389评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,032评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,798评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,026评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,473评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,606评论 2 350

推荐阅读更多精彩内容