单链表 的认识与实现

目录

1 单链表

1.1 链表优点

1.2 缺点

1.3 分析

1.3.1 尾插数据不同情况的分析

1.4 实现

1.4.1 创建节点

1.4.2 尾插

1.4.3 头插

1.4.4 尾删

1.4.5 头删

1.4.6 查找

1.4.7 在pos之前插入x

1.4.8 在pos之后插入x

1.4.9 删除pos后的一个节点

1.4.10 删除pos处节点

1.4.11 销毁链表


1 单链表

1.1 链表优点

  • 按需申请空间,需要就申请,不需要就释放

  • 头部或中间插入数据,不需要挪动数据

  • 不存在空间浪费

1.2 缺点

  • 每次存放一个数据,到要存一个指针去链接后面的数据节点

  • 不支持随机访问(用下标直接访问某i个)

1.3 分析

img

img

*cur->next指向下一个数据,然后cur->data指向下一个数据的数值

该示例中结构体 存储1个数值+下一个节点的地址

当下一个节点的地址为NULL,则结束

1.3.1 尾插数据不同情况的分析

123节点后插入一个新节点44后指向空指针

img

 void SLtPushBack(SLT* phead, SLTDataType x)
 {
     //    找尾节点
     SLT* tail = phead;
     while (tail->next != NULL)
     {
         tail = tail->next;
     }
     SLT* newcode = (SLT*)malloc(sizeof(SLT));
     newcode->data = x;
     newcode->next = NULL;
 ​
     tail->next = newcode;
 }

注意:mallocsizeof后应为SLT,是整个结构体类型大小

如果这样写代码,则会 报错

img 因为 tail 本就是NULL空,-> 操作是解引用,对空的tail解引用非法访问


如果这么写:

 void SLtPushBack(SLT* phead, SLTDataType x)
 {
     //    找尾节点
     SLT* newcode = (SLT*)malloc(sizeof(SLT));
     newcode->data = x;
     newcode->next = NULL;
     if(phead==NULL)
     {
         phead=newnode;
     }
     else
     {
         SLT* tail = phead;
     while (tail->next != NULL)
     {
         tail = tail->next;
     }
     tail->next = newcode;
     }
 }

改变了形参phead,未改变实参plist

img

实参是int,要改变a,要传int*,解引用找到a改变

img

实参是int*,要传int**,解引用找到int


同理:

 void SLtPushBack(SLT** pphead, SLTDataType x)
 {
     //    找尾节点
     SLT* newnode = (SLT*)malloc(sizeof(SLT));
     newnode->data = x;
     newnode->next = NULL; 
 ​
     if (*pphead == NULL)
     {
         *pphead = newnode;    // 解引用
     }
     else
     {
         SLT* tail = *pphead;
         while (tail->next != NULL)
         {
             tail = tail->next;
         }
         tail->next = newnode;
     }
 }

1.4 实现

1.4.1 创建节点

 SLT* CreatListNode(SLTDataType x)
 {
     SLT* newnode = (SLT*)malloc(sizeof(SLT));
     newnode->data = x;
     newnode->next = NULL;
 ​
     return newnode;
 }

1.4.2 尾插

 void SLtPushBack(SLT** pphead, SLTDataType x)
 {
     //    找尾节点
     /*SLT* newnode = (SLT*)malloc(sizeof(SLT));
     newnode->data = x;
     newnode->next = NULL; */    // 直接封装成一个函数,如上
 ​
     SLT* newnode = CreatListNode(x);
     if (*pphead == NULL)
     {
         *pphead = newnode;    // 解引用
     }
     else
     {
         SLT* tail = *pphead;
         while (tail->next != NULL)
         {
             tail = tail->next;
         }
         tail->next = newnode;
     }
 }

1.4.3 头插

 void SLtPushFront(SLT** pphead, SLTDataType x)
 {
     SLT* newnode = CreatListNode(x); // 创建新节点,值x已经传入newnode
     newnode->next = *pphead; // 将原来头的地址放入新节点的next
     *pphead = newnode;        //    将新节点作为新的头
 }

1.4.4 尾删

思路:

img

  • 如果直接这样写

 void SLtPopBack(SLT** pphead)
 {
   SLT* tail = *pphead;
   while (tail->next != NULL)
   {
     tail = tail->next;
   }
   free(tail);
   tail = NULL;
 }

tail指向的是最后一个结点,被释放后,其前面节点的next仍然指向他,并可访问到,会造成非法访问(前面的结点将成为野指针-指向一块被释放的空间)

  • 如果这样写

 void SLtPopBack(SLT** pphead)
 {
   SLT* tail = *pphead;
   SLT* prev = NULL;
   while (tail->next != NULL)
   {
     prev = tail;
     tail = tail->next;
   }
   free(tail);
   tail = NULL;
   prev->next = NULL;
 }

