数据结构顺序表

简介: 数据结构顺序表

今天主要讲解顺序表,实现顺序表的尾插,头插,头删,还有尾删等操作,和我们之前写的通讯录的增删查改有类似的功能。接下来让我们开始我们的学习吧。

1.线性表

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

构,常见的线性表:顺序表、链表、栈、队列、字符串…

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

理上存储时,通常以数组和链式结构的形式存储

顺序表其实本质上和数组差不多。

2.顺序表

2.1概念及结构

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

上完成数据的增删查改。

顺序表一般可以分为:

  1. 静态顺序表:使用定长数组存储元素。
    静态的顺序表通俗点讲就是死的,我们不能改变里面的元素,一开始是多少就是多少,不能实现我们的增删查改一些操作,所以我们写顺序表的时候基本上都是动态。
    静态顺序表的代码。
typedef struct SeqList
{
  int arry[100];
  int size;
}SL

我们可以写成这样,但是如果我存储数据的个数不是100的话又要改,我们可以写成这样,在前面define一下。

同时我们的顺序表可能不只是int 类型的 这个时候我们就可以typedef一下,下次改的时候就方便很多。

但是我们说静态的顺序表存在缺陷,这个时候我们就得动态开辟空间,我们可以写一个动态顺序表。

typedef int SLDataType;
typedef struct SeqList
{
  SLDataType* arry;//指向顺序表开始位置
  int size;//个数
  int capacity;//空间
}SL;

这样的话我们就可以用动态开辟的方式实现了。

首先我们要初始化顺序表。

我们一开始这样写的时候,经过调试可以发现我们的s并没有初始化,在VS2022下直接会提醒你使用未初始化的变量s,这是为什么,是因为我们形参是实参的一份临时拷贝,并不能改变实参,所以我们传值不行,要传地址过去次啊可以解决。

#include"SeqList.h"
void SLInit(SL* ps)
{
  ps->arry = NULL;
  ps->capacity = ps->size = 0;
}
void SLtest1()
{
  SL s;
  SLInit(&s);
}
int main()
{
  SLtest1();
  return 0;
}

那我们接下来初始化之后实现一个简单的尾插功能吧

void SLPushBack(SL* ps, SLDataType x)
{
  ps->arry[ps->size] = x;
  ps->size++;
}

我们一开始肯定会这样想,在末尾插入数字,那我们就在末尾找到这个位置,然后插入就行,但是这会有问题,一个原因就是如果这个数组满了,我们就不能插入,插入的化就要扩容,而我们一开始为什么要写动态顺序表的原因就是这个,除了这个原因以外,还有一个原因就是如果我们一开始放的是空指针的时候,这个是时候插入就相当于非法访问,所以我们在插入的时候要进行一下判断。那因为后面头插的时候也会遇到这样的问题,我们干脆给它写成一个函数,这样就方便我们使用。

尾插函数

void SLCheckCapacity(SL* ps)
{
  if (ps->capacity == ps->size)
  {
    int NewCapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;
    SLDataType* tmp = (SLDataType*)realloc(ps->arry, sizeof(SLDataType) * NewCapacity);
    if (tmp == NULL)
    {
      printf("realloc fail\n");
      exit(-1);
    }
    ps->arry = tmp;
    ps->capacity = NewCapacity;
  }
}

所以我们的尾插也可以写成

void SLPushBack(SL* ps, SLDataType x)
{
  SLCheckCapacity(ps);
  ps->arry[ps->size] = x;
  ps->size++;
}

我们为什么取得是结构体的地址,原因还是形参只是实参的一个临时拷贝,形参改变并不会改变实参

打印顺序表的函数。

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

打印函数可以传地址也可以传值,因为打印不会改变我们的内容,我们这里写的就是传址。

实现打印函数,我们其实可以在这些前面都加上assert,做好防御,因为如果传进来的是一个空指针的话,这就会,我们是没有办法进行修改的,所以在函数进来的时候都写好断言,从根本上解决问题。

接下来我们就写一个尾删,尾删就更简单了,直接–就行了,但是我们还要考虑一个问题就是里面一开始的时候有没有数字,如果这个顺序表为空就没有必要删除了。

void SLPopBack(SL* ps)
{
  assert(ps->size > 0);
  ps->size--;
}

从上面顺序表的尾删和尾插来看,其实顺序表相对来说效率还是比较快的,但是头删和头插的效率并不是特别高,我们继续接着写

头插

void SLPushFront(SL* ps, SLDataType x)
{
  assert(ps);
  SLCheckCapacity(ps);
  int end = ps->size - 1;
  while (end >= 0)
  {
    ps->arry[end + 1] = ps->arry[end];
    end--;
  }
  ps->arry[0] = x;
  ps->size++;
}

头插的时候也要进行检查 否则空间满的话,就会越界。

头删

void SLPopFront(SL* ps)
{
  assert(ps);
  assert(ps->size > 0);
  int begin = 0;
  while (begin < ps->size-1)
  {
    ps->arry[begin] = ps->arry[begin + 1];
    begin++;
  }
  ps->size--;
}

