当前位置:   article > 正文

单链表的实现(C语言)_c语言 单链表实现 csdn

c语言 单链表实现 csdn

目录

前言(顺序表的问题及思考)

一、链表

1.1. 概念与结构

 1.2 链表的分类

        1.2.1. 单向或者双向

        1.2.2.带头或者不带头 

        1.2.3.循环或者非循环

 1.3 链表的实现

 二、链表的具体实现

1.单链表的空间分配

2. 打印链表

3. 尾插

4. 头插

5. 尾删

 6. 头删

 7. 查找

 8. 在pos位置插入

9. 在pos位置删除

10. 销毁

总结

动态内存分配(malloc)详解-CSDN博客icon-default.png?t=N7T8https://blog.csdn.net/TheWhiteFox/article/details/108502906

前言(顺序表的问题及思考)

问题:
1. 中间/头部的插入删除,时间复杂度为O(N)
2. 增容需要申请新空间,拷贝数据,释放旧空间。会有不小的消耗。
3. 增容一般是呈2倍的增长,势必会有一定的空间浪费。例如当前容量为100,满了以后增容到200,我们再继续插入了5个数据,后面没有数据插入了,那么就浪费了95个数据空间。
思考:如何解决以上问题呢?下面给出了链表的结构来看看。

一、链表

1.1. 概念与结构

        概念:链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的 。

 

 1.2 链表的分类

        实际中链表的结构非常多样,以下情况组合起来就有8种链表结构:

        1.2.1. 单向或者双向

        1.2.2.带头或者不带头 

         

        1.2.3.循环或者非循环

         虽然有这么多的链表的结构,但是我们实际中最常用还是两种结构:

 1.3 链表的实现

  1. #define _CRT_SECURE_NO_WARNINGS 1
  2. #pragma once
  3. #include <stdio.h>
  4. #include <string.h>
  5. #include <stdlib.h>
  6. #include <assert.h>
  7. typedef int SLTDataType;
  8. typedef struct SListNode
  9. {
  10. SLTDataType data; // val
  11. struct SListNode* next; // 存储下一个节点的地址
  12. }SListNode, SLN;
  13. void SListPrint(SListNode* phead);
  14. void SListPushBack(SListNode** pphead, SLTDataType x);
  15. void SListPushFront(SListNode** pphead, SLTDataType x);
  16. void SListPopBack(SListNode** pphead);
  17. void SListPopFront(SListNode** pphead);
  18. SListNode* SListFind(SListNode* phead, SLTDataType x);
  19. // 在pos位置之前插入
  20. void SListInsert(SListNode** pphead, SListNode* pos, SLTDataType x);
  21. // 删除pos 位置
  22. void SListErase(SListNode** pphead, SListNode* pos);
  23. // 在pos之后插入
  24. void SListInsertAfter(SListNode* pos, SLTDataType x);
  25. // 删除pos位置后面的值
  26. void SListEraseAfter(SListNode* pos);
  27. void SListDestroy(SListNode** pphead);

 二、链表的具体实现

 注意以下几点:

1).每个节点都有一个data并且存放着下一个节点的地址;

2).单链表的每一次都要用malloc开辟一个新的空间,由于堆区每次malloc开辟的空间不一定是连续的,所以链表在物理上看上去连续,但是其实不连续,它只是在逻辑上是连续的。

3).链表有一个头指针变量,它可以说是第0个节点,称为整个单链表的头结点,其存放的是一个地址,一般不存放任何数据,只是存放第一个节点的地址,以找到该链表。

4).只有链表不为空的时候,才需要对单链表进行空间分配,需要一个节点,就开辟一个节点进行连接。

5).记住最后一个节点所存放的地址被要求为空,作为链表的结束,代表后续没有节点。

1.单链表的空间分配

  1. SListNode* BuySListNode(SLTDataType x)
  2. {
  3. SListNode* newnode = (SListNode*)malloc(sizeof(SListNode));
  4. if (newnode == NULL)
  5. {
  6. printf("BuySListNode error\n");
  7. exit(-1);
  8. }
  9. else
  10. {
  11. newnode->data = x;
  12. newnode->next = NULL;
  13. }
  14. return newnode;
  15. }

2. 打印链表

  1. void SListPrint(SListNode* phead)
  2. {
  3. SListNode* cur = phead;
  4. while (cur->next != NULL)
  5. {
  6. printf("%d->", cur->data);
  7. cur = cur->next;
  8. }
  9. printf("NULL\n");
  10. }

3. 尾插

        注意:传址,头指针的改变

  1. //空链表的创建
  2. //SListNode* SList = NULL;
  3. //SListPushBack(SList,i)
  4. //void SListPushBack(SListNode* phead,SLDataType x)尾插
  5. //SList是一个指向SListNode的指针,phead也是一个指向SListNode的指针,属于传值传参
  6. //这样只是一份临时拷贝,形参的改变不会影响实参
  7. //c语言指针有一个这样的原则:若想改变int变量,就需要传int*变量
  8. //&SList,SList的地址可以这么理解,SList存放的是SListNode的地址,解引用才能得到&SList
  9. //所以相当于传&SList可以改变SList
  10. //所以若这里传址的话即SListPushBack(&SList,i)
  11. //pphead是指向SListNode*类型的指针,即指向指针(该指针指向SListNode类型)的指针
  12. //即存放指向SListNode类型的指针的地址
  13. //&SList是指针的地址,而该指针存放的是SListNode的地址
  14. void SListPushBack(SListNode** pphead, SLTDataType x)
  15. {
  16. assert(pphead);//空指针的解引用是耍流氓
  17. SListNode* newnode = BuySListNode(x);
  18. if (*pphead == NULL)//pphead接收的&SList,*pphead就是SList本身
  19. {
  20. //这就是说该链表无节点,可以直接插入
  21. *pphead = newnode;//现在让SList指向newnode即可插入成功
  22. }
  23. else
  24. {//找尾,尾插即可
  25. SListNode* tail = *pphead;
  26. while (tail->next != NULL)
  27. {
  28. tail = tail->next;
  29. }
  30. tail->next = newnode;
  31. }
  32. }

