WHCSRL 技术网

【数据结构】顺序表(实现+详解+源码+通讯录项目(静态+动态+文件保存))

目录

概念及结构

接口实现

顺序表初始化

检查空间(动态)

顺序表尾插

顺序表头插

顺序表尾删

顺序表头删

顺序表查找

顺序表在pos位置插入x

顺序表删除pos位置的值

顺序表销毁(动态)

顺序表打印

通讯录实现

Contact.h

Contact.c

test.c

顺序表的问题及思考


概念及结构

顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构,一般情况下采用数组存储。在数组上完成数据的增删查改。
 

顺序表一般可以分为: 

  1. 静态顺序表:使用定长数组储存元素。
  2. 动态顺序表:使用动态开辟的数组储存。 

接口实现

  1. #include<stdio.h>
  2. #include<assert.h>
  3. #include<stdlib.h>
  4. typedef int SLDataType;
  5. //顺序表的静态储存
  6. //#define MAX 100 //静态通讯录总容量
  7. //typedef struct SeqList
  8. //{
  9. // SLDataType array[MAX];
  10. // size_t size;
  11. //}SeqList;
  12. // 顺序表的动态存储
  13. #define N 5 //第一次开辟容量大小
  14. typedef struct SeqList
  15. {
  16. SLDataType* array; // 指向动态开辟的数组
  17. size_t size ; // 有效数据个数
  18. size_t capicity ; // 容量空间的大小
  19. }SeqList;
  20. // 基本增删查改接口
  21. // 顺序表初始化
  22. void SeqListInit(SeqList* ps);
  23. // 检查空间,如果满了,进行增容
  24. void CheckCapacity(SeqList* ps);
  25. // 顺序表尾插
  26. void SeqListPushBack(SeqList* ps, SLDataType x);
  27. // 顺序表尾删
  28. void SeqListPopBack(SeqList* ps);
  29. // 顺序表头插
  30. void SeqListPushFront(SeqList* ps, SLDataType x);
  31. // 顺序表头删
  32. void SeqListPopFront(SeqList* ps);
  33. // 顺序表查找
  34. int SeqListFind(SeqList* ps, SLDataType x);
  35. // 顺序表在pos位置插入x
  36. void SeqListInsert(SeqList* ps, size_t pos, SLDataType x);
  37. // 顺序表删除pos位置的值
  38. void SeqListErase(SeqList* ps, size_t pos);
  39. // 顺序表销毁
  40. void SeqListDestory(SeqList* ps);
  41. // 顺序表打印
  42. void SeqListPrint(SeqList* ps);

顺序表初始化

静态版:因为在定义结构体时已经创建了固定容量的数组,所以在初始化时只需要将数组元素置为 0,再将有效元素个数置为0即可。

动态版:在创建结构体时结构体成员中只创建了指向顺序表的指针,并没有创建空间,所以在初始化时可以使用两种方法初始化:

  1. 初始化时创建一定容量的空间,当然在后面空间检查时会有一些变化。
  2. 初始化时不创建空间,将指针置为空。

这里我们使用第二种方法: 

  1. //静态版
  2. void SeqListInit(SeqList* ps)
  3. {
  4. memset(ps->array,0,sizeof(ps->array[0])*N);//将数组所有元素置为0
  5. ps->size = 0;//数组中的有效元素置为0
  6. }
  7. //动态版
  8. void SeqListInit(SeqList* ps)
  9. {
  10. ps->a = NULL;//将指向数组的指针置为空
  11. ps->capacity = 0;//数组容量为0
  12. ps->size = 0;//数组中的有效元素为0
  13. }

检查空间(动态)

