数据结构顺序表

Posted 白晨sama

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了数据结构顺序表相关的知识,希望对你有一定的参考价值。

目录

1.线性表

2.顺序表

2.1 顺序表的定义及分类 

2.2 顺序表的结构及基本接口实现

顺序表的结构

顺序表的初始化,增容及销毁

顺序表的查找

顺序表的插入

顺序表的删除

顺序表完整代码

后记


1.线性表


线性表(linear list):n个具有相同特性的数据元素的有限序列。

线性表是一种在实际中广泛使用的数据结构,常见的线性表:顺序表、链表、栈、队列、字符串...

简单来理解一下:就是“用一根线将不同的数据串联起来”,也就意味着通过一个线性表中的数据,最多只能找到此数据前面和后面两个数据(单链表只能找到一个)。

线性表在逻辑上是线性结构,也就说是连续的一条直线。但是在物理结构上并不一定是连续的,线性表在物理上存储时,通常以数组和链式结构的形式存储。

抽象概念毕竟还是不好理解,我们直接上图:

我们都知道,数组在储存空间中是连续的一块地址,我们就可以将其视为用一根线连起来的不同数据的结构,这也是数据集中有顺序的存放的典例。

再来看一个分散存放的:

这四个数据,在物理空间上是不连续的,但是他们每个都存放着下一个数据的地址,所以也可以视为用一根绳子连接起来的结构。

具体物理空间我们可以假设如下:

可见,这也可以视为一根绳子串连起来的。


2.顺序表


2.1 顺序表的定义及分类 


顺序表是在计算机内存中以数组的形式保存的线性表,线性表的顺序存储是指用一组地址连续的存储单元依次存储线性表中的各个元素、使得线性表中在逻辑结构上相邻的数据元素存储在相邻的物理存储单元中,即通过数据元素物理存储的相邻关系来反映数据元素之间逻辑上的相邻关系,采用顺序存储结构的线性表通常称为顺序表。顺序表是将表中的结点依次存放在计算机内存中一组地址连续的存储单元中。

以上是百度给顺序表的定义,看不懂的话,我们可以暂且不管这个定义。

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

所以,顺序表对储存物理空间有很强的要求:顺序表存储数据时,会提前申请一整块足够大小的连续的物理空间,然后将数据依次存储起来。

见下图:

并且,顺序表通过是否有固定大小空间可以分为:静态顺序表和动态顺序表

静态顺序表:

#define N 10000
typedef int SLDataType;

// 静态顺序表
typedef struct SeqList

	SLDataType a[N];
	int size; // 表示数组中存储了多少个数据
SL;


// 静态特点:如果满了就不让插入  缺点:给多少的合适呢?这个很难确定
// N给小了不够用,N给大了浪费

动态顺序表:

就使用场景来说,动态顺序表的使用范围是要大于静态顺序表的,因为静态顺序表的大小是固定的,如果数据过多,无法增容,过少又会造成空间浪费,所以,动态的优点是多于静态的。本篇文章的主要是对动态顺序表的讲解。


2.2 顺序表的结构及基本接口实现


顺序表的结构


typedef int SLDateType;
typedef struct SeqList

	SLDateType* a;
	int size;
	int capacity; 
SeqList;

首先,我们为了方便储存各种不同类型的数据,我们定义一个SLDataType,如果以后想改变储存数据的类型,直接修改 SLDataType 的定义即可。

接着,我们发现 SeqList 中有一个指针,这个指针是用来指向动态内存开辟的空间的,也就是指向数据的。

最后,size 指的是顺序表中元素的个数,capacity 是指顺序表中最多能容纳元素的个数。


下面,我们来探究顺序表的接口函数

// 对数据的管理:增删查改 
// 顺序表的初始化
void SeqListInit(SeqList* ps);
// 顺序表的销毁
void SeqListDestory(SeqList* ps);
// 顺序表的打印
void SeqListPrint(SeqList* ps);
// 顺序表的尾插
void SeqListPushBack(SeqList* ps, SLDateType x);
// 顺序表的尾删
void SeqListPopBack(SeqList* ps);
// 顺序表的头插
void SeqListPushFront(SeqList* ps, SLDateType x);
// 顺序表的头删
void SeqListPopFront(SeqList* ps);


