一、线性表
1)线性表基础
1、线性表定义
线性表,全名线性存储结构,理解的话可以理解成把所有的数据用一根线串起来,再存储到物理空间中
线性表在物理空间中可以有两种存储方式:
注:
第一种数据集中存放的存储方式人首先会想到的,而第二种数据分散存放的存储方式并不容易取想,因为这样潜意识里面会觉得很难形成有序的线性表,但实际上数据存储的成功与否,取决于是否能把数据完整的复原成原来的样子,如果想成一条线从线头扯起,会发现数据的位置并没发生变化。
所以两种存储方式都是正确的
也就是说,将一对一关系的数据线性的存储在存储空间中,这种存储结构就被称为”线性存储结构(简称线性表)“
其次线性表种存储的数据的数据类型必须相同
2、顺序存储结构和链式存储结构
观察数据集中存放的图来说,可以看出来,对于数据一条线连接的逻辑关系并不是十分重要的,相对而言来说,前后顺序关系更加重要,而对于第二图来说,前后顺序关系几乎没有什么必要存在的参考,而一条线连接的关系占连接元素的更大比重,依此而言
线性表可以细分为两类:
- 图一:将数据依次存储在可连续的整块物理空间中,这种存储结构称为顺序存储结构(简称顺序表)
- 图二:将数据分散的存储在物理空间中,而用一根线保持他们之间的逻辑关系,这种存储结构就称为链式存储结构(简称链表)
3、前驱和后继
数据结构种每个元素都被称为数据元素(简称元素)
- 某一元素的左邻元素称为直接前驱,所有左侧元素称为前驱元素
- 某一元素的右邻元素称为直接后继,所有右侧元素称为后继元素
2)顺序表
1、顺序表的定义
线性表是存储一对一对应关系的数据,顺序表则是”将具有一对一逻辑关系的数据按照次序连续存储到一整块的物理空间上“
原因:
- 顺序表在存储数据的时候会提前申请一块足够大小的物理空间,然后将数据存储起来,从而达到存储数据之间不留一丝缝隙
- 顺序表的存储数据和数组非常接近,其实顺序表存储数据使用的就是数组
2、顺序表的初始化
使用顺序表进行存储数据的时候一般除了需要申请足够大小的物理空间,需要记录两个数据
- 顺序表申请的存储容量
- 顺序表的长度,也就是表中存储数据元素的个数
注:一般的情况下,顺序表存储容量的大小要大于表中存储数据的大小
自定义顺序表:
#define Size 5
typedef struct Table{
int* head; //声明一个名为head但长度不确定数组(动态数组)
int size; // 记录当前顺序表的长度
int length; // 记录顺序表分配的存储容量
}table;
table initTable(){
//定义一个未初始化的顺序表
table t;
t.head = (int*)malloc(Size*sizeof(int)); //构造一个空的顺序表并动态申请存储空间(5个int类型的空间)
if (!t.head){ //如果申请失败
printf("初始化失败!");
exit(0);
}
t.length = 0; //空表的长度初始化为0
t.size = Size; //空表的初始化大小为Size
return t;
}
分析:
对于这个代码首先是创建一个顺序表的结构体里面有三个元素
- 一个动态数组
- 一个用于存储顺序表长度的size
- 一个用于存储分配的存储容量的length
这就简单的定义了一个顺序表类型
然后将顺序表类型的封装成一个初始化方法,返回一个已经初始化完成的顺序表(这样可以提高代码的可用性,也更加美观)
然后在初始化的方法种创建一个空的数组并动态申请一块连续的存储空间
注:顺序表初始化过程中,要对物理空间的申请进行判断,对申请失败的情况进行判断和处理。
然后为空表的长度赋值,空表申请的空间大小赋值,用于记录
最后返回一个table
这样就样一个相对完整的初始化方式就可以完成
测试一下:
#include <stdio.h>
#include <malloc.h>
#define Size 5
typedef struct Table{
int* head; //声明一个名为head但长度不确定数组(动态数组)
int size; // 记录当前顺序表的长度
int length; // 记录顺序表分配的存储容量
}table;
void displayTable(table t ){
for (int i = 0; i < Size; i++) {
printf("%d \n",t.head[i]);
}
}
table initTable(){
//定义一个未初始化的顺序表
table t;
t.head = (int*)malloc(Size*sizeof(int)); //构造一个空的顺序表并动态申请存储空间(5个int类型的空间)
if (!t.head){ //如果申请失败
printf("初始化失败!");
exit(0);
}
t.length = 0; //空表的长度初始化为0
t.size = Size; //空表的初始化长度为Size
return t;
}
int main() {
table t = initTable(); //定义一个空的顺序表
t.length = 0;
for (int i = 0; i < Size; i++) {
t.head[i] = i+1;
t.length++;
}
displayTable(t);
}
由此一个顺序表就初始化完成并且可以使用了
3、顺序表的基本操作
a、向顺序表种插入元素
分析:顺序表是一块连续的存储空间,那么向它的其中插入元素应该有以下几种情况:
- 向中间插入一个元素
- 向结尾增加一个元素
- 向顺序表的开头插入一个元素
使用同一种方式去解决(通过遍历,找到元素中需要插入的位置然后:)
- 将插入元素以及后续的元素整体向后移动一个位置
- 将元素放到腾出来的位置上
分析:
首先向顺序表里面插入元素可能会遇到这几种问题:
- 存放顺序表的空间大小不够,这种情况如何解决
- 向某一索引插入元素会把元素覆盖,那么只能遍历顺序表,然后让数据后移
- 插入的索引有问题,大于长度或者等等一系列的问题
解决方案:
- 第一个问题,重新申请内存空间
- 第二个问题只能这样做
- 第三个问题,条件判断
实现代码:
table addForTable(table t,int index,int newElement){
if (index > t.length && index < 0 ){
/**
* 这里值得思考一下:index>t.length为什么这样写
* 如果index < t.length 那么在这里就是向数据中插入的情况
* 如果index == t.length 那么在这里就是向数据尾插入的情况
* 如果index > t.length 就属于未知的情况了,因为数组动态申请的大小是优先的,所以不可以
* index<0 的情况就是 这个索引小于0,C语言不是python所以这里肯定不行
* 如果索引不对就把原来的table返回去
*/
printf("Error index");
return t;
}
//当length 和 size一样就说明当前的数组长度已满(注意length 记录一定要实时更新)
if (t.length == t.size){
//所以动态申请一块新的区域给数组
t.head = (int*)realloc(t.head,(t.size+1)*sizeof(int ));
if (!t.head){
//这里要对没申请下来的情况做出反应,如果没申请下来就输出有错误
printf("realloc false\n");
return t;
}
//这里需要更新大小的数据
t.size+=1;
}
//然后就需要遍历数组最后把元素插进去
for (int i = t.length-1; i >index-1 ;i--) {
t.head[i+1] = t.head[i];
}
t.head[index] = newElement;
return t;
}
思考:
这串代码其实有个问题,t.length 和 t.size 虽然都是table 中,但是传入时还是只传入了值,所以这里只是值传递,难受,如何解决
realloc函数重新对原来的动态数组进行内存分配
4、顺序表删除元素
实现比较简单,找到目标元素,然后将后续的所有元素前移一个位置即可(原因是后续元素前移一个位置会直接把目标元素覆盖掉,可以间接的达到删除元素的目的)
示例:
void deleteTableElem(table* t,int index){
if (index > t->length|index < 1){
printf("Error index");
return;
}
for (int i = index; i < t->length; i++) {
t->head[i] = t->head[i+1];
}
t->length--;
}
分析:刚才的问题在这里得到了有效的解决,传入的时候就传入一个table类型的指针,根据指针去修改值的时候,就能同步更新了
这里的思考比较有意思的是:
其实如果索引是2 那么索引为4的最后那个数其实没有删除,可以想象一下,虽然每一位都向前运动了一位但是实际上的产生的效果是最后一位并没有删除而是保留了下来。但是为什么就能当作是已经删除了这个数了呢?
因为即使确实的把这个数删除掉,那个位置留下空,也会默认的赋值给这个位置一个随机数,那么随机数和原有数其实之间并没有什么区别,所以这里的小心机就是最后把length – 了,将length减小了,在遍历和其他操作的时候依赖的是length,而不是调用sizeof函数,当然这里调用sizeof函数也同样不行,这里就更加的动态了。
5、顺序表查找元素
看到这简单的方法就是遍历,当然为了效率也可以用二分法balabala的一系列快速的数学方法
6、顺序更改元素
这个也没啥说的,直接传入索引定位元素,覆盖元素值