当前位置:   article > 正文

初级数据结构-->顺序表 单链表讲解〈一〉_将顺序表分为l1非素数单链表,以及l2素数单链表

将顺序表分为l1非素数单链表,以及l2素数单链表

                                                        优秀是一种习惯。

文章目录


首先我们先来看看线性表的概念,由以下的概念我看可以得出一个结论:在逻辑结构上为呈线性的我们便称之为线性结构。哪怕物理结构不是呈现线性   ps:结构分为两种 1.逻辑结构 2.物理结构      ps:物理结构包含两种存储方式(顺序存储:静态    链式存储:动态)

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5LiN6Ium55qE6Ium6KGM5YOn,size_20,color_FFFFFF,t_70,g_se,x_16


那么什么是逻辑结构 什么又是物理结构呢?

我们先从 顺序表 来开始了解

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5LiN6Ium55qE6Ium6KGM5YOn,size_20,color_FFFFFF,t_70,g_se,x_16

 顺序表本质就是数组,但是顺序表可以动态增长(利用realloc),(频繁扩容的缺点:空间碎片化:空间(硬盘物理空间、内存等)在长时间使用后,造成空间块不连续的现象,叫做空间碎片化。空间使用的时间越长,碎片化就越严重;所以使用内存对齐能让空间碎片没那么多)并且要求里面存储的数据要求按照从左到右的顺序存储。数组要是想增长空间只能用函数realloc来增容。

缺点:1.动态增容有性能消耗可能会造成空间浪费(频繁扩容),原因是顺序表的增容方式为一次增加原数组的2倍或者1.5倍的长度。 2.如果需要头部插入新的数据的话,那么整个数组都要往后移。    

头插中间插需要挪动数据  O(n)

优点:1.可以随机访问数据      2.cpu高速缓存命中率比较高(因为物理空间是连续的,打印数据时系统会预处理呀16个字节)

扩展:要是打印相同长度的顺序表跟链表,那么顺序表打印的速度比链表打印得要快,因为涉及到高速缓存命中率问题。

 watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5LiN6Ium55qE6Ium6KGM5YOn,size_20,color_FFFFFF,t_70,g_se,x_16

如图所示:逻辑结构(想象出来的)

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5LiN6Ium55qE6Ium6KGM5YOn,size_16,color_FFFFFF,t_70,g_se,x_16

而且数组在开辟空间的时候是在栈区(动态顺序表在堆区)上开辟一块连续的空间对数据进行存储的。 

如图所示:物理结构(内存中实际如何存储) 

 从开辟的方式我们也可以看到数组空间是一块连续的空间

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5LiN6Ium55qE6Ium6KGM5YOn,size_18,color_FFFFFF,t_70,g_se,x_16    

前面说到顺序表是有缺陷的,所以为了弥补这一个缺陷,又多出了一种方法,名叫:链表。

接下来往下再给大家简单的讲讲链表。

顺序表总结:综合上面两个图可以知道,顺序表在逻辑跟物理结构上是一致的,都为线性。


                        内容温馨提示: 接下来了解下的知识------>单链表

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5LiN6Ium55qE6Ium6KGM5YOn,size_20,color_FFFFFF,t_70,g_se,x_16

 watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5LiN6Ium55qE6Ium6KGM5YOn,size_17,color_FFFFFF,t_70,g_se,x_16

 

本节我们先来了解下单向链表的一些知识。

节点:用来存储单个数据的东西我们称它为链表的节点,一个节点至少有一个数据域跟指针域(这个指针域用来存储的是下一个节点的地址)指针是用来指向下一个节点的。

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5LiN6Ium55qE6Ium6KGM5YOn,size_20,color_FFFFFF,t_70,g_se,x_16

逻辑结构:单向链表的空间是一块一块独立的空间,链接顺序是前一个的指针指向后一个的数据(可以结合下面的逻辑结构图来理解),所以这样就不要求数据是顺序存储的了,而且在链表后尾增加数据的时候只需要开辟一个独立的空间(malloc开辟来的空间,不能够保证地址是连续的),使之前的尾部指针指向该数据就行,不需要挪动前面的数据。如果数头部加入数据,那就让头部的指针指向原来的头部数据就行,中间加入数据的话就让上一个数据的指针指向新插入的数据,让插入的数据指针指向下一个即可。最后的一个数据的指针指向NULL创建一个指针变量(phead),指针变量存放的是头节点的地址,所以称这个指针变量为头指针或者头节点,存储的是第一个节点的地址。

所以我们认为链表的存储顺序是连续的线性的,用指针把数据都链接了起来。(举例:好比你去买奶茶取了号,取完号以后你可以在奶茶店的任意地方站着,而不需要排成一排等待,虽然不是站着一排,但是奶茶号已经把取餐的人按顺序排列好了,这就相当于上面讲的逻辑结构跟物理结构)

