• 顺序表的实现和练习


    杂谈:

    有些数据结构(C语言实现)的教材/教程中会使用C++中引用的语法,引用确实在形式上比指针简洁,这样做无非是为了避免后续对二级指针的使用。

    我认为既然使用C语言实现数据结构,那么指针就不应该是门槛。增加了引用的C到底是C还是C++呢?如果使用C++完全可以用类实现数据结构,而不是使用兼容C的低级语法。

    C语言实现数据结构重要前置知识:指针、结构体、动态内存管理、(递归、函数栈帧...)。

    顺序表实现(动态版本)

    用C实现顺序表结构(动态版本,支持自动扩容),及相关操作函数:初始化、销毁、打印、插入(任意位置、头插、尾插)、删除(任意位置、头删、尾删)、查找、修改。

    注:本文(包括后续数据结构实现)函数命名均采用C++STL的命名风格

    定义顺序表及函数声明

    在SeqList.h头文件中定义顺序表结构及声明相关函数:

    1. #include
    2. #include
    3. #include
    4. typedef int SLDataType; // 顺序表数据类型
    5. typedef struct SeqList // 顺序表结构
    6. {
    7. SLDataType* a; // 动态数组,存放数据元素
    8. int size; // 元素个数
    9. int capacity; // 数组容量
    10. }SL;
    11. void SLInit(SL* psl); // 顺序表初始化
    12. void SLDestroy(SL* psl);// 销毁顺序表
    13. // 对数据的管理:增删查改
    14. void SLPrint(SL* psl); // 打印顺序表
    15. void SLPushBack(SL* psl, SLDataType x); // 尾插元素
    16. void SLPushFront(SL* psl, SLDataType x);// 头插元素
    17. void SLPopFront(SL* psl);// 头删
    18. void SLPopBack(SL* psl); // 尾删
    19. // 顺序表查找
    20. int SLFind(SL* ps, SLDataType x);
    21. // 顺序表在pos位置(下标)插入x
    22. void SLInsert(SL* ps, int pos, SLDataType x);
    23. // 顺序表删除pos位置(下标)的值
    24. void SLErase(SL* ps, int pos);
    25. // 顺序表修改pos位置的值
    26. void SLModify(SL* psl, int pos, SLDataType x);

    函数定义

    在SeqList.c文件中定义顺序表操作函数,为方便演示,所有函数将单独展示:

    初始化

    1. #include "SeqList.h"
    2. void SLInit(SL* psl)
    3. {
    4. assert(psl);// 断言psl是否为空指针
    5. psl->a = (SLDataType*)malloc(sizeof(SLDataType) * 4);// 初始容量设置为4
    6. if (psl->a == NULL) //动态申请失败,结束函数
    7. {
    8. perror("malloc fail");
    9. return;
    10. }
    11. psl->capacity = 4;
    12. psl->size = 0;
    13. }

    销毁

    注:对顺序表指针的销毁应由使用者操作 free(psl); psl = NULL;

    1. void SLDestroy(SL* psl)
    2. {
    3. assert(psl);
    4. free(psl->a); // 释放动态数组
    5. psl->a = NULL;// 指针置空
    6. psl->size = psl->capacity = 0;// 个数、容量置0
    7. }

    打印

    1. void SLPrint(SL* psl)
    2. {
    3. assert(psl);
    4. for (int i = 0; i < psl->size; i++)
    5. {
    6. printf("%d ", psl->a[i]);
    7. }
    8. printf("\n");
    9. }

    检查容量

    容量不够则扩容2倍

    1. void SLCheckCapacity(SL* psl)
    2. {
    3. assert(psl);
    4. if (psl->size == psl->capacity)// 顺序表已满,需要扩容
    5. {
    6. SLDataType* tmp = (SLDataType*)realloc(psl->a, sizeof(SLDataType) * psl->capacity * 2);
    7. if (tmp == NULL)
    8. {
    9. perror("realloc fail");
    10. return;
    11. }
    12. psl->a = tmp;
    13. psl->capacity *= 2;
    14. }
    15. }

    插入

    1. void SLInsert(SL* psl, int pos, SLDataType x)
    2. {
    3. assert(psl);
    4. assert(0 <= pos && pos <= psl->size);// 断言pos是否合法,可在原范围[0,size)和下一位置size插入
    5. SLCheckCapacity(psl);
    6. int end = psl->size - 1;
    7. while (end >= pos)
    8. {
    9. psl->a[end + 1] = psl->a[end];// 元素向后移动一位
    10. --end;
    11. }
    12. psl->a[pos] = x;// pos位置插入x
    13. psl->size++;
    14. }

    删除

    1. void SLErase(SL* psl, int pos)
    2. {
    3. assert(psl);
    4. assert(0 <= pos && pos < psl->size);// 只能删除原范围数据[0,size)
    5. int start = pos + 1;
    6. while (start < psl->size)
    7. {
    8. psl->a[start - 1] = psl->a[start];// 元素向前移动一位
    9. ++start;
    10. }
    11. psl->size--;
    12. }

    尾插

    在顺序表末尾增加一个元素

    1. void SLPushBack(SL* psl, SLDataType x)
    2. {
    3. assert(psl);
    4. SLInsert(psl, psl->size, x);
    5. }

    头插

    在顺序表第一个元素前插入一个元素

    1. void SLPushFront(SL* psl, SLDataType x)
    2. {
    3. assert(psl);
    4. SLInsert(psl, 0, x);
    5. }

    尾删

    删除顺序表最后一个元素

    1. void SLPopBack(SL* psl)
    2. {
    3. assert(psl);
    4. SLErase(psl, psl->size - 1);
    5. }

    头删

    删除顺序表第一个元素

    1. void SLPopFront(SL* psl)
    2. {
    3. assert(psl);
    4. SLErase(psl, 0);
    5. }

    查找

    查找元素第一个位置,返回其下标(未找到返回-1)

    1. int SLFind(SL* psl, SLDataType x)
    2. {
    3. assert(psl);
    4. for (int i = 0; i < psl->size; i++)
    5. {
    6. if (psl->a[i] == x)
    7. {
    8. return i;
    9. }
    10. }
    11. return -1;
    12. }

    修改

    1. void SLModify(SL* psl, int pos, SLDataType x)
    2. {
    3. assert(psl);
    4. assert(0 <= pos && pos < psl->size);
    5. psl->a[pos] = x;// 修改pos位置数据
    6. }

    测试

    在test.c文件中定义函数或直接在main函数中测试顺序表功能,建议每实现一部分功能就进行相关测试。如果实现完顺序表的所有操作函数在去测试,不容易发现错误。

    1 尾插测试

    1. void TestSeqList1()
    2. {
    3. SL s;
    4. SLInit(&s);
    5. SLPushBack(&s, 1);
    6. SLPushBack(&s, 2);
    7. SLPushBack(&s, 3);
    8. SLPushBack(&s, 4);
    9. SLPushBack(&s, 5);
    10. SLPrint(&s);
    11. SLDestroy(&s);
    12. }

    运行结果:

    2 头插测试

    1. void TestSeqList2()
    2. {
    3. SL s;
    4. SLInit(&s);
    5. SLPushFront(&s, 1);
    6. SLPushFront(&s, 2);
    7. SLPushFront(&s, 3);
    8. SLPushFront(&s, 4);
    9. SLPushFront(&s, 5);
    10. SLPrint(&s);
    11. SLDestroy(&s);
    12. }

    运行结果:

    3 尾删测试

    1. void TestSeqList3()
    2. {
    3. SL s;
    4. SLInit(&s);
    5. SLPushBack(&s, 1);
    6. SLPushBack(&s, 2);
    7. SLPushBack(&s, 3);
    8. SLPushBack(&s, 4);
    9. SLPushBack(&s, 5);
    10. SLPrint(&s);
    11. SLPopBack(&s);
    12. SLPopBack(&s);
    13. SLPopBack(&s);
    14. SLPrint(&s);
    15. SLDestroy(&s);
    16. }

    运行结果:

    4 头删测试

    1. void TestSeqList4()
    2. {
    3. SL s;
    4. SLInit(&s);
    5. SLPushBack(&s, 1);
    6. SLPushBack(&s, 2);
    7. SLPushBack(&s, 3);
    8. SLPushBack(&s, 4);
    9. SLPushBack(&s, 5);
    10. SLPrint(&s);
    11. SLPopFront(&s);
    12. SLPopFront(&s);
    13. SLPrint(&s);
    14. SLDestroy(&s);
    15. }

    运行结果:

    5 插入测试

    1. void TestSeqList5()
    2. {
    3. SL s;
    4. SLInit(&s);
    5. SLPushBack(&s, 1);
    6. SLPushBack(&s, 2);
    7. SLPushBack(&s, 3);
    8. SLPushBack(&s, 4);
    9. SLPushBack(&s, 5);
    10. SLPrint(&s);
    11. SLInsert(&s, 3, 30);
    12. SLPrint(&s);
    13. SLDestroy(&s);
    14. }

    运行结果:

    6 删除测试

    1. void TestSeqList6()
    2. {
    3. SL s;
    4. SLInit(&s);
    5. SLPushBack(&s, 1);
    6. SLPushBack(&s, 2);
    7. SLPushBack(&s, 3);
    8. SLPushBack(&s, 4);
    9. SLPushBack(&s, 5);
    10. SLPrint(&s);
    11. SLErase(&s, 2);
    12. SLPrint(&s);
    13. SLDestroy(&s);
    14. }

    运行结果:

    7 查找、修改测试

    1. void TestSeqList7()
    2. {
    3. SL s;
    4. SLInit(&s);
    5. SLPushBack(&s, 1);
    6. SLPushBack(&s, 2);
    7. SLPushBack(&s, 3);
    8. SLPushBack(&s, 4);
    9. SLPushBack(&s, 5);
    10. SLPrint(&s);
    11. int pos = SLFind(&s, 3);
    12. if (pos != -1)
    13. {
    14. SLErase(&s, pos);
    15. }
    16. SLPrint(&s);
    17. SLDestroy(&s);
    18. }

    运行结果:

    题目练习

    1.原地移除数组中所有的元素val。

    题目链接:移除元素

    方法1:

    1. 从前向后遍历数组nums,找到第一个val
    2. 将val后面的元素向前移动一位,即删除该val;numsSize-1
    3. 循环重复上述操作,直到所有val被删除
    4. 返回numSize

    时间复杂度:O(n^2) 空间复杂度:O(1)

    图解:

    代码:

    1. int removeElement(int* nums, int numsSize, int val)
    2. {
    3. for(int i = 0; i < numsSize; i++) // 遍历数组
    4. {
    5. if(nums[i] == val)// 找到val
    6. {
    7. for(int j = i;j < numsSize-1; j++)
    8. {
    9. nums[j] = nums[j+1];// val后面元素向前移动一位
    10. }
    11. i--; // val下一个元素可能也是val,需要重新判断该位置(图解省略了这种情况)
    12. numsSize--;// 数组个数-1
    13. }
    14. }
    15. return numsSize;
    16. }

    方法2:

    1. 设置变量count用来记录数组nums中val的个数
    2. 遍历数组,对于数组中每个元素,如果该元素=val则count++,否则将该元素向前移动count个位置(因为前面count个val已经被删除了,后面元素需要向前覆盖)
    3. 返回numsSize-count

    时间复杂度:O(n) 空间复杂度:O(1)

    图解:

    代码:

    1. int removeElement(int* nums, int numsSize, int val){
    2. int count = 0;
    3. for(int i = 0; i < numsSize; i++)
    4. {
    5. if(nums[i] == val)
    6. {
    7. count++;
    8. }
    9. else
    10. {
    11. nums[i-count] = nums[i];
    12. }
    13. }
    14. return numsSize - count;
    15. }

    方法3:双指针

    1. 用count记录val个数
    2. 指针i从前向后扫描数组nums,指针j指向nums[0],如果nums[i]不等于val,则赋值给nums[j],j指向下一位置,否则,count+1。
    3. 返回numsSize-count。

    时间复杂度:O(n) 空间复杂度:O(1)

    图解:

    代码:

    1. int removeElement(int* nums, int numsSize, int val){
    2. int count = 0;
    3. for (int i = 0, j = 0; i < numsSize; i++)
    4. {
    5. if (nums[i] != val)
    6. {
    7. nums[j] = nums[i];
    8. j++;
    9. }
    10. else
    11. {
    12. count++;
    13. }
    14. }
    15. return numsSize - count;
    16. }

    如果本文内容对你有帮助,可以点赞收藏,感谢支持,期待你的关注。

    本专栏下篇预告:单链表实现及练习

  • 相关阅读:
    孙卫琴的《精通Vue.js》读书笔记-命名路由
    分布式文件存储——分块上传和断点续传
    RFID技术引领汽车零部件加工新时代
    【甜点】URL化
    react使用react-split-pane分割面板
    “软件定义汽车”下的软件虚拟化技术
    CentOS 7 安装Java环境
    基于LSTM的诗词生成
    计算机毕业论文java毕业设计成品源码网站strust2+mybatis新能源汽车车辆采购系统[包运行成功]
    10.0 探索API调试事件原理
  • 原文地址:https://blog.csdn.net/2301_79391308/article/details/133235843