链表

作者: 里里角 | 来源:发表于2018-08-13 21:13 被阅读41次

基础概念:
节点(Node)是其中的元素,相邻节点彼此互称前驱(predecessor)或后继(successor);
没有前驱/后继的唯一节点称为首(first/front)/末(last/rear)节点
头(header)、尾(trailer)两个哨兵



1、单向链表(One-way LinkedList)
定义:一个序列中只含有指向它的后继结点的链接时,就称该链表为单链表。
每一个链表有一个头指针,通过头指针可以找到第一个节点,每个节点都可以通过指针域找到它的后继,最后一个节点的指针域为NULL,表示没有后继,即最后一个没有指向。
考查知识点:查找、删除、插入


/*单向链表节点定义*/
struct Node
{
    int val;
    Node * pnext;
};

当我们往一个空链表中插入一个节点时,新插入到节点就是链表的头指针,此时会改动头指针,因此必须把phead参数设置为指向指针的指针。

/*在链表末尾添加一个新节点*/
void AddToTail(Node ** phead,int value)//phead是一个指向指针的指针
{                                                               
     Node * pnew=new Node();//为新节点开辟空间
     pnew->val=value; //新节点数据域赋值
     pnew->pnext=NULL;//新节点指针域置空
    
    if(*phead==NULL) *phead=pnew;//空链表,则新节点为首节点
    else
    {
        Node * pnode=*phead;
        while(pnode->pnext != NULL) pnode=pnode->pnext;
        pnode->next=pnew;//从首节点出发,不断往后继找,直到找到后继  
          //指针域为空的节点,将新节点的指针赋给找到的那个节点的后继
    }
}

/*在单链表中间位置插入一个节点*/


链表中的内存不是一次性分配的,故内存不连续,所以无法寻秩访问。
查找时间效率为O(n)。
思路:①创建待删除节点存储空间;②若首节点即为查找节点,将首节点指针赋给待删除节点指针,首节点的后继作为首节点;③若查找节点在链表中间,创建节点等于首节点,沿next指针直至找到“节点后继数据”为待查找数据;④找到后,当前节点后继给之前创建的待删除指针,当前节点的后继的后继作为当前节点的后继;⑤待删除指针被赋值后,销毁(delete+置空);

/*查找并删除节点*/
void RomoveNode(Node **phead,int value)
{
    if(phead==NULL || *phead==NULL) return;
    
    Node * pTobeDeleted =NULL;//①
    
    if(phead->val==value)//①
    {
        pTobeDeleted=*phead;
        *phead=(*phead)->pnext;
    }
    else
    {
        Node *pnode=*phead;//③
        while(pnode->next!=NULL && pnode->pnext->val != value)
            pnode=pnode->pnext;
        if(pnode->pnext != NULL && pnode->pnext->val == value)//④
        {
            pTobeDeleted=pnode->pnext;
            pnode->pnext=pnode->pnext->pnext;
        }
    }
    
    if(pTobeDeleted!=NULL)//⑤
    {
        delete pTobeDeleted;
        pTobeDeleted=NULL;
    }
}

剑指offer-5-从尾到头打印链表
思路:从头到尾遍历链表,将遍历到的节点放入栈中,遍历完成,从栈中取出。
①创建栈;②创建节点pnode,指向首节点;③当链表不为空时,取出当前节点压入栈中+当前节点传递到后继节点;④从栈顶元素开始,先将其交给节点pnode,打印其数据域,再弹出(在这一过程中栈顶元素后移,注意先后顺序),直至栈变空;

    void PrintListReversingly_Iteratively(Node *phead)
    {
        stack<Node *> nodes;
        
        Node *pnode=phead;
        while(pnode!=NULL)
        {
            nodes.push(pnode);
            pnode=pnode->pnext;
        }
        
        while(!nodes.empty())
        {
            pnode=nodes.top();
            cout<<pnode->val;
            nodes.pop();
        }
    }

递归在本质上是一个栈结构,也可以用递归实现,但存在链表非常长的时候,递归调用层级很深,导致函数调用栈溢出。