优点:

1.链表的空间开辟是,按需申请空间,好处是不存在扩容的代价,不存在空间浪费。

2.头部或者中间插入数据不需要对数据再进行挪动了

3.任意数据插入删除 O(1)

缺点:不支持随机访问中间的数据,只能按顺序来访问

低命中还会导致缓存污染

如图所示: 逻辑结构(想象出来的)

 watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5LiN6Ium55qE6Ium6KGM5YOn,size_20,color_FFFFFF,t_70,g_se,x_16

 如图所示:物理结构(内存中实际如何存储)

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5LiN6Ium55qE6Ium6KGM5YOn,size_16,color_FFFFFF,t_70,g_se,x_16

 可以看到,这些数据都不是连续的,都是由一条链子把他们都链接了起来,所以在物理结构上呈现一个非线性。

链表总结:逻辑结构跟物理结构不一致一个是线性一个是非线性,但是我们也称它为线性表。


接下来将对动态顺序表(因为静态顺序表不实用所以不做展示)跟单链表代码进行一个具体的实现以及讲解 

                                                        首先是动态顺序表

第一步:先创建一个自定义库函数,对头文件 结构体 函数声明进行一个包含,并且通过这里简单了解下动态顺序表的基本功能,分别有:增删查改

                                                        1.自定义头文件

  1. #pragma once
  2. #include <stdio.h>
  3. #include <stdlib.h>
  4. #include <string.h>
  5. #include <errno.h>
  6. #include <assert.h>
  7. //函数声明 头文件
  8. typedef int SeqDateType;
  9. typedef struct SeqList
  10. {
  11. SeqDateType* a; //动态指针
  12. SeqDateType size; //当前数量大小
  13. SeqDateType seqcapacity;//总容量大小
  14. }SeqList; //结构体类型重定义 等价于struct SeqList
  15. //实现增删查改的函数声明
  16. void SeqListInfo(SeqList* p);//初始化
  17. void SeqPushBack(SeqList*p, SeqDateType x); //尾插
  18. void SeqPushFront(SeqList* p, SeqDateType x); //头插
  19. void SeqPopBack(SeqList* p, SeqDateType x); //尾删
  20. void SeqPopFront(SeqList* p, SeqDateType x); //头删
  21. void SeqListInsert(SeqList* p, SeqDateType x);//中间插入
  22. void SeqListErase(SeqList* p, SeqDateType x);//中间删除
  23. void SeqReCapacity(SeqList* p);//扩容函数
  24. void SeqPrint(SeqList* p);//输出数据
  25. void SeqDel(SeqList* p);//恢复初始化
  26. int SeqFind(SeqList* p,SeqDateType x);//查找数据

