详解初阶数据结构之顺序表(SeqList)——单文件实现SeqList的增删查改

简介: 详解初阶数据结构之顺序表(SeqList)——单文件实现SeqList的增删查改

一、线性表


线性表(linear list)是n个具有相同特性的数据元素的有限序列。 线性表是一种在实际中广泛使

用的数据结构,常见的线性表:顺序表、链表、栈、队列、字符串...

线性表在逻辑上是线性结构,也就说是连续的一条直线。但是在物理结构上并不一定是连续的,

线性表在物理上存储时,通常以数组和链式结构的形式存储。

6078fe44509e420182cfabf612e97b34.png


二、顺序表


2.1概念及结构


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

顺序表一般分为:

静态顺序表:使用定长数组储存元素

//静态顺序表
#define N 100
struct SeqList
{
  int a[N];//定长数组
  int size;//有效数据的个数
};


c873a560f9b3471e81ec7ec0690b65cf.png

缺点:不是很灵活

动态顺序表:使用动态开辟的数组储存。

e3e75eea98f447a08ac65041851c3f86.png


2.2接口实现


静态顺序表只适用于确定知道需要存多少数据的场景。静态顺序表的定长数组导致N定大了,空

间开多了浪费,开少了不够用。所以现实中基本都是使用动态顺序表,根据需要动态的分配空间

大小,所以下面我们实现动态顺序表。


所谓动态其实指的这个结构体里的指针是动态内存开辟来的,是可变的,用的时候动态开辟,不够的话继续开辟,程序结束的时候释放。


2.3动态顺序表的创建


typedef int SLDatatype;//将int重命名为SLDatatype
typedef struct SeqList
{
  SLDatatype* a;//指向动态开辟的数组
  SLDatatype capacity;//容量
  SLDatatype size;//有效数据的个数
}SL;//将结构体SeqList重命名为SL



2.3动态顺序表的初始化


2.3.1传值初始化

//传值初始化
void SLInit(SL s)
{
  s.a = NULL;
  s.size = 0;
  s.capacity = 0;
}



函数那个章节我们学过形参只是实参的临时拷贝,并没有实际作用,生命周期短,出了函数的作用域就会销毁,我们不考虑这种初始化方式。


2.3.2传址初始化


//传址初始化
void SLInit(SL* ps)
{
  ps->a = 0;
  ps->capacity = 0;
  ps->size = 0;
}


void SLInit(SL* ps)
{
  ps->a = (SLDatatype*)malloc(sizeof(SLDatatype) * 4);//开辟了4个字节的空间
  if (ps->a == NULL)
  {
    perror("malloc failed");
    exit(-1);
  }
  ps->capacity = 4;//开辟了空间就要给容量赋值
  ps->size = 0;
}


上面两种初始化方式都可以给予结构体成员变量赋值,但是我们使用第二种,因为第二种为我们开辟了空间。


2.4动态顺序表的清空


void SLDestr(SL* ps)
{
  free(ps->a);
  ps->a = NULL;
  ps->capacity = 0;//内存释放,容量清零
  ps->size = 0;//内存释放,有效数据清零
}


2.5动态顺序表的扩容


void SLCheckcapacity(SL* ps)
{
  if (ps->size == ps->capacity)
  {
    SLDatatype* tmp = (SLDatatype*)realloc(ps->a, ps->capacity * 2 *( sizeof(SLDatatype)));//扩容尾原来的倍数
    if (tmp == NULL)
        //判断是否扩容失败
    {
      perror("realloc failed");
      exit(-1);
    }
    ps->a = tmp;
    ps->capacity *= 2;//扩容后修改原来的容量
  }
}


这就是所谓的动态,当我们空间不够时,就需要开辟新的空间,使用realloc函数要注意是否开辟成功,定义一个中间指针,当开辟成功时将这个指针赋值给动态数组中的指针。


2.6动态顺序表内容的打印


