二、顺序表

👉 欢迎阅读这篇文章 👇

目录

1、线性表

1.1线性表的定义

线性表(linear list)是相同类型的n(n>=0)个数据元素的有限序列,用L命名线性表,一般表示为 L = ( a 1 , a 2 , . . . , a i , a i + 1 , . . . a n ) L=(a_1,a_2,...,a_i,a_{i+1},...a_n) L=(a1,a2,...,ai,ai+1,...an)。

a i a_i ai是表中的第 i i i个数据元素,称 i i i为数据元素 a i a_i ai的在线性表中的位序。(在C/C++等代码实现的过程中数组下标通常从0开始,这里的位序从1开始)

n是表的长度,当n=0时,为空表。

a 1 a_1 a1是表头元素, a n a_n an是表尾元素。

表头元素没有前驱,其余的任意元素都有唯一的前驱元素,表尾元素没有没有后继,其余的任意元素都有唯一的后继元素。

我们知道数据结构的三要素是逻辑结构、物理结构(存储结构)、数据运算和实现;这里的线性表定义就是线性表的逻辑结构 ,数据运算的定义是针对逻辑结构 的,运算的实现要针对线性表选择的存储结构 ,如果选择顺序存储 来实现,那么线性表就是顺序表 ,选择链式存储 实现那么线性表就是链式表

1.2线性表的两种实现方式

用顺序存储的方式实现线性表就是顺序表,用链式存储的方式实现线性表就是链式表。

  • 顺序存储:把逻辑上相邻的数据元素存放在一段连续物理存储单元中,数据元素之间的逻辑关系可以由物理存储关系体现。
  • 链式存储:逻辑上相邻的数据元素存放在任意的一组物理存储单元中,数据元素之间的逻辑关系⽤指针来表示。

假设有一个顺序表L=("张三","李四","王五","赵六")

2、 C++引用

2.1引用的概念和定义

引用不是新定义一个变量,而是给已存在的变量取一个别名,编译器不会为引用变量开辟内存空间,它和它引⽤的变量共⽤同⼀块内存空间。

cpp 复制代码
int b = 0;
int& a = b;

举例

c 复制代码
#include <stdio.h>

int main()
{
    int a = 0;
    
    //引用:b/c是别名

    int& b = a;
    int& c = a;
    int& d = b;

    printf("%p\n",&a);
    printf("%p\n",&b);
    printf("%p\n",&c);
    printf("%p\n",&d);
    return 0;
}

监视

3.2引用的特性

  • 引用在定义的时候需要初始化
  • 一个变量可以有多个引用
  • 引⽤⼀旦引⽤⼀个实体,再不能引⽤其他实体,可以再次给引用引用
  • 引⽤不能改变指向,如下方代码
cpp 复制代码
int a = 0;
int& b = a;
int c = 20;
b = c;// 这⾥并⾮让b引⽤c,因为C++引⽤不能改变指向,这⾥是⼀个赋值
//执行了这一步相当于把20赋值给了a

3.3引用做参数

  • 函数调⽤传值传参:将实参的值复制⼀份给形参,函数内对形参的修改不会影响实参。
  • 函数调⽤指针传参:传递实参的地址(指针),通过指针间接访问和修改实参。
  • 函数调⽤引⽤传参:形参是实参的别名,通过引⽤间接修改实参。
  • 指针传参和引⽤传参都可以起到通过形参修改实参的作⽤,引用传惨还有减少拷⻉提⾼效率的作⽤。
c 复制代码
// 传值传参
void Swap1(int rx, int ry) {
    int tmp = rx;
    rx = ry;
    ry = tmp;
}

// 传指针传参
void Swap2(int* rx, int* ry) {
    int tmp = *rx;
    *rx = *ry;
    *ry = tmp;
}

// 传引⽤传参
void Swap3(int& rx, int& ry) {
    int tmp = rx;
    rx = ry;
    ry = tmp;
}

int main() {
    int x = 0, y = 1;
    printf("%d,%d\n", x, y);
    // 实参x和y传递给形参rx和ry,rx和ry是x和y的拷⻉, 所以rx和ry的改变不影响x和y,⽆法实现交换
    Swap1(x, y);
    printf("%d,%d\n", x, y);
    x = 0, y = 1;
    // 实参x和y的地址传递给形参rx和ry,所以*rx就是x,*ry就是y,所以Swap2可以实现交换
    Swap2(&x, &y);
    printf("%d,%d\n", x, y);
    x = 0, y = 1;
    // 实参x和y传递给形参rx和ry,所以rx就是x的别名,ry就是y的别名,所以Swap3可以实现交换
    Swap3(x, y);
    printf("%d,%d\n", x, y);
    return 0;
}

