zoukankan      html  css  js  c++  java
  • 单链表

    前言

    顺序表是用一组地址连续的存储单元来保存数据的,所以它具有随机存取的特点。即查找快速,但是做插入或删除动作是,需要移动大量元素,效率较低。
    链表是线性表的链式存储结构,它相比于顺序表,在插入和删除元素时,效率要高很多。
    每个数据单元有两部分组成,一个是数据域,存储数据值;另一个是指针域,指向下一个数据单元。这样的数据单元叫做结点。
    链表可分为单链表、双链表、循环链表。

    基本算法

    • 创建链表
      顺序表存储结构的创建就是数组的初始化,即声明一个类型和大小的数组并赋值。单链表是一种松散的存储结构,动态的,同时指针域,必须为指针域赋值。创建链表的过程就是动态生成链表的过程。从空表开始,依次建立各个元素结点,逐个插入链表。
      头插法:采用插队的方式,始终让新建结点在第一的位置上。类似堆栈。
      这里写图片描述
      假设头指针后有数据,往里插入结点即可。
     1 LNode* pTempHead = *ppHead; 
     2 pTempHead->pNext = NULL;
     3 for (int i= 0;i<num;i++)
     4 {
     5     LNode* item = new LNode();//生成新的结点
     6     item->data = i;
     7     //这是头插法,就是在链表顶端往下插入
     8     item->pNext = pTempHead->pNext;
     9     pTempHead->pNext = item;//插入到表头,新插入的结点在表头   
    10 
    11 }
    头插法

    数组数据显示是:4,3,2,1,0
    尾插法:每次新的结点都插在终端结点的后面。

     1 LNode* pTempHead = *ppHead;
     2     //建立一个带头结点的单链表
     3     for (int i= 0;i<num;i++)
     4     {
     5         LNode* item = new LNode();//生成新的结点
     6         item->data = i;     
     7         pTempHead->pNext = item;//将表尾终端结点的指针指向新的结点,此时pphead中添加了结点,item在最后端
     8         pTempHead = item;//继续让pTempHead指向ppHead的终端。
     9 
    10     }   
    11     pTempHead->pNext = NULL;//尾插法是在这设置为null
    尾插法

    链表数据显示:0,1,2,3,4.
    - 插入结点
    核心算法就是如图中
    这里写图片描述
    现将p的后继结点保存,再赋值给s的后继结点,就成了s->Next = p->Next 。此时将s添加到链表中:p->Next = s; 单链表的表头和表尾的特殊情况,操作时相同的。

     1 //插入元素
     2 void SingleList::InsertElem(LNode** ppHead,int pos,int data)
     3 {
     4     pLinkList pTempHead = *ppHead;//指向需要操作的数据链
     5     if (pTempHead == NULL || pTempHead->pNext == NULL)
     6     {
     7         return;
     8     }
     9     pLinkList item;
    10     int i = 0;
    11     //选找到制定位置的结点,有效的可以在表尾和表头插入结点
    12     while(pTempHead && i< pos)
    13     {
    14         pTempHead = pTempHead->pNext;
    15         i++;
    16     }
    17     //位置不合适。有可能到了表尾此处不能为pTempHead->pNext,当为表尾是next没有分配内存,导致溢出。
    18     if (!pTempHead ||i>pos)
    19     {
    20         return;
    21     }
    22     //进行插入操作
    23     item = new LNode();
    24     if (!item)
    25     {
    26         return;
    27     }
    28     item->data = data;
    29     item->pNext = pTempHead->pNext;
    30     pTempHead->pNext = item;
    31 
    32 }
    插入元素
    • 删除结点
      删除结点就是将他的前级结点的指针绕过即可。实际上就一步:p->next = p->next-next;同时释放点指针。
      这里写图片描述
     1 //删除元素
     2 void SingleList::RemoveElem(LNode** ppHead,int pos,int* getData)
     3 {
     4     pLinkList pTempHead = *ppHead;//指向需要操作的数据链
     5     if (pTempHead == NULL || pTempHead->pNext == NULL)
     6     {
     7         return;
     8     }
     9     pLinkList item;
    10     int i = 0;
    11     //选找到制定位置的结点,此处是pTempHead->pNext,有效的能删除表尾,防止访问到表尾的next
    12     while(pTempHead->pNext && i< pos)
    13     {
    14         pTempHead = pTempHead->pNext;
    15         i++;
    16     }
    17     //位置不合适。有可能到了表尾,如果和插入时为ptempHead,此时item的item->next会报错
    18     if (!pTempHead->pNext ||i>pos)
    19     {
    20         return;
    21     }
    22     item = pTempHead->pNext;
    23     *getData = item->data;
    24     //item->pNext = pTempHead->pNext;
    25     pTempHead->pNext = item->pNext;
    26     //释放资源
    27     delete item;
    28 }
    删除结点

    在删除结点和插入结点时,注意循环跳出的条件,两者不一样。

    代码实例

    返回类型函数名称参数功能
    void InitNode (LNode** ppHead) 初始化结构体
    void CreateList (LNode** ppHead,int num) 创建链表
    void DestroyList (LNode** ppHead) 清空链表
    void InsertElem (LNode** ppHead,int pos,int data) 插入元素
    void RemoveElem (LNode** ppHead,int pos,int* getData) 移除元素
    bool IsEmptyList (LNode* pHead) 是否为空
    int GetElem (LNode* pHead,int pos,int* data) 获取制定位置元素
    void PrintList (LNode* pHead) 输出链表
    void ReverseList (LNode* pHead,LNode ** outList) 反转链表
      1 void SingleList::InitNode(LNode** ppHead)
      2 {
      3     if (*ppHead)
      4     {
      5         DestroyList(ppHead);
      6     }
      7     LNode* p = new LNode();
      8     p->data = 0;
      9     p->pNext = NULL;
     10     *ppHead = p;
     11 }
     12 //创建链表
     13 void SingleList::CreateList(LNode** ppHead,int num)
     14 {
     15     InitNode(ppHead);
     16     LNode* pTempHead = *ppHead;
     17     //建立一个带头结点的单链表
     18     //pTempHead = new LNode();
     19     //pTempHead->pNext = NULL;
     20     for (int i= 0;i<num;i++)
     21     {
     22         LNode* item = new LNode();//生成新的结点
     23         item->data = i;
     24         //这是头插法,就是在链表顶端往下插入
     25         //item->pNext = pTempHead->pNext;
     26         //pTempHead->pNext = item;//插入到表头,新插入的结点在表头
     27         //这是头插法
     28         //item->pNext = pTempHead;//将表尾终端节点的指针指向新的结点,此时item是多个点
     29         //pTempHead = item;//将item赋值给item
     30         pTempHead->pNext = item;
     31         pTempHead = item;
     32 
     33     }
     34 
     35     pTempHead->pNext = NULL;//尾插法是在这设置为null
     36     //*ppHead = pTempHead;
     37 }
     38 //销毁链表
     39 void SingleList::DestroyList(LNode** ppHead)
     40 {
     41     pLinkList pTempHead = *ppHead;
     42     //前期判断,已经为空,返回
     43     if (pTempHead == NULL||pTempHead->pNext == NULL)
     44     {
     45         return;
     46     }
     47     pLinkList item,item1;
     48     item = pTempHead->pNext;
     49     while(item)//没到表尾
     50     {
     51         item1= item->pNext;
     52         delete item;
     53         item = item1;
     54     }
     55     pTempHead->pNext = NULL;
     56 }
     57 //插入元素
     58 void SingleList::InsertElem(LNode** ppHead,int pos,int data)
     59 {
     60     pLinkList pTempHead = *ppHead;//指向需要操作的数据链
     61     if (pTempHead == NULL || pTempHead->pNext == NULL)
     62     {
     63         return;
     64     }
     65     pLinkList item;
     66     int i = 0;
     67     //选找到制定位置的结点
     68     while(pTempHead && i< pos)
     69     {
     70         pTempHead = pTempHead->pNext;
     71         i++;
     72     }
     73     //位置不合适。有可能到了表尾此处不能为pTempHead->pNext,当为表尾是next没有分配内存,导致溢出。
     74     if (!pTempHead ||i>pos)
     75     {
     76         return;
     77     }
     78     //进行插入操作
     79     item = new LNode();
     80     if (!item)
     81     {
     82         return;
     83     }
     84     item->data = data;
     85     item->pNext = pTempHead->pNext;
     86     pTempHead->pNext = item;
     87 
     88 }
     89 //删除元素
     90 void SingleList::RemoveElem(LNode** ppHead,int pos,int* getData)
     91 {
     92     pLinkList pTempHead = *ppHead;//指向需要操作的数据链
     93     if (pTempHead == NULL || pTempHead->pNext == NULL)
     94     {
     95         return;
     96     }
     97     pLinkList item;
     98     int i = 0;
     99     //选找到制定位置的结点,此处是pTempHead->pNext,有效的能删除表尾,防止访问到表尾的next
    100     while(pTempHead->pNext && i< pos)
    101     {
    102         pTempHead = pTempHead->pNext;
    103         i++;
    104     }
    105     //位置不合适。有可能到了表尾,如果和插入时为ptempHead,此时item的item->next会报错
    106     if (!pTempHead->pNext ||i>pos)
    107     {
    108         return;
    109     }
    110     item = pTempHead->pNext;
    111     *getData = item->data;
    112     //item->pNext = pTempHead->pNext;
    113     pTempHead->pNext = item->pNext;
    114     //释放资源
    115     delete item;
    116 }
    117 //判断单链表是否为空
    118 bool SingleList::IsEmptyList(LNode* pHead)
    119 {
    120     if (NULL == pHead||NULL == pHead->pNext)
    121     {
    122         return true;
    123     }
    124     else
    125         return false;
    126 }
    127 //获取单链表位置为pos的元素,从头开始找,直到第i元素为止,时间复杂度为N。
    128 int SingleList::GetElem(LNode* pHead,int pos,int* data)
    129 {
    130     int j = 1;//计数器
    131     if (pHead == NULL||pos<1)
    132     {
    133         return -1;
    134     }
    135     pLinkList item;//声明一个结点
    136     item = pHead->pNext;//指向头结点
    137     while(item && j<pos)
    138     {
    139         item =pHead->pNext;
    140         j++;
    141     }
    142     *data = item->data;
    143     return 1;
    144 }
    145 
    146 #pragma endregion
    147 
    148 #pragma region 链表扩展操作
    149 //从尾到头打印链表
    150 void SingleList::PrintList(LNode* pHead)
    151 {
    152     //此处是反转打印链表,可以利用堆栈
    153     stack<LNode*> stackList ;
    154     pLinkList item = pHead;
    155     while(item != NULL)
    156     {
    157         //压入堆栈
    158         stackList.push(item);
    159         item = item->pNext;
    160     }
    161     while(!stackList.empty())
    162     {
    163         //导出数据
    164         item = stackList.top();
    165         cout<<item->data;
    166         stackList.pop();
    167     }
    168 }
    169 
    170 void SingleList::PrintList1(LNode* pHead)
    171 {
    172     if (NULL == pHead || NULL == pHead->pNext) {
    173 
    174         printf("LinkList is empty
    ");
    175 
    176         return;
    177 
    178     }
    179 
    180     LNode *p = pHead->pNext;
    181 
    182     printf("LinkList:");
    183 
    184     while (p) {
    185 
    186         printf(" %d", p->data);
    187 
    188         p = p->pNext;
    189 
    190     }
    191 
    192     printf("
    ");
    193 }
    194 //反转链表
    195 void SingleList::ReverseList(LNode* pHead,LNode ** outList)
    196 {
    197     if (pHead == NULL||pHead->pNext == NULL)
    198     {
    199         return;
    200     }
    201     pLinkList pTempHead = *outList;
    202     pTempHead = new LNode();
    203     pLinkList pCurrent = pHead;
    204     while(pCurrent)
    205     {
    206         LNode * item = pCurrent;
    207         //采用尾插法,创建链表即可;
    208         item->pNext = pTempHead;
    209         pTempHead = item;
    210         pCurrent = pCurrent->pNext;
    211     }   
    212 
    213 }
    代码实例

    面试题

    查找单链表中的倒数第K个结点(k > 0)
    思路:使用两个指针,先让前面的指针走到正向第k个结点,这样前后两个指针的距离差是k-1,之后前后两个指针一起向前走,前面的指针走到最后一个结点时,后面指针所指结点就是倒数第k个结点。

    // 查找单链表中倒数第K个结点
     1 LNode * RGetKthNode(LNode * pHead, unsigned int k) // 函数名前面的R代表反向
     2 {
     3     if(k == 0 || pHead == NULL) // 这里k的计数是从1开始的,若k为0或链表为空返回NULL
     4         return NULL;
     5 
     6     LNode * pAhead = pHead;
     7     LNode * pBehind = pHead;
     8     while(k > 1 && pAhead != NULL) // 前面的指针先走到正向第k个结点
     9     {
    10         pAhead = pAhead->m_pNext;
    11         k--;
    12     }
    13     if(k > 1 || pAhead == NULL)     // 结点个数小于k,返回NULL
    14         return NULL;
    15     while(pAhead->m_pNext != NULL)  // 前后两个指针一起向前走,直到前面的指针指向最后一个结点
    16     {
    17         pBehind = pBehind->m_pNext;
    18         pAhead = pAhead->m_pNext;
    19     }
    20     return pBehind;  // 后面的指针所指结点就是倒数第k个结点
    21 }
    View Code
     

    查找单链表的中间结点
    思路:设置两个指针,只不过这里是,两个指针同时向前走,前面的指针每次走两步,后面的指针每次走一步,前面的指针走到最后一个结点时,后面的指针所指结点就是中间结点。

     1 这里写代码片// 获取单链表中间结点,若链表长度为n(n>0),则返回第n/2+1个结点
     2 ListNode * GetMiddleNode(LNode * pHead)
     3 {
     4     if(pHead == NULL || pHead->m_pNext == NULL) // 链表为空或只有一个结点,返回头指针
     5         return pHead;
     6 
     7     L * pAhead = pHead;
     8     L * pBehind = pHead;
     9     while(pAhead->m_pNext != NULL) // 前面指针每次走两步,直到指向最后一个结点,后面指针每次走一步
    10     {
    11         pAhead = pAhead->m_pNext;
    12         pBehind = pBehind->m_pNext;
    13         //防止表尾,内存溢出.走了两步
    14         if(pAhead->m_pNext != NULL)
    15             pAhead = pAhead->m_pNext;
    16     }
    17     return pBehind; // 后面的指针所指结点即为中间结点
    18 }
    View Code

    已知两个单链表pHead1 和pHead2 各自有序,把它们合并成一个链表依然有序

     1 // 合并两个有序链表
     2 ListNode * MergeSortedList(ListNode * pHead1, ListNode * pHead2)
     3 {
     4     //如果head1为空,返回head2
     5     if(pHead1 == NULL)
     6         return pHead2;
     7     //如果head2为空,返回head1,这些处理防止后面访问链表中元素时,内存溢出
     8     if(pHead2 == NULL)
     9         return pHead1;
    10     ListNode * pHeadMerged = NULL;
    11     //比较大小,确定合并链表的表头指向谁。
    12     if(pHead1->m_nKey < pHead2->m_nKey)
    13     {
    14         pHeadMerged = pHead1;
    15         pHeadMerged->m_pNext = NULL;
    16         pHead1 = pHead1->m_pNext;
    17     }
    18     else
    19     {
    20         pHeadMerged = pHead2;
    21         pHeadMerged->m_pNext = NULL;
    22         pHead2 = pHead2->m_pNext;
    23     }
    24     ListNode * pTemp = pHeadMerged;
    25     //开始向合并链表中添加节点
    26     while(pHead1 != NULL && pHead2 != NULL)
    27     {
    28         if(pHead1->m_nKey < pHead2->m_nKey)
    29         {
    30             //head1中元素小,通过尾插法,将小的结点先放在链表尾。
    31             //将head1插入链表中
    32             pTemp->m_pNext = pHead1;
    33             //head1下移,继续循环
    34             pHead1 = pHead1->m_pNext;
    35             //让temp始终指向表尾,也是就next
    36             pTemp = pTemp->m_pNext;
    37             //表尾设置为null.
    38             pTemp->m_pNext = NULL;
    39         }
    40         else
    41         {
    42             pTemp->m_pNext = pHead2;
    43             pHead2 = pHead2->m_pNext;
    44             pTemp = pTemp->m_pNext;
    45             pTemp->m_pNext = NULL;
    46         }
    47     }
    48     if(pHead1 != NULL)
    49         pTemp->m_pNext = pHead1;
    50     else if(pHead2 != NULL)
    51         pTemp->m_pNext = pHead2;
    52     return pHeadMerged;
    53 }
    View Code

    版权声明:本文为博主原创文章,未经博主允许不得转载。

  • 相关阅读:
    Spring容器启动前传——web应用
    三方依赖bean初始化导致项目启动失败问题
    一个静态内部类单例引发的思考
    依赖传递
    kafka
    设置旋转元素的基点位置
    keep-alive 的 Props && 钩子函数
    css处理文字: 单行居中 多行居左
    localStorage 存 取 删
    for循环中暂停,async await
  • 原文地址:https://www.cnblogs.com/polly333/p/4705658.html
Copyright © 2011-2022 走看看