void SLprint(SL* ps)
{
  int i = 0;
  for (i = 0; i < ps->size; i++)
  {
    printf("%d ", ps->a[i]);
  }
  printf("\n");
}

size为有效数据个数,使用循环打印其中的有效数据。


三、动态顺序表的使用


3.1尾插尾删


3.1.1尾插


void SLPushBack(SL* ps, SLDatatype x)
{
  SLCheckcapacity(ps);//检查空间是否足够插入
  ps->a[ps->size] = x;//赋值
  ps->size++;//插入一个有效数据,有效数据个数加一
}


首先一定要检查规矩是否足够,根据上面开辟的空间,容量为4,有效数据为size为0,所以从第一个空间开始插入数据。


3.1.2尾删


//尾删
void SLPopBack(SL* ps)
{
  assert(ps->size > 0);//判断是否会造成越界
  if (ps->size == 0)
  {
    return;
  }
  ps->size--;//删除一个数据,有效数据个数减一
}

插入数据后size的大小也会变化,数组中最后一个数字的下标刚好和size的大小一样我们只需要将size减1就行。


3.2头插头删


3.2.1头插


void SLPushFront(SL* ps, SLDatatype x)
{
  SLCheckcapacity(ps);//检查空间是否足够
  int end = ps->size;
  while (end > 0)
  {
    ps->a[end] = ps->a[end - 1];//将前一个数据后移动
    end--;
  }
  ps->a[0] = x;//将x赋给初始位置
  ps->size++;//加入一个数字,有效数据个数加1
}

老规矩一定要检查空间是否足够,头部插入数据我们只需要将原来的数据往后移动一格就将第一格的位置空出来,再将数值插入就行,插入一个数据,有效数据加1即可。


3.2.2头删


void SLPopFront(SL* ps)
{
  assert(ps->size > 0);//防止越界访问
  if (ps->size==0)
  {
    return;
  }
  int begin = 0;
  while (begin < ps->size)
  {
    ps->a[begin] = ps->a[begin+1];//将后一个数据往前移动
    begin++;
  }
  ps->size--;//减少一个数字,有效数据减1
}


这里的删除并不是真正意义上的删除,我们只需要将原来的数据往前移动一位使后一位的数据覆盖在前一位,这就做到了删除,顺便再将有效数据减1就行。


3.3在pos位置插入x


1. voidvoid SLInsert(SL* ps, int pos, int x)
{
  assert(pos >= 0 && pos <= ps->size);//防止越界访问
  SLCheckcapacity(ps);
  int end = ps->size;
  while (end >=pos)
  {
    ps->a[end] = ps->a[end-1];//和头插的思想差不多,将数据后移
    end--;
  }
  ps->a[pos] = x;//将x赋值给pos位置
  ps->size++;//有效数据加1
}


我们可以这样理解:将pos看成初始位置,是不是就转化为头插了?按照头插的思想就可以完成在pos位置上插入x。


3.4删除pos位置的值


void SLErase(SL* ps, int pos)
{
  assert(pos >= 0 && pos <= ps->size);//防止越界访问
  SLCheckcapacity(ps);
  int begin = pos;
  while (begin < ps->size)
  {
    ps->a[begin] = ps->a[begin + 1];//和头删的思想差不多,将数据前移
    begin++;
  }
  ps->size--;//有效数据减1
}


我们会发现3.4和3.5不仅可以做到某个位置值的插入和删除,也可以做到尾插尾删和头插头删。


3.5修改某个位置的值


void SLModify(SL* ps, SLDatatype pos, SLDatatype x)
{
  assert(pos >= 0 && pos < ps->size);//防止越界
  ps->a[pos] = x;
}


这样修改某个位置的值看起来是挺麻烦,但是是为了安全考虑。


四、完整代码


