链表的操作相对顺序表(数组)来说就复杂了许多。因为 PHP 确实已经为我们解决了很多数组操作上的问题,所以我们可以很方便的操作数组,也就不用为数组定义很多的逻辑操作。比如在 C 中,数组是有长度限制的,而在 PHP 中我们就不会考虑这个问题。如果是使用 C 的话,这个长度限制就是数组结构的一大劣势,而链表,不管是在 C 还是在 PHP 中,都不会受到长度问题的限制。能够限制链表的只有内存的大小。另外,链表的链式结构也能够为我们带来一种全新的不同于数组操作的体验,对某些功能算法来说,链表也更有优势。

话不多说,直接来进入今天的内容吧!

链式结构的定义

首先,在之前的关于线性表的第一篇文章中我们就说过链表的定义,在这里,我们再复习一下之前的那个关于链表的图来更清晰的理解链表的概念。

我们将图中的节点 Node 用类来表示:

/**
* 链表结构
*/
class LinkedList
{
public $data;
public $next;
}

一个链表类就看可以看做是链表中的一个节点,它包含两个内容,data 表示数据,next 表示下一个节点的指针。就像链条一样一环套一环,这就是传说中的链表结构。

生成链表及初始化操作

/**
* 生成链表
*/
function createLinkedList()
{
$list = new LinkedList();
$list->data = null;
$list->next = null;
return $list;
} /**
* 初始化链表
* @param array $data 链表中要保存的数据,这里以数组为参考
* @return LinkedList 链表数据
*/
function Init(array $data)
{
// 初始化
$list = createLinkedList();
$r = $list;
foreach ($data as $key => $value) {
$link = new LinkedList();
$link->data = $value;
$link->next = null;
$r->next = $link;
$r = $link;
}
return $list;
} $link = Init(range(1, 10)); print_r($link);
// LinkedList Object
// (
// [data] =>
// [next] => LinkedList Object
// (
// [data] => 1
// [next] => LinkedList Object
// (
// [data] => 2
// [next] => LinkedList Object
// (
// [data] => 3
// [next] => LinkedList Object
// (
// [data] => 4
// [next] => LinkedList Object
// (
// [data] => 5
// [next] => LinkedList Object
// (
// [data] => 6
// [next] => LinkedList Object
// (
// [data] => 7
// [next] => LinkedList Object
// (
// [data] => 8
// [next] => LinkedList Object
// (
// [data] => 9
// [next] => LinkedList Object
// (
// [data] => 10
// [next] =>
// ) // ) // ) // ) // ) // ) // ) // ) // ) // ) // )

在使用链表的时候,我们一般会让第一个结点不包含任何数据,仅仅是做为一个空的结点来指向第一个有数据的结点。这种结点我们可以称之为头结点,如果需要判断链表是否为空的话,只需要判断第一个结点的 next 是否为空就可以了。在上面的代码中,创建链表 createLinkedList() 函数其实就是生成了这样一个头结点。

然后,我们通过 Init() 初始化函数来构造这个链表。构造过程还是比较简单的,这里我们是固定的传递进来一个数组,按照这个数组结构来构造这个链表,当然,在实际应用中,我们可以使用任何数据来构造链表。构造过程也并不复杂,将当前结点赋值给 r 变量,然后创建一个新结点,让 r->next 等于这个新创建的节点就可以了。构造好的链表直接打印出来的结构就是注释中的形式。

遍历链表

function IteratorLinkedList(LinkedList $link)
{
while (($link = $link->next) != null) {
echo $link->data, ',';
}
echo PHP_EOL;
}

链表的遍历是不是非常像某些数据库的游标操作,或者像迭代器模式的操作一样。没错,其实游标和迭代器的结构就是链表的一种表现形式。我们不停的寻找 $next 直到没有下一个结点为止,这样就完成了一次链表的遍历。可以看出,这个过程的时间复杂度是 O(n) 。

插入、删除

