【小白必看】数据结构——顺序表

一、什么是顺序表

顺序表其实就是线性表的一种,他的底层实际上就是一个数组。我们一般称之为“SeqList”代表sequence + list 顺序表。

注:线性表结构在物理上不一定是线性的,但是逻辑上一定是线性的

        顺序表在物理结构上是相邻的,在逻辑结构上也是相邻的。

二、顺序表的实现

既然顺序表的底层是数组,那么我们用画图来生动形象的表示顺序表中的元素。

上图即为一个“灰常简单”的数组示意图,首先我们要知道,顺序表的实现分为两种,一种是静态数组(定死数组的长度),另一种是动态数组(你长我更长),那么两者之间有什么区别呢?

2.1 静态数组

 静态数组的结构体代码如下:

struct SeqList{
int arr[10]
int size;
};

在这里arr就是我们顺序表的数组,size指我们已经占用的有效元素的个数,但是这样就会出现一些问题,比如数组空间满了但是数据还没用完的情况出现。

猪大哥:“喂,我还没上车呢,怎么装不下我了!!”笑死,所以这也是顺序表非常不好的一个情况,于是我们引出了动态数组。

2.2动态数组

动态动态顾名思义就是让数组的容量动起来,随着数据的不断增多而增加,这个时候只有一个存放有效数据的size肯定是行不通的,于是就引入了另一个变量---capacity用来存放当前顺序表的最大容量。

typedef struct SeqList {
	SLDataType* arr;
	int size; //有效数据大小
	int capacity;//容量
}SL;

 满了我们就增容,不满我们就笑呵呵,无敌了。

2.3静态数组和动态数组的区别

两种方式都介绍完了,那么我们该用哪一种呢?

静态:定长 ,给小了不够用   给大了浪费空间  
动态:动态增容,非常灵活

由此看来,还是动态数组更胜一筹啊!!!

三、顺序表方法

利用顺序表我们可以实现许多功能,下面我们会一一讲解,先来看看都有什么方法。

//方法声明
void SLInit(SL *ps);//初始化
void SLDestory(SL* ps); //销毁 
void SLPushBack(SL* ps, SLDataType x); //尾插
void SLPushFront(SL* ps, SLDataType x);//头插
void SLPopBack(SL* ps); // 尾删
void SLPopFront(SL* ps);//头删
void SLPrint(SL s);
void SLInsert(SL* ps, int pos, SLDataType x);//在指定位置之前插入元素
void SLErase(SL* ps, int pos);//删除指定位置的元素
SLDataType SLFind(SL* ps, SLDataType x);//查找

下面我们就来依次讲解。

3.1初始化顺序表

初始化顺序表就是让一个顺序表“诞生”,就是空的什么样,我什么样。

可以看到里面什么也没有,就是数组空,容量空,有效元素空,三大皆空

void SLInit(SL* ps) {
	ps->arr = NULL;
	ps->size = ps->capacity = 0;

}

这样我们就完成了顺序表的初始化。

3.2销毁顺序表

与初始化类似,直接无脑归零,但是如果还有元素,记得给人家释放了。

void SLDestory(SL * ps){
	if (ps->arr) {
		free(ps->arr);
	}
	ps->arr = NULL;
	ps->capacity = ps->size = 0;
}

3.3尾插数据

在我们进行一系列操作时,首先要画图,这样会帮助我们更好地理解。以上面动物园的车为例,如果这个车是个动态的数组,猪大哥想要上车并且在队伍的最后,应该怎么操作呢

此时数组的长度是4,size正好在队伍的最后,猪大哥想要上去首先要进行扩容,在原有空间基础上进行扩容,这里我们使用realloc(待扩容的空间,扩容大小)。综合起来就是,先判断空间是否已经满了,没满直接插入,满了就进行扩容。

注:一定记得判断是否传入ps,表都没有自然没办法插入了

void SLPushBack(SL* ps, SLDataType x) {
	assert(ps);
	if (ps->size == ps->capacity) {
		//申请空间  增容--》realloc
		//一次增容增多大?  ---》2倍
		int newcapacity = ps->capacity == 0 ? 4 : 2 * ps->capacity;
		SLDataType* tmp =(SLDataType*)realloc(ps->arr, newcapacity * sizeof(SLDataType));//要申请多大的空间
		if (tmp == NULL) {
			perror("realloc fail");
			exit(1);//程序直接退出,不再继续执行。
		}
		//空间申请成功
		ps->arr = tmp;
		ps->capacity = newcapacity;
	}
	ps->arr[ps->size] = x;
	ps->size++;
}

 