#define _CRT_SECURE_NO_WARNINGS 67
#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
//静态顺序表
//#define N 100
//struct SeqList
//{
//  int a[N];//定长数组
//  int size;//有效数据的个数
//};
//动态顺序表
//创建
typedef int SLDatatype;
typedef struct SeqList
{
  SLDatatype* a;//指向动态开辟的数组
  SLDatatype capacity;//容量
  SLDatatype size;//有效数据的个数
}SL;
//传值初始化
//void SLInit(SL s)
//{
//  s.a = NULL;
//  s.size = 0;
//  s.capacity = 0;
//}
//传址初始化
//void SLInit(SL* ps)
//{
//  ps->a = 0;
//  ps->capacity = 0;
//  ps->size = 0;
//}
void SLInit(SL* ps)
{
  ps->a = (SLDatatype*)malloc(sizeof(SLDatatype) * 4);//开辟了4个字节的空间
  if (ps->a == NULL)
  {
    perror("malloc failed");
    exit(-1);
  }
  ps->capacity = 4;
  ps->size = 0;
}
//清空
void SLDestr(SL* ps)
{
  free(ps->a);
  ps->a = NULL;
  ps->capacity = 0;
  ps->size = 0;
}
//打印
void SLprint(SL* ps)
{
  int i = 0;
  for (i = 0; i < ps->size; i++)
  {
    printf("%d ", ps->a[i]);
  }
  printf("\n");
}
//检查容量
void SLCheckcapacity(SL* ps)
{
  if (ps->size == ps->capacity)
  {
    SLDatatype* tmp = (SLDatatype*)realloc(ps->a, ps->capacity * 2 *( sizeof(SLDatatype)));//扩容尾原来的倍数
    if (tmp == NULL)
    {
      perror("realloc failed");
      exit(-1);
    }
    ps->a = tmp;
    ps->capacity *= 2;
  }
}
//尾插
void SLPushBack(SL* ps, SLDatatype x)
{
  SLCheckcapacity(ps);
  ps->a[ps->size] = x;
  ps->size++;
}
//尾删
void SLPopBack(SL* ps)
{
  assert(ps->size > 0);
  if (ps->size == 0)
  {
    return;
  }
  ps->size--;
}
//头插
void SLPushFront(SL* ps, SLDatatype x)
{
  SLCheckcapacity(ps);
  int end = ps->size;
  while (end > 0)
  {
    ps->a[end] = ps->a[end - 1];
    end--;
  }
  ps->a[0] = x;
  ps->size++;
}
//头删
void SLPopFront(SL* ps)
{
  assert(ps->size > 0);
  if (ps->size==0)
  {
    return;
  }
  int begin = 0;
  while (begin < ps->size)
  {
    ps->a[begin] = ps->a[begin+1];
    begin++;
  }
  ps->size--;
}
//在pos位置插入x
void SLInsert(SL* ps, int pos, int x)
{
  assert(pos >= 0 && pos <= ps->size);
  SLCheckcapacity(ps);
  int end = ps->size;
  while (end >=pos)
  {
    ps->a[end] = ps->a[end-1];
    end--;
  }
  ps->a[pos] = x;
  ps->size++;
}
//删除pos位置的值
void SLErase(SL* ps, int pos)
{
  assert(pos >= 0 && pos <= ps->size);
  SLCheckcapacity(ps);
  int begin = pos;
  while (begin < ps->size)
  {
    ps->a[begin] = ps->a[begin + 1];
    begin++;
  }
  ps->size--;
}
int SLFind(SL* ps, int x)
{
  int i = 0;
  for (i = 0; i < ps->size; i++)
  {
    if (ps->a[i] == x)
      return i;
  }
  return -1;
}
void SLModify(SL* ps, SLDatatype pos, SLDatatype x)
{
  assert(pos >= 0 && pos < ps->size);
  ps->a[pos] = x;
}
int main()
{
  SL s1;
  //传值初始化
  //SLInit(s1);
  //传址初始化
  SLInit(&s1);
  //尾插
  SLPushBack(&s1, 1);
  SLPushBack(&s1, 2);
  SLPushBack(&s1, 3);
  SLPushBack(&s1, 4);
  SLPushBack(&s1, 5);
  SLPushBack(&s1, 6);
  SLPushBack(&s1, 7);
  //尾插测试
  printf("尾插:\n");
  SLprint(&s1);
  //尾删
  SLPopBack(&s1);
  //尾删测试
  printf("尾删:\n");
  SLprint(&s1);
  //头插
  SLPushFront(&s1,10);
  //头插测试
  printf("头插:\n");
  SLprint(&s1);
  //头删 
  SLPopFront(&s1);
  //头删测试
  printf("头删:\n");
  SLprint(&s1);
  //在pos位置插入x
  SLInsert(&s1, 0, 100);
  //pos插入x测试
  printf("pos位置插入x\n");
  SLprint(&s1);
  //删除pos位置的值
  SLErase(&s1, 0);
  //测试
  printf("删除pos位置的值\n");
  SLprint(&s1);
  //改
  SLModify(&s1, 2, 1);
  printf("修改某个位置上的值:\n");
  //
  SLprint(&s1);
  //清空
  SLDestr(&s1);
  return 0;
}


