C语言:顺序表(上) - 实践

C语言:顺序表(上)

1.顺序表的介绍
2.顺序表的实现

1.顺序表的介绍

线性表是n个具有相同特性的数据元素的有限序列。

线性表是一种在实际中广泛使用的数据结构,常见的线性表:顺序表、链表、栈、队列、字符串…

线性表在逻辑上是线性结构,也就说是连续的一条直线。但是在物理结构上并不一定是连续的,线性表在物理上存储时,通常以 数组链式结构 的形式存储。

顺序表的底层结构是数组,对数组的封装,实现了常用的增删改查等接口。

顺序表分为两类:

  • 静态顺序表:使用定长数组存储元素。容易出现空间不够用、空间浪费的问题。
    在这里插入图片描述

  • 动态顺序表:空间可以按需申请。

接下来以动态顺序表为例,编程实现顺序表。

2.顺序表的实现

首先,我们打开vs2022,创建一个头文件SeqList.h,用来定义结构体和函数声明,再创建SeqList.c来编写函数,创建test.c文件来进行测试。
在这里插入图片描述
实现顺序表的过程中,我们需要realloc函数来进行开辟和调整空间,用assert进行检查,所以我们在头文件SeqList.h中应包含stdio.h、stdlib.h、assert.h。

//SeqList.h
#pragma once
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
  #include<stdlib.h>
    #include<assert.h>

定义顺序表SeqList,再用typedef重命名为SL。

typedef struct SeqList
{
int* arr;
int size;
//有效数据个数
int capacity;
//空间大小(个数)
}SL;

指针arr可以指向数组、结构体、字符串等等数据,所以要把int重命名,以便后续更改arr指向的数据。

//SeqList.h
#pragma once
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
  #include<stdlib.h>
    #include<assert.h>
      typedef int SLDataType;
      //把int重命名,方便后续更改
      typedef struct SeqList
      {
      SLDataType* arr;
      int size;
      //有效数据个数
      int capacity;
      //空间大小(个数)
      }SL;

接下来进行函数声明,我们要编写函数实现顺序表的初始化、销毁、打印、尾插/删、头插/删、定位插入/删除、查找。

//SeqList.h
#pragma once
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
  #include<stdlib.h>
    #include<assert.h>
      typedef int SLDataType;
      //把int重命名,方便后续更改
      typedef struct SeqList
      {
      SLDataType* arr;
      int size;
      //有效数据个数
      int capacity;
      //空间大小(个数)
      }SL;
      void SLInit(SL* ps);
      //顺序表初始化
      void SLDestroy(SL* ps);
      //顺序表销毁
      void SLPrint(SL s);
      //顺序表打印
      void SLPushBack(SL* ps, SLDataType x);
      //尾插
      void SLPushFront(SL* ps, SLDataType x);
      //头插
      void SLPopBack(SL* ps);
      //尾删
      void SLPopFront(SL* ps);
      //头删
      void SLInsert(SL* ps, int pos, SLDataType x);
      //定位插入
      void SLErase(SL* ps, int pos);
      //定位删除
      int SLFind(SL* ps, SLDataType x);
      //查找

顺序表初始化:指针ps指向顺序表,把arr先置为NULL,有效数据的个数size为0,空间大小capacity为0。

void SLInit(SL* ps)//顺序表初始化
{
ps->arr = NULL;
ps->size = ps->capacity = 0;
}

顺序表销毁:需要判断顺序表是否为空,若为空则不需要销毁,若不为空则先用free释放arr指向的空间,再把size、capacity置为0。

void SLDestroy(SL* ps)//顺序表销毁
{
if (ps->arr)//若ps->arr为NULL,不再free,若不为NULL,则执行free
free(ps->arr);
ps->arr = NULL;
ps->size = ps->capacity = 0;
}

在创建顺序表之后,我们通过头插、尾插、定位插入的方式往顺序表中插入数据,但在插入之前,我们需要检查arr指向的空间是否足够,不够则用realloc函数增加空间。

我们先判断size与capacity是否相等,若相等,则说明空间不足,如图所示:
在这里插入图片描述
size与capacity不相等,则以原空间大小的2倍增加空间。

