【C语言】顺序表详解

【C语言】顺序表详解_第1张图片


目录

(一)顺序表是一种数据结构

(二)顺序表

(1)顺序表的必要性 

(2)顺序表的概念及结构 

 i,线性表

(3)顺序表的分类

i,顺序表和数组的区别:

ii,顺序表分类

(三)动态顺序表的实现:

 (1)头文件的解释:

(2)手把手实现动态顺序表 

a,初始化

 b,销毁

c,打印

d,扩容

e,头插与头删

 f,尾插与尾删

 g,指定位置插入与删除

h,顺序表查找某一元素


正文开始

(一)顺序表是一种数据结构

        顺序表是一种数据结构,C语言有一种内置的数据结构:数组

        当我们想要大量使用同⼀类型的数据时,通过手动定义大量的独立的变量对于程序来说,可读性非常差,我们可以借助数组这样的数据结构将大量的数据组织在⼀起,结构也可以理解为组织数据的方式。

        在我们的日常生活中,也有一些结构的概念,如 楼房,牛棚,排队的队列 等。我们希望通过一定组织,来提高查找效率。


 

 【C语言】顺序表详解_第2张图片

 这都是数据结构的意义,而数据结构是什么呢?


        数据结构是计算机存储、组织数据的⽅式。数据结构是指相互之间存在⼀种或多种特定关系的数据元素的集合。数据结构反映数据的内部构成,即数据由那部分构成,以什么方式构成,以及数据元素之间呈现的结构。
 

 顺序表是基于数组的数据结构。

(二)顺序表

(1)顺序表的必要性 

         【思考】有了数组,为什么还要学习其他的数据结构?

        求数组的长度,求数组的有效数据个数,向下标为数据有效个数的位置插入数据(注意:这里是否要判断数组是否满了,满了还能继续插入吗).....

        假设数据量非常庞大,频繁的获取数组有效数据个数会影响程序执行效率。
 

         结论:最基础的数据结构能够提供的操作已经不能完全满⾜复杂算法实现。        

(2)顺序表的概念及结构 

 i,线性表

         线性表(linear list)是n个具有相同特性的数据元素的有限序列。线性表是⼀种在实际中⼴泛使用的数据结构,常见的线性表:顺序表、链表、栈、队列、字符串......

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

(3)顺序表的分类

i,顺序表和数组的区别:

        顺序表的底层结构是数组,对数组的封装,实现了常用的增删改查等接口。

ii,顺序表分类

        顺序表分为静态顺序表和动态顺序表;

        a,静态顺序表

        概念:使用固定长度的数组存储数据

typedef int SLDatetype;

typedef struct SList
{
    SLDatatype a[N];//定长数组

    Size_t size;//记录有效数据个数
}SL;

        b,动态顺序表

        概念:按需申请,可增容

typedef struct SList
{
    SLDatatype* arr;
    size_t size;//有效数据个数
    size_t capacity;//容量空间
}SL;

        通常情况下,动态顺序表的适应性更广,使用场景多。

(三)动态顺序表的实现:

         这里不加解释,直接给出头文件,根据头文件的函数声明,实现顺序表的功能:初始化,销毁,打印,扩容,头插与头删,尾插与尾删,指定位置插入与删除,以及查找顺序表的某一元素。

(共11个功能)

​#include
#include
#include
#include

#define INIT_CAPACITY 4
typedef int SLDataType;
// 动态顺序表 -- 按需申请
typedef struct SeqList
{
    SLDataType* a;
    int size; // 有效数据个数
    int capacity; // 空间容量
}SL;

//初始化和销毁
void SLInit(SL* ps);
void SLDestroy(SL* ps);
void SLPrint(SL* ps);
//扩容
void SLCheckCapacity(SL* ps);

//头部插⼊删除 / 尾部插⼊删除
void SLPushBack(SL* ps, SLDataType x);
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);
int SLFind(SL* ps, SLDataType x);

​

 (1)头文件的解释:

i,宏定义作为动态申请的初始值

#define INIT_CAPACITY 4

ii,顺序表内部的数据统一替换为SLDatatype,便于以后修改后复用。

typedef int SLDataType;

iii,SeqList结构体就是顺序表,重命名为SL,便于使用。

typedef struct SeqList
{
SLDataType* a;
int size; // 有效数据个数
int capacity; // 空间容量
}SL;

iv,其余为函数声明。

(2)手把手实现动态顺序表 