// 顺序表查找
int SeqListFind(SeqList* ps, SLDateType x);
// 顺序表在pos位置插入x,原pos位置及以后元素向后移动
void SeqListInsert(SeqList* ps, int pos, SLDateType x);
// 顺序表删除pos位置的值,原pos位置及以后元素向前移动
void SeqListErase(SeqList* ps, int pos);

以上就是顺序表全部的基本接口函数。

顺序表的初始化,增容及销毁


void SeqListInit(SeqList* ps)

    assert(ps);
	ps->size = 0;
	ps->capacity = 0;
	ps->a = NULL;


void SeqListDestory(SeqList* ps)

    assert(ps);
	ps->capacity = 0;
	ps->size = 0;
	free(ps->a);
	ps = NULL;

初始化其实非常好理解,就传一个顺序表的指针,我们将其中的数据先置空。

销毁就是,先将size和capacity置为0,再释放掉储存数据的空间。

void SeqListCheckCapacity(SeqList* ps)

	if (ps->size == ps->capacity)
	
		int newcapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;

		SLDateType* tmp = (SLDateType*)realloc(ps, sizeof(SLDateType) * newcapacity);
		if (tmp == NULL)
		
			perror("SeqListCheckCapacity");
			exit(-1);
		

		ps->a = tmp;
		ps->capacity = newcapacity;
	

增容我们可以分为两种情况,首先是 capacity 为0时,我们给其分配4个字节的空间,其次 capacity 不为0时,如果此时 size==capacity ,代表顺序表满了,我们将其增容至以前大小的2倍。 


顺序表的查找


int SeqListFind(SeqList* ps, SLDateType x)

	assert(ps->size > 0);
	for (int i = 0; i < ps->size; i++)
	
		if (ps->a[i] == x)
		
			return i;
		
	
	return -1;

首先,保证顺序表不为空。

然后,遍历顺序表查找即可。


顺序表的插入


void SeqListInsert(SeqList* ps, int pos, SLDateType x)

	assert(pos >= 0 && pos <= ps->size);//限制位置

	SeqListCheckCapacity(ps);

	for (int i = ps->size - 1; i >= pos; i--)
	
		ps->a[i + 1] = ps->a[i];
	
	ps->a[pos] = x;
	ps->size++;

首先,必须限制pos在 0~size 间,然后检查容量是否足够,如果不够,要增容。

其次,由于顺序表所有元素是按顺序储存,不会留空当,所以要插入元素,必须先将pos及以后的元素向后挪。

最后,直接在 pos 位置赋值元素,size 加一。

根据插入函数,我们可以直接实现尾插和头插

void SeqListPushBack(SeqList* ps, SLDateType x)

	SeqListInsert(ps, ps->size, x);//在size位置插入,就是尾插


void SeqListPushFront(SeqList* ps, SLDateType x)

	SeqListInsert(ps, 0, x);//在0位置插入,就是头插

顺序表的删除


void SeqListErase(SeqList* ps, int pos)

	assert(pos >= 0 && pos < ps->size);
	

	for (int i = ps->size - 1; i > pos; i--)
	
		ps->a[i - 1] = ps->a[i];
	

	ps->size--;

首先,限制 pos 在0~size-1 之间。

其次,将pos之后的元素,向前挪一位。

最后,size减一即可;

通过删除函数,我们可以轻松实现尾删和头删

void SeqListPopBack1(SeqList* ps)

	SeqListErase(ps, ps->size - 1);


void SeqListPopFront1(SeqList* ps)

	SeqListErase(ps, 0);

顺序表完整代码


#include <stdio.h>
#include <assert.h>
#include <stdlib.h>

typedef int SLDateType;
typedef struct SeqList

	SLDateType* a;
	int size;
	int capacity; 
SeqList;

