数据结构:顺序表的奥秘

简介: 数据结构:顺序表的奥秘



一.顺序表的概念及存储结构

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

顺序表可以分为静态顺序表和动态顺序表。

1.1储存结构

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

动态顺序表:数组的大小是根据存储的数据个数,如果满了就动态开辟出来的,相对而言不会造成空间的浪费;

二.顺序表的实现

静态顺序表 只适用于确定知道需要存多少数据的场景;

静态顺序表的定长数组导致N定大了,空间开多了浪费,开少了不够用。例如:如果数组的大小定为200,却只储存了几十个数据,和动态的顺序表相比,空间浪费更大;所以现实中基本都是使用动态顺序表,根据需要动态的分配空间大小,所以下面我们实现动态顺序表。

一.功能函数的实现(含图解)

1.初始化函数

功能:对结构体内成员进行初始化操作;

代码实现:

void InitList(SL* s)
{
  assert(s);        //断言,防止传入空指针
  s->a = (SLDateType* )malloc(sizeof(SLDateType) * 4);  
  if (s->a == NULL)   //这里最开始开辟四个存储数据类型的大小
  {
    perror("malloc fail");
    exit(-1);
  }
  s->size = 0;            //存储的有效数据个数为0
  s->capacity = 4;       //空间置成4
}

2.顺序表的销毁

功能:对动态开辟的空间进行销毁,空间释放

代码实现:

void DestoryList(SL* s)
{
  assert(s);
  free(s->a);       //释放动态开辟的空间
  s->a = NULL;      //置空
  s->capacity = s->size = 0;
}

3.顺序表的打印

代码实现:

void SListPrintf(SL* s)
{
  assert(s);
  if (s->size < 0)     //如果没有数据,则直接返回
  {
    return;
  }
  for (int i = 0; i < s->size; i++)
  {
    printf("%d ", s->a[i]);       //遍历依次打印
  }
  printf("\n");        //打印完换行
}

4.扩容函数

功能:检查是否需要扩容

代码实现:

void CheckCapacity(SL* s)
{
  assert(s);
  if (s->capacity == s->size)    //相等则说明空间已经满了
  {
    SLDateType* tmp = realloc(s->a, sizeof(SLDateType)* 2 * s->capacity);  //二倍扩容
    if (tmp == NULL)    
    {
      perror("realloc fail");
      exit(-1);
    }
    s->a = tmp;
    s->capacity *= 2;
  }
}

5.尾插函数

功能:在顺序表最后插入一个数据

代码实现:

void PushBack(SL* s, SLDateType n)
{
  assert(s);
  CheckCapacity(s);  //检查空间是否满
  s->a[s->size] = n;    //直接插入到数组最后
  s->size++;       //有效数据个数++
}

6.尾删函数

功能: 删除顺序表的最后一个数据

代码实现

void PopBack(SL* s)
{
  assert(s);
  assert(s->size > 0);     //当数组中有效数据个数为0时,不能再删除
  s->size--;         //数据个数--
}

7.头插函数

功能:在顺序表最前面插入一个数据

代码实现:

void PushFront(SL* s, SLDateType n)
{
  assert(s);
  CheckCapacity(s);    //检查扩容
  int end = s->size;    
  while (end>0) 
  {
    s->a[end] = s->a[end - 1];   //挪动数据
    end--;
  }
  s->a[0] = n;         
  s->size++;
}

图解:

性能分析:头插一个数据,首先需要将全部数据一次往后挪一个位置,时间复杂度:O(N)  

是在原数组上进行挪动的,空间复杂度:O(1)

8.头删函数

功能:删除表中第一个元素

代码实现:

void Popfront(SL* s)
{
  assert(s);
  assert(s->size > 0);   //size为0时,不能再删除
  for (int i = 0; i < s->size; i++)
  {
    s->a[i] = s->a[i+1];      //向前挪动数据
  }
  s->size--;
}

图解:

性能分析:头删一个数据,首先需要将全部数据一次往前挪一个位置,将第一个元素覆盖掉,时间复杂度:O(N)  

是在原数组上进行挪动的,空间复杂度:O(1)

9.插入函数

功能:在某个位置插入一个数据

代码实现:

void SListInsert(SL* s, int pos, SLDateType n)
{
  assert(s);
  assert(pos >= 0 && pos <= s->size);    //判断pos合法
  int end = s->size;
  int begin = pos;
  while (begin < end)
  {
    s->a[end] = s->a[end - 1];   //挪动数据
    end--;
  }
  s->a[pos] = n;       //修改数据
  s->size++;
}

图解:

性能分析:中间插入一个数据和头插差不多,首先需要将该位置后面的全部数据依次往后挪一个位置,将该位置空出来,再将该数据插入,时间复杂度:O(N)  

