单链表经典题目

来源:互联网 发布:java 网上商城源码 编辑:程序博客网 时间:2024/06/16 12:36

比较顺序表和链表

  1. 顺序表便于理解,容易实现;链表不需要事先分配存储空间,每次插入和删除结点时动态的开辟和释放空间,便于存储空间的管理;
  2. 顺序表事先开辟和随后增加的空间是一块存储位置相邻的连续存储空间,当存储的数据量很大时,内存可能无法找到一块足够大的连续空间予以分配;链表的结点之间是通过指针连接,在内存中可以分散存储,可提高存储空间利用率。
  3. 顺序表可以通过下标快速访问某个结点;链表则需要从头遍历依次查找;
  4. 顺序表进行插入或删除操作时,平均需要移动半个表的元素,相当费时;链表进行插入或删除操作时,只需修改相关结点的指针域,方便又省时;
  5. 由于链表的每个结点都带有指针域,从存储密度来讲,是特别不经济的;顺序表中只存了结点数据,若不考虑空闲区,存储密度为1,链表的存储密度小于1。
    存储密度=结点数据本身所占的存储量/结点结构所占的存储总量

顺序表适用于尾插尾删较多的场景;链表适用于头插或中间插入较多的场景。


从尾到头打印单链表

递归方法的思想就是:先打印出cur结点之后的链表,再打印cur。

void PrintEndToStartR(pList plist){    pNode cur = plist;    if (plist == NULL)        return;    if (cur->_next != NULL)    {        PrintEndToStartR(cur->_next);    }    printf("%d ", cur->_data);}

非递归方法:借用栈,先把单链表的节点全部压入进栈,再依次取栈顶节点即可。

void PrintEndToStart()    {        stack<DataType> s;        Node *cur = _head;        while (cur)        {            s.push(cur->_data);            cur = cur->_next;        }        while (!s.empty())        {            cout << s.top() << " ";            s.pop();        }        cout << endl;    }

删除一个无头单链表的非尾节点

这实际是一种偷梁换柱的思想:把要删除的结点后一个结点的数据赋给删除结点,删除要删除结点后一个结点。是不是有点绕?看图吧!
这里写图片描述

void EraseWithoutHead(pNode pos){    assert(pos);    pNode del = NULL;    if (pos->_next)    {        del = pos->_next;        pos->_data = del->_data;        pos->_next = del->_next;        free(del);    }}

在无头单链表的一个节点前插入一个节点

思想:新建一个要插入数据的结点,把pos结点与新结点中的数据交换,再把新结点插到pos结点后。

void InsertWitnoutHead(pNode pos, int d){    assert(pos);    pNode newNode = (pNode)malloc(sizeof(pNode));    newNode->_data = pos->_data;    pos->_data = d;    newNode->_next = pos->_next;    pos->_next = newNode;}

单链表实现约瑟夫环

约瑟夫环问题:已知n个人(以编号1,2,3…n分别表示)围坐在一张圆桌周围。从编号为k的人开始报数,数到m的那个人出列;他的下一个人又从1开始报数,数到m的那个人又出列;依此规律重复下去,直到圆桌周围的人全部出列。
注意释放结点之前要先把释放的那个结点之前的结点和之后的结点连接起来!

pNode JosephCycle(pList *pplist, int num){    assert(pplist);    pNode cur = *pplist;    pNode del = *pplist;    int i = 0;    while (cur->_next != cur)    {        for (i = 0; i < num - 1; i++)        {            cur = cur->_next;        }        printf("%d ", cur->_data);        del = cur->_next;        cur->_data = del->_data;        cur->_next = del->_next;        free(del);        del = NULL;    }    return cur;}

逆置/反转单链表

void ReverseList(pList *pplist){    pNode prev = *pplist;    pNode cur = prev->_next;    pNode tmp = NULL;    if (*pplist == NULL || (*pplist)->_next == NULL)        return;    while (cur)    {        tmp = cur->_next;        cur->_next = prev;        prev = cur;        cur = tmp;    }    (*pplist)->_next = NULL;    *pplist = prev;}

单链表排序(冒泡排序)

直接运用冒泡排序的思想。