aac55633dad94dbcb7c101f6e598d52c.png

相关文章
|
2月前
|
存储 算法 程序员
【数据结构】C语言实现顺序表万字详解(附完整运行代码)
【数据结构】C语言实现顺序表万字详解(附完整运行代码)
40 0
|
2月前
|
存储 编译器
数据结构:顺序表详解
数据结构:顺序表详解
37 0
|
存储 索引
数据结构--动态顺序表
数据结构--动态顺序表
|
2月前
|
存储 消息中间件 算法
数据结构从入门到精通——顺序表
顺序表是一种常见的线性数据结构,它使用一段连续的存储单元依次存储数据元素。这种数据结构的特点是逻辑上相邻的元素在物理存储位置上也相邻,因此可以快速地访问表中的任意元素。 顺序表的实现通常依赖于数组,数组是一种静态的数据结构,一旦创建,其大小就是固定的。这意味着在顺序表中插入或删除元素可能会导致空间的浪费或不足。例如,如果在一个已经满了的顺序表中插入一个新元素,就需要重新分配更大的数组空间,并将原有元素复制到新数组中,这是一个相对耗时的操作。
54 0
|
4月前
|
存储 算法 C语言
数据结构 | 顺序表专题
数据结构 | 顺序表专题
|
6天前
|
存储 算法 C语言
C语言进阶:顺序表(数据结构基础) (以通讯录项目为代码练习)
C语言进阶:顺序表(数据结构基础) (以通讯录项目为代码练习)
|
2月前
|
存储 编译器
数据结构之顺序表的实现(详解!附完整代码)
数据结构之顺序表的实现(详解!附完整代码)
37 1
|
2月前
|
Java 数据库连接 API
Java 学习路线:基础知识、数据类型、条件语句、函数、循环、异常处理、数据结构、面向对象编程、包、文件和 API
Java 是一种广泛使用的、面向对象的编程语言,始于1995年,以其跨平台性、安全性和可靠性著称,应用于从移动设备到数据中心的各种场景。基础概念包括变量(如局部、实例和静态变量)、数据类型(原始和非原始)、条件语句(if、else、switch等)、函数、循环、异常处理、数据结构(如数组、链表)和面向对象编程(类、接口、继承等)。深入学习还包括包、内存管理、集合框架、序列化、网络套接字、泛型、流、JVM、垃圾回收和线程。构建工具如Gradle、Maven和Ant简化了开发流程,Web框架如Spring和Spring Boot支持Web应用开发。ORM工具如JPA、Hibernate处理对象与数
95 3
|
2月前
|
存储
【数据结构——顺序表的实现】
【数据结构——顺序表的实现】
|
2月前
|
存储
【数据结构】顺序表
【数据结构】顺序表