4. 头插

  1. void SListPushFront(SListNode** pphead, SLTDataType x)
  2. {
  3. assert(pphead);
  4. SListNode* newnode = BuySListNode(x);
  5. newnode->next = *pphead;//如果是空链表的时候,*pphead == NULL,先让newnode->next=NULL也是合理的
  6. *pphead = newnode;
  7. }

5. 尾删

        分情况讨论

                1)单节点2)多节点3)空脸变

  1. void SListPopBack(SListNode** pphead)
  2. {
  3. assert(pphead);
  4. if (*pphead == NULL)
  5. {
  6. //空链表
  7. return;
  8. }
  9. else
  10. {
  11. //单节点的时候,改变了头结点
  12. if ((*pphead)->next == NULL)
  13. {
  14. free(*pphead);//释放第一个节点
  15. *pphead = NULL;
  16. }
  17. else
  18. {
  19. //多节点找尾尾删
  20. /*经典错误,
  21. 单链表是以尾结点指向空作为结束,free(tail)后,会让上一个结点指向野指针
  22. while (tail->next != NULL)
  23. {
  24. tail = tail->next;
  25. }
  26. free(tail);
  27. tail = NULL;*/
  28. SListNode* tail = *pphead;
  29. SListNode* prev = NULL;
  30. while (tail->next != NULL)
  31. {
  32. prev = tail;//记录前一个指针
  33. tail = tail->next;
  34. }
  35. free(tail);
  36. tail = NULL;
  37. prev->next = NULL;//单链表的结束
  38. }
  39. }
  40. }

 6. 头删

  1. void SListPopFront(SListNode** pphead)
  2. {
  3. assert(pphead);
  4. if (*pphead == NULL)
  5. {
  6. return;
  7. }
  8. if ((*pphead)->next == NULL)//一个节点
  9. {
  10. free(*pphead);
  11. *pphead = NULL;
  12. }
  13. else//多个节点
  14. {
  15. SListNode* next = (*pphead)->next;
  16. free(*pphead);
  17. *pphead = next;
  18. }
  19. }

 7. 查找

        注意:别忘记尾结点也要查找得到才行

  1. SListNode* SListFind(SListNode* phead, SLTDataType x)
  2. {
  3. assert(phead);
  4. SListNode* cur = phead;
  5. while (cur != NULL)//只有这样才能检查最后一个节点
  6. {
  7. if (cur->data == x)
  8. {
  9. return cur;
  10. }
  11. cur = cur->next;
  12. }
  13. return NULL;//找不到返回NULL
  14. }

 8. 在pos位置插入

        思考:pos位置后插入更简单!

  1. // 在pos位置之前插入
  2. void SListInsert(SListNode** pphead, SListNode* pos, SLTDataType x)
  3. {
  4. assert(pphead);
  5. assert(pos);
  6. //1.空链表
  7. if (*pphead == NULL)
  8. {
  9. return;//空链表都没有pos位置,无意义
  10. }
  11. //2.单节点
  12. else if ((*pphead)->next == NULL)
  13. {
  14. SListPushFront(pphead, x);//pphead是SList的地址
  15. }
  16. //3.多节点
  17. else
  18. {
  19. SListNode* prev = *pphead;
  20. while (prev->next != pos)
  21. {
  22. prev = prev->next;
  23. }
  24. SListNode* newnode = BuySListNode(x);
  25. prev->next = newnode;
  26. newnode->next = pos;
  27. }
  28. }
  29. //在pos位置之后插入
  30. void SListInsertAfter(SListNode* pos, SLTDataType x)
  31. {
  32. assert(pos);
  33. //SListNode* next = pos->next;
  34. //SListNode* newnode = BuySListNode(x);
  35. //pos->next = newnode;
  36. //newnode->next = next;
  37. SListNode* newnode = BuySListNode(x);
  38. newnode->next = pos->next;
  39. pos->next = newnode;
  40. }

9. 在pos位置删除

  1. // 删除pos 位置
  2. void SListErase(SListNode** pphead, SListNode* pos)
  3. {
  4. assert(pphead);
  5. assert(pos);
  6. if (*pphead == NULL)
  7. {
  8. return;
  9. }
  10. else if ((*pphead) == pos)
  11. {
  12. SListPopFront(pphead);
  13. }
  14. else
  15. {
  16. SListNode* cur = *pphead;
  17. while (cur->next != pos)
  18. {
  19. cur = cur->next;
  20. }
  21. cur->next = pos->next;
  22. free(pos);
  23. pos = NULL;
  24. }
  25. }
  26. void SListEraseAfter(SListNode* pos)
  27. {
  28. assert(pos);
  29. SListNode* next = pos->next;
  30. if (next)
  31. {
  32. pos->next = next->next;
  33. free(next);
  34. next = NULL;
  35. }
  36. }

10. 销毁

        同样注意尾结点也需要销毁。

  1. void SListDestroy(SListNode** pphead)
  2. {
  3. assert(*pphead);
  4. SListNode* cur = *pphead;
  5. SListNode* next = NULL;
  6. while (cur)//这样才能free掉尾结点
  7. {
  8. next = cur->next;
  9. free(cur);
  10. cur = next;
  11. }
  12. *pphead = NULL;
  13. }


总结

        多动手画图!

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小蓝xlanll/article/detail/646005
推荐阅读
相关标签
  

闽ICP备14008679号