数据结构与算法【LeetCode-Offer】:2.1链表—链表理论基础【数据结构三要素,线性表的定义,顺序表,链表】

原文链接

1. 数据结构三要素

在这里插入图片描述
1)逻辑结构 指的是数据间的逻辑关系,与数据的存储无关,独立于计算机之外。它又分为线性结构和非线性结构

  • 线性结构:线性表,栈,队列,串,数组和广义表
  • 非线性结构:树,图,集合

2)存储结构 是逻辑结构的存储映像,就是数据间的关系在计算机中的表现形式。也称为物理结构。它又分为 4 类:顺序存储 ,链式存储,索引存储和散列存储

  • 顺序存储:把逻辑上相邻的元素存储在物理位置也相邻的存储单元里
  • 链式存储:不要求物理位置的相邻,借助指示元素存储地址的指针表示元素之间的逻辑关系
  • 索引存储:在存储元素信息的同时,添加附加的索引表,通过索引对节点进行操作
  • 散列存储:也称 Hash 存储,根据结点的关键字通过散列函数计算出结点的存储地址

相同的逻辑结构在计算机里可以用不同的存储结构实现。比如逻辑结构中的线性结构,可以用数组(顺序存储)或单向链表(链接存储)来实现。

3)数据运算: 施加在数据上的运算(包括定义与实现)。运算的定义是针对逻辑结构,运算的实现是针对物理结构

2. 线性表的定义

在这里插入图片描述
线性表定义:

  • 具有相同数据类型的 n 个数据元素的有限序列
  • 线性表是一种逻辑结构,表示元素之间一对一的逻辑关系

使用线性表存储数据的方式可以这样理解,把所有数据用一根线儿串起来,再存储到物理空间中

下图中,左侧是“串”起来的数据,右侧是空闲的物理空间。把这 “一串儿” 数据放置到物理空间,我们可以选择以下两种方式:
在这里插入图片描述
将具有“一对一”关系的数据“线性”地存储到物理空间中,这种存储结构就称为线性存储结构:

  • 顺序表(如上图左边):将数据依次存储在连续的整块物理空间中
  • 链表(如上图右边):数据分散的存储在物理空间中,通过一根线保存着它们之间的逻辑关系
    • 单链表
    • 双链表
    • 循环单链表
    • 循环双链表

下面详细讲解这两种存储结构 👇

3. 顺序表


① 顺序表定义

线性表的顺序存储。逻辑上相邻的两个元素在物理位置上也相邻
在这里插入图片描述
数组就是顺序表,下标一般从 0 开始:
在这里插入图片描述
顺序表的特点

  • 随机访问(通过首地址和元素序号可在时间 O(1) 内找到元素)
  • 插入和删除需要移动大量元素
  • 存储密度高,每个结点只存储数据元素

② 顺序表基本操作

Ⅰ 插入

在数组 a 的第 i 个位置 (下标 i-1) 插入元素 e

// 第i个元素及其之后的元素后移
for(int j = a.length; j >= i; j--){
	a[j] = a[j - 1];
}
a[i - 1] = e;
length++;

在这里插入图片描述

  • 最好情况:在表尾插入,时间复杂度 O(1)
  • 最坏情况:在表头插入,时间复杂度 O(n)
  • 平均情况:时间复杂度 O(n)

Ⅱ 删除

删除数组 a 的第 i 个位置的元素

// 从第 i 个位置元素前移
for(int j = i; j < a.length; j++){
	a[j - 1] = a[j];
}
length--;

在这里插入图片描述

  • 最好情况:删除表尾元素,时间复杂度 O(1)
  • 最坏情况:删除表头元素,时间复杂度 O(n)
  • 平均情况:时间复杂度 O(n)

Ⅲ 按值查找

查找数组 a 中值为 e 的元素的下标

for(int i = 0; i < a.length; i++){
	if(a[i] == e){
		return i;
	}
}
  • 最好情况:查找元素在表头,时间复杂度 O(1)
  • 最坏情况:查找元素在表尾,时间复杂度 O(n)
  • 平均情况:时间复杂度 O(n)

4. 链表


① 链表的定义与结构

线性表的链式存储。逻辑上相邻的两个元素在物理位置不一定也相邻
例如,使用链表存储 {1,2,3},数据的物理存储状态如下图所示:
在这里插入图片描述
可以看到,上图根本无法体现出各数据之间的逻辑关系。对此,链表的解决方案是,每个数据元素在存储时都配备一个指针,用于指向自己的直接后继元素。如下图所示:
在这里插入图片描述
当然,指针可以指向自己的直接后继元素,也可以指向自己的直接前驱元素。为此,链表可分为:

  • 单链表(指针指向自己的直接后继元素)
  • 双链表(指针指向自己的直接后继元素和直接前驱元素)
  • 循环单链表(指针指向自己的直接后继元素,表尾节点的指针指向头节点)
  • 循环双链表(指针指向自己的直接后继元素和直接前驱元素,表尾节点的指针指向头节点)

通过以上大家应该也知道了,链表中每个数据的存储都由以下两部分组成:

  • 数据域:数据元素本身
  • 指针域:指向该元素直接后继/前驱/…元素的指针

在这里插入图片描述
上图所示的结构在链表中称为节点。也就是说,链表实际存储的是一个一个的节点,真正的数据元素包含在这些节点中,举个单链表的例子:

在这里插入图片描述
🚨 当然,上所示的链表结构并不完整。一个完整的链表需要由以下几部分构成:

  • 头指针:一个普通的指针,它的特点是永远指向链表第一个节点的位置。很明显,头指针用于指明链表的位置,便于后期找到链表并使用表中的数据
  • 节点:链表中的节点又分为头节点、首元节点和其他节点
    • 头节点:其实就是一个不存任何数据的空节点,通常作为链表的第一个节点。对于链表来说,头节点不是必须的,它的作用只是为了方便解决某些实际问题
    • 首元节点:由于头节点(也就是空节点)的缘故,链表中称第一个存有数据的节点为首元节点。首元节点只是对链表中第一个存有数据节点的一个称谓,用于和头节点进行区分,没有实际意义
    • 其他节点:链表中其他的节点

因此,一个存储 {1,2,3} 的完整单链表结构如图所示:
在这里插入图片描述
💡 引入头节点的优点:

  • 链表的首元节点的操作与其他位置的元素操作一样,无须进行特殊处理
  • 无论链表是否为空,其头指针都是指向头节点的非空指针,空表和非空表的处理得到了统一

② 单链表

Ⅰ 定义

单链表就是指针指向自己的直接后继元素的链表
在这里插入图片描述
以 Java 为例,我们自定义一个单链表结构:

public class Node{
	private T t;
	private Node next;
	......
}

单链表可以解决顺序表需要大量连续存储空间的缺点,但单链表附加指针域,也存在浪费存储空间的缺点

单链表是非随机存储的存储结构:即不能直接找到表中某个特点的结点。需要从头开始遍历。

单链表访问前驱的时间复杂度为 O(n),访问后继 O(1)

Ⅱ 基本操作

头插法

在头节点 L 的后面插入节点 s,即 s 节点成为当前链表的首元节点

头插法的读入顺序和生成顺序相反
在这里插入图片描述

s.next = L.next;
L.next = s;
尾插法

在链表最后一个节点 r 的后面插入节点 s,即 s 节点成为当前链表的最后一个节点

尾插法的读入顺序和生成顺序相同

在这里插入图片描述

r.next = s;
r = s; 	//s 节点成为链表的最后一个节点
插入节点

p 节点之后插入 s 节点
在这里插入图片描述

s.next = p.next;
p.next = s;
删除节点

p 节点之后删除 q 节点

在这里插入图片描述

// 删除 p 之后的 q 
//1. 找到要删除的节点q的前一个节点p
//2. 记录要删除节点q的地址
q = p.next;
//3. 将p指向q的下一个节点,然后删除q节点
p.next = q.next;

还有一种方法就是把要删除的节点和它的后继节点值进行交换,然后直接删除这个后继节点就行,这样就不用找前驱节点了。

③ 双链表

Ⅰ 定义

双链表就是同时具有前驱指针和后继指针的链表
双链表访问前驱和后继结点时间复杂度都是 O(1)
在这里插入图片描述
以 Java 为例,我们自定义一个双链表结构:

public class Node{
	private T t;    
	private Node next;    
	private Node prior;    
	......
}

Ⅱ 基本操作

插入节点

p 节点之后插入 s 节点
在这里插入图片描述

s.next = p.next;
p.next.prior = s;
p.next = s;
s.prior = p;
删除节点

删除 p 节点之后的 s 节点
在这里插入图片描述

p.next = s.next;
s.next.prior = p;

③ 循环单链表

循环单链表就是表尾节点的指针指向头节点的单链表
在这里插入图片描述

④ 循环双链表

循环双链表就是表尾节点的指针指向头节点的双链表
在这里插入图片描述

5. 顺序表和链表的比较

1)存取方式

  • 顺序表可以顺序存取,也可以随机存取
  • 链表只能从表头顺序存取元素

2)逻辑结构与物理结构

  • 顺序存储时,逻辑上相邻的元素,对应的物理存储位置也相邻
  • 链式存储时,逻辑上相邻的元素,物理存储位置不一定相邻,其逻辑关系是通过指针链接来表示的

3)查找、插入和删除操作

  • 对于按值查找,顺序表无序时,两者的时间复杂度均为 O(n);顺序表有序时,可采用折半查找,此时的时间复杂度为 O(log2n)
    • 对于按序号查找,顺序表支持随机访问,时间复杂度仅为 O(1),而链表的平均时间复杂度为 O(n)。
  • 顺序表的插入、删除操作,平均需要移动半个表长的元素
    • 链表的插入、删除操作,只需要修改相关结点的指针域既可。由于链表的每个结点都有指针域,所以在存储空间上要比顺序表付出的代价大,存储密度不够大。

4)空间分配

  • 顺序存储在静态存储分配情况下,一旦存储空间装满就不能扩充,若再加入新元素,则会出现溢出,所以需要预先分配足够大的存储空间。预先分配过大,可能会导致顺序表候补大量闲置;预先分配过小,又会造成溢出。
    • 动态存储分配虽然存储空间可以扩充,但需要移动大量元素,导致操作效率降低,而且若内存中没有更大的连续存储空间,则会导致分配失败
  • 链表只在需要时申请分配,高效灵活
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

狂野小白兔

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值