当前位置:   article > 正文

数据结构之顺序表的相关知识点及应用

数据结构之顺序表的相关知识点及应用

 个人主页(找往期文章包括但不限于本期文章中不懂的知识点):我要学编程(ಥ_ಥ)-CSDN博客

目录

顺序表的概念及结构

顺序表的分类

顺序表的实现 

在顺序表中增加数据 

在顺序表中删除数据 

在顺序表中查找数据 

顺序表源码


 

顺序表的概念及结构

在了解顺序表之前,得先知道一个东西:线性表。线性表(linear list)是n个具有相同特性的数据元素的有限序列。简单理解就是:线性表指的是具有部分相同特性的一类数据结构的集合。例如:蔬菜分为绿叶类、瓜类、菌菇类。线性表是一种在实际中广泛使用的数据结构,常见的线性表:顺序表、链表、栈、队列、字符串... 线性表在逻辑上是线性结构,也就说是连续的一条直线。但是在物理结构上并不一定是连续的, 线性表在物理上存储时,通常以数组和链式结构的形式存储。 如何理解逻辑结构和物理结构?我们去超市买东西的时候,去付款时要排队,假设有很多人,也就意味着我们需要排成一条对去付款。这是在逻辑上就是一条线性的(我们下意识认为,是理想的),但是实际上在站队时不一定是线性的(现实情况)。但是顺序表在逻辑结构和物理结构上都是线性的。这是因为顺序表的底层实现逻辑是数组。我们知道数组在内存上是连续存放的(实际情况)。

注意:顺序表的底层虽然是数组,但是却是在数组的基础之上对数组进行了封装,实现了增删查改的一些功能。

顺序表的分类

我们知道数组有两种:一种是定长数组,也就是空间大小不可变化,是固定的;还有一种是变长数组,这个变长数组是我们用动态内存开辟函数申请来的(注意区分C99中引入的变长数组)。

根据数组的不同,顺序表也分为两种:静态顺序表(大小不可变),动态顺序表(大小可变)。

顺序表的实现 

这两种顺序表一比较,肯定是第二种的优势明显一些,同样在项目中,动态顺序表的应用远远大于静态顺序表。下面我们就来学习动态顺序表的实现。

首先创建三个文件:SeqList.h  —— 顺序表的头文件  SeqList.c  —— 顺序表的实现  test.c——>测试顺序表

顺序表的创建:

  1. typedef struct SeqList
  2. {
  3. SLDataType* arr;//数组指针
  4. int size;//记录当前有效的空间大小
  5. int capacity;//记录当前总空间大小
  6. }SL;//由于struct SeqList太长,比较麻烦,因此就重新定义

由于数组的类型是暂定为int,后续如果要改动的话,不是很方便,因此也是重定义。

typedef int SLDataType;//数组不一定是int类型

顺序表创建完了之后,就得开始实现它的基本功能:增 删 查 改。

在实现上面那些基本功能之前,我们肯定得把这个顺序表进行初始化。 

  1. //初始化顺序表
  2. void InitSeqList(SL* ps)//由于要改变顺序表,所以传地址
  3. {
  4. ps->arr = NULL;//没为数组分配内存空间
  5. ps->capacity = 0;
  6. ps->size = 0;
  7. }

在顺序表中增加数据 

接下来就开始实现增加数据,这个增加稍微有所不同:是在指定的位置增加数据。

我们先来实现两种特殊的情况:头插和尾插。头插是在顺序表的第一个位置(数组下标为0的位置)插入(增加)数据;尾插是在顺序表的有效数据的末尾插入(增加)数据。

