数据结构之顺序表

本文详细介绍了线性表的概念,特别是顺序表的存储结构、创建、插入、删除、查找等操作,以及其优缺点。通过实例展示了如何使用C语言实现顺序表,并讨论了顺序表在内存管理和操作效率方面的特性。
摘要由CSDN通过智能技术生成

 1.1线性表的定义

线性表:

线性表是一种数据结构,它可以包含零个或多个具有相同特性的数据元素,形成一个有限的序列。在逻辑上,线性表呈现出一种线性的结构,即元素之间按照某种顺序排列。然而,这种线性结构在物理存储上并不一定要求是连续的,也就是说,线性表的元素在内存中的地址空间可以是不连续的。

假设线性表的数据集合为 {a1, a2, a3, ..., an},其中每个元素的类型都是DataType。在这个集合中,除了第一个元素a1外,每个元素都有一个直接前驱元素;同样地,除了最后一个元素an外,每个元素都有一个直接后继元素。这种前驱和后继的关系在线性表中是一对一的,确保了线性表的顺序性。

举个例子来说明线性表的概念:假设我们有一个线性表,用于存储一个班级的学生名单。这个线性表的数据集合可以是 {张三, 李四, 王五, 赵六},其中每个元素(学生名字)的类型都是字符串类型(或者说DataType是字符串)。在这个线性表中,张三没有直接前驱元素,李张三四是的直接后继元素,同时也是王五的直接前驱元素,以此类推。赵六没有直接后继元素。这个线性表在逻辑上是线性的,但在物理存储上,这些学生的名字可能并不存储在连续的内存地址中。

关系图如下:

1.2.线性表顺序存储结构(顺序表)

顺序表的基本概念

概念:用一组地址连续的存储单元依次存储线性表的数据元素,这种存储结构的线性表称为顺序表。

特点:逻辑上相邻的数据元素,物理次序也是相邻的。

只要确定好了存储线性表的起始位置,线性表中任一数据元素都可以随机存取,所以线性表的顺序存储结构是一种随机存取的储存结构,因为高级语言中的数组类型也是有随机存取的特性,所以通常我们都使用数组来描述数据结构中的顺序储存结构,用动态分配的一维数组表示线性表。

顺序表存储结构

const int MAXSIZE = 5;//定义最大长度为5
typedef int datatype_t; //datatype_t的类型根据实际情况而定,这里假定为int,为了以后方便修改类型

//顺序表的结构体定义
//下面定义了一个长度为length,内元素为整数的顺序表
typedef struct
{
    datatype_t buf[MAXSIZE]; /* 数组,存储数据元素 */
    int length;             /* 线性表当前长度 */
}sqlist_t;//结构体的别名

1.3顺序表的操作

创建一个空的顺序表

sqlist_t *create_empty_sqlist(){
        //在堆上分配空间,而l是栈区上的,指向堆区内存
        sqlist_t *l = (sqlist_t *)malloc(sizeof(sqlist_t));
        if(NULL == l){//空间分配不成功
                return NULL;
        }
        //因为malloc分配的初始值不是0,而是其他未定义的值,所以最好还是使用memset初始化初始值
        memset(l,0,sizeof(sqlist_t));
        l->length = 0;

        return l;
}

顺序插入元素

void insert_data_sqlist(sqlist *l,datatype_t data){
        l->buf[l->length] = data;
        l->length++;
        return;
}

图示分析:

在某个位置插入某个元素

//在某个位置插入某个元素
int insert_place_sqlist(sqlist_t *l,int i,datatype_t data){
	//若顺序表满的话就不能插入
	if(l->length == MAXSIZE){
		return -1;
	}
	//判断的是大于长度-1,因为如果是length的话是在末尾插的
	if(i < 1 || i > l->length - 1){
		return -2;
	}
	for(int j = l->length;j >= i;j--)
		//交换位置(后移操作)
		l->buf[j] = l->buf[j - 1];

	//在对应位置插入元素
	l->buf[i - 1] = data;

	l->length++;

	return 0;
}