a,初始化

        初始化首先形参结构体指针不为空,并且将顺序表底层的数组置为空,有效数据个数和容量大小都初始为0。

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

 b,销毁

        在使用完毕后,需要将顺序表销毁,需要将动态申请的堆区空间释放掉,有效数据个数和容量大小置为0。

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

c,打印

        在调试代码中,需要用打印来帮助测试函数的功能

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

d,扩容

        确定顺序表的容量,检查是否需要扩容,如果需要,进行将当前容量的二倍的扩容;

如何检查?

        数据是一个一个插入的,当有效数据个数等于容量的时候数据就满了,每一次插入数据,都进行一次容量是否足够的检查,若满了,就进行扩容。(插入数据都需要用此函数先检查)

第一次如何使用?

        在传入NULL时,realloc函数的作用等同于malloc函数。

void SLCheckCapacity(SL* ps)
{
	if (ps->capacity == ps->size)
	{
		size_t Newcapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;
		SLtype* tem = (SLtype*)realloc(ps->arr, Newcapacity * sizeof(SLtype));
		if (tem == NULL)
		{
			perror("realloc fail");
			exit(1);
		}
		ps->arr = tem;
		ps->capacity = Newcapacity;
	}
}

e,头插与头删

        头插与头删:意思是在顺序表头部插入或者删除数据,由于插入数据,需要检查顺序表有效数据个数,于是要调用SL_check_capacity()函数;并且由于多了一个数据,要想将新数据插入,必须将后面的原数据后移一位:

头插:

​
void SLPushFront(SL* ps, SLDataType x)
{
	assert(ps);
	//空间不够,扩容
	SL_check_capacity(ps);
	//空间足够,从头插入
	for (int i = (int)ps->size ; i > 0 ; i--)
	{
		ps->arr[i] = ps->arr[i - 1];
	}
	ps->arr[0] = x;
	ps->size++;
}

​

        后移这一操作也可以用memove函数实现,

        (由于拷贝的两个空间有重叠部分,谨慎使用memcpy,memmove函数有分情况,可以解决这一问题)

        这样就省去了写for循环时考虑循环变量的范围,以及边界是否取等,以及重叠拷贝的拷贝顺序的确定,的麻烦:

​
void SLPushFront(SL* ps, SLDataType x)
{
	assert(ps);
	//空间不够,扩容
	SL_check_capacity(ps);
	//空间足够,从头插入

	memmove(&ps->arr[1],&ps->arr[0],ps->size*sizeof(SLDatatype));

	ps->arr[0] = x;
	ps->size++;
}

​

头删:

        删除头部的一个数据,需要将后面的数据向前移动一位:

​
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--;
}

​

同样可以用memmove函数避免麻烦:

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

	memmove(&ps->arr[0],&ps->arr[1],sizeof(SLDatatype)*(ps->size-1));

	ps->size--;
}

 f,尾插与尾删

        尾插与尾删:由于是在顺序表尾部插入或者删除数据,所以不需要进行数据移动,因此简单许多:

尾插:

        只需要有效数据个数加1,并将数据x存入即可;

void SLPushBack(SL* ps, SLDataType x)
{
	assert(ps);
	//空间不够,扩容
	SL_check_capacity(ps);
	//空间足够,直接插入
	ps->arr[ps->size++] = x;

}

尾删:

        只需要有效数据个数减少1即可;

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

	ps->size--;
}

 g,指定位置插入与删除

      指定位置插入与删除:

        断言形参有效,并且传入的位置在有效数据内部;检查容量是否需要扩容;同样可以用memmove函数省时省力。

指定位置插入:

void SLInsert(SL* ps, int pos, SLDataType x)
{
	assert(ps);
	assert(posi >= 0 && posi < ps->size);

	SL_check_capacity(ps);

	for (int i = (int)ps->size - 1; i >= posi; i--)
	{
		ps->arr[i + 1] = ps->arr[i];
	}
	ps->arr[posi] = x;
	ps->size++;

}

删除指定位置:

void SLErase(SL* ps, int pos)
{
	assert(ps);
	assert(posi >= 0 && posi < ps->arr);

	for (int i = (int)posi; i < ps->size - 1; i++)
	{
		ps->arr[i] = ps->arr[i + 1];
	}
	ps->size--;
}

h,顺序表查找某一元素

        查找某一元素,若找到,返回顺序表(数组)下标;找不到,返回-1;

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

        


完~

未经作者同意禁止转载 

你可能感兴趣的:(数据结构,数据结构,c语言)