👉 欢迎阅读这篇文章 👇
目录
- 1、线性表
- [2、 C++引用](#2、 C++引用)
- 3、顺序表
-
- [3.1 静态顺序表和动态顺序表](#3.1 静态顺序表和动态顺序表)
- 3.2动态顺序表实现
- 3.3动态顺序表复杂度分析
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设置NULL,size和capacity设置为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,满足则插入,否则无法插入。
- 参数检测:主要检测位序i是否满足
-
- 检测是否需要扩容,如果顺序表已经存满则需要进行扩容。
-
- 插入元素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是否满足
-
- 将下标为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)。