• 数据结构--单链表


    1.定义

    由于顺序表的插入删除操作需要移动大量的元素,影响了运行效率,因此引入了线性表的链式存储——单链表单链表通过一组任意的存储单元来存储线性表中的数据元素,不需要使用地址连续的存储单元,因此它不要求在逻辑上相邻的两个元素在物理位置上也相邻。

    2.特点

    1. 单链表不要求逻辑上相邻的两个元素在物理位置上也相邻,因此不需要连续的存储空间。
    2. 单链表是非随机的存储结构,即不能直接找到表中某个特定的结点。查找某个特定的结点时,需要从表头开始遍历,依次查找。
    3. 对于每个链表结点,除了存放元素自身的信息外,还需要存放一个指向其后继的指针。

    3.代码

    1.lishh

    //头文件 ".h",存放结构或者函数的声明
    //带头结点单链表,逻辑相邻,物理不一定相邻,为了找到下一个节点,就必须增加下一个节点的地址
    //尾节点:最后一个节点,在单链表中,尾节点的next为NULL,NULL是单链表的结尾标志
    //头结点:其数据域不使用或者存放链表长度.其作用,相对于一个标杆,简化操作

    1. #pragma once
    2. typedef int ElemType;
    3. typedef struct Node
    4. {
    5. ElemType data;//数据
    6. struct Node* next;//后继指针
    7. }Node,*List;//List == Node *
    8. //typedef Node* List;//List == Node *
    9. //List本质是Node*,但含义不同,List表示一条链表,Node*表示一个节点的地址
    10. //初始化plist
    11. void InitList(List plist);
    12. //书上的写法
    13. void InitList(List *pplist);
    14. //往plist中头部插入数字val
    15. bool Insert_head(List plist, ElemType val);
    16. //往plist中的尾部插入数字val
    17. bool Insert_tail(List plist , ElemType val);
    18. //在plist中查找val值,找到返回该节点地址,失败返回NULL
    19. Node* Search(List plist, ElemType val);
    20. //删除plist中的第一个val
    21. bool DeleteVal(List plist, ElemType val);
    22. //判断plist是否为空链表(没有数据节点)
    23. bool IsEmpty(List plist);
    24. //获取plist长度,数据节点的个数
    25. int GetLength(List plist);
    26. //获取plist链表的pos位置的值
    27. //int GetElem(List plist, int pos);//设计有问题:无法清晰的表示出错
    28. bool GetElem(List plist,int pos,int *rtval);//rtval:输出参数
    29. //获取val的前驱
    30. Node* Prior(List plist, ElemType val);
    31. //获取val的后继
    32. Node* Next(List plist, ElemType val);
    33. //输出plist的所有数据
    34. void Show(List plist);
    35. //清空数据
    36. void Clear(List plist);
    37. //销毁
    38. void Destroy(List plist);
    39. //逆置,考试非常多
    40. void Reverse(List plist);

    2.list.cpp

    1.初始化plist

    通常会用头指针来标识一个单链表,头指针为NULL时表示一个空表。但是,为了操作方便,会在单链表的第一个结点之前附加一个结点,称为头结点。头结点的数据域可以不设任何信息,也可以记录表长等信息。头结点的指针域指向线性表的第一个元素结点。

    空的带头节点的单链表

    含有三个结点的带头结点的点链表 

    头结点和头指针的区分:不管带不带头结点,头指针始终指向单链表的第一个结点,而头结点是带头结点的单链表中的第一个结点,结点内通常不存储信息。
    那么单链表的初始化操作就是申请一个头结点,将指针域置空。

    1. void InitList(List plist)
    2. {
    3. assert(plist != NULL);
    4. //头结点的数据不使用 plist->data可以不处理
    5. plist->next = NULL;
    6. }

    2.书上的写法

    1. void InitList(List* pplist)
    2. {
    3. assert(pplist != NULL);
    4. *pplist = (Node*)malloc(sizeof(Node));//动态创建头结点
    5. assert(*pplist != NULL);
    6. if (*pplist == NULL)
    7. return;
    8. (*pplist)->next = NULL;
    9. }

    3.往plist中头部插入数字val

    所谓头插法建立单链表是说将新结点插入到当前链表的表头,即头结点之后。如图所示:

     算法思想:首先初始化一个单链表,其头结点为空,然后循环插入新结点*s:将s的next指向头结点的下一个结点,然后将头结点的next指向s。

    1. bool Insert_head(List plist, ElemType val)//O(1)
    2. {
    3. //1.动态创建一个新节点
    4. Node* p = (Node*)malloc(sizeof(Node));
    5. assert(p != NULL);
    6. if (p == NULL)
    7. return false;
    8. //2.把数据val存放到新节点
    9. p->data = val;
    10. //3.把新节点插入在头结点的后面
    11. p->next = plist->next;
    12. plist->next = p;
    13. return true;
    14. }

    4.往plist中的尾部插入数字val

    需要指出的是,头插法建立的单链表中结点的次序和输入数据的顺序不一致,是相反的。若希望两者的顺序是一致的,则可采用尾插法建立单链表。

    所谓尾插法建立单链表,就是将新结点插入到当前链表的表尾。如下图所示:

     最后情况插入元素a[n-1],需将终结点的指针域置空

    1. bool Insert_tail(List plist, ElemType val)//O(n)
    2. {
    3. //1.动态创建新节点
    4. Node* p = (Node*)malloc(sizeof(Node));
    5. assert(p != NULL);
    6. if (p == NULL)
    7. return false;
    8. // 2.把值存放到新节点
    9. p->data = val;//p->next = NULL;
    10. // 3.找到链表尾巴
    11. Node* q;
    12. for (q = plist; q->next != NULL; q = q->next)
    13. ;
    14. // 4.把新节点插在尾节点的后面,把p插入在q的后面
    15. p->next = q->next;//p->next = NULL;
    16. q->next = p;
    17. return true;
    18. }

    5.在plist中查找val值,找到返回该节点地址,失败返回NULL

     查找值val在单链表L中的结点指针。

     算法思想:从单链表的第一个结点开始,依次比较表中各个结点的数据域的值,若某结点数据域的值等于val,则返回该结点的指针;若整个单链表中没有这样的结点,则返回空。

    1. Node* Search(List plist, ElemType val)
    2. {
    3. for (Node* p = plist->next; p != NULL; p = p->next)
    4. {
    5. if (p->data == val)//找到了
    6. return p;
    7. }
    8. return NULL;//没有找到
    9. }

    6.删除plist中的第一个val

    算法思想:先检查删除位置的合法性,然后从头开始遍历,找到表中val结点,即被删除结点的前驱结点*p,被删除结点为*q,修改*p的指针域,将其指向*q的下一个结点,最后再释放结点*q的存储空间。

    1. bool DeleteVal(List plist, ElemType val)
    2. {
    3. //1.找val的前驱
    4. Node* p = Prior(plist,val);//指向前驱节点
    5. if (p == NULL)//没有val
    6. return false;
    7. //free(p->next);//错误
    8. //p->next = p->next->next;
    9. Node* q = p->next;//标记需要删除的节点
    10. p->next = q->next;//p->next = p->next->next;将q从链表中剔除
    11. free(q);//释放节点
    12. return true;
    13. }

    7.判断plist是否为空链表(没有数据节点)

    算法思想:要判断带头结点的单链表是否为空,只需要看头结点的指针域即可,如果头结点的指针域为空,即单链表中只有一个头结点,那么该单链表为空表。

    1. bool IsEmpty(List plist)
    2. {
    3. return plist->next == NULL;//等同下面的if else
    4. /*if (plist->next == NULL)
    5. return true;
    6. else
    7. return false;*/
    8. }

    8.获取plist长度,数据节点的个数

    算法思想:声明一个指针p,p指向头结点指向的第一个结点,如果p指向的结点不为空,那么长度加一,将p指向下一个结点,直到遍历到最后一个结点为止。

    1. int GetLength(List plist)
    2. {
    3. int count = 0;//计数器
    4. for (Node* p = plist->next; p != NULL; p = p->next)
    5. count++;
    6. return count;
    7. }

    9.获取plist链表的pos位置的值

    1. //int GetElem(List plist, int pos)//设计有问题
    2. //{
    3. // if (pos < 0 || pos >= GetLength(plist))//下标不存在
    4. // return -1;//不可以,可能和正常的值冲突
    5. //}
    6. bool GetElem(List plist, int pos, int* rtval)//rtval:输出参数
    7. {
    8. if (pos < 0 || pos >= GetLength(plist))//出错
    9. return false;
    10. Node* p=plist->next;
    11. for (int i=0; i < pos; p = p->next, i++)
    12. {
    13. ;
    14. }
    15. *rtval = p->data;
    16. return true;
    17. }

    10.获取val的前驱

    1. Node* Prior(List plist, ElemType val)
    2. {
    3. for (Node* p = plist; p != NULL; p = p->next)//bug
    4. {
    5. if (p->next->data == val)//找到了
    6. return p;
    7. }
    8. return NULL;//失败了
    9. }

    11.获取val的后继

    1. Node* Next(List plist, ElemType val)
    2. {
    3. for (Node* p = plist->next; p != NULL; p = p->next)
    4. {
    5. if (p->data == val)
    6. return p->next;
    7. }
    8. return NULL;
    9. }

    12.输出plist的所有数据

    算法思想:声明一个指针p,从头结点指向的第一个结点开始,如果p不为空,那么就输出当前结点的值,并将p指向下一个结点,直到遍历到最后一个结点为止。

    1. void Show(List plist)
    2. {
    3. for (Node* p = plist->next; p != NULL; p = p->next)//遍历所有的数据节点
    4. {
    5. printf("%d ",p->data);
    6. }
    7. printf("\n");
    8. }

    13.清空数据

    1. void Clear(List plist)
    2. {
    3. Destroy(plist);
    4. }

    14.销毁

    1. void Destroy1(List plist)
    2. {
    3. if (plist == NULL || plist->next == NULL)
    4. return;
    5. Node* p = plist->next;
    6. Node* q;
    7. plist->next = NULL;
    8. while (p != NULL)
    9. {
    10. q = p->next;
    11. free(p);
    12. p = q;
    13. }
    14. }
    15. void Destroy(List plist)
    16. {
    17. Node* p;//指向第一个数据节点
    18. while (plist->next != NULL)//还有数据结构
    19. {
    20. p = plist->next;
    21. plist->next = p->next;//剔除p
    22. free(p);
    23. }
    24. }

    3.test.cpp

    1. //测试文件,测试用例
    2. #include
    3. #include "list.h"
    4. //#include //必须安装vld,如果没有安装不能使用
    5. //实现两个集合的并集,A=AUB(相同的部分取一次,不同部分全取)
    6. //例如A={1,2,3,4};B={5,3,1,6};结果={1,2,3,4,5,6};
    7. void Merge(List plistA, List plistB)
    8. {
    9. //算法:遍历plistB,查看当前值在plistA中是否存在,如果存在则什么都不做,
    10. //不存在则将值插入到plistA中
    11. int val;
    12. for (int i = 0; i < GetLength(plistB); i++)
    13. {
    14. GetElem(plistB,i,&val);//获取plistB,i下标的值
    15. if (Search(plistA, val) == NULL)//不存在
    16. {
    17. Insert_tail(plistA,val);
    18. }
    19. }
    20. }
    21. int main()
    22. {
    23. Node headA;
    24. Node headB;
    25. InitList(&headA);
    26. InitList(&headB);
    27. Insert_tail(&headA, 1);
    28. Insert_tail(&headA, 2);
    29. Insert_tail(&headA, 3);
    30. Insert_tail(&headA, 4);
    31. Show(&headA);
    32. Insert_tail(&headB, 5);
    33. Insert_tail(&headB, 3);
    34. Insert_tail(&headB, 1);
    35. Insert_tail(&headB, 6);
    36. Show(&headB);
    37. Merge(&headA,&headB);
    38. printf("合并后的值\n");
    39. Show(&headA);
    40. Show(&headB);
    41. Destroy(&headA);
    42. Destroy(&headB);
    43. return 0;
    44. }

    结果:

     

     

  • 相关阅读:
    Vb6 TCP Server服务端监听多个RFID读卡器客户端上传的刷卡数据
    利用TypeScript 和 jsdom 库实现自动化抓取数据
    SpringBoot +Mybatis + Redis实现缓存(案例解析)
    Android Java JVM常见问答分析与总结
    Shell脚本数组简介及运用
    C# 中的“智能枚举”:如何在枚举中增加行为
    ArcGIS: 第二届全国大学生GIS技能大赛(广西师范学院)详解-下午题
    [附源码]计算机毕业设计病人跟踪治疗信息管理系统Springboot程序
    Java 定时任务-最简单的3种实现方法
    mysql基础
  • 原文地址:https://blog.csdn.net/m0_59052131/article/details/127990576