当前位置:   article > 正文

【数据结构-C语言】单向链表,循环单向链表_单向循环链表

单向循环链表

1、基本概念

顺序表:顺序存储的线性表

链式表:链式存储的线性表,简称链表

由于顺序表的缺点(数据连续存储),顺序存储的数据因为挤在一起而导致需要成片移动,那很容易想到的解决方案是将数据离散地存储在不同内存块中,然后再用指针将他们串起来,这种朴素的思路所形成的链式线性表,就是所谓的链表。顺序表和链表存在的基本样态如下图所示

 2、链表的分类

根据链表中各个节点之间使用指针的个数,以及首尾节点是否相连,可以将链表细分为如下种类:

1、单向链表

2、单向循环链表

3、双向循环链表

不同链表的操作都是差不多的,只是指针数目的异同

最简单的单向链表的示意图如下:

 上图中,所有节点均保存一个指针,指向其逻辑上相邻的下一个节点(末尾节点指向空),整条链表用一个所谓的头指针head来指向,由head开始可以找到链表中的任意一个节点。head通常被称为给头指针。

3、链表的基本操作

1、节点设计

2、初始化空链表

3、增删节点

4、链表遍历

5、销毁链表

4、代码讲解

单向链表的节点非常简单,节点中除了要保存用户数据之外(以整形数据为例),只需要增加一个指向本类节点的指针即可

  1. //单向链表的节点设计
  2. typedef struct node
  3. {
  4. //数据域
  5. int data;
  6. //指针域
  7. //指向相邻的下一个节点的指针
  8. struct node* next;
  9. }node;

 单向链表的初始化:

空链表有两种常见的形式,一种是带所谓的头结点,一种是不带头结点,所谓的头结点是不存放有效数据的节点,仅仅用来方便操作

注意:头指针head是必须的,是链表的入口。头结点是可选的,是为了方便某些操作。

 由于头结点是不存放有效数据,因此如果空链表中带有头结点,那么头指针head将永远不变,一会给以后的链表操作带来些许便捷。

以带头结点的链表为例,首先是初始化单向链表

  1. node* init()
  2. {
  3. node* head = malloc(sizeof(node));
  4. if(head == NULL)
  5. {
  6. return NULL;
  7. }
  8. //将头结点的 next指针 置空
  9. //不对head的数据data任何处理
  10. head->next = NULL;
  11. return head;
  12. }

初始完单向链表之后,需要对链表执行增加节点,也可以对不为空的链表执行删除节点的动作。相对于顺序表需要整片移动数据,链表增删节点只需要修改几个相关指针的指向,动作非常快速。

与顺序表类似,可以对一条链表中的任意节点进行增删操作,在增加节点动作之前需要创建一个新节点,然后把这个节点插入到链表中

  1. //创建一个单向链表的新节点
  2. node* newNode(int data)
  3. {
  4. //分配一个新节点的内存
  5. node* new = malloc(sizeof(node));
  6. if(new == NULL)
  7. {
  8. return NULL;
  9. }
  10. //新节点的数据域与指针域
  11. new->next = NULL;
  12. new->data = data;
  13. return new;
  14. }
  15. //把一个节点头插到链表的表头
  16. void insertHead(node* head,node* new);
  17. {
  18. //新节点的指针域指向当前链表的首个节点
  19. new->next = head->next;
  20. //当前链表的收个节点更新为新插入的节点
  21. head->next = new;
  22. }
  23. //删除节点之前需要对链表进行一个判断,为空则删除失败
  24. bool isEmpty(node* head)
  25. {
  26. return head->next == NULL; //最简洁的判断方式,当链表不为空的时候除了头结点至少存在一个节点,那么头结点的指针域必定不指向空
  27. }
  28. //删除单向链表的节点
  29. node* removeNode(node* head,int data)
  30. {
  31. //创建一个临时节点用来帮助释放要删除的节点
  32. node* tmp;
  33. for(node* p = head;p != NULL;p = p->next)
  34. {
  35. if(p->next != NULL && p->next->data == data)
  36. {
  37. tmp = p->next;
  38. p->next = tmp->next;
  39. tmp->next = NULL;
  40. return tmp;
  41. }
  42. }
  43. return NULL;
  44. }