为什么要assert(ps->size > 0)是因为如果数都没有的话就不需要删了,所以就需要断言一下。

void SLInsert(SL* ps, int pos, SLDataType x)
{
  assert(ps);
  assert(pos >= 0 && pos <= ps->size);
  SLCheckCapacity(ps);
  int end = ps->size - 1;
  while (end >= pos)
  {
    ps->arry[end + 1] = ps->arry[end];
    end--;
  }
  ps->arry[pos] = x;
  ps->size++;
}

写完这个其实头插就可以改成SLInsert(SL* ps, 0, SLDataType x)尾插就可以改成SLInsert(SL* ps, ps->size-1, SLDataType x)

那我们在实现一个在pos位置进行删除的代码。

void SLErase(SL* ps, int pos)
{
  assert(ps);
  assert(pos >= 0 && pos < ps->size);
  int begin = pos;
  while (begin < ps->size-1)
  {
    ps->arry[begin] = ps->arry[begin + 1];
    begin++;
  }
  ps->size--;
}

那这样我们的每个功能基本都实现了,因为我们一开始realloc空间,所以我们现在要销毁原来的空间。

void SLDestory(SL* ps)
{
  if (ps->arry != NULL)
  {
    free(ps->arry);
    ps->arry = NULL;
  }
}

这样我们其实还可以用一个菜单来实现这写功能,和通讯录那个差不多,就设置一个菜单,这里小编就不搞了

给大家分享一下完整代码。

SeqList.h

#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
//#define N 100
//typedef int SLDataType;
//typedef struct SeqList
//{
//  int arry[N];
//  int size;
//}SL;
typedef int SLDataType;
typedef struct SeqList
{
  SLDataType* arry;//指向顺序表开始位置
  int size;//个数
  int capacity;//空间
}SL;
//初始化
void SLInit(SL* ps);
//尾插
void SLPushBack(SL* ps, SLDataType x);
//检查空间是否够
void SLCheckCapacity(SL* ps);
void SLPrint(SL* ps);
void SLPopBack(SL* ps);
//头插
void SLPushFront(SL* ps, SLDataType x);
//头删
void SLPopFront(SL* ps);
void SLInsert(SL* ps, int pos, SLDataType x);
void SLErase(SL* ps, int pos);
void SLDestory(SL* ps);

SeqList.c

#define _CRT_SECURE_NO_WARNINGS 1
#include"SeqList.h"
void SLInit(SL* ps)
{
  ps->arry = NULL;
  ps->capacity = ps->size = 0;
}
void SLCheckCapacity(SL* ps)
{
  assert(ps != NULL);
  if (ps->capacity == ps->size)
  {
    int NewCapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;
    SLDataType* tmp = (SLDataType*)realloc(ps->arry, sizeof(SLDataType) * NewCapacity);
    if (tmp == NULL)
    {
      printf("realloc fail\n");
      exit(-1);
    }
    ps->arry = tmp;
    ps->capacity = NewCapacity;
  }
} 
void SLPushBack(SL* ps, SLDataType x)
{
  assert(ps != NULL);
  SLCheckCapacity(ps);
  ps->arry[ps->size] = x;
  ps->size++;
}
void SLPrint(SL* ps)
{
  assert(ps != NULL);
  int i = 0;
  for (i = 0; i < ps->size; i++)
  {
    printf("%d ", ps->arry[i]);
  }
  printf("\n");
}
void SLPopBack(SL* ps)
{
  assert(ps);
  assert(ps->size > 0);
  ps->size--;
}
void SLPushFront(SL* ps, SLDataType x)
{
  assert(ps);
  SLCheckCapacity(ps);
  int end = ps->size - 1;
  while (end >= 0)
  {
    ps->arry[end + 1] = ps->arry[end];
    end--;
  }
  ps->arry[0] = x;
  ps->size++;
}
void SLPopFront(SL* ps)
{
  assert(ps);
  assert(ps->size > 0);
  int begin = 0;
  while (begin < ps->size-1)
  {
    ps->arry[begin] = ps->arry[begin + 1];
    begin++;
  }
  ps->size--;
}
void SLInsert(SL* ps, int pos, SLDataType x)
{
  assert(ps);
  assert(pos >= 0 && pos <= ps->size);
  SLCheckCapacity(ps);
  int end = ps->size - 1;
  while (end >= pos)
  {
    ps->arry[end + 1] = ps->arry[end];
    end--;
  }
  ps->arry[pos] = x;
  ps->size++;
}
void SLErase(SL* ps, int pos)
{
  assert(ps);
  assert(pos >= 0 && pos < ps->size);
  int begin = pos;
  while (begin < ps->size-1)
  {
    ps->arry[begin] = ps->arry[begin + 1];
    begin++;
  }
  ps->size--;
}
void SLDestory(SL* ps)
{
  if (ps->arry != NULL)
  {
    free(ps->arry);
    ps->arry = NULL;
  }
}

今天的分享就到这里了,我们下次再见!!!

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