<数据结构>线性表之顺序表增添查找

线性表

友情提示:下列带 * 号的看看即可!

线性表(List):零个或多个具有相同属性的数据元素的有限序列。当n=0的时候被称为空表。

常见的线性表:顺序表、链表、栈、队列、字符串…

image-20221030173345049

观察上图,我们可以发现线性表有以下基本特点:

1.第一个元素无直接前驱

2.最后一个元素无直接后继

3.除第一个和最后一个元素之外的其它元素都有一个前驱和后继

线性表在逻辑上是线性结构,在物理结构上并不一定是连续的,线性表在计算机中有两种基本的存储结构,分别是顺序存储结构和链式存储结构。常见的就是接下来要写的顺序表和链表。

image-20221030175422949

对一个线性表来说,插入数据和删除数据都是必须的操作。所以,线性表的抽象数据类型如下:

aHR0cHM6Ly9pbWcubXVidS5jb20vZG9jdW1lbnRfaW1hZ2UvZDE5NWUxNTMtNWQ4NS00OTFjLTgyMmMtZTA5ZWI1NjNjMzU3LTY3MDIwNy5qcGc

顺序表

顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构,可以理。解为顺序表的本质就是数组。

一般情况下采用数组存储。在数组上完成数据的增删查改

顺序表一般可以分为:

  1. 静态顺序表:使用定长(固定长度)数组存储。
  2. 动态顺序表:使用动态开辟的数组存储。

虽然说顺序表可以分为静态顺序表和动态顺序表。但是我们在大多数的时候用的是动态顺序表。

因为静态顺序表有个很大的弊端,就是静态顺序表空间不能灵活控制,就是给多了就会浪费。给少了又不够用。

所以在这里,我们主要的就是研究动态顺序表。

在研究之前呢,我们先研究顺序表基本操作的实现。主要有顺序表初始化,取值,查找,插入,删除等。

顺序表初始化

在这里插入图片描述

动态顺序表可以有效的利用系统空间,如果不需要的话直接销毁。

顺序表取值

在这里插入图片描述

顺序表取值算法的时间复杂度为O(1)

顺序表查找

在这里插入图片描述

顺序表按值查找算法的是平均时间复杂度为O(N)

顺序表插入

在这里插入图片描述

顺序表插入算法的是平均时间复杂度为O(N)

顺序表删除

在这里插入图片描述

顺序表删除算法的是平均时间复杂度为O(N)

用C实现一个顺序表

了解以上的基本操作后,接下来我们用C实现一个顺序表,使之能够实现 增 删 查 改

在此之前,我们先写一个静态顺序表的结构了解一下

静态顺序表:使用定长数组存储元素

#define N 10
//方便修改数据类型
typedef int SLDataType;	//DataType 数据类型
typedef struct SeqList
{
	SLDataType a[N];	//存放顺序表中的元素
	int Size;	//记录存储有效数据个数
}SL;

image-20221102194427948

动态顺序表:使用动态开辟的数组存储

typedef int SLDataType;
typedef struct SeqList
{
	SLDataType* a;
	int size;		//记录存储多少个有效数据
	int capacity;	//空间容量大小
}SL;

相关函数接口的实现

初始化(Init)顺序表
//初始化
void SLtInit(SL* ps)
{
	assert(ps);	//防止传入空指针
	ps->a = NULL;	//把指向顺序表的指针置为空指针
	ps->size = 0;	//数据个数
	ps->capacity = 0;	
}

可以为数组开辟空间这里并没有开辟。

我们要实现的顺序表是需要能扩容的,a的空间是malloc出来的,所以在用完之后要销毁,释放其所在的内存地址。

销毁(Destroy)顺序表
//销毁
void SLDestroy(SL* ps)
{
	assert(ps);	//防止传入空指针
	if (ps->a)
	{
		free(ps->a);	//检查出是否数组溢出
		ps->a = NULL;
	}
	ps->capacity = ps->size = 0;
}

销毁时先判断所指向的数组是否为空,把数据个数和容量都置为0,并且把指向顺序表的指针也要置为空指针。

同时如果我们要在顺序表内进行插入或删除数据时,必须要考虑容量的问题。因此我们在插入或删除之前要进行容量检查。

顺序表容量检查(CheckCapacity)
//顺序表容量检查
void SLCheckCapacity(SL* ps) {
    assert(ps);	//防止ps为空指针
    if (ps->size == ps->capacity) {
        //判断容量是否已满
        int newCapacity = ps->size ==0 ? 4 : 2 * ps->capacity;
        //刚开始用默认给4个空间,若用完空间扩容2倍
        SLDataType* tmp = (SLDataType*)realloc(ps->array, newCapacity * sizeof(SLDataType));
        //SLDataType* 接收这个新扩容的数组大小
        //若ps->arr是NULL,则realloc等同于malloc
        if (tmp == NULL) {
            //判断是否扩容成功
            perror(" Realloc Fail");
            exit(-1);
            //异常返回,程序提前结束。正常返回为0
        }
        ps->array = tmp;
        ps->capacity = newCapacity;
    }
}

