链表有没有其他的表现形式?

在上篇文章中,我们已经说过了链表除了简单的那一种单向链表外,还有其它的几种形式。当然,这也是链表这种结构的一大特点,非常地灵活和方便。我们简单的想一想,如果让最后一个节点的next指回第一个节点,那么这就样就形成了一个环,这就是一个循环链表了。

如果我们在每个节点上增加一个指向上一个节点的 prev 属性,那么这个链表就变成了一个双向链表了。如果我们在双向链表的基础上也让最后一个节点的 next 指向第一个节点,同时让第一个节点的 prev 指向最后一个节点,这不就是一个双向循环链表了嘛。下面我们就来具体的看一看。

循环链表

就像上文所说的,我们让最后一个节点指向第一个节点,这样形成的链表就是一个循环链表,如下图所示:

bVcTAuV.webp.jpg

关于循环的链表的操作我们不做详细的说明,其实大部分代码和单向链表是一样的,只是需要注意两个地方:

1.初始化、插入操作的时候,注意最后一个节点的指向,最后一个节点的 next 要指向第一个节点

2.判断链表遍历是否完成的条件为 item->next == head ,也就是说,判断这个节点的下一个节点如果是头节点的话,链表就遍历完成了。

双向链表

双向链表则是在 LinkedList 这个类里面增加一个属性来指向上一个节点。

// 双向链表class LinkedList{    public $data;    public $prev;    public $next;}

登录后复制

bVcTAuW.webp.jpg

接下来,我们初始化一个双向链表。

