【编织时空二:探究顺序表与链表的数据之旅】(下)

简介: 【编织时空二:探究顺序表与链表的数据之旅】

【编织时空二:探究顺序表与链表的数据之旅】(上):https://developer.aliyun.com/article/1424871


有问题,我们发现将 tail 置为空后,但是3结点位置的 next 并没有置为空,那么就会出现野指针的问题。解决这个问题的关键就是将3结点位置的 next置为空.


方法一:创建新结点,让这个结点的位置的 next 等于 tail

// 单链表的尾删
void SListPopBack(SLTNode** pplist)
{
    assert(pplist); 
    //1.链表为空
  assert(*pplist != NULL);
  //2.链表只剩下一个元素
  if ((*pplist)->next == NULL)
  {
    free(*pplist);
    *pplist = NULL;
  }
  //3.链表有多个元素
  else
  {
    SLTNode* tailPrev = NULL;
    SLTNode* tail = *pplist;
    while (tail->next!= NULL)
    {
      tailPrev = tail;
      tail = tail->next;
    }
    free(tail);
    tailPrev->next = NULL;
  }
}


方法二:不创建新结点,使用 tail->next->next找到3结点位置

// 单链表的尾删
void SListPopBack(SLTNode** pplist)
{
    assert(pplist); 
    //1.链表为空
  assert(*pplist != NULL);
  //2.链表只剩下一个元素
  if ((*pplist)->next == NULL)
  {
    free(*pplist);
    *pplist = NULL;
  }
  //3.链表有多个元素
  else
  {
    SLTNode* tail = *pplist;
    while (tail->next->next != NULL)
    {
      tail = tail->next;
    }
    free(tail->next);
    tail->next = NULL;
  }
}


(5)、单链表头删:void SListPopFront(SLTNode** pplist)


单链表如何进行头删呢?头删需要找到头结点的 next ,将其  next 用一个新结点 newhead 保存起来,然后释放原来的头结点,再将 newhead 赋给 pplist 即可。

// 单链表头删
void SListPopFront(SLTNode** pplist)
{
    assert(pplist);
  //空
  assert(*pplist);
  //非空
  SLTNode* newhead = (*pplist)->next;
  free(*pplist);
  *pplist = newhead;
}


(6)、单链表查找:SLTNode* SListFind(SLTNode* plist, SLTDataType x)


要查找某个数字的在链表中的位置,需要遍历链表,让 tail指向空的位置,这样链表中的每个数据都能被访问到,就能查找的数字的在链表中的位置。

// 单链表查找
SLTNode* SListFind(SLTNode* plist, SLTDataType x)
{
  SLTNode* cur = plist;
  while (cur != NULL)
  {
    if (cur->data == x)
      return cur;
    cur = cur->next;
  }
  //没找到数据
  return NULL;
}



(7)、单链表在pos位置之前插入x:void SListInsert(SLTNode** plist, SLTNode* pos, SLTDataType x)


我们这里设置plist为二级指针,因为pos位置可能会头插,需要改变头结点指向的结点。

// 单链表在pos位置之前插入x
void SListInsert(SLTNode** plist, SLTNode* pos, SLTDataType x)
{
    assert(plist);
  assert(pos);
  SLTNode* newnode = BuySListNode(x);
  //头插
  if (pos == *plist)
  {
    newnode->next = *plist;
    *plist = newnode; 
  }
  //中间插入,不可能尾插
  else
  {
    SLTNode* posPrev = *plist;
    while (posPrev->next != pos)
    {
      posPrev = posPrev->next;
    }
    newnode->next = posPrev->next;
    posPrev->next = newnode;
    /*posPrev->next = newnode;
    newnode->next = pos;*/
  }
}


单链表在pos位置之前插入x,有两种情况,一种是头插,一种是在中间插入。头插可以复用之前的函数,但是注意传入的参数,中间插入的话首先要找到pos位置前一个结点posPrev,将posPrev位置的next指向要插入的结点,再将要插入的结点的next给到pos,即可完成连接。


(8)、单链表在pos位置之后插入x:void SListInsertAfter(SLTNode* pos, SLTDataType x)


在pos位置之后插入x不会出现头插的现象,所以这里我们不会改变plist,所以这个参数也就不用掺入了

// 单链表在pos位置之后插入x
void SListInsertAfter(SLTNode* pos, SLTDataType x)
{
  assert(pos);
  SLTNode* newnode = BuySListNode(x);
  newnode->next = pos->next;
  pos->next = newnode;
}


(9)、删除pos位置的值:void SListErase(SLTNode* pos, SLTDataType x);


