顺序表(增删改查)

目录

1.线性表

2.顺序表

2.1静态顺序表

 2.2动态顺序表

3.顺序表接口

 4.顺序表实现

4.1初始化

 4.2销毁

 4.3打印

4.4尾插

4.5尾删

 4.5.1尾删改进

4.6检查是否存满

4.7头插

 4.8头删

4.9在任意位置插入

 4.10删除任意位置的数据

4.11查找数据


1.线性表

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

2.顺序表

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

2.1静态顺序表

使用定长数组存储元素

 

 2.2动态顺序表

使用动态开辟的数组存储元素

//静态顺序表
#define N 1000
typedef int SLDateType;
struct SeqList
{
	SLDateType a[N];
	int size;
};


//动态顺序表
typedef int SLDateType;
struct SeqList
{
	SLDateType* a;
	int size;//存储有效数据个数
	int capacity;//空间大小
};

为了方便后续修改数据类型,我们可以使用 typedef 定义一个新的数据类型,这里我们把它取名为 SLDataType(顺序表数据类型)。

 静态顺序表只适用于确定知道需要存多少数据的场景。静态顺序表的定长数组导致N定大了,空间开多了浪费,开少了不够用。所以现实中基本都是使用动态顺序表,根据需要动态的分配空间大小,相比之下,动态顺序表的可变性要方便很多,数据存储满后,可以通过realloc对内存空间调整,所以下面我们实现动态顺序表。


3.顺序表接口

顺序表分文件以接口的方式实现:

// SeqList.h
#pragma once
#include <stdio.h>
#include <assert.h>
#include <stdlib.h>

typedef int SLDataType;

typedef struct SeqList
{
	SLDataType* a;
	int size;//存储有效数据个数
	int capacity;//空间大小
}SL;
//管理数据- 增删查改
void SLInit(SL* ps);//初始化
void SLDestory(SL* ps);//销毁
void SLprint(SL* ps);//打印
void CheckCapacity(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);//在pos位置插入x
void SLErase(SL* ps, int pos);//在pos位置删除
void SLFind(SL* ps, SLDataType x);//在顺序表中查

 4.顺序表实现

4.1初始化

使用传址调用,形参的变化不会影响实参,将a数组置为NULL,对size和capacity初始化为0

/*void SLInit(SL* ps)
{
	ps->a = NULL;
	ps->size = 0;
	ps->capacity = 0;
}*/
void SLInit(SL* ps)
{
	ps->a =(SLDateType*)malloc(sizeof(SLDateType)*4);
	if (ps->a == NULL)
	{
		perror("malloc failed");
		exit(-1);
	}
	ps->size = 0;
	ps->capacity = 4;
}

 4.2销毁

因为是动态开辟的,所以如果空间不用我们就需要销毁掉。如果不销毁会存在内存泄漏的风险,所以与之对应的我们写一个销毁的接口函数。

void SLDestory(SL* ps)
{
	free(ps->a);
	ps->a = NULL;
	ps->capacity = 0;
	ps->size = 0;
}

 4.3打印

定义一个打印的函数,方便我们写一块调试一下

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

4.4尾插

1.顺序表没有空间。

2.我们创建的 capacity 满了。

3.空间足够,直接插入数据即可

void SLPushBack(SL* ps,SLDataType x)
{
	if (ps->size == ps->capacity)
	{
		SLDataType *tmp=realloc(ps->a, ps->capacity * 2 * (sizeof(SLDataType)));
		if (tmp == NULL)
		{
			perror(realloc);
			exit(-1);
		}
		ps->a = tmp;
		ps->capacity *= 2;
	}   
	ps->a[ps->size] = x;
	ps->size++;
}

4.5尾删

void SLPopBack(SL* ps)
{
	//ps->a[ps->size] = 0;
	ps->size--;
}

这里也有一些小小的问题