删除功能函数里面有不少代码和查找功能函数的实现效果是一样的,也就是说,我们可以让删除功能拆分一部分作为查找功能函数

  1. //查找单向链表的某个节点
  2. node* findNode(node* head,int data)
  3. {
  4. //创建一个临时节点指向要查找的节点
  5. node* tmp;
  6. for(node* p = head;p != NULL;p = p->next)
  7. {
  8. if(p->next != NULL && p->next->data == data)
  9. {
  10. tmp = p->next;
  11. return tmp;
  12. }
  13. }
  14. return NULL;
  15. }

执行完初始化,增删节点的动作之后,我们就需要有一个测试代码测试一下是否完成操作,所以就需要有一个查看链表内容的功能函数。也就是链表的遍历,遍历的意思就是逐个访问每一个节点,对于线性表而言,由于路径唯一的选择就是从头走到尾,因此相当而言比较简单

  1. //遍历查看链表
  2. void show(node* head)
  3. {
  4. //查看链表是否为空
  5. if(isEmpty(head))
  6. {
  7. return;
  8. }
  9. //遍历打印链表的数据域里面的内容
  10. for(node* p = head;p != NULL;p = p->next)
  11. {
  12. printf("%d ",p->data);
  13. }
  14. printf("\n");
  15. }

在我们使用完单链表之后,需要对我们初始化的单链表进行销毁,开辟了堆空间,在程序退出之前需要手动删除,不然可能会造成内存空间泄露,程序崩溃等情况。由于链表中的各个节点被离散地分布在各个随机的内存空间,因此销毁链表必须遍历每一个节点,释放每一个节点。

注意:销毁链表时,遍历节点要注意不能弄丢相邻节点的指针

  1. //销毁链表
  2. node* destroy(node* head)
  3. {
  4. //遍历销毁链表
  5. for(node* tmp = head,*n = tmp->next;tmp != NULL;tmp = n)
  6. {
  7. n = tmp->next; //n为销毁节点的指针域所指向的节点
  8. free(tmp);
  9. }
  10. return NULL:
  11. }

5、链表的优缺点

链式存储中,所有节点的存储位置是随机的,他们之间的逻辑关系用指针来确定,跟物理存储位置无关,在增删数据都非常迅速,不需要移动任何数据。又由于位置与逻辑关系无关,一次也无法直接访问某一个指定的节点,只能从头到尾按遍历的方式一个个找到想要的节点。简单地说,链式存储的优缺点与顺序存储几乎是i相对的

总结其特点如下:

优点:

        1、插入、删除时只需要调整几个指针,无需移动任何数据

        2、当数据节点数量较多时,无需一整片较大的连续内存空间,可以灵活利用离散的内存

        3、当数据节点数量变化剧烈时,内存的释放和分配灵活,速度快

缺点:

        1、在节点中,需要多余的指针来记录节点之间的关联

        2、所有数据都是随机存储的,不支持立即访问任意一个随机数据

6、循环单向链表

所谓的循环,指的时将链表末尾节点循环指向链表表头。示意图如下:

 循环链表的操作跟普通链表操作基本上是一致的,只要针对循环特性稍作修改即可,比如:

  1. //初始化循环单向链表
  2. node* init()
  3. {
  4. node* head = malloc(sizeof(node));
  5. //初始化空链表是,需要将末尾指针指向自身
  6. if(head != NULL)
  7. {
  8. head->next = head;
  9. }
  10. return head;
  11. }
  12. //若链表头节点的下一个指向自身
  13. //则代表链表为空
  14. bool isEmpty(node* head)
  15. {
  16. return head->next == head;
  17. }

7、主函数

最后附上我测试的主代码

  1. //单向链表
  2. int main()
  3. {
  4. //初始化一个带头节点的空链表
  5. node* head = initList();
  6. //插入一些数据到链表的头部
  7. for (int i = 1; i <= 5; i++)
  8. {
  9. //1、创建新节点
  10. node* new = newNode(i);
  11. //2、将新节点置入链表
  12. insertHead(head, new);
  13. }
  14. //遍历链表,打印各个元素
  15. show(head);
  16. //输入你删除的节点
  17. int n;
  18. printf("请输入你要删除的节点:\n");
  19. while (1)
  20. {
  21. scanf("%d", &n);
  22. node *p = removeNode(head, n);
  23. if (p == NULL)
  24. {
  25. printf("没有你要删除的节点!\n");
  26. continue;
  27. }
  28. free(p);
  29. show(head);
  30. }
  31. //销毁链表,销毁之后返回NULL
  32. head = destroy(head);
  33. return 0;
  34. }

声明:本文内容由网友自发贡献,转载请注明出处:【wpsshop博客】
推荐阅读
相关标签
  

闽ICP备14008679号