4.线性表之顺序表-数据结构入门(c语言实现)

目录

线性表

线性表的特点:

线性表的抽象数据类型定义:

顺序表

静态顺序表

动态顺序表

基本操作

打印函数

扩容函数

尾插函数

头插函数

头删函数

任意位置添加任意元素(下标位)

任意位置删除

查找元素,返回下标,没有则返回-1

排序

二分查找

查找某个数,并删除顺序表中的每个它 时间复杂度o(n),空间(1)

顺序表优劣:


线性表

线性表(linear list)是n个具有相同特性的数据元素的有限序列。线性表是一种在实际中广泛使用的数据结构,常见的线性表:顺序表、链表、栈、队列、字符串... 线性表在逻辑上是线性结构,也就说是连续的一条直线。但是在物理结构上并不一定是连续的,线性表在物 理上存储时,通常以数组和链式结构的形式存储。

线性表的特点:

在数据元素的非空有限集中,除第一个元素无直接前驱,最后一个元素无直接后继之外,其他每个数据元素都有且只有一个前驱和后继。

线性表的抽象数据类型定义:

ADT List

{

数据对象:D={ai I ai 属于 ElemSet, i=1, 2, …, n, n>=-0}

数据关系:R=(<ai-1,ai> I  ai-1,ai属于D, i=2, …, n}

基本操作:

}ADT List

        线性表的第一个数据元素a1的存储位置,通常称为线性表的首元结点或开始结点 。

顺序表

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

因为顺序表的逻辑结构和物理结构,次序都是相邻的。

所以要知道顺序表首地址和每个数据元素所占存储单元的个数,就可以求出第i个数据元素的存储地址:

LOC(a i) = LOC(a1)+(i-1)*d

d:一个元素占用的存储单元个数

LOC( a i ):线性表第i个元素的地址

LOC(a 1):起始地址,基地址

顺序表一般可以分为:

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

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

静态顺序表

     静态顺序表就是数组大小是固定的,开辟好空间之后不能再进行更改,定义的时候用宏定义常量数组大小,定义结构体时使用常量宏开辟好数组。如:

        静态顺序表只适用于确定知道需要存多少数据的场景。静态顺序表的定长数组MAXSIZE定大了导致空间开多了浪费,开少了不够用。所以实现的时候基本都是使用动态顺序表,根据需要动态的分配空间大小,所以接下来实现动态顺序表。

动态顺序表

        这种结构是结构体定义时不开辟数组,把结构体内的数组替换成一个该数据类型的指针,初始化顺序表时用malloc函数申请空间并让结构体链接该数组。

SeqLDataType* a:指向动态开辟的数组

size:有效数据个数  

capicity : 容量空间的大小

初始化:

这种结构如果数组满了就可以用realloc函数来进行增容。

分配内存空间函数: malloc

void *malloc(unsigned int size)
生成一大小为size(单位是字节)的结点空间

重置内存空间:realloc

void *realloc(void *p, unsigned int size)
将p所指向的已分配内存区的大小改为size,size可以比原分配的空间大或小。

释放内存空间函数:free

free(void *fp)
回收fp所指向的结点空间;

相同的数据结构有非常多不同的实现方法。这边只会记录个人涉及到的一些结构。

基本操作

打印函数

void SeqListPrint(struct SeqList* ps)
{
	assert(ps);
	//int i = 0;
	for (int i = 0; i < ps->size; i++)
	{
		printf("%d ", ps->a[i]);
	}
	printf("\n");
}

扩容函数

int SeqListCapacity2(struct SeqList* ps)
{
	assert(ps);
	//扩容的条件是顺序表存在
	if (ps->a != NULL)
	{
		//先把顺便表的地址存起来,防止realloc失败返回空指针损失原地址
		SeqLDataType* tmp = ps->a;
		ps->a = (SeqLDataType*)realloc(ps->a, sizeof(SeqLDataType) * (ps->capacity) * 2);
		if (ps->a == NULL)
		{
			ps->a = tmp;
			printf("内存扩容失败!\n");
			return 1;
		}
		else
		{
			ps->capacity *= 2;
			return 0;
		}
	}
	return 1;
}