是在原数组上进行挪动的,空间复杂度:O(1)

10.删除函数

功能:删除数组中任何位置的数据

代码实现:

void SListErase(SL* s, int pos)
{
  assert(s);
  assert(pos >= 0 && pos < s->size);    //pos范围合法判断
  int cur = pos;
  while (cur < s->size)
  {
    s->a[cur] = s->a[cur+1];    //挪动位置
    cur++;
  }
  s->size--;
}

图解:

性能分析:删除一个数据与头删差不多,首先需要待删数据位置后面全部数据依次往前挪一个位置,将待删元素覆盖掉,时间复杂度:O(N)  

是在原数组上进行挪动的,空间复杂度:O(1)

11.查找函数

功能:查找顺序表中某个数据,返回下标

代码实现:

int SListFind(SL* s,SLDateType n)
{
  assert(s);
  for (int i = 0; i < s->size; i++)    //遍历寻找
  {
    if (s->a[i] == n)     //找到了返回下标
    { 
      return i;
    }
  }
  printf("顺序表中无该数据\n");   
  exit(-1);
}

性能分析:将数组中的数据遍历一遍;时间复杂度:O(N)

空间复杂度:O(1)

12.修改函数

功能:修改数组中某个数据

代码实现:

void SListModify(SL* s, int pos,SLDateType n)
{
  assert(s);
  assert(pos >= 0 && pos < s->size);
  s->a[pos] = n;     //直接通过下标访问修改
}

三.总代码

#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
typedef int SLDateType;
struct SeqList
{
  SLDateType* a;      //指向一个数组的指针
  int size;           //记录数据个数
  int capacity;       //记录容量,如果与数据个数相等就扩容
};
typedef struct SeqList SL;
void InitList(SL* s);
void SListPrintf(SL* s);
void PushBack(SL* s, SLDateType n);
void PushFront(SL* s, SLDateType n);
void PopBack(SL* s);
void Popfront(SL* s);
int SListFind(SL* s,SLDateType n);
void SListModify(SL* s, int pos, SLDateType n);
void SListErase(SL* s, int pos);
void SListInsert(SL* s, int pos, SLDateType n);
void DestoryList(SL* s);
#include"SeqList.h"
void InitList(SL* s)
{
  assert(s);        //断言,防止传入空指针
  s->a = (SLDateType* )malloc(sizeof(SLDateType) * 4);  
  if (s->a == NULL)   //这里最开始开辟四个存储数据类型的大小
  {
    perror("malloc fail");
    exit(-1);
  }
  s->size = 0;            //存储的有效数据个数为0
  s->capacity = 4;       //空间置成4
}
void SListPrintf(SL* s)
{
  assert(s);
  if (s->size < 0)     //如果没有数据,则直接返回
  {
    return;
  }
  for (int i = 0; i < s->size; i++)
  {
    printf("%d ", s->a[i]);       //遍历依次打印
  }
  printf("\n");        //打印完换行
}
void CheckCapacity(SL* s)
{
  assert(s);
  if (s->capacity == s->size)    //相等则说明空间已经满了
  {
    SLDateType* tmp = realloc(s->a, sizeof(SLDateType)* 2 * s->capacity);  //二倍扩容
    if (tmp == NULL)    
    {
      perror("realloc fail");
      exit(-1);
    }
    s->a = tmp;
    s->capacity *= 2;
  }
}
void PushBack(SL* s, SLDateType n)
{
  assert(s);
  CheckCapacity(s);  //检查空间是否满
  s->a[s->size] = n;    //直接插入到数组最后
  s->size++;       //有效数据个数++
}
void PushFront(SL* s, SLDateType n)
{
  assert(s);
  CheckCapacity(s);    //检查扩容
  int end = s->size;    
  while (end>0) 
  {
    s->a[end] = s->a[end - 1];   //挪动数据
    end--;
  }
  s->a[0] = n;         
  s->size++;
}
void PopBack(SL* s)
{
  assert(s);
  assert(s->size > 0);     //当数组中有效数据个数为0时,不能再删除
  s->size--;         //数据个数--
}
void Popfront(SL* s)
{
  assert(s);
  assert(s->size > 0);   //size为0时,不能再删除
  for (int i = 0; i < s->size; i++)
  {
    s->a[i] = s->a[i+1];      //向前挪动数据
  }
  s->size--;
}
int SListFind(SL* s,SLDateType n)
{
  assert(s);
  for (int i = 0; i < s->size; i++)    //遍历寻找
  {
    if (s->a[i] == n)     //找到了返回下标
    { 
      return i;
    }
  }
  printf("顺序表中无该数据\n");   
  exit(-1);
}
void SListModify(SL* s, int pos,SLDateType n)
{
  assert(s);
  assert(pos >= 0 && pos < s->size);
  s->a[pos] = n;     //直接通过下标访问修改
}
void SListErase(SL* s, int pos)
{
  assert(s);
  assert(pos >= 0 && pos < s->size);    //pos范围合法判断
  int cur = pos;
  while (cur < s->size)
  {
    s->a[cur] = s->a[cur+1];    //挪动位置
    cur++;
  }
  s->size--;
}
void SListInsert(SL* s, int pos, SLDateType n)
{
  assert(s);
  assert(pos >= 0 && pos <= s->size);    //判断pos合法
  int end = s->size;
  int begin = pos;
  while (begin < end)
  {
    s->a[end] = s->a[end - 1];   //挪动数据
    end--;
  }
  s->a[pos] = n;       //修改数据
  s->size++;
}
void DestoryList(SL* s)
{
  assert(s);
  free(s->a);       //释放动态开辟的空间
  s->a = NULL;      //置空
  s->capacity = s->size = 0;
}