首先,来实现头插:(数据从后往前覆盖)

  1. //在顺序表的头部插入数据
  2. void SLPushFront(SL* ps, SLDataType x)
  3. {
  4. assert(ps);
  5. //判断是否需要扩容
  6. if (ps->size == ps->capacity)//数组满了
  7. {
  8. int newcapacity = 0;
  9. if (ps->capacity == 0)
  10. {
  11. newcapacity = BASE;//如果空间为0,先给BASE(4)个空间
  12. }
  13. else
  14. {
  15. newcapacity = 2 * ps->capacity;//扩容后为扩容前的两倍
  16. }
  17. //上面这个if...else语句,也可以写成下面这样
  18. //int newcapacity = (ps->capacity == 0) ? 4 : 2 * ps->capacity;
  19. SLDataType* tmp = (SLDataType*)realloc(ps->arr, newcapacity*sizeof(SLDataType));
  20. if (tmp == NULL)//扩容失败
  21. {
  22. perror("realloc");
  23. exit(1);//直接退出程序不在执行(异常退出)
  24. //return 1; //这样写也是可以的
  25. }
  26. //扩容成功
  27. ps->arr = tmp;
  28. ps->capacity = newcapacity;
  29. }
  30. //头插数据
  31. for (int i = ps->size; i > 0; i--)
  32. {
  33. ps->arr[i] = ps->arr[i - 1];
  34. //size[1] = size[0]; //根据边界来推上面的判断条件
  35. }
  36. ps->arr[0] = x;
  37. ps->size++;
  38. }

注意:在比较大型的项目中,我们写一些功能代码时,一定要去检查功能是否完整。比如:这里我们写这个头插功能的代码时,写完之后一定要去打印结果,看看是否满足我们的要求。 

头插写完就开始写尾插了。

  1. //在顺序表的末尾插入数据
  2. void SLPushBack(SL* ps, SLDataType x)
  3. {
  4. assert(ps);
  5. //判断是否需要扩容
  6. if (ps->capacity == ps->size)//数组满了
  7. {
  8. int newcapacity = 0;
  9. if (ps->capacity == 0)//还没分配空间
  10. {
  11. newcapacity = BASE;
  12. }
  13. else
  14. {
  15. newcapacity = 2 * ps->capacity;
  16. }
  17. SLDataType* tmp = (SLDataType*)realloc(ps->arr, newcapacity*sizeof(SLDataType));
  18. if (tmp == NULL)
  19. {
  20. perror("realloc");
  21. exit(1);
  22. }
  23. ps->arr = tmp;
  24. tmp = NULL;
  25. ps->capacity = newcapacity;
  26. }
  27. //尾插数据
  28. ps->arr[ps->size++] = x;
  29. //上面也可以转为下面的写法
  30. //ps->arr[ps->size] = x;
  31. //ps->size++;
  32. }

通过观察上面两个代码,我们会发现那个判断是否需要增容的部分是一样的,因此我们就可以把这个判断是否需要增容的部分写成一个函数(可以只判断,也可以把增容的部分也写进去)。

那么上面的代码就可以简化成下面的样子。 

  1. //判断是否需要增容(如果需要,则直接自动增容)
  2. void SLCheckCapacity(SL* ps)
  3. {
  4. if (ps->size == ps->capacity)//数组满了
  5. {
  6. int newcapacity = 0;
  7. if (ps->capacity == 0)
  8. {
  9. newcapacity = BASE;//如果空间为0,先给4个空间
  10. }
  11. else
  12. {
  13. newcapacity = 2 * ps->capacity;//扩容后为扩容前的两倍
  14. }
  15. //上面这个if...else语句,也可以写成下面这样
  16. //int newcapacity = (ps->capacity == 0) ? 4 : 2 * ps->capacity;
  17. SLDataType* tmp = (SLDataType*)realloc(ps->arr, newcapacity*sizeof(SLDataType));
  18. if (tmp == NULL)//扩容失败
  19. {
  20. perror("realloc");
  21. exit(1);//直接退出程序不在执行(异常退出)
  22. //return 1; //这样写也是可以的
  23. }
  24. //扩容成功
  25. ps->arr = tmp;
  26. ps->capacity = newcapacity;
  27. }
  28. }
  29. //在顺序表的头部插入数据
  30. void SLPushFront(SL* ps, SLDataType x)
  31. {
  32. assert(ps);
  33. //判断是否需要扩容
  34. SLCheckCapacity(ps);
  35. //头插数据
  36. for (int i = ps->size; i > 0; i--)
  37. {
  38. ps->arr[i] = ps->arr[i - 1];
  39. //size[1] = size[0]; //根据边界来推上面的判断条件
  40. }
  41. ps->arr[0] = x;
  42. ps->size++;
  43. }
  44. //在顺序表的末尾插入数据
  45. void SLPushBack(SL* ps, SLDataType x)
  46. {
  47. assert(ps);
  48. SLCheckCapacity(ps);
  49. //尾插数据
  50. ps->arr[ps->size++] = x;
  51. //上面也可以转为下面的写法
  52. //ps->arr[ps->size] = x;
  53. //ps->size++;
  54. }