尾插函数

void SeqListPushBack(struct SeqList* ps, SeqLDataType x)
{
	assert(ps);
	if (ps->size >= ps->capacity)
	{

		if (SeqListCapacity2(ps))
		{
			return ;
		}
	}
	ps->a[ps->size] = x;
	ps->size++;
}

头插函数

void SeqListPushFront(struct SeqList* ps, SeqLDataType x)
{
	assert(ps);
	//这个东西...首先应该把size+1,然后从最后一位往后挪,直到挪到第一位,再把x赋值给a[0]
	if (ps->size >= ps->capacity)
	{

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

尾删函数

void SeqListPopBack(struct SeqList* ps)
{
	assert(ps);
	ps->size--;
}

头删函数

void SeqListPopFront(struct SeqList* ps)
{
	assert(ps);
	for (int i = 0; i < ps->size - 1; i++)
	{
		ps->a[i] = ps->a[i + 1];
	}
	ps->size--;
}

任意位置添加任意元素(下标位)

void SeqListInsert(struct SeqList* ps, int pos, SeqLDataType x)
{
	assert(ps);
	assert(pos >= 0 && pos < ps->size);
	//和删除逻辑差不多,朝前循环朝后挪,再到传入位置赋值x,size+1
	if (ps->size >= ps->capacity)
	{

		if (SeqListCapacity2(ps))
		{
			return;
		}
	}
	//先检测需不需要扩容

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

任意位置删除

void SeqListErase(struct SeqList* ps, int pos)
{
	assert(ps);
	//变量控制下标朝后循环往前挪,循环条件为有效长度-下标位-1
	for (int i = pos;i<ps->size-1; i++)
	{
		ps->a[i] = ps->a[i+1];
	}
	ps->size--;
}

查找元素,返回下标,没有则返回-1

int SeqListFind(struct SeqList* ps, SeqLDataType data)
{
	assert(ps);
	for (int i = 0; i < ps->size; i++)
	{
		if (ps->a[i] == data)
			return i;
	}
	return -1;
}

排序

void SeqListSort(struct SeqList* ps)
{
	assert(ps);
	int count = 1;
	for (int i = 0; i < ps->size; i++,count++)
	{
	//	每一次循环都会把最大的数放到最后一位 所以每循环一次,遍历长度-1
		for (int j = 0; j < ps->size-count; j++)
		{
			if (ps->a[j] > ps->a[j+1])
			{
				SeqLDataType k = ps->a[j];
				ps->a[j] = ps->a[j+1];
				ps->a[j+1] = k;
			}
		}
//		SeqListPrint(ps);
	}
}

二分查找

int SeqListBinaryFind(struct SeqList* ps, SeqLDataType k)
{
	assert(ps);
	int right = ps->size - 1;
	int left = 0;
	int middle = (left+right) / 2;
	
	while (right>left+1)
	{
		if (k > ps->a[middle])
		{
			left = middle;
			middle = (left + right) / 2;
		}
		else if (k < ps->a[middle])
		{
			right = middle;
			middle = (left + right) / 2;
		}
		else
		{
			return middle;
		}
	}
	if (k == ps->a[left])
		return left;
	if (k == ps->a[right])
		return right;
	return -1;
}

查找某个数,并删除顺序表中的每个它 时间复杂度o(n),空间(1)

int SeqListDelRep(struct SeqList* ps, SeqLDataType del)
{
	assert(ps);
	int s1 = 0, s2 = 0;
	while ( s1 < ps->size )
	{ 
		if (ps->a[s2] != del)
		{
			ps->a[s1] = ps->a[s2];
			s2++;
			s1++;
		}
		else
		{
			ps->size--;
			s2++;  
		}
	}
	return s1;
}

顺序表优劣:

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

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

3.增容一般是呈2倍的增长,势必会有一定的空间浪费。

4.缓存命中率高。

5.实现随机存取

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值