#include"SeqList.h"
int main()
{
	SL s1;
	SLInit(&s1);
	SLPushBack(&s1, 1);
	SLPushBack(&s1, 2);
	SLPushBack(&s1, 3);
	SLPushBack(&s1, 4);
	SLPushBack(&s1, 5);
	SLPushBack(&s1, 6);
	SLPushBack(&s1, 6);
	SLPopBack(&s1);
	SLPopBack(&s1);
	SLPopBack(&s1);
	SLPopBack(&s1);
	SLPopBack(&s1);
	SLPopBack(&s1);
	SLPopBack(&s1);
	SLPopBack(&s1);
	SLprint(&s1);
	SLDestory(&s1);
}

 多次删除之后,其实已经发生了数组的越界,但是程序照常运行没有进行报错,但是将尾删的次数减少,与尾插持平的时候就会报错,举个形象的例子:查酒不会在同一时间全部道路都查,人力不足只会在重要的几个位置,酒驾频发的位置查。数组越界也是这样,只在容易发生越界的地方进行查看。

 4.5.1尾删改进

删除最后一个元素其实我们这里只需要元素数量减去一个就好了,即size--,这样我们就无法访问最后一个元素了,反正下一个数据在之后增加时要被覆盖。

void SLPopBack(SL* ps)
{
	//if (ps->size == 0)
		//return;
	assert(ps->size);
	ps->size--;
}

4.6检查是否存满

void CheckCapacity(SL* ps)
{
	if (ps->size == ps->capacity)
	{
		SLDataType* tmp = realloc(ps->a, ps->capacity * 2 * (sizeof(SLDataType)));
		if (tmp == NULL)
		{
			perror(realloc);
			exit(-1);
		}
		ps->a = tmp;
		ps->capacity *= 2;
	}
}

4.7头插

顺序表要求数据是连续存储的,且必须是从头开始存储。对于顺序表而言如果要实现头插,就需要把数据往后挪动。不能从前往后挪,如果从前往后挪就挪就会把后面的数据覆盖掉。

首先创建一个 end 变量用来指向要移动的数据,因为指向的是数据的下标,所以是 size 要减 1 。随后进入 while 循环,如果 end >= 0 说明还没有移动完,就会进入循环。循环体内利用下标,进行向后移动操作,移动结束后再 end-- ,进行下一个数据的向后移动。挪动数据成功后,就可以插入了。此时顺序表第一个位置就被腾出来了,就可以在下标0位置插入欲插入的数据 x 了。最后记得 size++ 。 

void SLPushFront(SL* ps, SLDataType x)
{
	CheckCapacity(ps);
	int end = ps->size - 1;
	while (end >= 0)
	{
		ps->a[end + 1] = ps->a[end];
		--end;
	}
	ps->a[0] = x;
	ps->size++;
}

 4.8头删

与头插思路类似不再赘述

void SLPopFront(SL* ps)
{
	CheckCapacity(ps);
	int start = 1;
	while (start<= ps->size)
	{
		ps->a[start - 1] = ps->a[start];
		start++;
	}

4.9在任意位置插入

在任意位置插入可以完全替代头插

void SLInsert(SL* ps, int pos, SLDataType x)
{
	assert(ps);
	assert(pos>0&& pos<=ps->size);
	CheckCapacity(ps);
	int end = ps->size-1;
	while (end <= pos)
	{
		ps->a[end + 1] = ps->a[end];
		--end;
	}
	ps->a[pos] = x;
	ps->size++;
}

 4.10删除任意位置的数据

同理,可替代尾删,循环条件与4.9不同的是,pos需要<ps->size而不是小于等于。

void SLErase(SL* ps, int pos)
{
	assert(ps);
	assert(pos > 0 && pos < ps->size);
	CheckCapacity(ps);
	int begin = pos+1;
	while ( begin<=ps->size-1 )
	{
		ps->a[begin-1] = ps->a[begin];
		++begin;
	}
	ps->size--;
}

4.11查找数据

将数组遍历一次查找

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

  • 7
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值