// 对数据的管理:增删查改 
// 顺序表的初始化
void SeqListInit(SeqList* ps);
// 顺序表的销毁
void SeqListDestory(SeqList* ps);
// 顺序表的打印
void SeqListPrint(SeqList* ps);
// 顺序表的尾插
void SeqListPushBack(SeqList* ps, SLDateType x);
// 顺序表的尾删
void SeqListPopBack(SeqList* ps);
// 顺序表的头插
void SeqListPushFront(SeqList* ps, SLDateType x);
// 顺序表的头删
void SeqListPopFront(SeqList* ps);


// 顺序表查找
int SeqListFind(SeqList* ps, SLDateType x);
// 顺序表在pos位置插入x,原pos位置及以后元素向后移动
void SeqListInsert(SeqList* ps, int pos, SLDateType x);
// 顺序表删除pos位置的值,原pos位置及以后元素向前移动
void SeqListErase(SeqList* ps, int pos);


void SeqListInit(SeqList* ps)

	assert(ps);
	ps->size = 0;
	ps->capacity = 0;
	ps->a = NULL;


void SeqListDestory(SeqList* ps)

	assert(ps);
	ps->capacity = 0;
	ps->size = 0;
	free(ps->a);
	ps = NULL;


void SeqListCheckCapacity(SeqList* ps)

	if (ps->size == ps->capacity)
	
		int newcapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;

		SLDateType* tmp = (SLDateType*)realloc(ps, sizeof(SLDateType) * newcapacity);
		if (tmp == NULL)
		
			perror("SeqListCheckCapacity");
			exit(-1);
		

		ps->a = tmp;
		ps->capacity = newcapacity;
	


void SeqListPrint(SeqList* ps)

	assert(ps);

	for (int i = 0; i < ps->size; i++)
	
		printf("%d ", ps->a[i]);
	



int SeqListFind(SeqList* ps, SLDateType x)

	assert(ps->size > 0);
	for (int i = 0; i < ps->size; i++)
	
		if (ps->a[i] == x)
		
			return i;
		
	
	return -1;


void SeqListInsert(SeqList* ps, int pos, SLDateType x)

	assert(pos >= 0 && pos <= ps->size);//限制位置

	SeqListCheckCapacity(ps);

	for (int i = ps->size - 1; i >= pos; i--)
	
		ps->a[i + 1] = ps->a[i];
	
	ps->a[pos] = x;
	ps->size++;


void SeqListErase(SeqList* ps, int pos)

	assert(pos >= 0 && pos < ps->size);
	

	for (int i = ps->size - 1; i > pos; i--)
	
		ps->a[i - 1] = ps->a[i];
	

	ps->size--;


void SeqListPushBack(SeqList* ps, SLDateType x)

	SeqListInsert(ps, ps->size, x);


void SeqListPopBack(SeqList* ps)

	SeqListErase(ps, ps->size - 1);


void SeqListPushFront(SeqList* ps, SLDateType x)

	SeqListInsert(ps, 0, x);


void SeqListPopFront(SeqList* ps)

	SeqListErase(ps, 0);


后记


最后,有几个问题大家可以思考一下

问题:

1. 中间/头部的插入删除,时间复杂度为O(N)

2. 增容需要申请新空间,拷贝数据,释放旧空间。会有不小的消耗。

3. 增容一般是呈2倍的增长,势必会有一定的空间浪费。例如当前容量为100,满了以后增容到200,我们再继续插入了5个数据,后面没有数据插入了,那么就浪费了95个数据空间。

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

这里就引出了下一篇文章的主角——链表,它将会解决上述问题,大家可以先根据我上文透露出的线索,想一想链表的结构。


以上就是本次的分享内容了,喜欢我的分享的话,别忘了点赞加关注哟!

如果你对我的文章有任何看法,欢迎在下方评论留言或者私信我鸭!

我是白晨,我们下次分享见!!

以上是关于数据结构顺序表的主要内容,如果未能解决你的问题,请参考以下文章

Mysql Innodb 表碎片整理

html 将以编程方式附加外部脚本文件的javascript代码片段,并按顺序排列。用于响应式网站,其中ma

数据结构 顺序表

数据结构代码实操04——————顺序表的总结(就是顺序表的基本操作一起用)

数据结构学习笔记二线性表---顺序表篇(画图详解+代码实现)

数据结构学习笔记二线性表---顺序表篇(画图详解+代码实现)