int newcapacity = ps->capacity == 0 ? 4 : 2 * ps->capacity;

这条语句的意思是,如果capacity=0,那么首先我要把她变成4,否则就在原有的基础上扩容2倍就可以了。不要忘记最后的size++。

3.4头插数据

头插数据与尾插数据十分类似,但是头插的空间不是凭空在前面创造出一个,而是将后面的元素都向后挪动一位从而空出一个空间来。

问题:怎么挪,直接从0开始arr[i+1] =arr[i] 吗?

我们画个图来直观的看一下:

我们要头插一个数据888,如果进行从0开始的arr[i+1] =arr[i] 的话就会出现以下情况

他会把后面的元素进行覆盖,空间是出来一个了但是元素全乱了,于是我们采用从后面开始移动的办法。

 

都挪动过去之后,空间自然就出来了。 

void SLPushFront(SL* ps, SLDataType x) {
	assert(ps);
	if (ps->size == ps->capacity) {
		//申请空间  增容--》realloc
		//一次增容增多大?  ---》2倍
		int newcapacity = ps->capacity == 0 ? 4 : 2 * ps->capacity;
		SLDataType* tmp = (SLDataType*)realloc(ps->arr, newcapacity * sizeof(SLDataType));//要申请多大的空间
		if (tmp == NULL) {
			perror("realloc fail");
			exit(1);//程序直接退出,不再继续执行。
		}
		//空间申请成功
		ps->arr = tmp;
		ps->capacity = newcapacity;
	}
	for (int i = ps->size; i > 0; i--) {
		ps->arr[i] = ps->arr[i - 1];
	}
	ps->arr[0] = x;
	ps->size++;
}

 3.5打印数据

这个没什么好说的,直接上代码

void SLPrint(SL s) {
	for (int i = 0; i < s.size; i++) {
		printf("%d ", s.arr[i]);
	}
}

3.6尾删数据

尾删特别简单,不用那种free掉,直接size--就可以了,但是需要判断里面是不是有数据。

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

3.7头删数据

头删也需要进行数据的挪动,不过这次是从前往后的进行挪动,这样才不会影响其他数据。

void SLPopFront(SL* ps) {
	assert(ps);
	assert(ps->size);
	for (int i = 0; i < ps->size - 1; i++) {
		ps->arr[i] = ps->arr[i + 1];
	}
	ps->size--;
}

3.8指定位置之前插入数据

void SLInsert(SL* ps, int pos, SLDataType x) 

看着函数声明,谈谈注意事项。

1.首先ps肯定要加个断言,毋庸置疑。这个pos,也需要加,因为肯定会有pos不合法的地方,由于我们实在pos前面加,所以pos是可以等于size的。

2.怎么插入呢?首先肯定要进行数据的挪动,那么我们要找到pos处,从pos开始进行挪动。

3.空间满了进行扩容。

4.size++

void SLInsert(SL* ps, int pos, SLDataType x) {
	assert(ps);
	assert(pos >= 0 && pos <= ps->size);
	if (ps->size == ps->capacity) {
		//申请空间  增容--》realloc
		//一次增容增多大?  ---》2倍
		int newcapacity = ps->capacity == 0 ? 4 : 2 * ps->capacity;
		SLDataType* tmp = (SLDataType*)realloc(ps->arr, newcapacity * sizeof(SLDataType));//要申请多大的空间
		if (tmp == NULL) {
			perror("realloc fail");
			exit(1);//程序直接退出,不再继续执行。
		}
		//空间申请成功
		ps->arr = tmp;
		ps->capacity = newcapacity;
	}
	for (int i = ps->size; i > pos; i--) {
		ps->arr[i] = ps->arr[i - 1];//最后应该是arr[pos+1] = arr[pos]
	}
	ps->arr[pos] = x;
	ps->size++;
}

3.9指定位置删除数据

void SLErase(SL* ps, int pos) {
	assert(ps);
	assert(pos >= 0 && pos < ps->size);//检验pos合法性
	for (int i = pos; i < ps->size-1; i++) {//挪动数据
		ps->arr[i] = ps->arr[i + 1];
	}
	ps->size--;
}

3.10查找数据

SLDataType SLFind(SL* ps, SLDataType x) {
	assert(ps);
	for (int i = 0; i < ps->size; i++) {
		if (ps->arr[i] == x) {
			return i;
		}
	}
	return -1;
}

四、小结

其实顺序表的实现十分简单,真正的重点还在链表这里,下一次我会给大家带来链表相关的讲解包括力扣上的编程题。

  • 17
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值