两种特殊的插入写完之后,就得开始写指定插入,就是说在指定的位置插入数据。 (和头插一样,从后往前覆盖)

  1. //在指定位置插入数据
  2. void SLInsert(SL* ps, int pos, SLDataType x)
  3. {
  4. assert(ps);
  5. SLCheckCapacity(ps);
  6. for (int i = ps->size; i > pos; i--)
  7. {
  8. ps->arr[i] = ps->arr[i - 1];
  9. //arr[2] = arr[1];
  10. }
  11. ps->arr[pos] = x;
  12. ps->size++;
  13. }

在顺序表中删除数据 

接下来就是删除数据。同样删除数据也根据上面的方式来先实现特殊方式:头删和尾删。 

注意删除数据时,一定要判断是否有数据。

先来看头删。(数据从前往后覆盖)

  1. void SLPopFront(SL* ps)
  2. {
  3. assert(ps);
  4. assert(ps->size);//看看有没有数据
  5. for (int i = 0; i < ps->size - 1; i++)
  6. {
  7. ps->arr[i] = ps->arr[i + 1];
  8. }
  9. ps->size--;
  10. }

尾删就比较简单了。 

  1. void SLPopBack(SL* ps)
  2. {
  3. assert(ps);
  4. assert(ps->size);
  5. //ps->arr[size-1] = 0; //这一步可有可无
  6. ps->size--;
  7. }

接下来就是写在指定位置删除数据。 

在顺序表中查找数据 

最后,我们就要实现在顺序表中查找数据。 

找数据的话,就是简单的循环找就行了。

  1. //在顺序表中查找数据
  2. int SLFind(SL* ps, SLDataType x)
  3. {
  4. assert(ps);
  5. for (int i = 0; i < ps->size; i++)
  6. {
  7. if (ps->arr[i] == x)
  8. {
  9. return i;//找到了,返回x在顺序表中的下标
  10. }
  11. }
  12. return -1;//没找到
  13. }

上面就是顺序表的全部逻辑以及实现。

顺序表源码

下面是顺序表的源码:

SeqList.c

  1. #include "SeqList.h"
  2. //初始化顺序表
  3. void InitSeqList(SL* ps)
  4. {
  5. ps->arr = NULL;//没为数组分配内存空间
  6. ps->capacity = 0;
  7. ps->size = 0;
  8. }
  9. //销毁顺序表
  10. void SLDestroy(SL* ps)
  11. {
  12. if (ps->arr)//有可能我们还没有使用(为空)
  13. {
  14. free(ps->arr);
  15. }
  16. ps->arr = NULL;
  17. ps->size = 0;
  18. ps->capacity = 0;
  19. }
  20. //打印顺序表
  21. void SLPrint(const SL* ps)//只是打印不想被更改数据
  22. {
  23. for (int i = 0; i < ps->size; i++)
  24. {
  25. printf("%d ", ps->arr[i]);
  26. }
  27. printf("\n");//可以选择换行,不写也没关系
  28. }
  29. //判断是否需要增容(如果需要,则直接自动增容)
  30. void SLCheckCapacity(SL* ps)
  31. {
  32. if (ps->size == ps->capacity)//数组满了
  33. {
  34. int newcapacity = 0;
  35. if (ps->capacity == 0)
  36. {
  37. newcapacity = BASE;//如果空间为0,先给4个空间
  38. }
  39. else
  40. {
  41. newcapacity = 2 * ps->capacity;//扩容后为扩容前的两倍
  42. }
  43. //上面这个if...else语句,也可以写成下面这样
  44. //int newcapacity = (ps->capacity == 0) ? 4 : 2 * ps->capacity;
  45. SLDataType* tmp = (SLDataType*)realloc(ps->arr, newcapacity*sizeof(SLDataType));
  46. if (tmp == NULL)//扩容失败
  47. {
  48. perror("realloc");
  49. exit(1);//直接退出程序不在执行(异常退出)
  50. //return 1; //这样写也是可以的
  51. }
  52. //扩容成功
  53. ps->arr = tmp;
  54. ps->capacity = newcapacity;
  55. }
  56. }
  57. //在顺序表的头部插入数据
  58. void SLPushFront(SL* ps, SLDataType x)
  59. {
  60. assert(ps);
  61. //判断是否需要扩容
  62. SLCheckCapacity(ps);//ps是一个指针(没有&,因此也是一个值)
  63. //头插数据
  64. for (int i = ps->size; i > 0; i--)
  65. {
  66. ps->arr[i] = ps->arr[i - 1];
  67. //size[1] = size[0]; //根据边界来推上面的判断条件
  68. }
  69. ps->arr[0] = x;
  70. ps->size++;
  71. }
  72. //在顺序表的末尾插入数据
  73. void SLPushBack(SL* ps, SLDataType x)
  74. {
  75. assert(ps);
  76. SLCheckCapacity(ps);
  77. //尾插数据
  78. ps->arr[ps->size++] = x;
  79. //上面也可以转为下面的写法
  80. //ps->arr[ps->size] = x;
  81. //ps->size++;
  82. }
  83. //在指定位置插入数据
  84. void SLInsert(SL* ps, int pos, SLDataType x)
  85. {
  86. assert(ps);
  87. SLCheckCapacity(ps);
  88. for (int i = ps->size; i > pos; i--)
  89. {
  90. ps->arr[i] = ps->arr[i - 1];
  91. //arr[2] = arr[1];
  92. }
  93. ps->arr[pos] = x;
  94. ps->size++;
  95. }
  96. //删除顺序表头部的数据
  97. void SLPopFront(SL* ps)
  98. {
  99. for (int i = 0; i < ps->size - 1; i++)
  100. {
  101. ps->arr[i] = ps->arr[i + 1];
  102. }
  103. ps->size--;
  104. }
  105. //删除顺序表尾部的数据
  106. void SLPopBack(SL* ps)
  107. {
  108. assert(ps);
  109. assert(ps->size);
  110. //ps->arr[size-1] = 0; //这一步可有可无
  111. ps->size--;
  112. }
  113. //删除指定位置的数据
  114. void SLErase(SL* ps, int pos)
  115. {
  116. assert(ps);
  117. assert(ps->size);
  118. for (int i = pos; i < ps->size - 1; i++)
  119. {
  120. ps->arr[i] = ps->arr[i + 1];
  121. }
  122. ps->size--;
  123. }
  124. //在顺序表中查找数据
  125. int SLFind(SL* ps, SLDataType x)
  126. {
  127. for (int i = 0; i < ps->size; i++)
  128. {
  129. if (ps->arr[i] == x)
  130. {
  131. return i;//找到了,返回x在顺序表中的下标
  132. }
  133. }
  134. return -1;//没找到
  135. }

SeqList.h 

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <assert.h>
  4. #define BASE 4
  5. typedef int SLDataType;//数组不一定是int类型
  6. typedef struct SeqList
  7. {
  8. SLDataType* arr;//数组指针()
  9. int size;//记录当前有效的空间大小
  10. int capacity;//记录当前总空间大小
  11. }SL;
  12. void InitSeqList(SL* ps);//初始化顺序表
  13. void SLDestroy(SL* ps);//销毁顺序表
  14. void SLPrint(const SL* ps);//打印顺序表的数据
  15. void SLPushFront(SL* ps, SLDataType x);//在顺序表的头部插入数据
  16. void SLPushBack(SL* ps, SLDataType x);//在顺序表的末尾插入数据
  17. void SLInsert(SL* ps, int pos, SLDataType x);//在指定位置插入数据
  18. void SLPopFront(SL* ps);//删除顺序表头部的数据
  19. void SLPopBack(SL* ps);//删除顺序表尾部的数据
  20. void SLErase(SL* ps, int pos);//删除指定位置的数据
  21. int SLFind(SL* ps, SLDataType x);//在顺序表中查找数据

好啦!本期数据结构顺序表的学习就到此为止啦!我们下一期再一起学习吧! 

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

闽ICP备14008679号