剑指offer-13-在O(1)时间内删除链表节点
题目:给定单向链表的头指针和一个节点指针,定义一个函数在O(1)时间删除该节点。
首先需要明确的是,要删除链表中的节点,必须找到该节点的前驱,通过将前驱指针指向该节点的后继,才能删除;若如此,需要遍历,时间为O(n);
改进思路:通过待删除节点i找到待删除节点i的下一个节点j,将j的内容复制到i内,删除j节点,把i的指针指向j的下一个节点,此时再删除节点j。
以上思路不适用于待删除节点为尾节点时的情况。
同时考虑链表为空和链表只有一个元素的情况

    void DeleteNode(Node ** phead,Node * pTobeDeleted)
    {
        //链表非空、待删节点非空
        if(!phead || !pTobeDeleted) return;
        
        //若所删节点不是尾节点
        if(pTobeDeleted->next != NULL)
        {
            Node *pDnext = pTobeDeleted->pnext;
            pTobeDeleted->val = pDnext->val;
            pTobeDeleted->next = pDnext->pnext;
            
            delete pDnext;
            pDnext=NULL;
        }
        //链表只有一个节点,删除头节点(同时也是尾节点)
        else if(*phead==pTobeDeleted)
        {
            delete pTobeDeleted;
            pTobeDeleted = NULL;
            *phead = NULL;
        }
        //链表中有多个节点,删除尾节点
        else
        {
            Node * Pnode=*phead;
            while(Pnode->pnext != pTobeDeleted)
            {
                Pnode=pnode->pnext;
            }
            Pnode->pnext = NULL;
            delete pTobeDeleted;
            pTobeDeleted=NULL;
        }
    }

剑指offer-15-链表中倒数第k个节点
<单向链表无法从后往前找>
思路①:遍历链表两次,第一次确定链表长度n,第二次遍历定位到n-k+1个;
思路②:遍历链表一次,定义两个指针,第一个指针从链表的头指针开始遍历向前走k-1步,第二个指针保持不动;从第k步开始,第二个指针开始从链表的头指针开始遍历。(保持两者的距离为k-1)
写代码要注意鲁棒性。
此题有以下三种情况可能导致代码崩溃:
①phead为空指针,代码试图访问空指针指向的内存,程序崩溃;
②链表节点数目少于k,依然会导致空指针问题;
③输入的参数为0;

ListNode* FindKthToTail(ListNode* pListHead, unsigned int k) {
        if(pListHead == NULL || k == 0) return NULL;
        ListNode * pAhead = pListHead;
        ListNode * pBehind = NULL;
        
        for(unsigned int i=0;i<k-1;i++)
        {
            if(pAhead->next != NULL)
                pAhead=pAhead->pnext;
            else 
                return NULL;
        }
        
        pBehind=pListHead;
        while(pAhead->pnext != NULL)
        {
            pAhead=pAhead->pnext;
            pBehind=pBehind->pnext;
        }
        return pBehind;
    }

可拓展至:
①求链表的中间节点,双指针解决:一个指针走一步,一个指针走两步,同时出发;
②判断一个单向链表是否形成了环形结构,双指针解决:一个指针走两步,一个指针走一步,同时出发,如果在走一步的的指针走到链表末尾时都没有遇见走得两步的,那么链表就不是环形链表。


剑指offer-16-反转链表
思路:定义三个指针,分别指向当前节点、前驱节点及后继节点。将当前节点的next指向前驱节点,同时,事先需要准备保存i的后继,以防止链表断开。
测试用例:
①链表为空;
②链表只有一个节点;
③链表有多个节点。

ListNode* ReverseList(ListNode* pHead) {
        ListNode * pReverseHead =NULL;
        ListNode * pNode=pHead;
        ListNode * pPrev=NULL;
        
        while(pNode != NULL)
        {
            ListNode * pNext=pNode->next; //用于保存当前节点的后继
            if(pNext==NULL) pReverseHead=pNode;
            
            pNode->next=pPrev;    //next指针反转,指向当前节点的前驱
            pPrev=pNode;       //当前节点变成它之前前驱的前驱
            pNode=pNext; //后继变成当前节点
        }
        return pReverseHead;
    }

剑指offer-17-合并有序列表
递归思想
测试用例:
①链表1为空或链表2为空;
②正常链表。

ListNode* Merge(ListNode* pHead1, ListNode* pHead2)
{
    if(pHead1 == NULL) return pHead2;
    if(pHead2 == NULL) return pHead1;
    
    ListNode * pMergeHead =NULL;
    
    if(pHead1->val < pHead2->val)
    {
        pMergeHead=pHead1;
        pMergeHead->next=Merge(pHead1->next,pHead2);
    }
    else 
    {
        pMergeHead=pHead2;
        pMergeHead->next=Merge(pHead1,pHead2->next);
    }
    return pMergeHead;
}

