这一章节的内容是顺序表,加油!!
划重点:编写代码过程中要勤测试,避免写出大量代码后再测试而导致出现问题,问题定位无从下手。
文章目录
- 一.线性表
- [二. 顺序表](#二. 顺序表)
-
- [1. 静态顺序表](#1. 静态顺序表)
- [2. 动态顺序表](#2. 动态顺序表)
- [3. 动态顺序表的简单使用说明](#3. 动态顺序表的简单使用说明)
- 4.练习
-
- [4.1 插入数据(尾插SLPushBack和头插SLPushFront)](#4.1 插入数据(尾插SLPushBack和头插SLPushFront))
- 删除数据(尾删SLPopBack和头删SLPopFront)
- 在指定位置插入(SLInsert)或删除数据(SLErase)
- 查找数据
一.线性表
线性表中包含着顺序表(线性表:顺序表,链表...)。所以在说顺序表之前,我们需要先了解一下线性表。
线性表:是n个具有 相同特性的数据元素 的有限序列,(是一种在实际中广泛使用的数据结构),常见的线性表:顺序表、链表、栈、队列、字符串...
相同特性从两个维度来分析:物理结构 和 逻辑机构
物理结构(数据在内存上的存储形式):不一定线性
逻辑结构(人为想象出来的数据的组织形式):都是线性的!!!
二. 顺序表
逻辑结构:线性的
物理结构(这个由顺序表的底层结构决定):线性的
顺序表的底层结构是?
顺序表是 (用一段物理地址连续 的存储单元) 依次 (存储数据元素) 的 (线性结构),一般情况下采用数组存储。
由此可知,顺序表的底层结构是数组
顺序表和数组的区别?
顺序表的底层结构是数组,是对数组的封装,实现了常用的增删改查等接口。
(而对数组进行封装,会用到结构体)
1. 静态顺序表
定义之前已经知道数组的大小,直接int arr[3]={4,5,6};
已知数组大小和顺序表大小:静态顺序表
c
struct SeqList {
int arr[100];
int size; //顺序表中有效数据的个数
};
但是数组里还可以存储其它类型的数据,比如字符。如果之后需求发生变更,全部修改:ctrl+h(但我们只想修改特定的那部分)。一个一个改又很繁琐,这时我们可以用typedef一键替换想要修改的位置。
c
typedef int SLDataType
2. 动态顺序表
定义之前不知道数组的大小,我们可以用动态内存管理,来创建动态内存顺序表。而且动态顺序表可以增容。
c
struct SeqList {
int* arr; //数组的指针
//之后再为指针指向的空间去申请空间malloc,calloc(初始化),之后想扩容的话用realloc
int capacity; //顺序表空间大小
//空间大小可以修改,实时可能会变化,所以需要一个值来保存顺序表空间大小
int size; //有效数据的个数
};
3. 动态顺序表的简单使用说明
先在VS上新创建两个源文件和一个头文件。
- SeqList.h相当于目录。
在目录里,有定义,初始化,销毁动态顺序表。
包含头文件#include<stdio.h>
- SeqList.c是用来写实现的具体方法
在SeqList.c里需要包含头文件,#include"SeqList.h"
,stdio.h那个不用再写,它就在SeqList.h里,已经被包含进去了- test.c也要记得包含头文件
#include<stdio.h>
4.每次写struct SeqList很麻烦,可以定义为:typedef struct SeqList SL;(第二种方式在代码中体现)
c
//定义动态顺序表结构
typedef int SLDataType;
typedef struct SeqList {
SLDataType* arr;
int capacity; //不需要修改int,空间大小本来就是整数
int size; //有效数据个数不修改
}SL;
5.初始化动态顺序表(传址)
初始化动态顺序表是在SeqList.c中
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 。
c
//销毁
void SLDestory(SL* ps)
{
if (ps->arr != NULL)
{
free(ps->arr);
ps->arr = NULL;
}
ps->capacity = ps->size = 0;
}
4.练习
4.1 插入数据(尾插SLPushBack和头插SLPushFront)
- 尾插
- 在SeqList.h中
c
//尾插
void SLPushBack(SL* ps, SLDatatype x);
// 顺序表 插入数据的类型是SLDatatype,之前定义了
- 在SeqList.c中定义函数时,要考虑在尾插时,空间是否充足
- 1.充足:即(空间大小capacity) > (有效数据size),这样的话,还可以再插入数据
- 2.不充足:即在没有插入数据之前,(空间大小capacity)=(有效数据size),则没有空间再接纳新的数据。需要先扩容再插入数据。
如何增容呢?分为两种情况:
(1)原来的空间不为0,则一般情况下是将原来的空间大小扩大两倍。
(2)原来的空间为0,乘2之后也是0,所以需要先给capacity赋值,之后再不够的话,再增容。
为什么不可以一个一个的增加,这样不就不会浪费空间了吗?注意:增容这个操作本身就有一定的程序性能的消耗,若频繁增容,会导致程序效率低下。
增容分为两种:
原地增容:原地空间足够,直接在此处增容
异地增容:原地空间不够,重新找一个内存足够的地址,将数据拷贝到新地址,再销毁旧地址。
c
//原空间大小不是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++;
}
c
//原空间大小是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++;
}
为了检测是否插入数据,可以将其打印出来
无论是头插还是尾插,都需要判断空间大小和有效数据是否相等,是否需要增容,我们可以将这部分内容分装,之后直接调用即可,不用重新写。
- 头插
头插需要将所有的数据后移一位。先将最后一个数据(下标是size-1)移到size处
删除数据(尾删SLPopBack和头删SLPopFront)
顺序表为空,不可删除。
顺序表有数据,将最后一个数据=0,再将size--即可。
尾删
头删
c
//头删
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)
c
void SLInsert(SL* ps, SLDatatype x, int pos);
pos的范围是
pos>=0&&pos<=size;
为什么可以=0和=size呢?
当pos=0时,叫做头插。pos=size时,叫做尾插
在下标为pos的地方插入数据将pos及pos以后的数据全部往后移一位。从后往前移动,先将最后一个数据往后移动。
c
//指定位置插入
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++;
}
c
//删除指定位置的数据
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。