数据结构之“顺序表”

这一章节的内容是顺序表,加油!!

划重点:编写代码过程中要勤测试,避免写出大量代码后再测试而导致出现问题,问题定位无从下手。

一.线性表

线性表中包含着顺序表(线性表:顺序表,链表…)。所以在说顺序表之前,我们需要先了解一下线性表。

线性表:是n个具有 相同特性的数据元素 的有限序列,(是一种在实际中广泛使用的数据结构),常见的线性表:顺序表、链表、栈、队列、字符串…

相同特性从两个维度来分析:物理结构 和 逻辑机构

物理结构(数据在内存上的存储形式):不一定线性
逻辑结构(人为想象出来的数据的组织形式):都是线性的!!!

二. 顺序表

逻辑结构:线性的
物理结构(这个由顺序表的底层结构决定):线性的

顺序表的底层结构是?

顺序表是 (用一段物理地址连续的存储单元) 依次 (存储数据元素) 的 (线性结构),一般情况下采用数组存储。
由此可知,顺序表的底层结构是数组

顺序表和数组的区别?

顺序表的底层结构是数组,是对数组的封装,实现了常用的增删改查等接口。
(而对数组进行封装,会用到结构体)

1. 静态顺序表

定义之前已经知道数组的大小,直接int arr[3]={4,5,6};

已知数组大小和顺序表大小:静态顺序表

struct SeqList {
	int arr[100];
	int size; //顺序表中有效数据的个数
};

但是数组里还可以存储其它类型的数据,比如字符。如果之后需求发生变更,全部修改:ctrl+h(但我们只想修改特定的那部分)。一个一个改又很繁琐,这时我们可以用typedef一键替换想要修改的位置。

typedef int SLDataType

2. 动态顺序表

定义之前不知道数组的大小,我们可以用动态内存管理,来创建动态内存顺序表。而且动态顺序表可以增容。

struct SeqList {
	int* arr; //数组的指针
	          //之后再为指针指向的空间去申请空间malloc,calloc(初始化),之后想扩容的话用realloc
	int capacity; //顺序表空间大小
	              //空间大小可以修改,实时可能会变化,所以需要一个值来保存顺序表空间大小
	int size;  //有效数据的个数
};

3. 动态顺序表的简单使用说明

先在VS上新创建两个源文件和一个头文件。

在这里插入图片描述

在这里插入图片描述

  1. SeqList.h相当于目录。
    在目录里,有定义,初始化,销毁动态顺序表。
    包含头文件#include<stdio.h>
  2. SeqList.c是用来写实现的具体方法
    在SeqList.c里需要包含头文件,#include"SeqList.h",stdio.h那个不用再写,它就在SeqList.h里,已经被包含进去了
  3. test.c也要记得包含头文件#include<stdio.h>

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

4.每次写struct SeqList很麻烦,可以定义为:typedef struct SeqList SL;(第二种方式在代码中体现)

//定义动态顺序表结构
typedef int SLDataType;
typedef struct SeqList {
	SLDataType* arr;
	int capacity; //不需要修改int,空间大小本来就是整数
	int size;  //有效数据个数不修改
}SL;

5.初始化动态顺序表(传址)

初始化动态顺序表是在SeqList.c中

void SLInit(SL* ps)
{
	ps->arr = NULL;
	ps->capacity = s.size = 0;
}

6.测试初始化是否成功(在test.c中)

在这里插入图片描述
由此可知,在测试时传送的是地址。(在解引用时用的也不再是.了,而是->)

函数的调用是SLtest01();用在main函数里的。
函数的声明是void SLtest01();是在目录里的。
函数的定义是第一次写它。

7.销毁动态顺序表(在SeqList中)

在销毁时,要先判断是否是空指针。

  • 若指针(ps->arr)不是空指针,则进入if语句,先将其释放,再置为空指针。
  • 将capacity和size均置为0 。
//销毁
void SLDestory(SL* ps)
{
	if (ps->arr != NULL)
	{
		free(ps->arr);
		ps->arr = NULL;
	}
	ps->capacity = ps->size = 0;
}

4.练习

4.1 插入数据(尾插SLPushBack和头插SLPushFront)

  1. 尾插
  • 在SeqList.h中
//尾插
void SLPushBack(SL* ps, SLDatatype x);
//             顺序表    插入数据的类型是SLDatatype,之前定义了
  • 在SeqList.c中定义函数时,要考虑在尾插时,空间是否充足
  • 1.充足:即(空间大小capacity) > (有效数据size),这样的话,还可以再插入数据
  • 2.不充足:即在没有插入数据之前,(空间大小capacity)=(有效数据size),则没有空间再接纳新的数据。需要先扩容再插入数据。