空间不够则继续扩容,继续扩容有两种选择,一种是所使用的空间后面的空间没有占用,则原地扩容,若被占用则是异地扩容,如下图所示

顺序表容量方面已经通过以上代码搞定。那接下来就要对数据进行 “ 增删 查 改 ”操作。

在顺序表中,我们主要对数据插入和删除有尾插,尾删,头插,头删,指定位置插入,指定位置删除。

* 尾插(PushBack)
//尾插
void SLPushBack(SeqList* ps, SLDateType x)
{
	assert(ps);
	SLCheckCapcity(ps);	//检查容量
	ps->a[ps->size] = x;	//插入数据
	ps->size++;
}
* 尾删(PopBack)
//尾删
void SLPopBack(SeqList* ps)
{
	assert(ps);
	assert(ps->size > 0);	//暴力检查(防止越界),避免非法删除
	ps->size--;	
}

在尾删的时候要注意一下越界的问题。

* 头插(PushFront)
//头插
void SLPushFront(SeqList* ps, SLDateType x)
{
	assert(ps);
	SLCheckCapcity(ps);
	int end = ps->size - 1;
	while (end >= 0)
	{
		ps->a[end + 1] = ps->a[end];
		end--;
	}
	ps->a[0] = x;	//在下标为0的位置插入数据X
	ps->size++;
}

在研究头插的时候,要注意:

因为我们是从头部插入,因此要有足够的空间可以存放数据,同时在移动数据的时候,要从最后一个数据开始一个一个往后挪。

若从第一个数据往后挪,便会导致数据覆盖,这是错误的。

在这里插入图片描述

在这里插入图片描述

非特殊条件下,我们尽量选择尾插,因为头插复杂度为O(N),而尾插的复杂度为O(1).

相对于头插来说,头删是从前往后挪,也就是说从第二个数据往前挪动,但要注意其终止条件,以防造成越界。

* 头删(PopFront)
//头删
void SLPopFront(SeqList* ps)
{
	assert(ps);
	assert(ps->size > 0);	//暴力检查,防止越界
	int begin = 1;	//第二个数开始
	while (begin < ps->size)
	{
		ps->a[begin - 1] = ps->a[begin];	//赋给前一个数
		begin++;
	}
	ps->size--;
}

在这里插入图片描述

注意其终止条件,以防越界。

学会接下来的两个,上面那个尾插 尾删 头插 头删 都可以靠边站,对,就是这样,我也很生气。

指定位置插入(Insert)
//指定位置插入
void SLInsert(SeqList* ps, int pos, SLDateType x)	//在pos位置插入数据
{
	assert(ps);	//防止ps为空指针
	assert(pos >= 0 && pos <= ps->size);	//检查pos是否在规定区域内
	SLCheckCapcity(ps);	//增容
	int end = ps->size - 1;
	while (end >= pos)
	{
		ps->a[end + 1] = ps->a[end];	//从下标为pos的位置开始整体往后覆盖
		end--;
	}
	ps->a[pos] = x;	//在下标为pos的位置插入数据
	ps->size++;
}

image-20221107142817541

指定位置删除(Erase)
//指定位置删除
void SLErase(SeqList* ps, int pos)	//在pos位置删除数据
{
	assert(ps);
	assert(pos >= 0 && pos < ps->size);	//检查pos是否在规定区域内
	//assert(ps->size > 0);
	int begin = pos + 1;
	while (begin < ps->size)
	{
		ps->a[begin - 1] = ps->a[begin];	//从下标为pos+1的位置开始整体往前覆盖
		begin++;
	}
	ps->size--;
}

同时尾删,头删可更改为

//尾删
void SLPopBack(SeqList* ps)
{
	/*
    assert(ps);
	assert(ps->size > 0);	//暴力检查(防止越界),避免非法删除
	ps->size--;	
	*/
    SLErase(ps, ps->size - 1);
}

//头删
void SLPopFront(SeqList* ps)
{
	/*
    assert(ps);
	assert(ps->size > 0);	//暴力检查,防止越界
	int begin = 1;	//第二个数开始
	while (begin < ps->size)
	{
		ps->a[begin - 1] = ps->a[begin];	//赋给前一个数
		begin++;
	}
	ps->size--;
	*/
    SLErase(ps,0);
}
查找数据(Find)
//查找数据
int SLFind(SeqList* ps, SLDateType x, int begin)
{
	assert(ps);
	assert(begin >= 0);
	for (int i = begin; i < ps->size; i++)	//遍历
	{
		if (ps->a[i] == x)
		{
			return i;	//返回该数值下标
		}
	}
	return -1;	//没找到
}

在这里我们也可以使用二分查找,但是二分查找规定数据必须是有序的。

顺序表的优缺点

优点:

无需为表示表中元素之间的逻辑关系而增加额外的存储空间
可以快速地存取表中任一位置的元素

缺点:

插入和删除操作需要移动大量元素
当线性表长度变化较大时,难以确定存储空间的容量
造成存储空间的“碎片”

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

艾莜薇

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值