剑指offer-37-两个链表的第一个公共节点
明确:节点具有数据域和指针域,若两节点相同,则两个域的内容
也相同,即指向的下一个节点也相同。
测试用例:
①公共节点的位置(中间、首、尾、没有公共节点);
②链表头节点是NULL指针。

class Solution {
public:
    unsigned int GetListLength(ListNode * phead)
    {
        unsigned int length=0;
        ListNode*pnode=phead;
        while(pnode->next != NULL)
        {
            length++;
            pnode=pnode->next;
        }
        return length;
    }
    ListNode* FindFirstCommonNode( ListNode* pHead1, ListNode* pHead2) {
        if(pHead1==NULL ||pHead2==NULL ) return NULL;
        unsigned int length1=GetListLength(pHead1);
        unsigned int length2=GetListLength(pHead2);
        int lengthDif=length1-length2;
        ListNode* pListHeadLong=pHead1;
        ListNode* pListHeadShort=phead2;
        
        if(length1<length2)
        {
            lengthDif=length2-length1;
            pListHeadLong=pHead2;
            pListHeadShort=pHead1;
        }
        
        for(int i=0;i<lengthDif;i++) //长链表先遍历长度差步
        {
            pListHeadLong=pListHeadLong->next;
        }
        
        while((pListHeadLong!= NULL) && (pListHeadShort != NULL) && (pListHeadLong != pListHeadShort))
        {
            pListHeadLong=pListHeadLong->next;
            pListHeadShort=pListHeadShort->next;
        }
        
        ListNode* pFirstCommonNode = pListHeadLong;
        return pFirstCommonNode;
    }
};


2、双向链表
在单链表的每个结点中,再设置一个指向其前驱节点的指针域。

/*双向链表存储结构*/
struct ListNode
{
    int val;
    ListNode * front;
    ListNode * next;
};

双链表反向查找结点优于单链表 ,实质上是以空间换取时间.
缺点:在插入和删除一个结点时需要维护两个指针变量,特别是在双链表的插入中指针的指向顺序是不可修改的。


双向链表的插入


image.png
/*双向链表插入*/
1 NewNode->front = p;
2 NewNode->next = p->next;
3 p->next->front = NewNode;
4 p-next = NewNode;

双向链表的删除


image.png
/*双向链表删除*/
1 p->front->next = p->next;
2 p->next-front = p->front;

剑指offer-27-二叉搜索树与双向链表

相关文章

  • 链表基础

    链表基础 链表长度 链表为空 链表结构 链表增加

  • 双向链表&双向循环链表

    链表分为:单链表、单向循环链表、双向链表、双向循环链表本节主要说明:双向链表、双向循环链表 定义结点 一、双向链表...

  • 算法与数据结构:链表

    链表 链表还分为单向链表和双向链表, 但是这篇文章只说单向链表 , 下次再讲双向链表 . 链表和数组的区别 ? 链...

  • 链表

    链表 单链表反转链表中环的检测两个有序链表合并删除链表倒数第n个节点求链表的元素总个数 一.单向链表 链表共有特征...

  • 五、双向链表

    双向链表 此前介绍的链表,也叫做单向链表使用双向链表可以提升链表的综合性能 修改之前的单链表的源码: 双向链表 –...

  • 链表

    内容 链表数据结构 向链表添加元素 从链表移除元素 使用 LinkedList 表 双向链表 循环链表 链表数据结...

  • 数据与算法结构

    线性表 顺序表 链表(物理上离散,逻辑上连续) 链表的类别 单链表 循环链表 双链表 链表的操作 顺序表与链表的比...

  • 数据结构——链表

    本文所讲的链表是单链表,链表采用无头链表 科普下:一般链表可以分为有头节点的链表与无头节点的链表 有头节点的链表:...

  • 链表

    文章结构 链表的定义 链表的插入和删除操作 链表的特性 常见的链表结构 自定义链表 链表的经典操作 使用链表实现L...

  • Algorithm小白入门 -- 单链表

    单链表递归反转链表k个一组反转链表回文链表 1. 递归反转链表 单链表节点的结构如下: 1.1 递归反转整个单链表...

网友评论

      本文标题:链表

      本文链接:https://www.haomeiwen.com/subject/otlybftx.html