/** * 生成链表 */function createLinkedList(){    $list = new LinkedList();    $list->data = null;    $list->next = null;    $list->prev = null; // ** 全部都初始化为 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;        $link->prev = $r; // ** 增加上级指向 **        $r = $link;    }    return $list;}$link = Init(range(1, 10));var_dump($link);var_dump($link->next->next->next->next);// object(LinkedList)#5 (3) {//     ["data"]=>//     int(4)//     ["prev"]=>//     object(LinkedList)#4 (3) {//       ["data"]=>//       int(3)//       ["prev"]=>//       object(LinkedList)#3 (3) {//         ["data"]=>//         int(2)//         ["prev"]=>//         object(LinkedList)#2 (3) {//           ["data"]=>//           int(1)//           ["prev"]=>//           object(LinkedList)#1 (3) {//             ["data"]=>//             NULL//             ["prev"]=>//             NULL//             ["next"]=>//             *RECURSION*//           }//           ["next"]=>//           *RECURSION*//         }//         ["next"]=>//         *RECURSION*//       }//       ["next"]=>//       *RECURSION*//     }//     ["next"]=>//     object(LinkedList)#6 (3) {//       ["data"]=>//       int(5)//       ["prev"]=>//       *RECURSION*//       ["next"]=>//       object(LinkedList)#7 (3) {//         ["data"]=>//         int(6)//         ["prev"]=>//         *RECURSION*//         ["next"]=>//         object(LinkedList)#8 (3) {//           ["data"]=>//           int(7)//           ["prev"]=>//           *RECURSION*//           ["next"]=>//           object(LinkedList)#9 (3) {//             ["data"]=>//             int(8)//             ["prev"]=>//             *RECURSION*//             ["next"]=>//             object(LinkedList)#10 (3) {//               ["data"]=>//               int(9)//               ["prev"]=>//               *RECURSION*//               ["next"]=>//               object(LinkedList)#11 (3) {//                 ["data"]=>//                 int(10)//                 ["prev"]=>//                 *RECURSION*//                 ["next"]=>//                 NULL//               }//             }//           }//         }//       }//     }//   }echo $link->next->next->next->next->data, PHP_EOL; // 4echo $link->next->next->next->next->prev->data, PHP_EOL; // 3

登录后复制

可以看出,与单向链表不同的地方就在于多增加了对于 prev 属性的操作。这里还是比较好理解的。直接打印链表会显示很多的 *RECURSION* 内容,这是 PHP 的一种输出的保护机制,这个标识说明当前这个属性变量是有递归类型的。

/** * 链表指定位置插入元素 * @param LinkedList $list 链表数据 * @param int $i 位置 * @param mixed $data 数据 */function Insert(LinkedList &$list, int $i, $data){    $j = 0;    $item = $list;    // 遍历链表,找指定位置的前一个位置    while ($j next;        $j++;    }    // 如果 item 不存在或者 $i > n+1 或者 $i  $i - 1) {        return false;    }    // 创建一个新节点    $s = new LinkedList();    $s->data = $data;    // 新创建节点的下一个节点指向原 i-1 节点的下一跳节点,也就是当前的 i 节点    $s->next = $item->next;    // ** 增加当前新创建的节点的上级指向 **    $s->prev = $item;    // 将 i-1 节点的下一跳节点指向 s ,完成将 s 插入指定的 i 位置,并让原来的 i 位置元素变成 i+1 位置    $item->next = $s;    // ** 将下级节点的 prev 指向新创建的这个节点 **    $s->next->prev = $s;    return true;}

登录后复制

链表的插入其实就是增加了两行代码,一个是当前新创建的节点的上级的指向,也就是将这个新节点的上级指定为 i-1 个节点。而另一个是将原来 i 位置节点的上级指向修改为当前新创建的这个节点。

/** * 删除链表指定位置元素 * @param LinkedList $list 链表数据 * @param int $i 位置 */function Delete(LinkedList &$list, int $i){    $j = 0;    $item = $list;    // 遍历链表,找指定位置的前一个位置    while ($j next;        $j++;    }    // 如果 item 不存在或者 $i > n+1 或者 $i  $i - 1) {        return false;    }    // 使用一个临时节点保存当前节点信息,$item 是第 i-1 个节点,所以 $item->next 就是我们要找到的当前这个 i 节点    $temp = $item->next;    // 让当前节点,也就是目标节点的上一个节点, i-1 的这个节点的下一跳(原来的 i 位置的节点)变成原来 i 位置节点的下一跳 next 节点,让i位置的节点脱离链条    $item->next = $temp->next;    // ** 让目标下一个节点的上级指针指向当前这个节点 **    $temp->next->prev = $item;    return true;}

登录后复制

与插入节点操作类似,删除节点操作除了将 i-1 个位置节点的数据的下一个节点的指向变为 i 节点的下一级节点的指向之外,还要将 i 的下一级节点的上级节点指向改为 i-1 节点。

其实,双向链表的定义和操作相比单向链表来说差别并不大,就是多了一个 prev 用来指向上一级节点而已,本质上也只是多了对于 prev 这个属性的操作而已。那么,多出来的这一个上级指针能带来什么好处呢?在遍历链表的时候,我们通过 prev ,就多了一种遍历方式,也就是反向的对链表进行遍历。在查找某个元素的时候,我们可以从两个方向同时进行查找,效率是不是一下子就提升了一倍。原来 O(n) 的时间复杂度瞬间可以变成 O(n/2) 的时间复杂度。

双向循环链表

最后,我们也简单的来介绍一下双向循环链表。顾名思义,它就是在双向链表的基础上加上循环链表的概念。让最后一个节点的 next 指向头节点,让头节点的 prev 指向最后一个节点。说起来容易但实现起来其实要复杂很多,因为你不仅要关注最后一个节点的下级节点指向问题,而且还要关注头节点的上级指向问题。所以在这里我们就不多做代码演示了,最主要的就是在插入和删除头、尾节点的时候需要多注意它们上下级节点的指向。

bVcTAuX.webp.jpg

总结

突然发现新大陆了吧?链表原来还有这么多种形式。当然,这还没有说完,我们还有一个很高大上的十字链表没说,不过其实十字链表也只是增加了更多的指向属性而已,基本的数据域永远都还是那一个 data 。其实最普通的单向链表,也就是上一篇文章详细介绍的那个才是我们对于链表学习真正要掌握的重点。

因此,大家不必焦虑,也不用恐慌,掌握好普通的单向链表你就可以秒杀绝大部分人了,而今天学习的这些呢?能掌握最好,掌握不了最少混个脸熟就可以了,做人,最重要的是开心了,不要把自己逼的太狠,太狠的话,要么成龙,要么成虫,认清自己的现状和能力才是最重要的。

关于线性表的内容到此为止。物理结构的存储问题就是这样了,接下来我们就要逻辑结构的世界了。也是从最简单的开始,那就是栈和队列,不要怕,它们和 树、图 比起来真的是洒洒水啦!!

测试代码:

https://github.com/zhangyue0503/Data-structure-and-algorithm/blob/master/2.线性表/source/2.4%20链表的其它形式.php

登录后复制

推荐学习:php视频教程

以上就是链表有没有其他的表现形式?的详细内容,更多请关注【创想鸟】其它相关文章!

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至253000106@qq.com举报,一经查实,本站将立刻删除。

发布者:PHP中文网,转转请注明出处:https://www.chuangxiangniao.com/p/2112057.html

(0)
上一篇 2025年2月24日 16:10:15
下一篇 2025年2月23日 06:22:20

AD推荐 黄金广告位招租... 更多推荐

相关推荐

  • PHP日历之儒略历法的日期如何转换为儒略日计数

    在php中,有一种方法可以将儒略历法的日期转换为儒略日计数,今天我们就来介绍一下这个算法,有需要的小伙伴可以参考参考。 在之前我们肯定学过php的日历扩展,但是你们知不知道其实日历扩展中还有一个十分有意思的函数,那就是juliantojd(…

    编程技术 2025年2月24日
    200
  • php日历之儒略日计数与法国共和历法的日期间如何相互转换

    在上篇文章中,我们了解了什么是儒略历法,知道了什么是儒略日,同时我们也知道了《php中如何将儒略历法的日期转换为儒略日计数》,这次我们来看看儒略日计数是如何与法国共和历法的日期相互转换的吧。 本篇文章,小编会向大家介绍一下将法国共和历法的日…

    编程技术 2025年2月24日
    200
  • php日历之儒略日计数如何转换为Unix时间戳

    在上篇文章中,我们了解了什么是法国共和历法,知道了什么是儒略日,同时我们也知道了《php日历之儒略日计数与法国共和历法的日期间如何相互转换》,这次我们来看看儒略日计数如何转换为unix时间戳的吧。 今天我们接着这个专题来学一下php日历中的…

    编程技术 2025年2月24日
    200
  • php日历之格利高里历法的日期与儒略日计数如何相互转换

    在上篇文章中,我们介绍了《php日历之儒略日计数如何转换为unix时间戳》,提到了格利高里历法的日期,但是小编没有介绍,这篇文章我们就来好好介绍一下格利高里历法的日期与儒略日计数相互转换的方法。 在开始本篇文章介绍之前,我们先来了解上篇文章…

    编程技术 2025年2月24日
    200
  • php中图是什么?如何才能进行存储?

    随着学习的深入,我们的知识也在不断的扩展丰富。树结构有没有让大家蒙圈呢?相信我,学完图以后你就会觉得二叉树简直是简单得没法说了。其实我们说所的树,也是图的一种特殊形式。 图的概念 还记得我们学习树的第一篇文章时看到的那张关于树的图片吗? 在…

    2025年2月24日 编程技术
    200
  • PHP和Go如何进行环路链表检测

    链表中环的入口结点问题是一个超级经典的问题,不管是在面试中,还是考研的过程中都是一个经典问题。通常的公认解法就是双指针(快慢指针)的解法,当然这已经的老生长谈的了。今天我们就来介绍介绍。 给定一个链表,如果它是有环链表,实现一个算法返回环路…

    编程技术 2025年2月24日
    200
  • php中二进制子串如何进行计数

    最近刷题,按照题目的难度顺序刷到了这一题,一开始写的代码都因为超时而没有ac过,经过百度后看了一下别人的思路后感叹自己之前的逻辑是有多么的耗时,下面是我之前的代码和ac过后的代码。 题目描述: 给定一个字符串 s,计算具有相同数量0和1的非…

    编程技术 2025年2月24日
    200
  • php函数之如何用默认参数和可变长度参数方式传递?

    上一篇文章中我们了解了向函数传递参数中的引用传递参数,有需要的请看《php函数之如何引用传递参数?》。这次我们向大家介绍向函数传递参数中的另外两种传递方式,有需要的可以参考参考。 向函数传递参数的方式有四种,分别是值传递、引用传递、默认参数…

    编程技术 2025年2月24日
    200
  • php命名空间之如何定义空间?

    本篇文章将开始介绍命名空间。命名空间是一种封装事物的方法,在很多地方都可以见到这种抽象概念。今天我们就来介绍介绍,有需要的可以参考一下。 首先,我们了解一下什么是命名空间。(有需要的可以参考PHP 命名空间) 在PHP中,名称空间可以解决编…

    编程技术 2025年2月24日
    200
  • php命名空间之子命名空间是什么?

    上一篇文章中我们了解了命名空间,知道了如何去定义命名空间,有需要的请看《php命名空间之如何定义空间?》。这次我们向大家介绍子命名空间,有需要的可以参考参考。 在PHP中,命名空间可以帮我们做成许多事情。可以让我们自己定义的名称不与php内…

    编程技术 2025年2月24日
    200

发表回复

登录后才能评论