void SLCheckCapacity(SL* ps)//检查插入前空间是否足够
{
if (ps->capacity == ps->size)//空间大小和有效数据个数一致,则空间不足
{
int newCapacity = ps->capacity == 0 ? 4 : 2 * ps->capacity;
SLDataType* t = (SLDataType*)realloc(ps->arr, newCapacity * sizeof(SLDataType));
//要申请多大空间
if (t == NULL)//若空间申请失败
{
perror("realloc fail !");
exit(1);
//退出程序
}
ps->arr = t;
//空间申请成功
ps->capacity = newCapacity;
//记录新的空间大小
}
}

尾插:尾插函数需要参数指向顺序表的指针ps和插入的数据x,先用assert检查ps是否为NULL,再通过SLCheckCapacity函数检查空间是否足够,若不足则增加空间,在插入数据之后,还要让size加1,记录新的有效数据个数。
在这里插入图片描述

void SLPushBack(SL* ps, SLDataType x)//尾插
{
assert(ps);
SLCheckCapacity(ps);
//检查插入前空间是否足够
ps->arr[ps->size] = x;
ps->size++;
}

头插:头插与尾插类似,头插还需要循环把数据整体向后挪动一位。

void SLPushFront(SL* ps, SLDataType x)//头插
{
assert(ps);
SLCheckCapacity(ps);
//检查插入前空间是否足够
for (int i = ps->size;i >
0;i--)//让顺序表中已有的数据整体往后挪一位
{
//从最后一位开始挪,避免覆盖 
ps->arr[i] = ps->arr[i - 1];
}
ps->arr[0] = x;
ps->size++;
}

顺序表打印函数:

void SLPrint(SL s)//打印
{
for (int i = 0;i < s.size;i++)
printf("%d ", s.arr[i]);
printf("\n");
}

尾删:只要size减1,让原本最后一个数据无法被访问,就实现了尾删。

void SLPopBack(SL* ps)//尾删
{
assert(ps);
assert(ps->size);
//顺序表不为空
--ps->size;
}

在这里插入图片描述
头删:与尾删类似,头删还要数据整体向前挪一位,且从第二位数据开始挪,通过覆盖第一位数据实现头删。

void SLPopFront(SL* ps)//头删
{
assert(ps);
assert(ps->size);
for (int i = 0;i < ps->size - 1;i++)//数据整体往前挪一位
  {
  //从第二位往前挪,覆盖第一位实现删除
  ps->arr[i] = ps->arr[i + 1];
  }
  ps->size--;
  }

定位插入:类似头插、尾插,但多了一个参数pos,pos为数据插入后的下标,所以0<=pos<=size,在插入前,下标为pos及大于pos的数据往后挪一位。

void SLInsert(SL* ps, int pos, SLDataType x)//定位插入,插入后下标为pos
{
assert(ps);
assert(pos >= 0 && pos <= ps->size);
  SLCheckCapacity(ps);
  for (int i = ps->size;i > pos;i--)//pos及之后的数据整体往后挪一位
  {
  ps->arr[i] = ps->arr[i - 1];
  }
  ps->arr[pos] = x;
  ps->size++;
  }

定位删除:pos为下标,显然0<=pos<size,下标大于pos的数据往前挪一位,通过arr[pos+1]把arr[pos]覆盖掉来实现定位删除。

void SLErase(SL* ps, int pos)//定位删除
{
assert(ps);
assert(pos >= 0 && pos < ps->size);
  for (int i = pos;i < ps->size-1;i++)//pos之后数据往前挪一位
    {
    //arr[pos+1]覆盖掉arr[pos]
    ps->arr[i] = ps->arr[i + 1];
    }
    ps->size--;
    }

查找:SLFind函数的参数x为要查找的数据,遍历整个顺序表,找到返回下标,找不到返回-1。

int SLFind(SL* ps, SLDataType x)//查找
{
assert(ps);
for (int i = 0;i < ps->size;i++)
  {
  if (ps->arr[i] == x)
  return i;
  //找到了,返回下标
  }
  return -1;
  //未找到
  }

