【数据结构-C语言】顺序表

在了解顺序表之前我们先要知道线性表这个概念

1、线性表

1.1 线性表的概念

对于一组拥有n个数据元素的线性表,其严格数学定义是:其中任何一个数据元素a(i)​,有且仅有一个直接前驱a(i-1),有且仅有一个直接后继a(i+1)。首元素a0无直接前驱,尾元素a(n-1)无直接后继。

满足这种数学关系的一组数据,当中的数据是一个挨着一个的,常被称为一对一关系。反之,如果数据之间的关系不是一对一的,就是非线性的。

1.2 生活中的例子

例如:一个班级中的以学号编排的学生,一条正常排队等候的队列等。

拥有同样的特点:除了首尾元素,其余任何一个元素前后都对应相邻的另一个元素

1.3 注意:

线性表是一种数据内部的逻辑关系,与存储形式无关

线性表既可以采用连续的顺序存储,也可以采用离散的链式存储

2、顺序表

2.1 顺序表的基本概念

顺序表:顺序存储的线性表

链式表:链式存储的线性表,简称链表

顺序存储:将数据存储到一片连续的内存中,在c语言环境下,可以是具名的栈数组,或者是匿名的堆数组

存储方式不仅仅只是提供数据的存储空间,而是必须要能体现数据之间的逻辑关系。

当采用顺序存储的方式来存放数据时,唯一能用来表达数据间本身的逻辑关系的就是存储位置。

 2.2 基本操作

在执行增删改删等基本动作之前,需要初始化顺序表。在此篇博客中创建的顺序表是带有管理结构体的。

顺序表设计:为了方便操作顺序表,需要一个专门管理顺序表的“管理结构体”

管理结构体中一般会包含:1、顺序表总容量

                                           2、顺序表当前最末元素下标位置

                                           3、顺序表指针

//管理结构体代码
typedef struct        //给类型取别名
{

    int capacity;    //顺序表容量
    int last;        //最末元素下标
    int* data;       //顺序表,以整型数据为例
    
}sequenceList

在使用顺序表之前需要初始化顺序表,所谓初始化就是建立一个不包含任何元素的顺序表,设置好管理结构体中的表的总容量、末元素下标,申请号顺序表内存空间等系列准备工作。

//初始化顺序表
sequenceList* init(int cap)
{

    sequenceList* list = malloc(sizeof(sequenceList));    //开辟堆区空间成功则开始初始化
    if(list != NULL)
    {
        list->capacity = cap;        //顺序表的总容量为传参进来的cap
        list->last = -1;             //顺序表的末元素下标从-1开始                                   
        list->data = malloc(cap * sizeof(int));    //顺序表的数据部分也需要开辟堆区空间
        if(list->data == NULL)
        {
            free(list);              //开辟失败需要释放顺序表开辟的堆区空间,避免内存泄漏
            return NULL;
        }
    }

    return list;
}

初始化成功之后就可以对顺序表进行一些基本操作,例如增加删除节点。

在顺序表中增加一个数据,可以有多种方式,比如在原数组的末尾增加,或者在原数组的头部增加,或者在数组中间任意一个位置增加。根据实际需要来决定。

//顺序表头部增加或删除数据
//在顺序表增加之前要进行顺序表是否为满的判断,如果顺序表为满,则不可以再进行增加
//在顺序表删除之前要进行顺序表是否为空的判断,如果顺序表为空,则不可以再进行删除

//判断顺序表是否为空
bool isEmpty(sequenceList *list)
{
    return list->last == -1;    //最简洁的判断方式,当顺序表末元素的下标为-1时则为空
}


//判断顺序表是否为满
bool isFull(sequenceList* list)
{
    return list->last == list->capacity - 1;    //最简洁的判断方式,当顺序表的下标等于顺序表总容量大小 - 1则说明顺序表已满(顺序表第一个元素的下标为0)
}

//在顺序表表头插入一个新数据
bool insert(sequenceList* list,int data)
{
    //判断顺序表是否为满
    if(isFull(list))
    {
        return false;
    }
    
    //将所有数据往后移动一位
    for(int i = list->last;i >= 0;i--)
    {
        list->data[i + 1] = list->data[i]; 
    }

    //将增加的数据放到表头,末元素的下标+1
    list->data[0] = data;
    list-<last++;
    
    return true;
}

//将顺序表表头的数据删除掉
bool remove(sequenceList* list)
{
    //判断顺序表是否为空
    if(isEmpty(list))
    {
        return false;
    }

    //将所有数据往前移动一位
    for(int i = 0;i < list->last;i++)
    {
        list->data[i] = list->data[i + 1];
    }

    //顺序表的末元素下标-1
    list->last--;

    return true;
}

但是很多时候都不是删除顺序表的表头数据,而是删除指定的数据,怎么实现呢,只需要在删除表头数据的代码增加一小段代码就可以

