talking:这里会先介绍线性表,一种最简单的储存方式 。
一、线性表
1. 定义:零个或多个元素的有限序列;
一对一的关系构成线性表;
即有头有尾,前后相接,只有一条链;
在较复杂的线性表中,一个数据元素通常由多个数据项组成,这里的线性表一般建立在结构体的基础上
2. 重要属性:
- 直接前继元素
- 直接后继元素
- 线性表的长度n:线性表元素的个数
- 空表(n=0)
3.储存方式:
-
顺序储存结构:数组
-
链式储存结构:链表
这两种都只的是在内存中的物理存储方式;
4.抽象数据类型
- 数据类型:性质相同的值的集合以及定义在这个集合上的操作的总称;
二、数组(线性表的顺序储存结构)
此处强调数据结构的线性表,故只介绍一维数组。
1.概述:
-
定义:用一段地址连续的存储单元依次存储线性表的数据元素;
-
特点:在内存中占据连续的储存空间;在插入等算法中需移动大量数据,效率较低
-
三个必要属性:
- 储存空间的起始位置
- 线性表的最大存储容量
- 线性表的当前长度
-
线性表长度<=数组长度
-
存取时间性能为O(1),为随机存储结构
2.算法:创建、读取、插入、删除
记得对数组是否为空/满进行检验(要不检验空要不检验满,依该函数实际而定,例如:读取,删除时检验其是否为空,插入时检验其是否为满)
可以注意算法对数据元素的位置处在表尾的处理
以插入函数为例:
//初始化线性表
typedef struct{
int data[maxsize]; //假装最多能放下maxsize个元素(数组长度)
int length; //线性表长度,表示当前已经使用的长度
}list;
//然后假装线性表里面存放了数据
//插入函数:现将e插入到第i个元素的位置
int listInsert(list *l,int i,int e){ //l为数组首地址
int j;
if(l->length==maxsize){ //注意,这个地方不能直接写length,此时表示线性表已满时
return 0;
}
if(i>l->length-1 || i<1){ //i不在范围内时
return 0;
}
if(i<l->length-1){ //i不在末尾时
for(j=l->length-1;j>=i;j--){
l->data[j+1]=l->data[j];
}
}
l->data[i]=e; //把这个值赋值给第i个元素
l->length++;
return 1;
}
可以看到该算法重复度很少,效率较高。
三、链表(线性表的链式储存结构)
1.概述:
-
定义:在内存中的储存位置并不连续,靠指针的作用将其一个一个串起来,形成一个链式结构;
-
特点:在内存中不占据连续的储存空间;整体效率较高,插入等算法中只需要简单的修改指针域的值;
-
属性:
- 指针域:存放指针的区域,可以有一个两个或者多个
- 数据域:存放数据的区域
- 结点:指针域和数据域共同构成一个结点,很多结点相连构成一个链表
- 头结点:在第一个结点之前,其指针域里的指针指向第一个结点,对于链表来说它不是必须存在的。
- 头指针:头指针不是头结点里面的指针!如果头结点存在,则指向头结点;如果没有头结点,则指向第一个结点,对于链表来说它必须存在。
2.单链表
-
简单来说,其特点为:尾结点指向NULL或^,指针域中只有指向下一个结点的指针;
-
算法:读取、插入、删除、整表创建、整表删除;
需要注意的地方:
- 单链表的插入时,在所有检验语句之后,即判断确定可以插入时再为新结点申请内存空间(使用
malloc()
函数); - 单链表的删除中效率更高的方法是以要删除的前一个结点为主,用
p->next->next
指向要删除的后一个结点(假设p是要删除的结点); - 删除时注意删除语句的顺序,记得用
free()
让系统收回一个结点,释放内存; - 整表创建包括头插法、尾插法,使用
malloc()
函数; - 整表删除时记得对已释放的结点的指针域的替代与保存,以便下一个结点的删除
- 单链表的插入时,在所有检验语句之后,即判断确定可以插入时再为新结点申请内存空间(使用
//单链表的整表删除,初始条件:线性表l已存在且有头节点;
int Clearlist (linklist *l){ //l为头指针;
linklist p,q;
p=(*l)->next; //p指向第一个结点
while(p!=NULL){
q=p->next; //用q暂时存储下一个结点,防止在释放p的空间后找不到下一个结点
free(p);
p=q;
}
(*l)->next=NULL;
return 1;
}
- 在实际编程时,每个块都要记得对链表是否为空/满进行检验(要不检验空要不检验满,依该函数实际而定,例如:读取,删除时检验其是否为空,插入时检验其是否为满)
- 注意检验语句的先后顺序,尽量使其效率最高,时间复杂度最低。
- 要经常检查头指针是否为NULL,避免其初始化失败或者传值失败。
这里有自己做的一个 家庭财务管理系统 的实例,当时没有学数据结构,代码的层次等也不是很清晰,没有使用free()语句,但是对于链表的实际应用也有参考意义。
地址如下:https://blog.csdn.net/qq_44263261/article/details/95222343
3.静态链表(数组和链表的转化)
-
静态链表实际是用数组来描述链表,也被称为游标实现法
-
也有其类似于单链表的插入、删除等算法,实际应用中基本不用,但可以以之拓展链表的应用,更好的理解链表的算法。
-
其算法最关键的部分在于
- 将数组每个元素像结点那样分成两个部分,储存数据和下一个元素的地址;
- 将已用的部分和未用的部分分别当作两个链表;
4.循环链表
- 最后一个结点的指针域存放的是第一个结点的地址,相当于将其首尾相连
- 小技巧:如果第一个结点和尾结点的地址都需要知道,可以将头指针指向尾结点,可以大大增加效率;
5.双向链表
- 指针域有两个指针,分别指向前一个结点和后一个结点
- 用空间来换取时间