/**
* 链表指定位置插入元素
* @param LinkedList $list 链表数据
* @param int $i 位置
* @param mixed $data 数据
*/
function Insert(LinkedList &$list, int $i, $data)
{
$j = 0;
$item = $list;
// 遍历链表,找指定位置的前一个位置
while ($j < $i - 1) {
$item = $item->next;
$j++;
} // 如果 item 不存在或者 $i > n+1 或者 $i < 0
if ($item == null || $j > $i - 1) {
return false;
} // 创建一个新节点
$s = new LinkedList();
$s->data = $data; // 新创建节点的下一个节点指向原 i-1 节点的下一跳节点,也就是当前的 i 节点
$s->next = $item->next;
// 将 i-1 节点的下一跳节点指向 s ,完成将 s 插入指定的 i 位置,并让原来的 i 位置元素变成 i+1 位置
$item->next = $s; return true;
} /**
* 删除链表指定位置元素
* @param LinkedList $list 链表数据
* @param int $i 位置
*/
function Delete(LinkedList &$list, int $i)
{
$j = 0;
$item = $list;
// 遍历链表,找指定位置的前一个位置
while ($j < $i - 1) {
$item = $item->next;
$j++;
}
// 如果 item 不存在或者 $i > n+1 或者 $i < 0
if ($item == null || $j > $i - 1) {
return false;
} // 使用一个临时节点保存当前节点信息,$item 是第 i-1 个节点,所以 $item->next 就是我们要找到的当前这个 i 节点
$temp = $item->next;
// 让当前节点,也就是目标节点的上一个节点, i-1 的这个节点的下一跳(原来的 i 位置的节点)变成原来 i 位置节点的下一跳 next 节点,让i位置的节点脱离链条
$item->next = $temp->next; return true;
} // 插入
Insert($link, 5, 55);
// 遍历链表
IteratorLinkedList($link); // 1,2,3,4,55,5,6,7,8,9,10, // 删除
Delete($link, 7);
// 遍历链表
IteratorLinkedList($link); // 1,2,3,4,55,5,7,8,9,10,

链表的插入和删除其实很类似,都是需要寻找到插入或删除位置的前一个元素,也就是第 i-1 这个位置的元素。然后通过对这个元素的 next 指针的操作来实现链表元素的插入删除操作。它们在遍历和位置判断这两个功能中的代码其实都是一样的,不同的是创建时要新创建一个结点,然后让这个结点的 next 指向之前 i-1 位置元素的 next ,再将 i-1 位置元素的 next 指向新创建的这个结点。而删除操作则是保存要删除这个位置 i 的结点到一个临时变量中,然后将 i-1 位置元素的 next 指向删除位置 i 的 next 。

上面的解释需要结合代码一步一步来看,当然,我们也可以结合下面的这个图来学习。插入和删除操作是链表操作的核心,也是最复杂的部分,需要多多理解掌握。

根据位置查找、根据数据查找

/**
* 根据位置查找元素位置
* @param LinkedList $list 链表数据
* @param int $i 位置
*/
function GetElem(LinkedList &$list, int $i)
{
$item = $list;
$j = 1; // 从第一个开始查找,0是头结点
while ($item && $j <= $i) {
$item = $item->next;
$j++;
} if (!$item || $j > $i + 1) {
return false;
}
return $item->data; } /**
* 根据数据查找数据元素所在位置
* @param LinkedList $list 链表数据
* @param mixed $data 数据
*/
function LocateElem(LinkedList &$list, $data)
{
$item = $list;
$j = 1; // 从第一个开始查找,0是头结点
while (($item = $item->next)!=null) {
if($item->data == $data){
return $j;
}
$j++;
} return false;
} // 获取指定位置的元素内容
var_dump(GetElem($link, 5)); // int(55) // 获取指定元素所在的位置
var_dump(LocateElem($link, 55)); // int(5)

链表的查找有两种形式,一种是给一个位置,比如要我要第五个位置的元素内容,那么就是根据指定位置查找元素的值,就像数组的下标一样。不过需要注意的是,链表的下标是从 1 开始的,因为 0 的位置是我们的头结点了。当然,我们也可以变换代码忽略掉头结点让它和数组保持一致,但这样的话,链表的特点就不明显了,所以这里的测试代码我们还是以 1 为起始。

另一种查找就是给定一个数据内容,查找它在链表的什么位置。其实这两种算法都是在遍历整个链表,本质上没有什么区别。由于链表不像数组一样有下标的能力,所以它的这些查找操作的时间复杂度都是 O(n) 。

总结

怎么样,难度上来了吧。链表的操作一下就复杂了很多吧,别急,这只是开胃菜。后面学习的内容基本上都会围绕着顺序表(数组)和链表这两种形式进行。而且我们的链表学习还没有结束,下一篇文章,我们将更深入的了解一下链表的另外几种形式:双向链表、循环链表、双向循环链表。

测试代码:

https://github.com/zhangyue0503/Data-structure-and-algorithm/blob/master/2.线性表/source/2.3%20链表的相关逻辑操作.php

参考资料:

《数据结构》第二版,严蔚敏

《数据结构》第二版,陈越

《数据结构高分笔记》2020版,天勤考研

关注公众号:【硬核项目经理】获取最新文章

添加微信/QQ好友:【xiaoyuezigonggong/149844827】免费得PHP、项目管理学习资料

知乎、公众号、抖音、头条搜索【硬核项目经理】

B站ID:482780532