3、顺序表

3.1 静态顺序表和动态顺序表

3.1.1静态顺序表

静态顺序表是用固定大小的静态数组来存储数据。优点是实现简单,缺点是使用场景局限,只适用于确定知道自己存放多少数据的场景,否则申请的空间少了不够用,申请的空间多了浪费。

c 复制代码
//静态顺序表
typedef int SqDataType;     //typedef是为了方便类型替换
#define Sq_MAX_SIZE 10      //顺序表最大存储的数据个数

//静态顺序表结构定义
typedef struct SequenceList
{
    SqDataType arr[Sq_MAX_SIZE];//存储数据的静态数组
    int size;                   //记录顺序表中已经存入的数据的个数
}SqList;
  • C语言中上述结构体类型为struct SequenceList,太长,所以typedef一个别名,如上方的SqList
  • 上述代码把结构体定义和typedef嵌套在⼀起,也可以单独定义typedef struct SequenceList SqList;

我们还可以对结构体定义进行简化,使用匿名结构体,再typedef一个名称

c 复制代码
typedef struct
{
    SqDataType arr[Sq_MAX_SIZE];
    int size;
}SqList;

顺序表 L = (10, 20, 30, 40, 50) ,最⼤容量10

3.1.2动态顺序表

动态顺序表就是用一个堆上动态申请的数组来存储数据,如果空间不够可以扩容处理。优点是使用场景多,不确定要存放多少数据的场景也可以使用。

c 复制代码
//动态顺序表
typedef int SqDateType;

//动态顺序表结构体定义
typedef struct 
{
    SqDateType* arr;    //存储数据的动态数组的动态指针
    int size;           //记录顺序表中已经存入的数据的个数
    int capacity;       //动态数组的容量空间的大小
}SqList;

顺序表 L = (10, 20, 30, 40, 50) ,当前容量空间为10,空间满了再扩容。

3.2动态顺序表实现

3.2.2接口函数定义

cpp 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>

typedef int SqDateType;

typedef struct
{
    SqDateType* arr;
    int size;
    int capacity;
}SqList;

//初始化顺序表
void SqListInit(SqList* ps);

//销毁顺序表
void SqListDestroy(SqList*ps);

//返回顺序表中第i个下标位置元素的值
SqDateType GetElem(SqList*ps,int i);

//返回第一个等于x的数据元素的下标,若不存在返回-1
int LocateElem(SqList*ps,SqDateType x);

//插入
void SqListInsert(SqList*ps,int i,SqDateType x);

//删除
SqDateType SqListDelete(SqList* ps,int i);

//打印
void SqListPrint(SqList*ps);

// 获取顺序表中有效元素个数
int SqListSize(SqList* ps);

3.2.2初始化

顺序表的结构体创建好后,系统会以随机值进行填充,所以在使用前须先初始化,步骤如下:

  • 使用malloc申请一个默认大小动态数组,比如默认大小是4,这个空间一般不要太大。
  • 申请成功后,将有效元素个数初始化为0,因为初始化阶段,顺序表中还未存放任何有效元素
  • capacity设置为所申请空间的实际⼤⼩
cpp 复制代码
//SqList.cpp ⼀般这些接⼝函数实现放到.cpp中
//初始化顺序表
void SqListInit(SqList* ps)
{
    assert(ps);

    ps->arr=(SqDateType*)malloc(4*sizeof(SqList));
    if(ps->arr==NULL)
    {
        perror("Init:use malloc");
        return;
    }

    ps->size = 0;
    ps->capacity = 4;
}

3.2.3销毁

由于顺序表中的空间是⽤malloc从堆上动态申请的,使⽤完后必须释放,否则会内存泄漏。具体步骤如下:

  • 检测顺序表s的空间是否销毁
  • 如果未销毁,使⽤ free 将其释放掉,并将 arr 设置 NULLsizecapacity 设置为0
  • 销毁的本质是释放空间,防止内存泄漏
cpp 复制代码
void SqListDestory(SqList* ps)
{
    assert(ps);
    if(ps->arr)
    {
        free(ps->arr);
        ps->arr=NULL;
        ps->size = 0;
        ps->capacity = 0;
    }
}

3.2.4查找

3.2.4.1返回顺序表中第i个下标位置元素的值

返回下标为i的元素值,返回之前需要判断下标i是否小于顺序表的数据元素个数,防止使用的过程越界访问

