[关闭]
@coder-pig 2019-05-31T14:03:03.000000Z 字数 3734 阅读 1585

小猪的数据结构辅助教程——2.2 线性表中的单链表

数据结构


本节学习路线图与学习要点

学习要点

  • 1.理解顺序表以及单链表各自的有点以及缺点!
  • 2.熟悉单链表的形式,对于头指针,头结点,尾结点,数据域和指针域这些名词要知道是什么!
  • 3.熟悉单链表的结点结构
  • 4.区分头指针头结点
  • 5.熟悉创建单链表的两种方式:头插法和尾插法
  • 6.了解单链表12个基本操作的逻辑
  • 7.有趣的算法题:查找单链表的中间结点~

1.单链表的引入(顺序表与单链表的PK)


2.单链表的结构图以及一些名词


3.单链表结点的存储结构

从上面的结构图我们就知道单链表的结点的结构分为数据域和指针域,对应的存储结构的代码如下:

  1. typedef struct LNode
  2. {
  3. ElemType data; //数据域
  4. struct LNode *next; //指针域
  5. }LNode,*LinkList; //定义两个只是方便使用而已,Linklist是一个结点指针哦~

4.头指针与头结点的比较

2中我们看到了头指针和头结点,大家可能对这两个名词有点疑惑,下面我们来解释下~


5.头插法与尾插法创建表的比较

两种方法的区别无非是插入的位置:
头插法:新插入结点始终未当前的第一个结点
尾插法:新插入结点始终为当前的最后一个结点

1)头插法建表

实现代码:

  1. //头插法建链表
  2. void HeadCreateList(LinkList L,int n)
  3. {
  4. LinkList p;
  5. int i;
  6. srand(time(0)); //初始化随机数种子
  7. L = (LinkList)malloc(sizeof(LNode));
  8. L ->next = NULL;
  9. //利用循环生成结点并添加到单链表中
  10. for(i = 0;i < n;i++)
  11. {
  12. p = (LinkList)malloc(sizeof(LNode)); //生产新结点
  13. p ->data = rand()%100 + 1; //生产两位随机数100,三位就是1000
  14. p ->next = L ->next;
  15. L ->next = p; //插到表头
  16. }
  17. }

2)尾插法建表

实现代码:

  1. void TailCraeteList(LinkList L,int n)
  2. {
  3. LinkList p,r;
  4. int i;
  5. srand(time(0)); //初始化随机数种子
  6. L = (LinkList)malloc(sizeof(LNode));
  7. r = L;
  8. for(i = 0;i < n;i++)
  9. {
  10. p = (LinkList)malloc(sizeof(LNode)); //生产新结点
  11. p ->data = rand()%100 + 1; //生产两位随机数100,三位就是1000
  12. r ->next = p; //将表尾终端结点的指针指向新结点
  13. r = p; //将当前的新结点定义为表尾的尾结点
  14. }
  15. r->next = NULL; //当前链表结束
  16. }

6.有趣的算法:查找单链表的中间结点

就是给你一个单链表,要你获得单链表中位置中间的结点?你会怎么做?
一般我们可能用一个指针,从头到尾撸一遍,同时记录单链表的长度,然后再除以2得出第几
项为中间结点,然后再撸length / 2获得中间节点,重复遍历很繁琐,有没有其他的方法呢?
有,肯定有,这里提供一个简单的方法:
用两个不同的指针,按照不同的移动顺序来移动,这里我们暂且把他们成为快慢指针
每次循环,
快指针向后移动两个结点: p = p -> next -> next;
慢指针向后移动一个结点: q = q -> next;

当快指针到达尾部的时候,慢指针不就指向中间结点了,你说是吧~
原理非常简单,下面我们写下代码实现:

  1. Status GetMidLNode(LinkList *L,ElemType *e)
  2. {
  3. LinkList p,q;
  4. p = q = *L;
  5. while(p ->next ->next != NULL)
  6. {
  7. if(p ->next ->next != NULL)
  8. {
  9. p = p ->next ->next;
  10. q = q ->next;
  11. }else{
  12. p = p ->next;
  13. }
  14. }
  15. e = q ->data;
  16. return OK;
  17. }

7.12种基础基本操作代码实现

从本节开始就不像上一节一样一步步地讲解了,直接上代码,难点部分会写下注释!

1)构造空表

  1. void InitList(LinkList L)
  2. {
  3. L = (LinkList)malloc(sizeof(LNode));
  4. if(!L)exit(ERROR);
  5. L ->next = NULL;
  6. }

2)将链表置为空表

  1. void ClearList(LinkList L)
  2. {
  3. LinkList p = L ->next;
  4. L ->next = NULL;
  5. //接着就是释放头结点以外的结点了
  6. while(p)
  7. {
  8. p = L->next;
  9. free(L); //释放首元结点
  10. L = p; //移动到当前的首元结点
  11. }
  12. }