【PHP数据结构】链表的相关逻辑操作的更多相关文章

  1. 【PHP数据结构】队列的相关逻辑操作

    在逻辑结构中,我们已经学习了一个非常经典的结构类型:栈.今天,我们就来学习另外一个也是非常经典的逻辑结构类型:队列.相信不少同学已经使用过 redis . rabbitmq 之类的缓存队列工具.其实, ...

  2. 【PHP数据结构】栈的相关逻辑操作

    对于逻辑结构来说,我们也是从最简单的开始.堆栈.队列,这两个词对于大部分人都不会陌生,但是,堆和栈其实是两个东西.在面试的时候千万不要被面试官绕晕了.堆是一种树结构,或者说是完全二叉树的结构.而今天, ...

  3. 【PHP数据结构】顺序表(数组)的相关逻辑操作

    在定义好了物理结构,也就是存储结构之后,我们就需要对这个存储结构进行一系列的逻辑操作.在这里,我们就从顺序表入手,因为这个结构非常简单,就是我们最常用的数组.那么针对数组,我们通常都会有哪些操作呢? ...

  4. 数据结构和算法(Golang实现)(12)常见数据结构-链表

    链表 讲数据结构就离不开讲链表.因为数据结构是用来组织数据的,如何将一个数据关联到另外一个数据呢?链表可以将数据和数据之间关联起来,从一个数据指向另外一个数据. 一.链表 定义: 链表由一个个数据节点 ...

  5. Redis数据结构—链表与字典的结构

    目录 Redis数据结构-链表与字典的结构 链表 Redis链表节点的结构 Redis链表的表示 Redis链表用在哪 字典 Redis字典结构总览 Redis字典结构分解 Redis字典的使用 Re ...

  6. Redis数据结构—链表与字典

    目录 Redis数据结构-链表与字典 链表 Redis链表节点的结构 Redis链表的表示 Redis链表用在哪 字典 Redis字典结构总览 Redis字典结构分解 哈希算法 解决键冲突 rehas ...

  7. [LeetCode] 链表反转相关题目

    暂时接触到LeetCode上与链表反转相关的题目一共有3道,在这篇博文里面总结一下.首先要讲一下我一开始思考的误区:链表的反转,不是改变节点的位置,而是改变每一个节点next指针的指向. 下面直接看看 ...

  8. Python—数据结构——链表

    数据结构——链表 一.简介 链表是一种物理存储上非连续,数据元素的逻辑顺序通过链表中的指针链接次序,实现的一种线性存储结构.由一系列节点组成的元素集合.每个节点包含两部分,数据域item和指向下一个节 ...

  9. (js描述的)数据结构[链表](4)

    (js描述的)数据结构 [链表](4) 一.基本结构 二.想比于数组,链表的一些优点 1.内存空间不是必须连续的,可以充分利用计算机的内存,事项灵活的内存动态管理. 2.链表不必再创建时就确定大小,并 ...

随机推荐

  1. 漏洞复现|Dubbo反序列化漏洞CVE-2019-17564

    01漏洞描述 - Apache Dubbo支持多种协议,官方推荐使用Dubbo协议.Apache Dubbo HTTP协议中的一个反序列化漏洞(CVE-2019-17564),该漏洞的主要原因在于当A ...

  2. MATLAB—数组运算及数组化编程

    文章目录 前言 一.数组的结构和创建 1.数组及其结构 2.行数组的创建 3.对数组构造的操作 二.数组元素编址及寻访 1.数组元素的编址 2.二维数组元素的寻访 三.数组运算 非数的问题 前言 编程 ...

  3. ReentrantLock中的Condition(等待和唤醒)

    Condition 类的 awiat 方法和 Object 类的 wait 方法等效 Condition 类的 signal 方法和 Object 类的 notify 方法等效 Condition 类 ...

  4. java-通过ip获取地址

    添加maven依赖 <dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-all&l ...

  5. Python爬虫(二)——发送请求

    1. requests库介绍 ​ 在python中有许多支持发送的库.比如:urlib.requests.selenium.aiohttp--等.但我们当前最常用的还是requests库,这个库是基于 ...

  6. springboot整合多数据源解决分布式事务

    一.前言        springboot整合多数据源解决分布式事务.             1.多数据源采用分包策略              2.全局分布式事务管理:jta-atomikos. ...

  7. 根据当前设备的宽度,动态计算出rem的换算比例,实现页面中元素的等比缩放

    ~function anonymous(window){ //根据当前设备的宽度,动态计算出rem的换算比例,实现页面中元素的等比缩放 let computedREM = function compu ...

  8. C++ Opencv图像直方图

    Mat image = imread("D:/ju.jpg"); imshow("素材图", image); int bins = 256; //直条为256 ...

  9. wpf toggleSwitch 的只读属性

    xml code --------------------------------------------- <Page x:Class="UWPDemo.MainPage" ...

  10. docker 安装Hive

    转自:https://www.cnblogs.com/upupfeng/p/13452385.html#%E9%83%A8%E7%BD%B2hive 使用docker快速搭建hive环境   记录一下 ...