当前位置:   article > 正文

数据结构——单链表_单链表内结点可以是结构体吗?

单链表内结点可以是结构体吗?

一.单链表的定义

链表是由一个个结点,通过指针串联起来的,一个结点是一个结构体,结构体中包括数据域和指针域。数据域中存储数据,指针域存储的是下一个结点的地址。

    1.不带头结点的单链表

无论带头还是不带头,都应该先创建一个指针,这个指针可以指向一个结构体(结点)。

先定义一个结构体:

  1. typedef struct s {
  2. int data;
  3. struct s* next;
  4. }s;

这个结构体中有int类型的数据域 ,和指向下一个相同类型结构体的指针

什么叫不带头结点,就是初始化时只创建了一个指针,但是这个指针并没有指向一个结构体,而是指向NULL。

如下图所示:

 相应的代码:

  1. void init(s* ps) //ps为创建的结构体指针
  2. {
  3. ps = NULL;
  4. }

2.带头结点的单链表

带头结点的单链表会比不带头结点的单链表有很多的好处

首先,什么叫带头结点的单链表,和不带头结点的区别是创建了一个新的结构体指针,这个指针不再指向NULL。而是指向一个结构体,而这个结构体里面的一个指针指向NULL.

如图所示:

 相应的代码如下:

  1. s* ps;
  2. ps = (s*)malloc(sizeof(s));
  3. ps->next = NULL;

二.单链表的插入,删除

1.前插操作

先输入的排在后面,而后输入的排在前面

如图所示:

 从头指针往后看,先创建的排在后面,而后创建的排在前面。

代码实现如下:

  1. int main()
  2. {
  3. s* p; //先创建一个指针
  4. p = (s*)malloc(sizeof(s));
  5. p->next = NULL;//创建一个带头结点的单链表
  6. s* s1; //创建一个新指针,指向一个新的结构体
  7. int x = 10;//要输入到一个结点的数据,可以是任何数字
  8. s1 = (s*)malloc(sizeof(s));
  9. s1->data = x;
  10. s1->next = p->next;
  11. p->next = s1;//注意的地方
  12. return 0;
  13. }

如图:

 这里需要注意的是上面代码中两个指针改变的语句。上下两句顺序不可改变。

首先应用新创建的结构体里面的指针指向头指针指向的内容

然后再覆盖头指针只得地方,改为s1指针指向的地方,

这样新创建的结点就串在这个单链表中了

2.后插操作

从头指针往后看,先插入的排在前面,后插入的排在后面。

如图所示:

 实现代码如下:

  1. int main()
  2. {
  3. s* p;
  4. p = (s*)malloc(sizeof(s));//和前插法不同,这里一开始并没有让头结点的指针指向NULL
  5. s* p1;
  6. s* end = p;//指向最后一个结点位置
  7. int x = 10;
  8. p1 = (s*)malloc(sizeof(s));
  9. p1->data = x;
  10. end->next = p1;//让最后一个结点的指针指向要再后面传进来的结点
  11. end = p1;// p1 = end; //end指向新串进来的最后一个结点
  12. end->next = NULL;//在插入完之后,最后才让最后一个结点里面的指针指向NULL
  13. return 0;
  14. }

如图:

 3.在已有的一串单链表中后插一个结点

假设,现在获取了一串单链表中的一个结点,并知道指向这个结点的一个指针

那么我们就可以进行操作了

首先如图所示:

 代码实现:

  1. int main()
  2. {
  3. s* p;//表示获取的一个指针,我们这里假设就是头指针了
  4. p = (s*)malloc(sizeof(s));
  5. p->next = NULL;
  6. s* s1;
  7. int x = 10;//要插入的数据
  8. s1 = (s*)malloc(sizeof(s));
  9. s1->data = x;
  10. s1->next = p->next;
  11. p->next = s1;
  12. return 0;
  13. }

 4.在已有的一串单链表中前插一个结点

这里我们其中有两种方法

第一种就是从头开始,然后往后查找,找到指定的结点,在前面插入一个新的结点

这种方法的时间复杂度是O(n)。

第二种方法是在这个结点后面插入一个新的结点,然后进行数据的交换,这样就可以实现前插操作

代码实现:

  1. int main()
  2. {
  3. s* p;//假设这是我们获取的一个结点,要在这个结点前插入一个结点
  4. p = (s*)malloc(sizeof(s));
  5. s* s1;//创建的一个新的结点指针
  6. s1 = (s*)malloc(sizeof(s));//新的结点
  7. int x = 10;//要前插的数据
  8. s1->next = p->next;
  9. p->next = s1;//先实现后插操作
  10. s1->data = p->data;//前一个结点的数据赋值到后面
  11. p->data = x;//前面结点的数据被新的数据覆盖
  12. return 0;
  13. }

5.删除第i个结点的内容

首先,要删除第i个结点的内容,我们要找到第i-1个结点。因为我们要修改第i-1个结点的指针,指向第i+1个结点,然后把第i个结点的内容释放掉。

如图所示:

代码如下:

  1. int main()
  2. {
  3. s* p;//
  4. s* p1 = p;//用来标记查找到哪个节点了
  5. int j = 0;//当前p指向的是第几个结点
  6. int i = 5;//假设我们要删除第5个结点的内容
  7. while (p1 != NULL && j < i - 1)//目的是找到第i-1个结点
  8. {
  9. p1 = p1->next;
  10. j++;
  11. }
  12. if (p1 == NULL)
  13. {
  14. printf("i的数值不合法");
  15. return 0;
  16. }
  17. s* shanchu = p1->next;
  18. p->next = shanchu->next;//第i-1个结点指针指向第i+1个结点
  19. free(shanchu);//释放掉这个结点
  20. return 0;
  21. }

这里我只是按照思路写的代码,并不能运行,因为我还没有初始化一个单链表

值得思考的是,这里我新建了一个shanchu这个指针,指向要删除的结点,为什么要新建一个,而不是直接用p1->next来进行操作

因为p1->next在过程中会发生改变。最后如果释放掉p1->next的值,会错误释放,因此需要特地来新建一个指针,指向要删除的结点。

三.查找结点

1.指定要查找第几个结点

代码如下:

  1. int main()
  2. {
  3. s* p;//为头指针
  4. s* p1 = p;//用来移动指向
  5. int j = 0;//用来标记第几个结点
  6. //假设要查找第i个结点
  7. int i = 5;//我们随便设一个数字
  8. while (p1 != NULL && j < i) //思考为什么是j<i
  9. {
  10. p = p->next;
  11. j++;
  12. }
  13. return 0;
  14. }

2.根据结点的数据内容查找结点

和上面的代码类似,时间复杂度都是O(n)

代码如下:

  1. int main()
  2. {
  3. s* p;//p为头指针
  4. s* p1 = p;//用来移动,防止头指针因移动导致找不到头结点
  5. int j = 0;//用来标记找到了第几个结点
  6. //假设要查找的这个结点的数据为5
  7. int i = 5;
  8. while (p != NULL && p->data != i)
  9. {
  10. p = p->next;
  11. j++;
  12. }
  13. return 0;
  14. }

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

闽ICP备14008679号