空间检查只会在动态版中有,静态顺序表的空间大小是固定的,不需要扩容,容量满了之后不能插入元素。

  1. void CheckCapacity(SeqList* ps)
  2. {
  3. if (ps->capacity == ps->size)//当空间容量和有效元素相等时才需要增容
  4. {
  5. if (ps->capacity == 0)//当空间容量为0时
  6. {
  7. ps->a = (SLDataType*)malloc(sizeof(SLDataType) * N);
  8. if (ps->a == NULL)
  9. {
  10. printf("malloc erro");//开辟失败
  11. exit(-1);
  12. }
  13. ps->capacity = N;
  14. }
  15. else
  16. {
  17. SLDataType* tmp = (SLDataType*)realloc(ps->a ,sizeof(SLDataType) * ps->capacity * 2);//空间容量不够时
  18. if (tmp == NULL)
  19. {
  20. printf("realloc erro");
  21. exit(-1);
  22. }
  23. ps->a = tmp;
  24. ps->capacity *= 2;//增容2倍
  25. }
  26. }

顺序表尾插

尾插即在顺序表的最后一个有效元素的后面一个位置插入。 

  1. void SeqListPushBack(SeqList* ps, SLDataType x)
  2. {
  3. SeqListCheckCapacity(ps);//插入元素时需先检查容量是否足够
  4. ps->a[ps->size] = x;//直接在最后插入即可
  5. ps->size++;//有效元素增加1
  6. }

顺序表头插

头插即在顺序表的第一个位置插入元素,但第一个位置是有元素的,所以需要将顺序表中所有元素向后移动一个位置。

  1. void SeqListPushFront(SeqList* ps, SLDataType x)
  2. {
  3. SeqListCheckCapacity(ps);//空间检查
  4. //1.可直接使用memmove移动
  5. memmove(ps->a + 1, ps->a, sizeof(SLDataType) * ps->size);
  6. //2.依次移动
  7. int i = 0;
  8. for(i = ps->size; i>0; i--)//注意数组不要越界
  9. {
  10. ps->array[i] = ps->array[i-1];
  11. }
  12. ps->a[0] = x;//在第一个位置插入
  13. ps->size++;
  14. }

顺序表尾删

直接删除最后一个元素,并将有效元素个数减一即可。 

  1. void SeqListPopBack(SeqList* ps)
  2. {
  3. assert(ps->size > 0);//删除必须保证数组中有有效元素
  4. ps->size --;//直接将有效元素个减1即可
  5. } //注:不要将该位置置为0,因为可能该位置处原来放的就是0

顺序表头删

删除第一个元素,再将剩余元素像左移动一个位置。 

  1. void SeqListPopFront(SeqList* ps)
  2. {
  3. assert(ps->size > 0);//同样删除需保证数组中存在有效元素
  4. //1.通过memmove移动
  5. memmove(ps->a, ps->a + 1, sizeof(SLDataType) * (ps->size - 1));
  6. //2.依次移动
  7. int i = 0;
  8. for(i = 0; i < ps->szie-1; i++)
  9. {
  10. ps->array[i] = ps->array[i+1];
  11. }
  12. ps->size--;//元素个数减1
  13. }

顺序表查找

  1. int SeqListFind(SeqList* ps, SLDataType x)
  2. {
  3. int i = 0;
  4. for (i = 0; i < ps->size; i++)//因为不确定数组中的元素是否排序,所以只能依次查找
  5. {
  6. if (ps->a[i] == x)
  7. {
  8. return i;//找到返回该元素在数组中的下标
  9. //注:这里没有考虑重复元素的情况
  10. }
  11. }
  12. return -1;//未找到返回-1;
  13. }

顺序表在pos位置插入x

将指定位置处向右所有的元素向后移动一个位置,将x插入下标为pos位置处。 

  1. void SeqListInsert(SeqList* ps, int pos, SLDataType x)
  2. {
  3. SeqListCheckCapacity(ps);//同样增加元素需先检查容量
  4. //保证输入的下标是有效的
  5. //1.温柔方法
  6. if (pos > ps->size || pos < 0)
  7. {
  8. printf("输入位置信息错误 ");
  9. return;
  10. }
  11. //2.暴力方法
  12. //assert(pos <= ps->size && pos >= 0)
  13. //将该位置和该位置以后的元素向后移动一个空间
  14. int i = 0;
  15. for (i = ps->size - 1; i >= pos; i--)
  16. {
  17. ps->a[i + 1] = ps->a[i];
  18. }
  19. ps->a[pos] = x;
  20. ps->size++;
  21. }

顺序表删除pos位置的值

同理,将pos位置处右边所有元素向左移动一个位置,覆盖pos位置处的值。 

  1. void SeqListErase(SeqList* ps, int pos)
  2. {
  3. assert(ps->size > 0 && pos >= 0 && pos < ps->size - 1);//保证数组中的有效元素不为0,且输入的下标有效
  4. int i = 0;
  5. for (i = pos + 1; i < ps->size; i++)//将该位置以后的元素向前移动一个位置,将该位置覆盖
  6. {
  7. ps->a[i - 1] = ps->a[i];
  8. }
  9. ps->size--;
  10. }

顺序表销毁(动态)

动态创建的空间在程序结束后必须将该空间释放,不然会导致内存泄漏。 

  1. void SeqListDestory(SeqList* ps)
  2. {
  3. free(ps->a);//释放开辟的空间
  4. ps->a = NULL;//指针置为空
  5. ps->capacity = ps->size = 0;//空间容量和有效元素都置为0
  6. }

顺序表打印

  1. void SeqListPrint(SeqList* ps)
  2. {
  3. int i = 0;
  4. for (i = 0; i < ps->size; i++)
  5. {
  6. printf("%%%%d ", ps->a[i]);
  7. }
  8. printf(" ");
  9. }

 

通讯录实现

Contact.h

  1. #include<stdio.h>
  2. #include<string.h>
  3. #include<stdlib.h>
  4. #define NAME_MAX 10
  5. #define ADDR_MAX 10
  6. #define TELE_MAX 12
  7. #define CONTACT_MAX 1000
  8. #define CAPACITY 3
  9. struct PeopleInfo
  10. {
  11. char name[NAME_MAX];
  12. int age;
  13. char addr[ADDR_MAX];
  14. char tele[TELE_MAX];
  15. };
  16. //静态
  17. //struct Contact
  18. //{
  19. // struct PeopleInfo data[CONTACT_MAX];
  20. // int size;
  21. //};
  22. //动态
  23. struct Contact
  24. {
  25. struct PeopleInfo* data;
  26. int size;//通讯录中有效元素个数
  27. int capacity;//通讯录容量
  28. };
  29. //初始化通讯录
  30. void InitContact(struct Contact* pc);
  31. //通讯录扩容
  32. void BuyContact(struct Contact* pc);
  33. //增加联系人
  34. void AddContact(struct Contact* pc);
  35. //删除联系人
  36. void DelContact(struct Contact* pc);
  37. //查找联系人
  38. void SearchContact(const struct Contact* pc);
  39. //修改联系人
  40. void ModifyContact(struct Contact* pc);
  41. //排序通讯录列表(姓名)
  42. void SortContact(struct Contact* pc);
  43. //显示通讯录
  44. void ShowContact(const struct Contact* pc);
  45. //销毁通讯录
  46. void DestoryContact(struct Contact* pc);
  47. //保存信息到通讯录中
  48. void SaveContact(struct Contact* pc);
  49. //加载文件中的信息到通讯录中
  50. void LoadContact(struct Contact* pc);

Contact.c

  1. #include"Contact.h"
  2. //通讯录扩容
  3. void BuyContact(struct Contact* pc)
  4. {
  5. if (pc->size == pc->capacity)
  6. {
  7. struct PeopleInfo* tmp = (struct PeopleInfo*)realloc(pc->data, sizeof(struct PeopleInfo) * (pc->capacity) * 2);
  8. if (tmp == NULL)
  9. {
  10. perror("BuyContact::fail");
  11. return;
  12. }
  13. pc->data = tmp;
  14. pc->capacity *= 2;
  15. printf("增容成功 ");
  16. }
  17. }
  18. void LoadContact(struct Contact* pc)
  19. {
  20. //打开文件
  21. FILE* pf = fopen("Contact.txt", "rb");
  22. if (pf == NULL)
  23. {
  24. perror("LoadContact::fopen");
  25. return;
  26. }
  27. //读文件
  28. struct PeopleInfo tmp = { 0 };
  29. while (fread(&tmp, sizeof(struct PeopleInfo), 1, pf))
  30. {
  31. BuyContact(pc);
  32. pc->data[pc->size] = tmp;
  33. pc->size++;
  34. }
  35. //关闭文件
  36. fclose(pf);
  37. pf = NULL;
  38. }
  39. //初始化通讯录(静态)
  40. //void InitContact(struct Contact* pc)
  41. //{
  42. // memset(pc->data, 0, sizeof(struct PeopleInfo) * CONTACT_MAX);
  43. // pc->size = 0;
  44. //}
  45. //初始化通讯录(动态)
  46. void InitContact(struct Contact* pc)
  47. {
  48. pc->data = (struct PeopleInfo*)malloc(sizeof(struct PeopleInfo) * CAPACITY);
  49. pc->capacity = CAPACITY;
  50. pc->size = 0;
  51. //加载文件信息到通讯录
  52. LoadContact(pc);
  53. }
  54. //添加联系人
  55. void AddContact(struct Contact* pc)
  56. {
  57. //静态
  58. /*if (pc->size >= CONTACT_MAX)
  59. {
  60. printf("通讯录已满 ");
  61. return;
  62. }*/
  63. //动态(通讯录满了需增容)
  64. BuyContact(pc);
  65. printf("请输入名字>");
  66. scanf("%%%%s", pc->data[pc->size].name);
  67. printf("请输入年龄>");
  68. scanf("%%%%d", &(pc->data[pc->size].age));
  69. printf("请输入住址>");
  70. scanf("%%%%s", pc->data[pc->size].addr);
  71. printf("请输入电话>");
  72. scanf("%%%%s", pc->data[pc->size].tele);
  73. printf("添加成功! ");
  74. pc->size++;
  75. }
  76. //显示通讯录列表
  77. void ShowContact(const struct Contact* pc)
  78. {
  79. if (pc->size == 0)
  80. {
  81. printf("通讯录为空 ");
  82. exit(1);
  83. }
  84. printf(" %%%%-10s %%%%-3s %%%%-10s %%%%-12s ", "name", "age", "addr", "tele");
  85. int i = 0;
  86. for (i = 0; i < pc->size; i++)
  87. {
  88. printf(" %%%%-10s %%%%-3d %%%%-10s %%%%-12s ",
  89. pc->data[i].name,
  90. pc->data[i].age,
  91. pc->data[i].addr,
  92. pc->data[i].tele);
  93. }
  94. }
  95. int FindByName(const struct Contact* pc,const char*name)
  96. {
  97. int i = 0;
  98. for (i = 0; i < pc->size; i++)
  99. {
  100. if (strcmp(pc->data[i].name, name) == 0)
  101. {
  102. return i;
  103. }
  104. }
  105. return -1;
  106. }
  107. //删除联系人
  108. void DelContact(struct Contact* pc)
  109. {
  110. if (pc->size <= 0)
  111. {
  112. printf("通讯录为空 ");
  113. return;
  114. }
  115. char name[NAME_MAX] = { 0 };
  116. printf("请输入需要删除的联系人>");
  117. scanf("%%%%s", name);
  118. int i = FindByName(pc,name);
  119. if (i != -1)
  120. {
  121. for (; i < pc->size - 1; i++)
  122. {
  123. pc->data[i] = pc->data[i + 1];
  124. }
  125. pc->size--;
  126. printf("删除成功 ");
  127. }
  128. else
  129. {
  130. printf("无此联系人 ");
  131. }
  132. }
  133. //查找联系人
  134. void SearchContact(const struct Contact* pc)
  135. {
  136. char name[NAME_MAX] = { 0 };
  137. printf("请输入需要查找的联系人>");
  138. scanf("%%%%s", name);
  139. int i = FindByName(pc, name);
  140. if(i!=-1)
  141. {
  142. printf(" %%%%-10s %%%%-3s %%%%-10s %%%%-12s ", "name", "age", "addr", "tele");
  143. printf(" %%%%-10s %%%%-3d %%%%-10s %%%%-12s ",
  144. pc->data[i].name,
  145. pc->data[i].age,
  146. pc->data[i].addr,
  147. pc->data[i].tele);
  148. }
  149. else
  150. {
  151. printf("无此联系人 ");
  152. }
  153. }
  154. //修改联系人
  155. void ModifyContact(struct Contact* pc)
  156. {
  157. char name[NAME_MAX] = { 0 };
  158. printf("请输入需要修改的联系人>");
  159. scanf("%%%%s", name);
  160. int i = FindByName(pc, name);
  161. if (i != -1)
  162. {
  163. printf("请输入修改后的名字>");
  164. scanf("%%%%s", pc->data[i].name);
  165. printf("请输入修改后的年龄>");
  166. scanf("%%%%d", &(pc->data[i].age));
  167. printf("请输入修改后的住址>");
  168. scanf("%%%%s", pc->data[i].addr);
  169. printf("请输入修改后的电话>");
  170. scanf("%%%%s", pc->data[i].tele);
  171. }
  172. else
  173. {
  174. printf("无此联系人 ");
  175. }
  176. }
  177. //排序通讯录列表(姓名)
  178. int cmp_name(const void* a, const void* b)
  179. {
  180. return strcmp(((struct PeopleInfo*)a)->name , ((struct PeopleInfo*)b)->name);
  181. }
  182. void SortContact(struct Contact* pc)
  183. {
  184. qsort(pc->data, pc->size, sizeof(struct PeopleInfo), cmp_name);
  185. printf("排序成功 ");
  186. }
  187. //销毁通讯录
  188. void DestoryContact(struct Contact* pc)
  189. {
  190. free(pc->data);
  191. pc->data = NULL;
  192. pc->capacity = 0;
  193. pc->size = 0;
  194. }
  195. //保存信息到通讯录中
  196. void SaveContact(struct Contact* pc)
  197. {
  198. //打开文件
  199. FILE* pf = fopen("Contact.txt", "w");
  200. if (pf == NULL)
  201. {
  202. perror("SaveContact::fopen");
  203. exit(1);
  204. }
  205. //写文件
  206. int i = 0;
  207. for (i = 0; i < pc->size; i++)
  208. {
  209. fwrite(&(pc->data[i]), sizeof(struct PeopleInfo), 1, pf);
  210. }
  211. //关闭文件
  212. fclose(pf);
  213. pf = NULL;
  214. }

test.c

  1. #include"Contact.h"
  2. void menu()
  3. {
  4. printf("****************************** ");
  5. printf("******* 1.add 2.del ******* ");
  6. printf("******* 3.search 4.modify **** ");
  7. printf("******* 5.sort 6.show ****** ");
  8. printf("********* 0.exit ************* ");
  9. printf("****************************** ");
  10. }
  11. enum contact
  12. {
  13. EXIT,
  14. ADD,
  15. DEL,
  16. SEARCH,
  17. MODIFY,
  18. SORT,
  19. SHOW
  20. };
  21. int main()
  22. {
  23. //创建通讯录
  24. struct Contact con;
  25. //初始化通讯录
  26. InitContact(&con);
  27. int input = 0;
  28. do
  29. {
  30. menu();
  31. printf("请选择功能>");
  32. scanf("%%%%d", &input);
  33. switch (input)
  34. {
  35. case EXIT:
  36. //保存信息
  37. SaveContact(&con);
  38. DestoryContact(&con);
  39. printf("退出通讯录 ");
  40. break;
  41. case ADD:
  42. AddContact(&con);
  43. break;
  44. case DEL:
  45. DelContact(&con);
  46. break;
  47. case SEARCH:
  48. SearchContact(&con);
  49. break;
  50. case MODIFY:
  51. ModifyContact(&con);
  52. break;
  53. case SORT:
  54. SortContact(&con);
  55. break;
  56. case SHOW:
  57. ShowContact(&con);
  58. break;
  59. default:
  60. printf("选择错误,请重新输入 ");
  61. break;
  62. }
  63. } while (input);
  64. return 0;
  65. }

顺序表的问题及思考

问题:
1. 中间/头部的插入删除,时间复杂度为O(N)

2. 增容需要申请新空间,拷贝数据,释放旧空间。会有不小的消耗。

3. 增容一般是呈2倍的增长,势必会有一定的空间浪费。例如当前容量为100,满了以后增容到200,我们再继续插入了5个数据,后面没有数据插入了,那么就浪费了95个数据空间。

思考:如何解决以上问题呢?

我们下期再来解答~
 

推荐阅读