c 复制代码
SqDateType GetElem(SqList*ps,int i)
{
    assert(ps);
    assert(i<ps->size);

    return ps->arr[i];
}
3.2.4.2返回第一个等于x的数据元素的下标,若不存在返回-1
c 复制代码
int LocateElem(SqList* ps,SqDateType x)
{
    assert(ps);
    int i = 0;
    for(i = 0;i<ps->size;i++)
    {
        if(ps->arr[i]==x)
        {
            return i;
        }
    }
    return -1;
}

3.2.6插入

  • 顺序表经过初始化后需要插入元素。插入函数原型为void SqListInsert(SqList* ps, int i, SqDataType x)即在顺序表的第i个位置之前插入元素x,若I的位置非法,则不插入。步骤如下:
    • 参数检测:主要检测位序i是否满足0<=i<=ps->size,满足则插入,否则无法插入。
    • 检测是否需要扩容,如果顺序表已经存满则需要进行扩容。
    • 插入元素x。将i及其之后的所有元素向后挪动一个位置,然后将x放入待插入位置。
    • 插⼊成功后,给有效元素个数加1。

当顺序表中元素满的时候,就需要进行扩容,否则无法继续插入元素。扩容时不仅要考虑插入当前数据没有空间了,还要考虑插入当前数据后再插入后面的数据的时候是否还需要空间,所以一次扩容要稍微大一点,一般是2倍左右扩容。

扩容使用C语言函数realloc来实现。当ps->size==capacity的时候就证明目前空间满了,需要扩容了。

代码实现

c 复制代码
void SqListInsert(SqList*ps,int i,SqDateType x)
{
    assert(ps&&i<=ps->size);
    //检查是否需要扩容
    if(ps->size==ps->capacity)
    {
        SqDateType* temp=(SqDateType*)realloc(ps->arr,sizeof(SqDateType)*ps->capacity*2); 
        //检查是否扩容成功
        if(temp==NULL)
        {
            perror("SqListInsert:use realloc");
            return;
        } 
        ps->arr=temp;
        ps->capacity = 2*ps->capacity;
    }

    //挪动数据
    int j = ps->size-1;
    while(j>=i)
    {
        ps->arr[j+1]=ps->arr[j];
        j--;
    }

    //插入
    ps->arr[i]=x;
    ps->size+=1;
}

3.2.7删除

  • 删除的函数原型SqDateType SqListDelete(SqList* ps,int i);
  • 功能:删除顺序表中下标为i的元素,返回删除的元素的值,注意i必须在0<=i<ps.size。具体操作:
    • 参数检测:检测i是否满足0<=i<ps.size,满足则删除,不满足无法删除。
    • 将下标为i的元素之后的元素整体向前移动一位。
    • 删除成功后,将有效元素个数减1。

代码演示

cpp 复制代码
SqDateType SpLitDelet(SqList* ps,int i)
{
    assert(ps);
    assert(i>=0&&i<ps->size);

    SqDateType ret = ps->arr[i];

    //将下标为i的元素之后的所有元素向前挪动一位
    for(int j =i+1;j<ps->size;j++)
    {
        ps->arr[j-1]=ps->arr[j];
    }

    --ps->size;
    return ret;
}

3.3动态顺序表复杂度分析

3.3.1插入的时间复杂度分析

时间复杂度为 O ( n ) O(n) O(n)。

3.3.2删除的时间复杂度

时间复杂度为 O ( n ) O(n) O(n)。

3.3.3下标查找的时间复杂度

时间复杂度为 O ( 1 ) O(1) O(1)。

3.3.2按值查找的时间复杂度

时间复杂度为 O ( n ) O(n) O(n)。

相关推荐
BAGAE2 小时前
星链卫星数据获取:从太空安全到实时通信的技术革命
网络·数据结构·数据库·算法·云计算·hbase
h_a_o777oah2 小时前
【算法专项】扩展域并查集:原理详解及解决大部分种类并查集问题(洛谷P5937 P2024 C++代码)
数据结构·c++·算法·acm·并查集·扩展域·逻辑建模
吴阿福|一人公司3 小时前
深度解析 Python 类变量修改的命名空间隔离
java·服务器·数据结构
不知名的老吴3 小时前
经典算法题之行星碰撞
数据结构·算法
丘山望岳4 小时前
剑起霜华——平衡二叉树(AVL树 )精讲
开发语言·数据结构·c++
LuminousCPP4 小时前
数据结构 - 单链表第一篇:单链表基础操作
c语言·数据结构·经验分享·笔记·学习
WL学习笔记4 小时前
通讯录(顺序表实现)
c语言·数据结构·算法
JieE2125 小时前
树与二叉树--JS实例
javascript·数据结构
To_OC5 小时前
搞懂二叉树递归遍历,我居然是从爬楼梯开始的
前端·javascript·数据结构