//将顺序表的数据删除掉
bool remove(sequenceList* list)
{
    //判断顺序表是否为空
    if(isEmpty(list))
    {
        return false;
    }
    
    //找到要删除的节点
    int pos = -1;
    for(int i = 0;i < list->last;i++)
    {
        if(list->data[i] == data)
        {
            pos = i;
            break;
        }
    }
    
    //如果顺序表没有我们要删除的数据
    //if(pos == -1)
    if(i > list->last)
    {
        return false;
    }    


    //将要删除的数据之后的数据往前移动一位
    for(int i = pos;i < list->last;i++)
    {
        list->data[i] = list->data[i + 1];
    }

    //顺序表的末元素下标-1
    list->last--;

    return true;
}

这样貌似就满足了我们的指定删除数据的需求,但仔细一看,似乎代码长度好像又太长了,又想到我们的需求里面本身就有查找的功能,那我们就可以把上面的代码拆分为删除和查找两个功能函数

//将指定数据删除掉
bool remove(sequenceList* list)
{
    //判断顺序表是否为空
    if(isEmpty(list))
    {
        return false;
    }

    //获取删除数据的下标
    int pos = find(list,data);
    if(pos == -1)
    {
        return false;
    }

    //将所有数据往前移动一位
    for(int i = pos;i < list->last;i++)
    {
        list->data[i] = list->data[i + 1];
    }

    //顺序表的末元素下标-1
    list->last--;

    return true;
}

//查找指定数据的下标
int find(sequenceList* list,int data)
{
    //判断顺序表是否为空
    if(isEmpty(list))
    {
        return false;
    }
    
    //找到要删除的节点
    int pos = -1;
    for(int i = 0;i < list->last;i++)
    {
        if(list->data[i] == data)
        {
            pos = i;
            return pos;
        }
    }
    
    //如果顺序表没有我们要删除的数据
    //if(pos == -1)
    if(i > list->last)
    {
        return -1;
    }    

}

现在有了查找这个函数,那么写顺序表的修改功能就很容易了

//修改顺序表的指定数据
bool change(sequenceList* list,int find,int data)
{
    //需要判断顺序表是否为空
    if(isEmpty(list))
    {
        return false;
    }

    //利用查找函数找到要修改的顺序表数据的下标
    int pos = find(list,find);
    if(pos == -1)
    {
        return false;
    }

    //直接赋值就可以完成修改
    list->data[pos] = data;

    return true;

}

那我们初始化顺序表之后,对顺序表进行了一系列的操作之后,怎么知道自己是否操作成功呢,那我们就需要一个查看的功能函数,它能帮助我们测试我们的操作是否成功

//查看顺序表的内容
void show(sequenceList* list)
{
    //判断顺序表是否为空
    if(ifEmpty(list))
    {
         return;   
    }

    for(int i = 0;i < list->last;i++)
    {
        printf("%d ",list->data[i]);
    }

    printf("\n");
}

在我们使用完顺序表之后,程序之前需要销毁顺序表,因为开辟了堆区空间,如果不使用free,就有内存泄漏的风险,可以造成程序崩溃等危险

//销毁顺序表
void destroy(sequenceList* list)
{
    //判断顺序表是否为空
    if(list == NULL)
    {
         return;   
    }
    

    //释放顺序表的数据部分,再释放掉顺序表
    free(list->data);
    free(list);

}

2.3 顺序表的优缺点

顺序存储中,由于逻辑关系是用物理位置来表达的,因此上述示例代码可以很清楚看到,增删数据都非常困难,需要成片地移动数据。顺序表对数据节点地增删操作时很不友好的。

优点:

       1、不需要多余地信息来记录数据间地关系,存储密度高

       2、所有数据顺序存储在一片连续地内存中,支持立即访问任意一个随机数据,例如:顺序  表中第i个节点是list->data[i]

缺点:

        1、插入、删除时需要保持数据地物理位置反映其逻辑关系,一般需要成片移动数据

        2、当数据节点数量较多时,需要一整片较大的连续内存空间

        3、当数据节点数量变化剧烈时,内存的释放和分配不灵活

那么,关于顺序表的相关函数接口就到这里了,那么最后再加上我的主函数测试代表

int main()
{
	sequenceList* list = init_list(10);

	if (list == NULL)
	{
		printf("初始化顺序表失败!\n");
	}
	else
	{
		printf("初始化顺序表成功!\n");
	}

	int n;
	while (1)
	{
		scanf("%d", &n);
		if (n > 0)
		{
			if (!insert(list, n))
			{
				printf("容量已满,插入失败!\n");
				continue;
			}
		}
		else if (n < 0)
		{
			if (!removeNode(list, -n))
			{
				printf("查无此数,删除失败!\n");
				continue;
			}
		}
		show(list);
	}
	destroy(list);

	return 0;
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值