则删除时可以正常进行

img

但是,如果删到最后一个

img

4个数据删4次,程序直接崩了

跟上面类似的情况

img

因为此处prev的下一个已经为空,->解引用操作,又造成非法访问

因此

3. 分三种情况

  • 直接为空

  • 有一个结点,第二个为空

  • 大于等于3结点

 void SLtPopBack(SLT** pphead)
 {
   //1 .直接为空
   assert(*pphead != NULL);
 ​
   //2 .只有一个结点(第二个为空)
   if ((*pphead)->next == NULL)
   {
     free(*pphead);
     *pphead = NULL;
   }
   //3 .结点 >= 3
   else
   {
     SLT* tail = *pphead;
     SLT* prev = NULL;
     while (tail->next != NULL)
     {
       prev = tail;
       tail = tail->next;
     }
     free(tail);
     tail = NULL;
     prev->next = NULL;
   }
 }

同时,最后情况3可以这样写

     SLT* tail = *pphead;
     while (tail->next->next)
     {
       tail = tail->next;
     }
     free(tail->next);
     tail->next = NULL;

1.4.5 头删

img

不能上来就freeplist,因为会找不到下一个

所以先保存头,并指向下一个地址

free掉原来的头,再把原来的头指向刚保存的

 void SLtPopFront(SLT** pphead)
 {
   assert(*pphead != NULL);
 ​
   SLT* head = (*pphead)->next;
   free(*pphead);
   *pphead = head;
 }

并要考虑到,若链表中无任何节点,(pphead)->next,又对下一个地址解引用,又非法访问

所以要考虑是否有节点,加入assert / if

1.4.6 查找

 SLT* SListFind(SLT* phead, SLTDataType x)
 {
   SLT* cur = phead;
   while (cur)
   {
     if (cur->data == x)
     {
       return cur;
     }
     else
     {
       cur = cur->next;
     }
   }
   return NULL;
 }

找到之后,可以修改

 pos = SListFind(plist, 33);
   if (pos)
   {
     pos->data = 333;
   }

如图,找到33后改为333

1.4.7 在pos之前插入x

img

分2种情况:

  • pos就在头部

此时,相当于头插,而且不能直接用插入,因为newnode->next永远不是pos(newnode)

  • pos在其他任何地方

 void SListInsert(SLT** pphead, SLT* pos, SLTDateType x)
 {
   SLT* newnode = CreatListNode(x);//  创建节点x
 ​
   if (*pphead == pos)//如果pos等于头
   {
     //newnode->next = *pphead;  // 直接把新节点的next存入原来的头
     //*pphead = newnode;      // 把新节点的地址放入作为新的头
     SLtPushFront(pphead, x);  //  头插
   }
   else
   {
     SLT* posPrev = *pphead;
     while (posPrev->next != pos)
     {
       posPrev = posPrev->next;
     }
     posPrev->next = newnode;
     newnode->next = pos;
   }
 }

1.4.8 在pos之后插入x

创建节点,然后pos->next指向新节点的头,新节点的next指向原来posnext

img

 void SListInsertAfter(SLT* pos, SLTDateType x)
 {
   SLT* newnode = CreatListNode(x);
   newnode->next = pos->next;
   pos->next = newnode;
 }

1.4.9 删除pos后的一个节点

img

 void SListEraseAfter(SLT* pos)
 {
   assert(pos->next != NULL);
   SLT* posPrev = pos;
   posPrev->next = posPrev->next->next;
   //free(pos->next);
 }

最后可以free,也可以不free,因为pos传过来的是形参,这里free不会影响外面的实参

1.4.10 删除pos处节点

时间复杂度O(N)

思路:

分2种情况

  • pos不在头

img

  • pos就是头

img

 void SListErase(SLT**pphead,SLT* pos)
 {
   if (pos == *pphead)
   {
     //  1.正常写法
     /**pphead = pos->next;
     free(pos);
     pos == NULL;*/
     //  2.调用头删
     SLtPopFront(pphead);
   }
   else
   {
     SLT* posPrev = *pphead;
     while((posPrev)->next != pos)
     {
       posPrev = posPrev->next;
     }
     posPrev->next = pos->next;
     free(pos);
   }
 }

同时pos是头的话,也有2种写法,可以直接调用头删函数,也可以正常删

因为其时间复杂度是O(N),所以一般不考虑删除pos处的节点,一般是删除后面的

1.4.11 销毁链表

img

 void SListDestory(SLT** pphead)
 {
   SLT* cur = *pphead;
   while (cur)
   {
     SLT* next = cur->next;
     free(cur);
     cur = next;
   }
   *pphead == NULL;
 }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值