图示分析:

判断顺序表是否满

//判断顺序表是否满,满了返回1,否则返回0
int is_full_sqlist(sqlist_t *l){
	return l->length == MAXSIZE ? 1 : 0;
}

判断顺序表是否空

//判空
//若空则返回1,否则返回0
int is_empty_sqlist(sqlist_t *l){
    return l->length == 0 ? 1 : 0;
}
      

删除顺序表中某个位置的元素

//删除某个位置的元素
int delete_place_sqlist(sqlist_t *l,int i,datatype_t *e) 
{ 
    if (l->length == 0)               /* 线性表为空 */
	return -1;
    if (i < 1 || i > l->length)         /* 删除位置不正确 */
        return -2;
    *e = l->buf[i - 1];
    if (i < l->length)                /* 如果删除不是最后位置 */
    {
        for(int k = i;k < l->length;k++)/* 将删除位置后继元素前移 */
		l->buf[k-1] = l->buf[k];
    }
    l->length--;
    return 0;
}

图示分析:

/*
下面图片稍微改一下,并不是因为用*e返回才有的下面的代码
不用*e接收的情况,循环体和上面的代码一样
不用*e接收的和用*e接收的唯一区别就是 用*e接收的能够返回要删除的元素, 不用*e接收的话,向前移动的过程中要删除的元素被覆盖了,就没了
仅此而已
好好理解一下
*/

删除顺序表中特定的元素

int delete_data_sqlist(sqlist_t *l,datatype_t data){

//没有图示分析,自己动手画一下就理解了
	
	int i = 0;
	int j = 0;

	//若为空,不能删除
	if(is_empty_sqlist(l)){
		return -1;
	}	
	//删除对应的数据
	for(i = 0;i < l->length;i++){
		if(l->buf[i] != data){
			l->buf[j] = l->buf[i];
			j++;
		}
	}
	//更新l->length的值
	l->length = j;

	//判断删除的数据是否存在
	if(i == j){
		return -2;//表示没有那个元素
	}else{
		printf("delete %d is successful.\n",data);
	}

	return 0;
}

打印顺序表中的所有元素

//输出元素
void print_data_sqlist(sqlist_t *l){
        int i = 0;
        for(i = 0;i <l->length;i++){
                printf("%d ",l->buf[i]);
        }
        printf("\n");
}

按值查找

//按值查找
int locate_elem_sqlist(sqlist_t *l,datatype_t data){

    	int i;
	if(l->length == 0)
		return -1;
	for(i = 0;i < l->length;i++){
		if(l->buf[i] == data){
			//找到了立马退出
			break;
		}
	}
	if(i >= l->length)//说明找到最后了也没有找到
        	return -2;

    	//索引位置i加上个1才是要查找的第i个元素
  	return i + 1;
}

这个应该不需要图示分析了,很简单的逻辑,就是遍历整个顺序表然后如果找到了就返回,只是要注意的一点就是数组的下标和我们平常说的位序要区分开,数组是从0开始的,位序是从1开始的,所以上述的代码的返回值是返回的i+1。

按位查找

//按位查找
datatype_t get_elem_sqlist(sqlist_t *l,int i){
	if(l->length == 0 || i < 1 || i > l->length)
		return -1;
		
	return l->buf[i-1];
}

1.4main函数中的测试代码

