第一章 线性表
定义:由n个(n >= 0)个同一类型的数据元素构成的有限序列的线性结构
按线性关系存放数据的表,表的地址空间可以连续(顺序表,底层为数组),也可以不连续(链表,为节点链接而成,每个节点都能找到下一个节点的位置,仿佛有一条链将所有节点串起来)
一、顺序表
顺序表, 字面意义就是有顺序的表,可以连续存储数据,本质上为数组
1.1 逻辑结构
逻辑结构采用图来表示 L为指向SeqList的指针 SeqList中的array指向数据的指针
1.2 存储结构
存储结构 在计算机中定义该结构 利用代码表示
一维数组在内存中占用的存储空间就是一组连续的存储区域,数组容量需足够大
将底层的数组封装为结构体,结构成员有capacity(表的容量)和length(当前表长)
//为类型取别名 方便更改类型
typedef int ElementType;
typedef struct{ //省略结构名
ElementType *array; //存放数据的指针
int length; //已有数据个数
int capacity; //容量
}SeqList;
讲完逻辑结构和存储结构,接下来就该说说顺序表结构的操作了
2.1 创建表
定义createSeqList()函数 实现创建表的功能 并返回指向表结构的指针
SeqList* createList(int capacity)
{
SeqList *L = (SeqList*)malloc(sizeof(SeqList)); //动态分配
L->length = 0;
L->capacity = capacity;
L->array = (ElementType*)malloc(capacity * sizeof(capacity));
return L;
}
使用动态分配内存的方式,先从堆区分配一块内存用于存放封装表的结构,再将L指针强制转换成SeqList结构类型,即让L指针指向表结构。同理,对结构中封装的表(数组)也是用动态分配内存的方法 sizeof(ElementType)为类型大小,乘上capacity意为分配capacity个大小为ElementType的空间,array指针指向这个空间(本质上数组也为指针)
2.2 判空
判断顺序表是否为空 若为空 返回 1 否则返回0
int isEmpty(SeqList *L)
{
return L->length == 0;
}
很简短 只有一行代码 也可以使用if-else语句
2.3 输出
输出顺序表中的所有元素 (注意:空顺序表没有输出)
void printList(SeqList *L)
{
if (!L->length) return;
for (int i = 0; i < L->length; i ++ ) printf("%d ", L->array[i]);
puts("");
}
2.4 长度
返回顺序表的长度
int getLength(SeqList *L)
{
return L->length;
}
2.5 插入
在顺序表第i个位置插入元素 插入成功返回1 否则返回0
int insertList(SeqList *L, int i, ElementType x)
{
//顺序表已满
if (L->length == L->capacity) return 0;
//插入位置不合法 (从第一个位置开始插入)
if (i < 1 || i > L->length + 1) return 0;
for (int j = L->length; j > i - 1; j -- )
L->array[j] = L->array[j - 1];
L->array[i - 1] = x;
//插入数据后 表长加一
L->length ++ ;
return 1;
}
2.6 查找
查找顺序表中值为x的元素,并返回其在顺序表中首次出现的位置 若不存在 返回-1
(第一个元素在第一个位置,但其所在下标为0)
int find(SeqList *L, ElementType x)
{
for (int i = 0; i < L->length; i ++ )
if (L->array[i] == x) return i + 1;
return -1;
}
2.7 获取数据
获取顺序表中第i个位置的数据 并让p指向它 获取成功返回-1,否则返回0
int getElement(SeqList *L, int i, ElementType *p)
{
if (L->length == 0) return 0;
if (i < 1 || i > L->length) return 0;
*p = L->array[i - 1];
return 1;
}
2.8 删除数据
删除顺序表中的第i个元素的数据 并让p指向被删除的数据
int delElement(SeqList *L, int i, ElementType *p)
{
if (L->length == 0) return 0;
if (i < 1 || i > L->length) return 0;
*p = L->array[i - 1];
for (int j = i - 1; j < L->length - 1; j ++ )
L->array[j] = L->array[j + 1];
L->length -- ;
return 1;
}
删除的代码和插入的很像 注意判断删除位置是否合法与插入数据不太一样
2.9 删除重复数据
三重循环版本
void delRepeatElement(SeqList *L)
{
for (int i = 0; i < L->length - 1; i ++ )
for (int j = i + 1; j < L->length; j ++ )
{
if (L->array[i] == L->array[j])
{
for (int k = j; k < L->length - 1; k ++ ) //注意覆盖元素的下标:从j开始
L->array[k] = L->array[k + 1];
L->length -- ;
j -- ;
}
}
}
2.10 有序合并
按从小到大的顺序合并顺序表(归并排序中的合并操作!)
void mergeList(SeqList *LA, SeqList *LB, SeqList *LC)
{
int i = 0, j = 0, k = 0;
while (i < LA->length && j < LB->length)
{
if (LA->array[i] <= LB->array[j])
LC->array[k ++ ] = LA->array[i ++ ];
else
LC->array[k ++ ] = LB->array[j ++ ];
}
while (i < LA->length)
LC->array[k ++ ] = LA->array[i ++ ];
while (j < LB->length)
LC->array[k ++ ] = LB->array[j ++ ];
LC->length = k;
}
注意下标和表的对应问题 i对应LA表, j对应LB表, k对应LC表
当LA和LB都不为空时,依次比较两表中的元素,将小的元素存放到LC中
后面两个while循环只会执行其中一个,当LA或LB中还有元素时将其存入LC中
LC的长度即为下标k(k ++ 在后)
2.11 清除数据
清除数据 顺序表空间仍然保留
void clearList(SeqList *L)
{
L->length = 0;
}
直接将L->length置为0,不释放顺序表空间
2.12 销毁
销毁整个顺序表(释放掉动态分配的内存空间)
void destroyList(SeqList *L)
{
free(L->array);
free(L);
}
注意在释放内存的时候应先释放最底层的内存,若先释放L,L->array的内存空间仍然保留并且无法释放
3 总结
顺序表是一种线性存储结构,最底层为数组,通过封装成为结构实现顺序表。顺序表的动态分配内存可能是初学者的难点,需要对malloc函数还有计算机内存,指针等知识有所了解。在今后数据结构的学习中,许多数据结构都是用动态分配内存的方式(当然也可以不使用动态分配)许多数据结构都是基于顺序表和链表演化而成,例如栈、队列、二叉树等,打好线性表的基础对今后的学习很有帮助。
创作不易!希望大家多多支持!保持更新到图论!