第二章 线性表
第二章:线性表#
线性表的逻辑结构#
- 定义:线性表是具有相同数据类型的n(n≥0)个数据元素的有限序列。其中n为表长。当n=0时 线性表是一个空表
- 特点:线性表中第一个元素称为表头元素;最后一个元素称为表尾元素。
除第一个元素外,每个元素有且仅有一个直接前驱。
除最后一个元素外,每个元素有且仅有一个直接后继。
线性表的顺序存储结构#
-
线性表的顺序存储又称为顺序表。
它是用一组地址连续的存储单元(比如C语言里面的数组),依次存储线性表中的数据元素,从而使得逻辑上相邻的两个元素在物理位置上也相邻。 -
建立顺序表的三个属性:
- 存储空间的起始位置(数组名data)
- 顺序表最大存储容量(MaxSize)
- 顺序表当前的长度(length)
- 一维数组静态分配
typedef struct{ ElemType data[MaxSize]; int length; }Sqlist;
- 一维数组动态分配,存储数组的空间是在程序执行过程中通过动态存储分配语句分配
typedef struct{ ElemType *data; // 动态分配数组的指针 int length, MaxSize; }Sqlist; L.data = (ElemType *)malloc(sizeof(ElemType) * initSize);
-
总结:
- 顺序表最主要的特点是随机访问(C语言中基于数组),即通过首地址和元素序号可以在的时间内找到指定的元素。
- 顺序表的存储密度高,每个结点只存储数据元素。无需给表中元素花费空间建立它们之间的逻辑关系(因为物理位置相邻特性决定)
- 顺序表逻辑上相邻的元素物理上也相邻,所以插入和删除操作需要移动大量元素。
顺序表的操作#
1.插入
-
代码
Sqlist L; bool insertElem(Sqlist &L, int p, int e) // 位置p插入新元素e { if(p < 0 || p > L.length || L.length >= maxSize) return false; for (int i = L.length -1; i >= p; i -- ) L.data[i + 1] = L.data[i]; // 从后向前到第i个位置,分别将这些元素都向后移动一位 L.data[p] = e; // 将该元素插入位置i L.length ++ ; // 并修改表长 return true; }
-
分析:
- 最好情况:在表尾插入(即i=n+1),元素后移语句将不执行,时间复杂度为O(1)。
- 最坏情况:在表头插入(即i=1),元素后移语句将执行n次,时间复杂度为O(n)。
- 平均情况:在长度为n的线性表中插入一个结点时所需移动结点的平均次数为
2.删除
-
算法思路:
- 1.判断i的值是否正确
- 2.取删除的元素
- 3.将被删元素后面的所有元素都依次向前移动一位
- 4.修改表长
-
代码
Sqlist L; // 位置p元素删掉,并将被删除元素赋值给e bool deleteElem(Sqlist &L, int p, int e) { if(p < 0 || p > L.length - 1) return false; e = L.data[i]; for (int i = L.length -1; i >= p; i -- ) L.data[i] = L.data[i + 1]; L.length -- ; // 并修改表长 return true; }
-
分析
- 最好情况:删除表尾元素(即i=n),无须移动元素,时间复杂度为O(1)。
- 最坏情况:删除表头元素(即i=1),需要移动除第一个元素外的所有元素,时间复杂度为O(n)。
- 平均情况:在长度为n的线性表中删除一个结点时所需移动结点的平均次数为
线性表的链式存储结构#
-
线性表的链式存储是指通过一组任意的存储单元来存储线性表中的数据元素。
typedef struct LNode{ ElemType data; struct LNode *next; }LNode, *LinkList;
-
头结点和头指针的区别?
- 头指针:头指针始终指向链表的第一个结点(无论是否有头结点)(链表必要元素)
- 头结点:在单链表第一个元素结点前附加一个空结点(结点内通常不存储信息;数据域:可以不记录信息,也可以记录表长等信息;指针域:指向单链表的第一个元素结点)
-
为什么要设置头结点?
- 1.处理操作起来方便 例如:对在第一元素结点前插入结点和删除第一结点起操作与其它结点的操作就统一了
- 2.无论链表是否为空,其头指针是指向头结点的非空指针,因此空表和非空表的处理也就统一了。
无头结点 有头节点 空表(判断表空) p == null
p->next == null
非空表(判断表尾) p->next == null
p->next == null
单链表的操作#
- 头插法建立单链表:
- 读入数据的顺序与链表中元素的顺序相反
- 建立新的结点分配内存空间,将新结点插入到当前链表的表头(即头结点之后)
s -> data = a[i];
-----------------------------
s -> next = c -> next;
c -> next = s;
- 代码
void createlistFront(LNode *&C, int a[], int n)
{
LNode *s;
c = (LNode*) malloc(sizeof(LNode));
c -> next = NULL;
for (int i = 0; i < n; i ++ )
{
s = (LNode*) malloc(sizeof(LNode));
s -> data = a[i];
// 头插法的关键
c -> next = s -> next;
c -> next = s;
}
}
- 尾插法建立单链表:
- 读入数据的顺序与链表中元素的顺序相同
- 建立新的结点分配内存空间,将新结点插入到当前链表的表尾(需要增设表为指针r,使其始终指向表尾结点)
s -> data = a[i];
-----------------------------
r -> next = s;
r = r -> next;
- 代码
void createlistRear(LNode *&C, int a[], int n)
{
LNode *s, *r; // s用来指向新申请的结点,r始终指向c的终端结点
c = (LNode*) malloc(sizeof(LNode));
c -> next = NULL;
r = c; // r指向头结点,因为词是头节点就是终端结点
for (int i = 0; i < n; i ++ )
{
s = (LNode*) malloc(sizeof(LNode));
s -> data = a[i];
// 头插法的关键
r -> next = s;
r = r -> next;
}
r -> next = NULL; // 数组a中所有的元素都已经装入链表c中,c的终端结点的指针域置为 // null
}
- 按序号查找结点
- 在单链表中从第一个结点出发,顺指针next域逐个往下搜索,直到找到第i个结点为止,否则返回最后一个结点指针域NULL。
// 取出单链表L(带头结点)中第i个位置的结点指针
LNode *getElem(LinkList L,int i)
{
int j = 1; // 用来计数
LNode *p = L -> next;
if(i == 0) return L; // i为0,返回头结点
if(i < 1) return NULL;
while(p != NULL && j < i)
{
p = p -> next;
j ++ ;
}
return p;
}
- 按值查找结点
- 从单链表第一个结点开始,由前往后依次比较表中各结点数据域的值,若某结点数据域的值等于给定值e,则返回该结点的指针;若整个单链表中没有这样的结点,则返回NULL。
LNode *locateElem(LinkList L, ElemType e)
{
LNode *p = L -> next;
while(p != NULL && p -> data != e)
p = p -> next;
return p;
}
- 插入
- 插入操作是将值为x的新结点插入到单链表的第i个位置上。先检查插入位置的合法性,然后找到待插入位置的前驱结点,即第i−1个结点,再在其后插入新结点。
// 在L的第i个位置上插入值为x的结点
void insertNode(LinkList L, int i, ElemType x)
{
if (i < 1 || i > L.length ) return ;
// 创建一个新结点并赋值
LNode *s = (LNode*) malloc(sizeof(LNode));
s -> data = x;
// 找到待插入位置的前驱结点,即第i−1个结点
LNode p = getElem(L, i - 1);
// 再在其后插入新结点
s - > next = p -> next;
p -> next = s;
}
- 删除
- 删除操作是将单链表的第i个结点删除。先检查删除位置的合法性,然后查找表中第i−1个结点,即被删结点的前驱结点,再将其删除。
// 删除第i个节点,并将数据域通过e返回,成功true
bool deleteNode(LinkList L, int i, ElemType &e)
{
if (i < 1 || i > L.length ) return ;
LinkNode *p, *q;
p = getElem(L, i - 1);
q = p -> next;
p -> next = q -> next;
e = q -> data;
free(q); // 调用free函数释放q所指的结点的内存空间
return true;
}
双链表#
- 双链表是的可以通过某节点访问它的直接前驱和后继
- 插入:
s -> next = p -> next;
s -> prior = p;
p -> next -> prior = s;
p -> next = s;
- 删除:
p -> next = q -> next;
q -> next -> prior = p;
free(q);
循环链表&&静态链表#
循环链表
- 循环单链表:循环单链表和单链表的区别在于,表中最后一个结点的指针不是NULL,而改为指向头结点,从而整个链表形成一个环
- 循环双链表:类比循环单链表,循环双链表链表区别于双链表就是首尾结点构成环
- 当循环双链表为空表时,其头结点的prior域和next域都等于Head。
- 注:尾结点的next指向的是头节点
静态链表
- 静态链表:静态链表是用数组来描述线性表的链式存储结构。
- 数组第一个元素不存储数据,它的指针域存储第一个元素所在的数组下标。链表最后一个元素的指针域值为-1。
- 插删不需要移动元素,只需要修改指针
typedef struct
{
ElemType data;
int next;
}SLinkList[MaxSize];
顺序表、链表总结#
顺序表 | 链表 | |
---|---|---|
存取方式 | 顺序存取(随机访问) | 顺序存取 |
逻辑\物理结构 | 顺序存储:逻辑相邻,物理相邻 | 链式存储:逻辑相邻,物理不一定相邻 |
查找(按值查找) | / | |
(按序查找) | ||
插入删除 | ||
存储密度 | 存储密度大 | 存储密度大(需额外存储信息) |
作者:stdxiaozhang
出处:https://www.cnblogs.com/stdxiaozhang/p/13377499.html
版权:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议进行许可。
吼吼,如果对你有帮助的话,可以点个赞呀!
标签:
数据结构
U Can Buy me a cup of 蜜雪冰城 ☕.
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?