在这里插入图片描述

如何增容呢?分为两种情况:
(1)原来的空间不为0,则一般情况下是将原来的空间大小扩大两倍。
(2)原来的空间为0,乘2之后也是0,所以需要先给capacity赋值,之后再不够的话,再增容。

为什么不可以一个一个的增加,这样不就不会浪费空间了吗?
注意:增容这个操作本身就有一定的程序性能的消耗,若频繁增容,会导致程序效率低下。
增容分为两种:
原地增容:原地空间足够,直接在此处增容
异地增容:原地空间不够,重新找一个内存足够的地址,将数据拷贝到新地址,再销毁旧地址。

//原空间大小不是0,在学习capacity之后,使用那个即可,这个不全面
//尾插
void SLPushBack(SL* ps, SLDatatype x)
{
	if (ps->capacity == ps->size)
	{
		SLDatatype* tmp = (SLDatatype*)realloc(ps->arr, 2 * ps->capacity); //tmp临时指针
		if (tmp == NULL)
		{
			perror("realloc fail!");
			return 1;
		}
		ps->arr = tmp;   //将tmp这个地址赋给顺序表的地址
		ps->capacity *= 2;
	}
	ps->arr[ps->size] = x;
	ps->size++; 
}
//原空间大小是0
//尾插
void SLPushBack(SL* ps, SLDatatype x)
{
	if (ps->capacity == ps->size)
	{
		//若capacity=size=0,则也可以进入if语句
		int newcapacity = ps->capacity == 0 ? 4 : 2 * ps->capacity;
		SLDatatype* tmp = (SLDatatype*)realloc(ps->arr, newcapacity*sizeof(SLDatatype)); //tmp临时指针
		                                                //不能只写newcapacity,这里的单位是字节。
														//这里代表的是newcapacity个SLDatatype大小的空间
		if (tmp == NULL)
		{
			perror("realloc fail!");
			return 1;
		}
		ps->arr = tmp;   //将tmp这个地址赋给顺序表的地址
		ps->capacity = newcapacity;
	}
	ps->arr[ps->size] = x;
	ps->size++; 
}

为了检测是否插入数据,可以将其打印出来
在这里插入图片描述

在这里插入图片描述

无论是头插还是尾插,都需要判断空间大小和有效数据是否相等,是否需要增容,我们可以将这部分内容分装,之后直接调用即可,不用重新写。
在这里插入图片描述

  1. 头插

头插需要将所有的数据后移一位。先将最后一个数据(下标是size-1)移到size处
在这里插入图片描述

删除数据(尾删SLPopBack和头删SLPopFront)

顺序表为空,不可删除。
顺序表有数据,将最后一个数据=0,再将size–即可。

尾删
在这里插入图片描述
头删

//头删
void SLPopFront(SL* ps)
{
	assert(ps);
	assert(ps->size);
	for (int i = 0; i<ps->size-1; i++)
	{
		ps->arr[i] = ps->arr[i + 1];
	}
	ps->size--;
}

在指定位置插入(SLInsert)或删除数据(SLErase)

void SLInsert(SL* ps, SLDatatype x, int pos);

pos的范围是pos>=0&&pos<=size;
为什么可以=0和=size呢?
当pos=0时,叫做头插。pos=size时,叫做尾插

在下标为pos的地方插入数据
将pos及pos以后的数据全部往后移一位。从后往前移动,先将最后一个数据往后移动。

//指定位置插入
void SLInsert(SL* ps, SLDatatype x, int pos)
{
	assert(ps);
	assert(pos >= 0 && pos <= ps->size);
	//记得检查空间是否足够
	SLCheckcapacity(ps);
	for (int i = ps->size; i > pos; i--)
	{
		ps->arr[i] = ps->arr[i - 1];
	}
	ps->arr[pos] = x;
	ps->size++;
}
//删除指定位置的数据
void SLErase(SL* ps, int pos)
{
	assert(ps);
	assert(pos >= 0 && pos < ps->size);//下标为size的地方没有数据
	for (int i = pos; i < ps->size - 1; i++) {
		ps->arr[i] = ps->arr[i + 1];
	}
	ps->size--;
}

查找数据

查找成功,返回数据的下标。
查找失败,返回-1。

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值