单链表删除pos位置的值,有两种情况,一种是头删,一种是在中间删除。头删可以复用之前的函数,但是注意传入的参数,中间删除的话首先要找到pos位置前一个结点posPrev,将posPrev位置的next指向pos位置的next,即可完成删除。

// 删除pos位置的值
void SListErase(SLTNode** plist, SLTNode* pos)
{
  assert(plist);
    assert(pos);
  if (*plist == pos)
  {
    SLTNode* Next = pos->next;
    free(pos);
    *plist = Next;
  }
  else
  {
    SLTNode* posPrev = *plist;
    while (posPrev->next != pos)
    {
      posPrev = posPrev->next;
    }
    posPrev->next = pos->next;
    free(pos);
  }
}


(10)、单链表删除pos位置之后的值:void SListEraseAfter(SLTNode* pos)


这个函数有个缺点:不能删头,同时pos为尾结点,那么此时删除pos位置之后的值就无意义

// 单链表删除pos位置之后的值
void SListEraseAfter(SLTNode* pos)
{
  assert(pos);
  //删除pos位置之后的值就无意义
  if (pos->next == NULL)
    exit(-1);
  SLTNode* posNext = pos->next;
  pos->next = posNext->next;
  free(posNext);
}


这里我们为什么不写成pos->next = pos->next->next呢?为什么还要传教一个posNext结点呢?


因为如果我们写成pos->next = pos->next->next,那么删除的那个结点我们就丢失了,无法找到,就会早成内存泄露的问题。


(11)、单链表打印:void SListPrint(SLTNode* plist);


第一步:输出第一个节点的数据域,输出完毕后,让指针保存后一个节点的地址

第二步:输出移动地址对应的节点的数据域,输出完毕后,指针继续后移

第三步:以此类推,直到节点的指针域为NULL

// 单链表打印
void SListPrint(SLTNode* plist)
{
  SLTNode* cur = plist;
  while (cur != NULL)
  {
    printf("%d->", cur->data);
    cur = cur->next;
  }
  printf("NULL\n");
}


(12)、单链表销毁:void SListDestroy(SLTNode** pplist);


单链表的销毁比较简单,只保存cur下一个位置next,然后将当前的cur结点释放,再将cur移动到下一个结点的位置next,依次遍历直至cur为空即可完成销毁。

void SListDestroy(SLTNode** pplist)
{
  assert(pplist);
  SLTNode* cur = *pplist;
  SLTNode* next = NULL;
  while (cur)
  {
    next = cur->next;
    free(cur);
    cur = next;
  }
  *pplist = NULL;
}

相关文章
|
1月前
|
存储
顺序表和链表(2)
【10月更文挑战第23天】
顺序表和链表(2)
|
1月前
|
存储 算法 数据管理
顺序表和链表(1)
【10月更文挑战第22天】
|
2月前
|
存储 安全 Java
【用Java学习数据结构系列】探索顺序表和链表的无尽秘密(附带练习唔)pro
【用Java学习数据结构系列】探索顺序表和链表的无尽秘密(附带练习唔)pro
28 3
|
6月前
|
存储 缓存 算法
数据结构和算法学习记录——总结顺序表和链表(双向带头循环链表)的优缺点、CPU高速缓存命中率
数据结构和算法学习记录——总结顺序表和链表(双向带头循环链表)的优缺点、CPU高速缓存命中率
56 0
|
4月前
|
存储 算法
【初阶数据结构篇】顺序表和链表算法题
此题可以先找到中间节点,然后把后半部分逆置,最近前后两部分一一比对,如果节点的值全部相同,则即为回文。
39 0
|
4月前
|
存储 缓存
【数据结构】——顺序表与链表
【数据结构】——顺序表与链表
|
6月前
|
存储 索引
顺序表和链表
通过以上示例,我们可以看到顺序表和链表在实际应用中如何操作。顺序表适合于需要频繁读取数据的场景,而链表则更适用于频繁修改数据的情况。在选择使用哪种数据结构时,应考虑到实际应用的需求和上下文环境。
43 2
|
6月前
|
存储 缓存
【海贼王的数据航海】链表—双向链表
【海贼王的数据航海】链表—双向链表
31 0
|
6月前
|
存储
【海贼王的数据航海】链表—单链表
【海贼王的数据航海】链表—单链表
37 0
|
6月前
|
存储 SQL 算法
LeetCode力扣第114题:多种算法实现 将二叉树展开为链表
LeetCode力扣第114题:多种算法实现 将二叉树展开为链表