四.顺序表的性能分析

问题:

1. 中间/头部的插入删除,时间复杂度为O(N);
2. 增容需要申请新空间,拷贝数据,释放旧空间,会有不小的消耗;
3. 增容一般是呈2倍的增长,势必会有一定的空间浪费。例如当前容量为100,满了以后增容到200,我们再继续插入了5个数据,后面没有数据插入了,那么就浪费了95个数据空间;

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

下一章链表结构就可以解决这个问题;




目录
相关文章
|
3月前
|
存储 C语言
【数据结构】顺序表
数据结构中的动态顺序表
39 3
【数据结构】顺序表
|
4月前
|
存储
数据结构—顺序表(如果想知道顺序表的全部基础知识点,那么只看这一篇就足够了!)
数据结构—顺序表(如果想知道顺序表的全部基础知识点,那么只看这一篇就足够了!)
|
16天前
|
存储 Java 程序员
【数据结构】初识集合&深入剖析顺序表(Arraylist)
Java集合框架主要由接口、实现类及迭代器组成,包括Collection和Map两大类。Collection涵盖List(有序、可重复)、Set(无序、不可重复),Map则由键值对构成。集合通过接口定义基本操作,具体实现由各类如ArrayList、HashSet等提供。迭代器允许遍历集合而不暴露其实现细节。List系列集合元素有序且可重复,Set系列元素无序且不可重复。集合遍历可通过迭代器、增强for循环、普通for循环及Lambda表达式实现,各有适用场景。其中ArrayList实现了动态数组功能,可根据需求自动调整大小。
28 11
|
4月前
|
存储 缓存 算法
数据结构和算法学习记录——总结顺序表和链表(双向带头循环链表)的优缺点、CPU高速缓存命中率
数据结构和算法学习记录——总结顺序表和链表(双向带头循环链表)的优缺点、CPU高速缓存命中率
41 0
|
24天前
|
存储 C语言 C++
数据结构基础详解(C语言) 顺序表:顺序表静态分配和动态分配增删改查基本操作的基本介绍及c语言代码实现
本文介绍了顺序表的定义及其在C/C++中的实现方法。顺序表通过连续存储空间实现线性表,使逻辑上相邻的元素在物理位置上也相邻。文章详细描述了静态分配与动态分配两种方式下的顺序表定义、初始化、插入、删除、查找等基本操作,并提供了具体代码示例。静态分配方式下顺序表的长度固定,而动态分配则可根据需求调整大小。此外,还总结了顺序表的优点,如随机访问效率高、存储密度大,以及缺点,如扩展不便和插入删除操作成本高等特点。
|
24天前
|
存储 算法 C语言
C语言手撕数据结构代码_顺序表_静态存储_动态存储
本文介绍了基于静态和动态存储的顺序表操作实现,涵盖创建、删除、插入、合并、求交集与差集、逆置及循环移动等常见操作。通过详细的C语言代码示例,展示了如何高效地处理顺序表数据结构的各种问题。
|
2月前
|
存储 算法
【数据结构与算法】顺序表
【数据结构与算法】顺序表
17 0
【数据结构与算法】顺序表
|
2月前
|
存储 算法
【初阶数据结构篇】顺序表和链表算法题
此题可以先找到中间节点,然后把后半部分逆置,最近前后两部分一一比对,如果节点的值全部相同,则即为回文。
|
2月前
|
存储 测试技术
【初阶数据结构篇】顺序表的实现(赋源码)
线性表(linearlist)是n个具有相同特性的数据元素的有限序列。
|
2月前
|
存储 编译器
【数据结构】顺序表(长期维护)
【数据结构】顺序表(长期维护)