zoukankan      html  css  js  c++  java
  • 数据结构-单链表

    1. 单链表介绍

    链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。链表由一系列结点(链表中每一个元素称为结点)组成,结点可以在运行时动态生成。每个结点包括两个部分:一个是存储数据元素的数据域,另一个是存储下一个结点地址的指针域。

    相比于顺序存储结构(一般指数组),链表在插入和删除的时候可以达到O(1)的时间复杂度,如果你的数据容器需频繁进行插入删除操作,那链表是不错的选择。单向链表结构图如下所示:

    单向链表结构

    2. 单链表常见问题汇总

    链表是最基本的数据结构,在面试中,面试官也常常会选择链表来考察面试者的基本能力。主要由于链表的实现涉离不开指针,而指针往往又很容易出错,因此使得链表成为面试中一个比较好的考察点。

    那这边是我从互联网上收集到的,在面试及生产中用的比较多的单链表知识点,具体如下:

    (主要参考于 http://blog.csdn.net/luckyxiaoqiang/article/details/7393134#topic2)

    1. 设计链表,实现链表基本的创建,添加,删除,查询操作

    2. 合并两个有序链表

    3. 反转链表

    4. 删除链表的倒数第K个节点(k>0)

    5. 查找链表的中间结点

    6. 判断一个单链表中是否有环

    7. 求两个单链表相交的第一个节点

    8. 已知一个单链表中存在环,求进入环中的第一个节点

    9. 给出一单链表头指针pHead和一节点指针pToBeDeleted,O(1)时间复杂度删除节点pToBeDeleted

    3. 单链表基本实现(C++)

    首先,我们实现一下对单链表的基本封装,具体代码如下: (编译平台:Linux centos 7.0 编译器:gcc 4.8.5 )

    由于采用C++ 类进行封装,头节点不会提供给外部使用,因此对于处理多条链表关系,以及有环等问题,会专门用一节来整理,这些问题更偏向于提供一个解决问题的思路,偏算法,至于单链表数据结构,下面的封装基本可以满足。

    头文件: single_list.h

    #include <iostream>
    
    /** @file single_list.h
     * 
     *  This is an list header file, implement single list warppes  
     * 
     *  Created by yejy on 18-8-21
     *  copyright (c) yejy. all rights reserved
     * 
     */
    
    #define INVALID_VALUE  -1
    
    struct _single_list_node
    {
        int dataEntry;
        _single_list_node* Next;
    };
    
    /* single list */
    class T_Single_List
    {
      typedef _single_list_node* Link_type;
    
    public:
      T_Single_List()
      { 
        pHeadNode = new _single_list_node();
        pHeadNode->Next = nullptr; 
    
        _size = 0;
      }
    
      ~T_Single_List()
      {
        clear();
    
        if(pHeadNode)
        {
          delete pHeadNode;
          pHeadNode = nullptr;
        }
    
        _size = 0;
      }
    
        // head | data -> insert2 | data -> insert1 | data -> null
      void InsertValueAtHead(int value) // 前插
      {
        if(pHeadNode)
        {
           Link_type pNode = new _single_list_node();
    
          if(pNode)
          {
            pNode->dataEntry = value;
            pNode->Next = pHeadNode->Next;
            pHeadNode->Next = pNode; 
    
            ++_size;
          }
        }
      }
    
      // head | data -> insert1 | data -> insert2 | data -> null
      void InsertValueAtTail(int value) // 尾插
      {
        if(pHeadNode)
        {
          Link_type pNode = new _single_list_node();
          Link_type pPollNode = pHeadNode; //
    
          if(pNode)
          {
            // 单向链表实现尾插,需要遍历到最后一个节点插入
            while(pPollNode->Next != nullptr)
            {
              pPollNode = pPollNode->Next;
            }
    
            pNode->Next = nullptr;
            pNode->dataEntry = value;
            pPollNode->Next = pNode; 
    
            ++_size;
          }
        }
      }
    
      // 按序号插入
      void InsertValueAtIndex(int index, int val)
      {
        if(index > _size || index <= 0)
        {
          std::cout << "error Not in range" << std::endl; 
          return ;
        }
    
        Link_type pPollNode = findbyindex(index);
        Link_type pNode = new _single_list_node();
    
        if(pNode)
        {
          pNode->Next = pPollNode->Next;
          pPollNode->Next = pNode;
          pNode->dataEntry = val;
    
          ++_size;
        }
      }
    
      // 根据index删除
      void deleteValueAtIndex(int index)
      {
        if(index > _size || index <= 0)
        {
          std::cout << "error Not in range" << std::endl; 
          return ;
        }
    
        Link_type pNode = findbyindex(index); //找到前一个
        RemoveNext(pNode); //删除Next即可
      }
    
      // 根据index获取值
      int GetValueByIndex(int index)
      {
        if(index > _size || index <= 0)
        {
          std::cout << "error Not in range" << std::endl; 
          return INVALID_VALUE;
        }
    
        Link_type pNode = findbyindex(index);
    
        if(pNode->Next)
        {
          return pNode->Next->dataEntry;
        }
    
        return INVALID_VALUE;
      }
    
      // 修改值
      void update(int value, int modify_value)
      {
        Link_type pNode = findbyvalue(value);
    
        if(pNode->Next)
        {
            pNode->Next->dataEntry = modify_value;
        }
      }
    
      // 查找链表的中间结点 使用快慢指针处理
      // 一个step为1 另一个step为2
      Link_type GetMidNode()
      {
        Link_type pNodeStep1 = pHeadNode;
        Link_type pNodeStep2 = pHeadNode;
    
        // while(pNodeStep2)
        // {
        //   pNodeStep2 = pNodeStep2->Next;
        //   if(pNodeStep2)
        //   {
        //     pNodeStep2 = pNodeStep2->Next;
        //   }
        //   else
        //   {
        //     break;
        //   }
    
        //   pNodeStep1 = pNodeStep1->Next;
        // }
    
        while(pNodeStep2 && pNodeStep2->Next)
        {
          pNodeStep1 = pNodeStep1->Next;
          pNodeStep2 = pNodeStep2->Next->Next;
        }
    
        return pNodeStep1;
      }
    
      // 求单链表反转 O(n)
      void ReverseList()
      {
        if(pHeadNode->Next == nullptr)
        {
          return;
        }
    
        Link_type pNode = pHeadNode->Next->Next;
        pHeadNode->Next->Next = nullptr;
        _size = 1;
    
        while(pNode)
        {
          Link_type pInsertNode = pNode;
          pNode = pNode->Next;
    
          pInsertNode->Next = pHeadNode->Next;
          pHeadNode->Next = pInsertNode; 
        }
      }
    
      // 查找链表中倒数第k个节点的值( n - k + 1 )
      int GetRePosNodeValue(int k)
      {
        if(k > _size || k <= 0)
        {
          std::cout << "error Not in range" << std::endl; 
          return INVALID_VALUE;
        }
    
        Link_type pNode = findbyindex(_size - k + 1);
    
        if(pNode->Next)
        {
          return pNode->Next->dataEntry;
        }
    
        return INVALID_VALUE;
      }
    
        // 清空链表
      void clear()
      {
        Link_type pNode = pHeadNode;
    
        while(pNode->Next)
        {
          RemoveNext(pNode);
        }
      }
    
      // 打印
      void printf()
      {
        if(_size <= 0)
        {
          return;
        }
    
        Link_type pNode = pHeadNode->Next;
        while(pNode)
        {
          std::cout << pNode->dataEntry << " ";
    
          pNode = pNode->Next;
        }
    
        std::cout << std::endl;
      }
    
    
    private:
      // 查找 (返回前一个指针)
      Link_type findbyvalue(int value)
      {
        Link_type pNode = pHeadNode;
        while(pNode)
        {
          if(pNode->Next && pNode->Next->dataEntry == value)
          {
            return pNode;
          }
    
          pNode = pNode->Next;
        }
    
        return nullptr;
      }
    
      Link_type findbyindex(int index)
      {
        int i = 0;
        Link_type pNode = pHeadNode;
    
        while(pNode) // 查找前一个
        {
          if(++i == index) 
          {
            break;
          }
    
          pNode = pNode->Next;
        }
    
        return pNode;
      }
    
      void RemoveNext(Link_type pNode)
      {
       if(pNode->Next)
        {
          Link_type pNextNext = pNode->Next->Next; 
    
          delete pNode->Next;
          pNode->Next = pNextNext;
          --_size;
        }
      }
    
    private:
      Link_type pHeadNode; // 头节点指针
      unsigned int _size;  // 链表节点个数
    };
    

    测试代码: main.h

    #include "single_list.h"
    
    int main(int argc, char * argv[])
    {
        T_Single_List link_list;
        
        // 前插
        link_list.InsertValueAtHead(1);
        link_list.InsertValueAtHead(2);
        link_list.InsertValueAtHead(3);
        link_list.InsertValueAtHead(4);
        link_list.InsertValueAtHead(5);
    
        std::cout << "The link_list:";
        link_list.printf();
    
        T_Single_List link_list1;
    
        // 尾插
        link_list1.InsertValueAtTail(1);
        link_list1.InsertValueAtTail(2);
        link_list1.InsertValueAtTail(3);
        link_list1.InsertValueAtTail(4);
        link_list1.InsertValueAtTail(5);
    
        std::cout << "The link_list1:";
        link_list1.printf();
    
        std::cout << "get link_list The third node: " << link_list.GetValueByIndex(3) << std::endl;
    
        std::cout << "get link_list The RE sec node: " << link_list.GetRePosNodeValue(2) << std::endl;
    
        std::cout << "get link_list The Mid node: " << link_list.GetMidNode()->dataEntry << std::endl;
    
        std::cout << "get link_list1 The third node: " << link_list1.GetValueByIndex(3) << std::endl;
    
        std::cout << "get link_list1 The RE sec node: " << link_list1.GetRePosNodeValue(2) << std::endl;
    
        std::cout << "get link_list1 The Mid node: " << link_list1.GetMidNode()->dataEntry << std::endl;
    
    
        std::cout << "The RE link_list:";
        link_list.ReverseList();
        link_list.printf();
    
        std::cout << "The RE link_list1:";
        link_list1.ReverseList();
        link_list1.printf();
    
    
        std::cout << "GetValueByIndex:";
    
        std::cout << "get link_list1 The second node: " << link_list1.GetValueByIndex(2) << std::endl;
    
        std::cout << "update:";
    
        link_list1.update(2, 100);
    
        link_list1.deleteValueAtIndex(1);
    
        link_list1.printf();
    
        link_list.clear();
    
        link_list1.clear();
    
        link_list.printf();
    
        link_list1.printf();
    
        return 0;
    }
    

    测试结果:

    bash-4.2$ ./single_list 
    The link_list:5 4 3 2 1 
    The link_list1:1 2 3 4 5 
    get link_list The third node: 3
    get link_list The RE sec node: 2
    get link_list The Mid node: 3
    get link_list1 The third node: 3
    get link_list1 The RE sec node: 4
    get link_list1 The Mid node: 3
    The RE link_list:1 2 3 4 5 
    The RE link_list1:5 4 3 2 1 
    GetValueByIndex:get link_list1 The second node: 4
    update:4 3 100 1 
    

    4. 单链表热门问题

    单链表定义:

     // Definition for singly-linked list.
     struct ListNode {
         int val;
         ListNode *next;
         ListNode(int x) : val(x), next(NULL) {}
      };
    

    1. 合并两个有序链表

    解决思路类似于归并排序,边界情况,主要注意其中一个链表为空及两个链表都为空的情况,空间复杂度O(1),时间复杂度O(max(len1, len2))

    实现代码

    ``` ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) { if(l1 == NULL) { return l2; }
    if(l2 == NULL)
    {
        return l1;
    }
        
    ListNode newNode(-1);
    ListNode* pNode = &newNode;
        
    while(l1 || l2)
    {
        if(l1 == NULL)
        {
            pNode->next = l2;
            break;
        }
            
        if(l2 == NULL)
        {
            pNode->next = l1;
            break;
        }
            
        if(l1->val > l2->val)
        {
            ListNode* pTemp = l2;
            l2 = l2->next;
                
            pTemp->next = pNode->next;
            pNode->next = pTemp;
            pNode = pNode->next;
        }
        else
        {
            ListNode* pTemp = l1;
            l1 = l1->next;
                
            pTemp->next = pNode->next;
            pNode->next = pTemp;
            pNode = pNode->next;
        }
    }
        
    return newNode.next;
    

    }

    
    <p style="font-size: 15px;font-weight: bold;text-indent:2em;letter-spacing:1px; font-family: '微软雅黑';">2. 查找链表的中间结点</p>
    <p style="font-size: 15px;text-indent:2em;letter-spacing:1px; font-family: '微软雅黑';"> 设置两个指针,两个指针同时向前走,一个走两步,一个走一步,当快指针到达链表末尾时,慢指针刚好到中间,刚好可以得到中间节点。空间复杂度<font color="#ff0000">O(1)</font>,时间复杂度<font color="#ff0000">O(n)</font></p>
    
    <p style="font-size: 15px;font-weight: bold;text-indent:2em;letter-spacing:1px; font-family: '微软雅黑';">实现代码</p>
    

    ListNode* middleNode(ListNode* head) {
    ListNode* pNodeStep1 = head; // slow 慢指针
    ListNode* pNodeStep2 = head; // fast 快指针

    while(pNodeStep2 && pNodeStep2->next)
    {
        pNodeStep2 = pNodeStep2->next->next;
        pNodeStep1 = pNodeStep1->next;       
    }
        
    return pNodeStep1;   
    

    }

    
    <p style="font-size: 15px;font-weight: bold;text-indent:2em;letter-spacing:1px; font-family: '微软雅黑';">3. 求两个单链表相交的第一个节点</p>
    <p style="font-size: 15px;text-indent:2em;letter-spacing:1px; font-family: '微软雅黑';"> 首先,我们遍历一下两个链表获取链表长度lengthA,lengthB。然后对于长度较长的链表,先向前走|lengthA-lengthB|的步数,接着两个链表一起往前走,直到找到第一个地址相同的节点,任务完成。空间复杂度<font color="#ff0000">O(1)</font>,时间复杂度<font color="#ff0000">O(n)</font></p>
    
    <p style="font-size: 15px;font-weight: bold;text-indent:2em;letter-spacing:1px; font-family: '微软雅黑';">实现代码</p>
    

    ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
    if(headA == NULL || headB == NULL)
    {
    return NULL;
    }

    int lengthA = 0;
    int lengthB = 0;
    
    ListNode *travelNodeA = headA;
    ListNode *travelNodeB = headB; 
    
    while(travelNodeA)
    {
        ++lengthA;
        travelNodeA = travelNodeA->next;
    }
    
    while(travelNodeB)
    {
        ++lengthB;
        travelNodeB = travelNodeB->next;
    }
    
    travelNodeA = headA;
    travelNodeB = headB; 
    
    // offset = |lengthB - lengthA|
    int offset;
    if(lengthB > lengthA)
    {
        offset = lengthB - lengthA;
        
        while(offset > 0)
        {
            travelNodeB = travelNodeB->next;
            --offset;
        }
    }
    else
    {
        offset = lengthA - lengthB;
        
        while(offset > 0)
        {
            travelNodeA = travelNodeA->next;
            --offset;
        }
    }
    
    
    while(travelNodeA)
    {
        if(travelNodeA == travelNodeB)
        {
            return travelNodeA;
        }
        
        travelNodeA = travelNodeA->next;
        travelNodeB = travelNodeB->next;
    }
    
    return NULL;
    

    }

    
    <p style="font-size: 15px;font-weight: bold;text-indent:2em;letter-spacing:1px; font-family: '微软雅黑';">4. 给出一单链表头指针pHead和一节点指针pToBeDeleted,O(1)时间复杂度删除节点pToBeDeleted</p>
    <p style="font-size: 15px;text-indent:2em;letter-spacing:1px; font-family: '微软雅黑';"> 主要思路,对于单链表,想要删除某个节点,那我们必须知道他的前一个节点,然后通过改变前一个节点的next指针来将该节点删除,而又因为链表中保存的dataEntry结构是一样的 ,都为整形,因此我们可以将该节点和他的后一个节点数据进行交换,使得该节点变成前一个节点,接着通过删除后一个节点的方式,即完成了该节点的删除。空间复杂度<font color="#ff0000">O(1)</font>,时间复杂度<font color="#ff0000">O(1)</font></p>
    
    <p style="font-size: 15px;font-weight: bold;text-indent:2em;letter-spacing:1px; font-family: '微软雅黑';">实现代码</p>
    

    void deleteNode(ListNode* node) {
    if(node == NULL || node->next == NULL)
    {
    return;
    }

    ListNode* pNode = node->next;
    
    node->val = pNode->val;    
    node->next = pNode->next;
    
    delete pNode;
    pNode = NULL;
    

    }

    
    <p style="font-size: 15px;font-weight: bold;text-indent:2em;letter-spacing:1px; font-family: '微软雅黑';">5. 链表成环的问题</p>
    
    <p style="font-size: 15px;text-indent:2em;letter-spacing:1px; font-family: '微软雅黑';">1. 判断一个链表中是否有环存在</p>
    <p style="font-size: 15px;text-indent:2em;letter-spacing:1px; font-family: '微软雅黑';"> 主要思路,也是使用两个指针去遍历,一个指针一次走两步,一个指针一次走一步,如果有环,两个指针肯定会在环中相遇。空间复杂度<font color="#ff0000">O(1)</font>,时间复杂度<font color="#ff0000">O(1)</font></p>
    
    <p style="font-size: 15px;font-weight: bold;text-indent:2em;letter-spacing:1px; font-family: '微软雅黑';">实现代码</p>
    

    bool hasCycle(ListNode *head) {
    if(head == NULL || head->next == NULL)
    {
    return false;
    }

    ListNode *step1Node = head; // slow
    ListNode *step2Node = head; // fast
    
    while(step2Node && step2Node->next)
    {
        step2Node = step2Node->next->next;
        step1Node = step1Node->next;
        
        if(step2Node == step1Node)
        {
            return true;
        }
    }
    
    return false;
    

    }

    ------
    
    <p style="font-size: 15px;text-indent:2em;letter-spacing:1px; font-family: '微软雅黑';">2. 如果存在环,找出环的入口点;</p>
    ![链表成环示意图](https://www.cnblogs.com/images/cnblogs_com/blog-yejy/1281855/o_71135105-file_1483521194130_8c39.png)
    
    <p style="font-size: 15px;text-indent:2em;letter-spacing:1px; font-family: '微软雅黑';">对于链表成环的问题,最关键的一点就在于如何获取到环的入口点,如果获取到了,那关于环的各种信息,都可以很容易的得到,那么,我们来分析一下入口节点的获取思路</p>
    <p style="font-size: 15px;text-indent:2em;letter-spacing:1px; font-family: '微软雅黑';">我们使用快慢节点可以知道链表是否有环,因为快慢节点一直往前走的话,是肯定会相遇的,但是具体相遇在哪里,入口点是哪里呢,<font color="#ff0000">首先我们可以确定的一点是,慢指针在跑完一圈的过程中,快指针是肯定会和慢指针相遇的</font>,因为快指针速度是慢指针的两倍,想象一下如果所有节点都在环内,那就和跑圈是一样的了,跑圈最终是在起点的地方相遇,示意图则是过程中相遇。</p>
    <p style="font-size: 15px;text-indent:2em;letter-spacing:1px; font-family: '微软雅黑';">那么我们在这个前提下进行分析,根据示意图,我们假设图中 <font color="#ff0000">m1</font> 为相遇点,那慢节点走过的距离为 <font color="#ff0000">a+b</font>,这个时候快节点绕圈多少次呢,我们不知道,假设为 <font color="#ff0000">n</font> 次,环一周距离为 <font color="#ff0000">k</font>,那我们可以得出这个等式:</p>
    
    <p style="font-size: 15px;text-indent:2em;letter-spacing:1px; font-family: '微软雅黑';"><font color="#ff0000">(a + b) * 2 = a + b + n * k     (慢指针跑过距离的两倍等于快指针跑过的距离)</font></p>
    
    <p style="font-size: 15px;text-indent:2em;letter-spacing:1px; font-family: '微软雅黑';">我们对等式变一下形,因为我们要求入口节点,那么首先需要得到 <font color="#ff0000">a</font>   的等式分析一下</p>
    
    <p style="font-size: 15px;text-indent:2em;letter-spacing:1px; font-family: '微软雅黑';"><font color="#ff0000">a = n * k - b  </font></p>
    
    <p style="font-size: 15px;text-indent:2em;letter-spacing:1px; font-family: '微软雅黑';">好像还是看不出来,只知道 <font color="#ff0000">a+b</font> 刚好等于一圈的 <font color="#ff0000">n</font> 倍,我们再换个样子看一下</p>
    
    <p style="font-size: 15px;text-indent:2em;letter-spacing:1px; font-family: '微软雅黑';"><font color="#ff0000">a = (n-1) * k  + (k - b)  </font></p>
    
    <p style="font-size: 15px; font-weight: bold;text-indent:2em;letter-spacing:1px; font-family: '微软雅黑';">那现在呢,因为我们链表是顺一个方向走的,而 <font color="#ff0000">a</font> 的距离刚好是从头节点到入口点的距离,而 <font color="#ff0000">k - b</font>,刚好是相遇点,走到入口点的距离,因为我们在实现链表是否成环中已经找到了相遇点,那么我们改变一下思路,<font color="#ff0000">我们不走两步,走一步,让一个指针从相遇点开始一步一步往前走,让另一个指针从头节点开始一步一步往前走,最后是不是在入口点就相遇了,只是从相遇点开始走的那个指针可能是多次经过了入口点而已。</font></p>
    
    <p style="font-size: 15px;font-weight: bold;text-indent:2em;letter-spacing:1px; font-family: '微软雅黑';">实现代码</p>
    

    ListNode *detectCycle(ListNode *head) {
    if(head == NULL || head->next == NULL)
    {
    return NULL;
    }

    ListNode *step1Node = head; // slow
    ListNode *step2Node = head; // fast
    
    while(step2Node && step2Node->next)
    {
        step2Node = step2Node->next->next;
        step1Node = step1Node->next;
    
        if(step2Node == step1Node)
        {
            ListNode *startNode = head;
            ListNode *meetNode = step1Node;
            while(startNode)
            {   
                if(meetNode == startNode)
                {
                    return meetNode;
                }
                startNode = startNode->next;
                meetNode = meetNode->next;
            }
        }
    }
    
      return NULL;
    

    }

    
    <p style="font-size: 15px;text-indent:2em;letter-spacing:1px; font-family: '微软雅黑';">实现都是这两天在leetcode上刷的,有关链表的问题上面基本都有,如果想要锻炼一下的话,可以去上面尝试一下!! </p>
    <p style="font-size: 15px;text-indent:45em;letter-spacing:1px; font-family: '微软雅黑';"> 2018年8月25日23:16:37 </p>
  • 相关阅读:
    Visual Studio2019安装步骤
    写在第一页的话
    数状数组
    hdu 3501 数学题
    静态邻接表
    最长子序列
    hdu 1094 所想到的
    bellman_ford
    郁闷的一晚
    SPFA + 静态邻接表 模板
  • 原文地址:https://www.cnblogs.com/blog-yejy/p/9508114.html
Copyright © 2011-2022 走看看