第二步:创建两个源文件,一个用来对函数进行实现,一个是主函数。

                                                               2. 主函数代码

  1. #include "seqlist.h"
  2. void Seqtest1()
  3. {
  4. SeqList s;
  5. SeqListInfo(&s); //初始化; 数组传参最好用传地址
  6. SeqPushBack(&s,1); //尾插
  7. SeqPushBack(&s,2);
  8. SeqPushFront(&s, 0);
  9. SeqPushBack(&s, 3);
  10. //SeqPrint(&s);
  11. //SeqListInsert(&s, 2, 30);//在哪个位置插入多少
  12. //SeqListErase(&s,3);//在哪个位置删除谁
  13. SeqPopBack(&s,1); //尾删几位
  14. //SeqPrint(&s);
  15. //SeqPopFront(&s, 2); //头删几位
  16. SeqPrint(&s);
  17. // SeqPrint(&s);
  18. SeqDel(&s);
  19. }
  20. int main()
  21. {
  22. Seqtest1();
  23. return 0;
  24. }

                                                        3.函数实现的源文件代码

  1. #include "seqlist.h"
  2. //函数的实现
  3. void SeqListInfo(SeqList* p) //初始化
  4. {
  5. assert(p);
  6. p->a = NULL;
  7. p->size = p->seqcapacity = 0;
  8. }
  9. void SeqDel(SeqList* p) //恢复初始化
  10. {
  11. free(p->a);
  12. p->a = NULL;
  13. p->size = p->seqcapacity = 0;
  14. }
  15. void SeqPrint(SeqList* p) //打印
  16. {
  17. assert(p);
  18. for (int i = 0; i < p->size; i++)
  19. {
  20. printf("%d ", p->a[i]);
  21. }
  22. printf("\n");
  23. }
  24. void SeqReCapacity(SeqList* p) //扩容
  25. {
  26. assert(p);
  27. if (p->size == p->seqcapacity)
  28. {
  29. SeqDateType newcapacity = p->seqcapacity == 0 ? 4 : p->seqcapacity * 2; //一般开启二倍空间
  30. SeqDateType* newA = realloc(p->a, sizeof(SeqList) * newcapacity);
  31. if (newA == NULL)
  32. {
  33. printf("Capacity errno");
  34. exit(-1);
  35. }
  36. p->a = newA; //开辟成功就让原来的a指向新开辟的空间
  37. p->seqcapacity = newcapacity;
  38. }
  39. }
  40. void SeqPushFront(SeqList* p, SeqDateType x) // 头插 从后往前挪动
  41. {
  42. assert(p);
  43. SeqReCapacity(p);
  44. int end = p->size - 1;
  45. while (end >= 0)
  46. {
  47. p->a[end + 1] = p->a[end]; //把前一个数据往后挪动,让a[0]的位置空出来后插入新数据就行
  48. end--;
  49. }
  50. p->a[0] = x;
  51. p->size++;
  52. }
  53. void SeqPushBack(SeqList* p, SeqDateType x) //尾插
  54. {
  55. assert(p);
  56. //插入前判断内存够不够,满没满
  57. SeqReCapacity(p);
  58. p->a[p->size] = x; //因为size是表示目前有几个元素,而最后一个元素的下标为size-1,所以
  59. p->size++; //新添加元素的时候把元素覆盖到size的位置就行了
  60. }
  61. void SeqPopBack(SeqList* p,SeqDateType x) //尾删 只需要把最后一个数字删掉就行
  62. {
  63. assert(p);
  64. assert(p->size > 0); //判读数组中有没有数字
  65. while (x--)
  66. {
  67. --p->size; //size代表有几个数 所以size--以后 ‘\0'就往前挪动了一个 就相当于把后面的删除了
  68. }
  69. }
  70. void SeqPopFront(SeqList* p, SeqDateType x) //头删 1 2 3 4 5 2
  71. {
  72. assert(p);
  73. assert(p->size > 0);
  74. int input = 0;
  75. while (x)
  76. {
  77. input = 0;
  78. while (input < p->size-1)
  79. {
  80. p->a[input] = p->a[input + 1];
  81. input++;
  82. }
  83. p->size--;
  84. --x;
  85. }
  86. }
  87. //1 2 30 3 4 5 6
  88. //这个函数包括了头插 尾插 中间插的三种功能,只需要把这个接口放到其他两个接口里面就行
  89. void SeqListInsert(SeqList* p, SeqDateType pos, SeqDateType x)//pos:插在哪个位置 x:加入哪个数字
  90. {
  91. assert(p);
  92. assert(pos > 0 && pos <= p->size); //pos == p->size的时候 相当于尾插的接口函数,这时候可以把这个函数副用在尾插函数
  93. SeqReCapacity(p);
  94. //利用双下标
  95. int end = p->size - 1;
  96. while (end>=pos) //取等于是因为要把pos原本的数字移走,让pos位置空出来
  97. {
  98. p->a[end + 1] = p->a[end];
  99. end--;
  100. }
  101. p->a[pos] = x; //把插入的数据覆盖在pos上
  102. p->size++;
  103. }
  104. //这个接口包含了头删 中间删 尾删的三种功能
  105. void SeqListErase(SeqList* p, SeqDateType pos)
  106. {
  107. assert(p);
  108. assert(pos >= 0 && pos < p->size);
  109. //利用下标,把pos后面的数往前移动把pos覆盖上就行
  110. int end = p->a + pos; //数组名=首元素地址 首元素地址+i访问第i个位置
  111. while (end<=p->size-1)
  112. {
  113. p->a[end] = p->a[end + 1];
  114. end++;
  115. }
  116. p->size--;
  117. }


                            内容温馨提示:接下来讲解单链表代码的具体实现

                                                        1.自定义头文件

  1. #pragma once
  2. #include <stdio.h>
  3. #include <stdlib.h>
  4. typedef int SLTDataType;
  5. typedef struct SListNode
  6. {
  7. SLTDataType data;
  8. struct SListNode* next;
  9. }SLTNode;
  10. void SListPrint(SLTNode* plist); //打印
  11. void SListPushBack(SLTNode** pplist,SLTDataType x); //尾插
  12. void SListPushFront(SLTNode** pplist, SLTDataType x);//头插
  13. void SListNweNode(SLTDataType x);//创建新空间 头尾插都需要开辟新空间
  14. void SListPopBack(SLTNode** pplist);//尾删
  15. SLTNode* SListFind(SLTNode* plist,SLTDataType x);//查找
  16. void SListInsertAfter(SLTNode* pos, SLTDataType x);//在POS前面增加数据

                                                   2. 主函数代码

  1. #include "SList.h"
  2. void SList1()
  3. {
  4. SLTNode* plist = NULL; //因为是链表所以用指针来操作 要是不用指针那就是顺序表了
  5. SListPushBack(&plist, 1);//
  6. SListPushBack(&plist, 2);
  7. SListPushBack(&plist, 3);
  8. SListPrint(plist);
  9. SListPushFront(&plist,0);
  10. SListPrint(plist);
  11. SListPopBack(&plist);
  12. SListPrint(plist);
  13. SLTNode* pos = SListFind(plist,2);//查找
  14. if (pos)
  15. {
  16. printf("该数据存在\n");
  17. }
  18. else
  19. {
  20. printf("该数据不存在\n");
  21. }
  22. SListInsertAfter(pos, 30);//在POS后面增加数据
  23. SListPrint(plist);
  24. }
  25. int main()
  26. {
  27. SList1();
  28. return 0;
  29. }

                                                 3.函数实现的源文件代码

  1. #include "SList.h"
  2. void SListPrint(SLTNode* plist) //打印链表
  3. {
  4. SLTNode* cur = plist;
  5. while (cur != NULL) //判断第一个节点是不是空
  6. {
  7. printf("%d->",cur->data);//访问第一个节点的指针域
  8. cur = cur->next;
  9. }
  10. printf("NULL\n");
  11. }
  12. SLTNode* BuySLTNode(SLTDataType x) //返回个结构体类型的节点 所以返回类型为结构体类型
  13. {
  14. SLTNode* node = (SLTNode*)malloc(sizeof(SLTNode));
  15. node->data = x;
  16. node->next = NULL;
  17. //指针域跟数据域都赋值好 头插就让node->next 指向头节点*pplist就行
  18. //尾插就让尾部最后一个指针域指向node这个节点就行
  19. return node;
  20. }
  21. void SListPushBack(SLTNode** pplist, SLTDataType x) //尾插
  22. {
  23. SLTNode* newnode = BuySLTNode(x); //无论头插尾插都要创建节点 所以先创建好
  24. //判断刚开始的有没有节点
  25. if (*pplist == NULL)
  26. {
  27. *pplist = newnode;
  28. }
  29. //找尾指针
  30. else
  31. {
  32. SLTNode* tail = *pplist;
  33. while (tail->next != NULL) //访问下一个节点的指针域
  34. {
  35. tail = tail->next; //指向下一个节点
  36. }
  37. tail->next = newnode; //尾指针域存放尾插节点的地址
  38. }
  39. }
  40. void SListPushFront(SLTNode** pplist, SLTDataType x)
  41. {
  42. SLTNode* newnode = BuySLTNode(x); //无论头插尾插都要创建节点 所以先创建好
  43. newnode->next = *pplist; //直接让创建的那个新节点的next指向头节点就行,让后再让头节点的指针转到第一位newnode
  44. *pplist = newnode;
  45. }
  46. void SListPopBack(SLTNode** pplist)//尾删
  47. {
  48. //没有节点
  49. if (*pplist == NULL)
  50. {
  51. return;
  52. }
  53. //一个节点
  54. else if((* pplist)->next == NULL)//如果只有一个节点那么直接释放就行
  55. {
  56. free(*pplist);
  57. *pplist = NULL;
  58. }
  59. else
  60. {
  61. SLTNode* prev = NULL; //找到尾部前一个节点
  62. SLTNode* tail = *pplist;//找到尾部的节点
  63. while (tail->next != NULL)
  64. {
  65. prev = tail;
  66. tail = tail->next;
  67. }
  68. free(tail); //释放最后一个节点的空间
  69. tail = NULL; //把最后一个节点的指针置为空
  70. prev->next = NULL;//让前一个节点的指针域指向NULL 因为这时候前一个节点已经是最后一个节点了
  71. }
  72. }
  73. SLTNode* SListFind(SLTNode* plist, SLTDataType x) //查找X的位置
  74. {
  75. SLTNode* cur = plist;
  76. while (cur != NULL)
  77. {
  78. if (cur->data == x)
  79. {
  80. return cur; //返回节点
  81. }
  82. cur = cur->next;
  83. }
  84. return NULL; //没找到或者说*pplist为NULL
  85. }
  86. void SListInsertAfter(SLTNode* pos, SLTDataType x)//在POS后面增加数据
  87. {
  88. SLTNode* newnode = BuySLTNode(x);
  89. SLTNode* cur = pos->next;
  90. //newnode->next = pos->next;
  91. //pos->next = newnode;
  92. pos->next = newnode;
  93. newnode->next = cur;
  94. }

声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:【wpsshop博客】
推荐阅读
相关标签
  

闽ICP备14008679号