int main(){

	//初始化一个顺序表
	sqlist_t *l = create_empty_sqlist();

	//数据类型
	datatype_t data;
	int ret = 0;
	int place = 0;

	printf("========================================\n");
	printf("请输入 %d 个数:",MAXSIZE);
	//没满则插入元素
	while(!(is_full_sqlist(l))){
		scanf("%d",&data);
		insert_data_sqlist(l,data);//顺序表插满了
	}
	
	//打印
	printf("打印插入的元素:\n");
	print_data_sqlist(l);

	printf("========================================\n");
	printf("请输入您想删除的数据:");
	scanf("%d",&data);
	ret = delete_data_sqlist(l,data);
	if(ret < 0){
		printf("顺序表是空的或删除的元素不存在!\n");
	}
	printf("删除元素%d之后的顺序表为:",data);
	print_data_sqlist(l);

	printf("========================================\n");
	printf("请输入您想要删除哪个位置的元素:");
	scanf("%d",&place);
	datatype_t e = -1;//用来存储删除的元素,-1表示删除错误
	ret = delete_place_sqlist(l,place,&e);
	if(ret < 0){
		printf("顺序表是空的或删除的位置不对!\n");	
	}
	printf("删除的元素是:%d\n",e);
	printf("删除之后的顺序表为:");
	print_data_sqlist(l);

	printf("========================================\n");
	printf("请输入您想要在顺序表中的哪个位置插入:");
	scanf("%d",&place);
	printf("请输入您想要在顺序表中的第%d个位置插入什么数据:",place);
	scanf("%d",&data);
	ret = insert_place_sqlist(l,place,data);
	if(ret < 0){
		printf("插入的位置不对或者顺序表中的元素已满!\n");
	}
	printf("在第%d个位置处添加元素%d之后的顺序表为:",place,data);
	print_data_sqlist(l);

	printf("========================================\n");
	printf("顺序表是否为空 %d (1:空、0:非空)\n",is_empty_sqlist(l));
	printf("顺序表是否为满 %d (1:满、0:非满)\n",is_full_sqlist(l));

	//按值查找
	printf("========================================\n");
	printf("请输入您要查找的数据:");
	scanf("%d",&data);
	ret = locate_elem_sqlist(l,data);
	if(ret < 0)
		printf("顺序表为空或者没有找到此数据!\n");
	else
		printf("你要查找的值%d在顺序表中的第%d个位置!\n",data,ret);
	
	//按位查找
	printf("========================================\n");
	printf("请输入你要查找哪个位置的元素:");
	scanf("%d",&place);
	datatype_t ret1 = get_elem_sqlist(l,place);
	if(ret1 < 0)
		printf("顺序表为空或者查找的位置不对!\n");
	else
		printf("您要找的第%d个位置的元素是:%d\n",place,ret1);
		

	//释放堆空间
	free(l);
	//防止称为野指针,方便后面使用
	l = NULL;

	return 0;
}

1.5现象测试

创建空的顺序表和顺序插入元素测试

没有报错并且成功插入数据就表示创建成功了。

删除指定元素测试

删除成功演示

删除失败演示

删除对应位置的数据测试

删除成功测试

删除失败测试:

在某个位置插入元素测试

插入成功

插入失败

判空和判满测试

按值查找测试

成功查找

查找失败

按位查找测试

查找成功

查找失败

1.6顺序表的优缺点

前面通过代码和而是已经实现了顺序表的一些基本的功能,从中可以得出其优缺点。

优点

  1. 存储密度高:顺序表在物理存储上是连续的,因此内存利用率高,没有额外的空间开销(如指针或引用)。
  2. 访问元素快:由于顺序表中的数据元素在内存中连续存放,因此可以通过下标直接定位元素,访问速度快。即随机存取的特性。

缺点

  1. 插入和删除操作慢:在顺序表中插入或删除元素时,可能需要移动大量元素以保持数据的连续性,因此操作效率较低。
  2. 空间分配不灵活:顺序表在创建时就需要分配固定的存储空间,如果分配的空间过大,会造成空间浪费;如果分配的空间过小,则可能出现空间不足的情况,需要重新分配存储空间并复制数据,导致效率下降。
  3. 扩容开销大:当顺序表的空间不足时,需要进行扩容操作,这通常涉及到分配新的内存空间、复制原有数据以及释放旧内存空间,这些操作都会带来额外的开销。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值