在SeqList.c文件中的代码如下:

//SeqList.c
#include"SeqList.h"
void SLInit(SL* ps)//顺序表初始化
{
ps->arr = NULL;
ps->size = ps->capacity = 0;
}
void SLDestroy(SL* ps)//顺序表销毁
{
if (ps->arr)//若ps->arr为NULL,不再free,若不为NULL,则执行free
free(ps->arr);
ps->arr = NULL;
ps->size = ps->capacity = 0;
}
void SLCheckCapacity(SL* ps)//检查插入前空间是否足够
{
if (ps->capacity == ps->size)//空间大小和有效数据个数一致,则空间不足
{
int newCapacity = ps->capacity == 0 ? 4 : 2 * ps->capacity;
SLDataType* t = (SLDataType*)realloc(ps->arr, newCapacity * sizeof(SLDataType));
//要申请多大空间
if (t == NULL)//若空间申请失败
{
perror("realloc fail !");
exit(1);
//退出程序
}
ps->arr = t;
//空间申请成功
ps->capacity = newCapacity;
}
}
void SLPushBack(SL* ps, SLDataType x)//尾插
{
assert(ps);
SLCheckCapacity(ps);
//检查插入前空间是否足够
ps->arr[ps->size] = x;
ps->size++;
}
void SLPushFront(SL* ps, SLDataType x)//头插
{
assert(ps);
SLCheckCapacity(ps);
//检查插入前空间是否足够
for (int i = ps->size;i >
0;i--)//让顺序表中已有的数据整体往后挪一位
{
//从最后一位开始挪,避免覆盖 
ps->arr[i] = ps->arr[i - 1];
}
ps->arr[0] = x;
ps->size++;
}
void SLPrint(SL s)//打印
{
for (int i = 0;i < s.size;i++)
printf("%d ", s.arr[i]);
printf("\n");
}
void SLPopBack(SL* ps)//尾删
{
assert(ps);
assert(ps->size);
//顺序表不为空
--ps->size;
}
void SLPopFront(SL* ps)//头删
{
assert(ps);
assert(ps->size);
for (int i = 0;i < ps->size - 1;i++)//数据整体往前挪一位
  {
  //从第二位往前挪,覆盖第一位实现删除
  ps->arr[i] = ps->arr[i + 1];
  }
  ps->size--;
  }
  void SLInsert(SL* ps, int pos, SLDataType x)//定位插入,插入后下标为pos
  {
  assert(ps);
  assert(pos >= 0 && pos <= ps->size);
    SLCheckCapacity(ps);
    for (int i = ps->size;i > pos;i--)//pos及之后的数据整体往后挪一位
    {
    ps->arr[i] = ps->arr[i - 1];
    }
    ps->arr[pos] = x;
    ps->size++;
    }
    void SLErase(SL* ps, int pos)//定位删除
    {
    assert(ps);
    assert(pos >= 0 && pos < ps->size);
      for (int i = pos;i < ps->size-1;i++)//pos之后数据往前挪一位
        {
        //arr[pos+1]覆盖掉arr[pos]
        ps->arr[i] = ps->arr[i + 1];
        }
        ps->size--;
        }
        int SLFind(SL* ps, SLDataType x)//查找
        {
        assert(ps);
        for (int i = 0;i < ps->size;i++)
          {
          if (ps->arr[i] == x)
          return i;
          //找到了,返回下标
          }
          return -1;
          //未找到
          }

最后,我们就可以在test.c文件中做测试了,例如测试头插和尾插:

#include"SeqList.h"
void test1()
{
SL s;
SL* ps = &s;
SLInit(ps);
SLPushFront(ps, 1);
SLPushBack(ps,2);
SLPushBack(ps, 3);
SLPushBack(ps, 4);
SLPrint(s);
SLDestroy(ps);
}
int main()
{
test1();
return 0;
}

在这里插入图片描述

拙作一篇,望诸位同道不吝斧正。

posted @ 2025-07-30 17:19  wzzkaifa  阅读(10)  评论(0)    收藏  举报