3)判断是否为空表

这里要区分两种情况:

有头结点:L -> next = NULL;此时表为空表!
无头结点:L = NULL;此时为空表!

  1. Status ListEmpty(LinkList L)
  2. {
  3. //有头节点的情况,只需判断头结点的指针域是否为空即可
  4. if(L ->next)return FALSE;
  5. else return TRUE;
  6. }

4)销毁单链表

  1. void DestoryList(LinkList L)
  2. {
  3. LinkList q;
  4. //删除头结点外的结点
  5. while(L)
  6. {
  7. //指向首元结点,而不是头结点
  8. q = L ->next;
  9. free(L);
  10. L = q; //删除后指向首元
  11. }
  12. }

5)获得表长度

  1. int ListLength(LinkList L)
  2. {
  3. int i = 0;
  4. LinkList p = L ->next;
  5. while(p)
  6. {
  7. i++;
  8. p = p ->next;
  9. }
  10. return i;
  11. }

6)获得表中第i个元素的值

  1. Status GetElem(LinkList L,int i,ElemType *e)
  2. {
  3. int j = 1;
  4. //指向首元,然后依次后移,假如到了结尾或者j的值大于i
  5. //还没找个改元素说明i不合法
  6. LinkList p = L ->next;
  7. while(p && j < i)
  8. {
  9. j++;
  10. p = p ->next;
  11. }
  12. if(!p || j> i)return ERROR;
  13. e = p ->data;
  14. return OK;
  15. }

7)查找表中是否存在满足条件的元素

  1. int LocateElem(LinkList L,ElemType e,Status(*compare)(ElemType,ElemType))
  2. {
  3. int i = 0;
  4. LinkList p = L -> next;
  5. while(p)
  6. {
  7. i++;
  8. if(compare(p->data,e))return i;
  9. p = p -> next;
  10. }
  11. return 0;
  12. }

8)获得某个结点的直接前驱

  1. Status BeforeElem(LinkList L,ElemType choose,ElemType *before)
  2. {
  3. LinkList q,p = L ->next;
  4. while(p ->next)
  5. {
  6. q = p ->next;
  7. //判断p的后继是否为choose,是的话返回OK,否则继续后移
  8. if(q ->data == choose)
  9. {
  10. before = p ->data;
  11. return OK;
  12. }
  13. p = q;
  14. }
  15. return ERROR;
  16. }

9.获得某个结点的直接后继

  1. Status NextElem(LinkList L,ElemType choose,ElemType *behind)
  2. {
  3. LinkList p = L ->next;
  4. while(p ->next)
  5. {
  6. if(p ->data == choose)
  7. {
  8. behind = p ->next ->data;
  9. return OK;
  10. }
  11. p = p ->next;
  12. }
  13. return ERROR;
  14. }

10.往表中第i个位置插入元素

  1. Status ListInsert(LinkList L,int i,ElemType e)
  2. {
  3. int j = 0;
  4. LinkList p,q =L; //让q指向头结点
  5. while(p && j < i - 1)
  6. {
  7. j++;
  8. p = p ->next; //p指向下一个节点
  9. }
  10. if(!p || j > i - 1)return ERROR;
  11. p = (LinkList)malloc(sizeof(LNode));
  12. //要先让插入的结点的指针域指向插入位置的后继结点
  13. //再让插入节点的前驱的指针域指向插入结点
  14. //!!!顺序不能乱哦1
  15. p ->data = e;
  16. p ->next = q ->next;
  17. q ->next = p;
  18. return OK;
  19. }


11.删除表中第i个元素

  1. Status ListDelete(LinkList L,int i,ElemType *e)
  2. {
  3. int j = 0;
  4. LinkList p,q = L;
  5. while(q ->next && j < i -1)
  6. {
  7. j++;
  8. q = q->next;
  9. }
  10. if(!q || j >i -1)return ERROR;
  11. p = q ->next; //指向准备删除的结点
  12. q ->next = p ->next; //删除结点的前驱的指针域指向删除结点的后继
  13. e = p ->data;
  14. free(p); //释放要删除的结点
  15. return OK;
  16. }


12.遍历单链表中的所有元素

  1. void ListTraverser(LinkList L,void(*visit)(ElemType))
  2. {
  3. LinkList p = L ->next;
  4. while(p)
  5. {
  6. visit(p ->data);
  7. p = p ->next;
  8. }
  9. printf("\n");
  10. }

8.本节代码下载:

https://github.com/coder-pig/Data-structure-auxiliary-tutorial/blob/master/List/list2.c

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注