void BubbleSortList(pList *pplist, size_t length){    pNode cur = *pplist;    pNode next = NULL;    DataType tmp = 0;    if (*pplist == NULL || (*pplist)->_next == NULL)        return;    for (int i = 0; i < length; i++)    {        //每次循环都从链表的第一个节点开始        cur = *pplist;        next = cur->_next;        for (int j = 0; j < length - i - 1; j++)        {            if (cur->_data > next->_data)            {                tmp = cur->_data;                cur->_data = next->_data;                next->_data = tmp;            }            cur = cur->_next;            next = cur->_next;        }    }}

合并两个有序链表,合并后依然有序

//合并两个有序链表,合并后依然有序(非递归) pList MergeList(pList plist1, pList plist2){    assert(plist1 != NULL || plist2 != NULL);    pList newList = NULL;    pNode cur = newList;    if (plist1 == plist2)        return plist1;    if (plist1 == NULL)        return plist2;    if (plist2 == NULL)        return plist1;    //1.确定新链表的首结点    if (plist1->_data < plist2->_data)    {        newList = plist1;        plist1 = plist1->_next;    }    else    {        newList = plist2;        plist2 = plist2->_next;    }    //2.合并两个链表    cur = newList;    while (plist1 && plist2)    {        if (plist1->_data < plist2->_data)        {            cur->_next = plist1;            plist1 = plist1->_next;        }        else        {            cur->_next = plist2;            plist2 = plist2->_next;        }        cur = cur->_next;    }    //2.若合并之后,还有剩余的节点,直接链到新链表之后     if (plist1)    {        cur->_next = plist1;    }    if (plist2)    {        cur->_next = plist2;    }    return newList;}//合并两个有序链表,合并后依然有序(递归) pList MergeListR(pList plist1, pList plist2){    assert(plist1 != NULL || plist2 != NULL);    pList newList = NULL;    if (plist1 == plist2)        return plist1;    if (plist1 == NULL)        return plist2;    if (plist2 == NULL)        return plist1;    //递归合并    if (plist1->_data < plist2->_data)    {        newList = plist1;        newList->_next = MergeListR(plist1->_next, plist2);    }    else    {        newList = plist2;        newList->_next = MergeListR(plist1, plist2->_next);    }    return newList;}

查找单链表的中间节点,要求只能遍历一次链表

快慢指针法:快指针每次走两步,慢指针每次走一步,快指针走到链表最后一个结点时,慢指针指向中间节点

pNode FindMidNode(pList plist){    assert(plist);    pNode fast = plist;    pNode slow = plist;    while (fast && fast->_next)    {        fast = fast->_next->_next;        slow = slow->_next;    }    return slow;}

查找单链表的倒数第k个节点,要求只能遍历一次链表

快慢指针法:快指针先走k步,然后快慢指针再一起走,当快指针走到尾时,慢指针指向倒数第k个结点

pNode FindLastK(pList plist, int k){    assert(k > 0);    pNode fast = plist;    pNode slow = plist;    int i = 0;    for (i = k - 1; i > 0; i--)    {        if (fast && fast->_next)            fast = fast->_next;    }    while (fast->_next)    {        fast = fast->_next;        slow = slow->_next;    }    return slow;}

判断单链表是否带环?若带环,求环的长度?求环的入口点?并计算每个算法的时间复杂度&空间复杂度。

判断是否带环
快慢指针法:快指针每次走两步,慢指针每次走一步,若快慢指针可以相遇则带环,返回相遇点,否则不带。
求环的长度
从相遇点开始遍历环,回到相遇点时即得到环的长度。
求环的入口点
这里写图片描述

//判断链表是否带环pNode CheckCycle(pList plist)//O(N)=N{    assert(plist);    pNode fast = plist;    pNode slow = plist;    while (fast && fast->_next)    {        fast = fast->_next->_next;        slow = slow->_next;        if (fast == slow)            return slow;    }    return NULL;}//O(N)=Nint GetCyleLength(pNode meet){    pNode cur = meet;    int count = 0;    do    {        count++;        cur = cur->_next;    } while (cur != meet);    return count;}//O(N)=NpNode GetCycleEntryNode(pList plist, pNode meetNode){    pNode head = plist;    pNode meet = meetNode;    while (head != meet)    {        head = head->_next;        meet = meet->_next;    }    return head;}

判断两个链表是否相交,若相交,求交点。(假设链表不带环)

若两个链表相交,则它们的尾结点一定相等

//判断两个链表是否相交(假设链表不带环)//O(N)=Nbool CheckCross(pList plist1, pList plist2){    pNode p1 = plist1;    pNode p2 = plist2;    if (plist1 == NULL || plist2 == NULL)        return false;    while (p1->_next)    {        p1 = p1->_next;    }    while (p2->_next)    {        p2 = p2->_next;    }    if (p1 == p2)        return true;    else        return false;}

求交点:先遍历两条链表,求出它们的长度差为k,两个指针分别从长链表的第k个节点和短链表的第一个节点开始同时走,当两个指针第一次指向的节点相同时,则此节点为交点。
当然也可调用前面的函数来求得交点。

//求交点(假设链表不带环)//O(N)=NpNode GetCrossNode1(pList plist1, pList plist2){    pNode p1 = plist1;    pNode p2 = plist2;    int length1 = 1;    int length2 = 1;    int i = 0;    while (p1->_next)    {        p1 = p1->_next;        length1++;    }    while (p2->_next)    {        p2 = p2->_next;        length2++;    }    if (length1 > length2)    {        for (i = length1 - length2; i > 0; i--)        {            plist1 = plist1->_next;        }    }    else    {        for (i = length2 - length1; i > 0; i--)        {            plist2 = plist2->_next;        }    }    while (plist1 != plist2)    {        plist1 = plist1->_next;        plist2 = plist2->_next;    }    return plist1;}pNode GetCrossNode2(pList plist1, pList plist2){    pNode p1 = plist1;    pNode meetNode = NULL;    pNode crossNode = NULL;    while (plist1->_next)        plist1 = plist1->_next;    plist1->_next = plist2;    meetNode = CheckCycle(p1);    crossNode = GetCycleEntryNode(p1, meetNode);    return crossNode;}

判断两个链表是否相交,若相交,求交点。(假设链表可能带环)

不带环的情况不再分析;
一条链表带环,一条链表不带环则一定不相交;
带环的情况如下图所示:
这里写图片描述

//判断两个链表是否相交(假设链表可能带环)bool CheckCrossC(pList plist1, pList plist2){    pNode EntryNode1 = NULL;    pNode EntryNode2 = NULL;    pNode p1 = NULL;    if (plist1 == NULL || plist2 == NULL)        return false;    if (CheckCycle(plist1) == NULL && CheckCycle(plist2) == NULL)        return CheckCross(plist1, plist2);    else if ((CheckCycle(plist1) != NULL && CheckCycle(plist2) == NULL) || \        (CheckCycle(plist1) == NULL && CheckCycle(plist2) != NULL))        return false;    else    {        if (GetCycleEntryNode(plist1, CheckCycle(plist1)) == GetCycleEntryNode(plist2, CheckCycle(plist2)))            return true;        else        {            EntryNode1 = GetCycleEntryNode(plist1, CheckCycle(plist1));            p1 = EntryNode1;            EntryNode2 = GetCycleEntryNode(plist2, CheckCycle(plist2));            do{                if (p1 == EntryNode2)                    return true;                p1 = p1->_next;            } while (p1 != EntryNode1);            return false;        }    }}
//求交点(假设链表可能带环)pNode GetCrossNodeC(pList plist1, pList plist2){    pNode EntryNode1 = NULL;    pNode EntryNode2 = NULL;    pNode p1 = NULL;    pNode p2 = NULL;    int length1 = 1;    int length2 = 1;    int i = 0;    if (CheckCycle(plist1) == NULL && CheckCycle(plist2) == NULL)        return GetCrossNode1(plist1, plist2);    EntryNode1 = GetCycleEntryNode(plist1, CheckCycle(plist1));    EntryNode2 = GetCycleEntryNode(plist2, CheckCycle(plist2));    p1 = plist1;    p2 = plist2;    if (EntryNode1 == EntryNode2)    {        while (p1->_next != EntryNode1)        {            p1 = p1->_next;            length1++;        }        while (p2->_next != EntryNode1)        {            p2 = p2->_next;            length2++;        }        if (length1 > length2)        {            for (i = length1 - length2; i > 0; i--)            {                plist1 = plist1->_next;            }        }        else        {            for (i = length2 - length1; i > 0; i--)            {                plist2 = plist2->_next;            }        }        while (plist1 != plist2)        {            plist1 = plist1->_next;            plist2 = plist2->_next;        }        return plist1;    }    if (EntryNode1 != EntryNode2)    {        return EntryNode1;    }}

复杂链表的复制

一个链表的每个节点,有一个指向next指针指向下一个节点,还有一个random指针指向这个链表中的一个随机节点或者NULL,现在要求实现复制这个链表,返回复制后的新链表。

复杂链表的节点结构:

typedef struct ComplexNode{    DataType _data;    struct ComplexNode *_next;    struct ComplexNode *_random;}CNode,*pCNode,*pCList;

复杂链表的复制,主要是节点之间指向的变化问题,只要掌握方法,实现起来并不复杂。
这里写图片描述

pCNode CreateComplexNode(DataType d){    pCNode newNode = (pCNode)malloc(sizeof(pCNode));    if (newNode == NULL)    {        perror("malloc");        return NULL;    }    newNode->_data = d;    newNode->_next = NULL;    newNode->_random = NULL;    return newNode;}void PrintComplexList(pCList pclist){    pCNode cur = pclist;    if (pclist == NULL)        return;    while (cur)    {         printf("[%d]->random:", cur->_data);        if (cur->_random)        {            printf("%d--->", cur->_random->_data);        }        else        {            printf("NULL--->");        }        cur = cur->_next;    }}//复杂链表的复制pCList CloneComplexList(pCList pclist){    pCNode cur = pclist;    pCNode cloneNode = NULL;    pCList l1 = NULL;    pCList l2 = NULL;    pCNode tail = NULL;    if (pclist == NULL)        return NULL;    //1.拷贝原始链表的每一个节点,连到当前节点后    while (cur)    {        cloneNode = CreateComplexNode(cur->_data);        cloneNode->_next = cur->_next;        cur->_next = cloneNode;        cur = cur->_next->_next;    }    //2.调整random指针    cur = pclist;    while (cur)    {        cur->_next->_random = cur->_random->_next;        cur = cur->_next->_next;    }    //3.分离两条链表    cur = pclist;    l1 = cur;    l2 = cur->_next;    tail = l2;    while (cur)    {        cur->_next = tail->_next;        if (cur->_next == NULL)        {            tail->_next == NULL;            break;        }        tail->_next = cur->_next->_next;        cur = cur->_next;        tail = tail->_next;    }    return l2;}

求两个单链表交集/差集

前提:两个单链表有序且各自无重复的节点
算法思想与合并两个有序单链表如出一辙,些许不同的地方会在注释中阐明。

//求两个单链表交集pList Intersection(pList plist1, pList plist2){    pNode newList = BuyNode(0);    pNode cur = newList;    if (plist1 == NULL || plist2 == NULL)        return NULL;    //相等同时走并把结点保存,不相等小的那个走    while (plist1 && plist2)    {        if (plist1->_data == plist2->_data)        {            cur->_next = plist1;            plist1 = plist1->_next;            plist2 = plist2->_next;            cur = cur->_next;        }        else if (plist1->_data < plist2->_data)            plist1 = plist1->_next;        else            plist2 = plist2->_next;    }    cur->_next = NULL;    return newList->_next;}//求两个单链表差集pList DifferenceSet(pList plist1, pList plist2){    pNode newList = BuyNode(0);    pNode cur = newList;    if (plist1 == NULL || plist2 == NULL)        return NULL;    //不相等保存小的且小的向后走,相等直接同时向后走    while (plist1 && plist2)    {        if (plist1->_data != plist2->_data)        {            if (plist1->_data < plist2->_data)            {                cur->_next = plist1;                plist1 = plist1->_next;                cur = cur->_next;            }            else            {                cur->_next = plist2;                plist2 = plist2->_next;                 cur = cur->_next;            }        }        else        {            plist1 = plist1->_next;            plist2 = plist2->_next;        }    }     //将cur->置空    cur->_next = NULL;    //若还有结点剩余,再链在cur后    if (plist1)        cur->_next = plist1;    if (plist2)        cur->_next = plist2;    return newList->_next;}
原创粉丝点击