PHP 链表的使用

前面已经介绍了很多关于数组的内容.下面我们来讨论一下链表.在多数语言中,数组是定长的结构,当然就不能动态增长,缩减和移除.出于这些原因,大多数开发者都偏向于使用链表来代替数组.考虑到PHP数组中有额外的字节消耗,而链表的对内存使用的效率很高.下面将介绍PHP中不同类型的链表以及实现.

链表是什么?

链表是节点的集合.每一个节点通过链表相互连接.节点可以存储任何数据结构和内容.

链表有以下操作:

  • 链表是否为空
  • 遍历所有节点
  • 搜索某一节点
  • 求链表长度
  • 插入新节点
  • 移除节点
  • 链表的反转

下面描述一种简单的节点,包含节点数据和下一个节点的地址
next:

class ListNode { 
    public $data = NULL; 
    public $next = NULL; 

    public function __construct(string $data = NULL) { 
        $this->data = $data; 
    } 
}

下面看一个 链表 LinkedList 的实现 ,包含insert和display操作

class LinkedList { 
    private $_firstNode = NULL; 
    private $_totalNodes = 0; 

    public function insert(string $data = NULL) { 
       $newNode = new ListNode($data); 

        if ($this->_firstNode === NULL) {           
            $this->_firstNode = &$newNode;             
        } else { 
            $currentNode = $this->_firstNode; 
            while ($currentNode->next !== NULL) { 
                $currentNode = $currentNode->next; 
            } 
            $currentNode->next = $newNode; 
        } 
       $this->_totalNode++; 
        return TRUE; 
    } 

    public function display() { 
      echo "Total book titles: ".$this->_totalNode."\n"; 
        $currentNode = $this->_firstNode; 
        while ($currentNode !== NULL) { 
            echo $currentNode->data . "\n"; 
            $currentNode = $currentNode->next; 
        } 
    } 
} 

$linked_list = new LinkedList();

$linked_list->insert(1);
$linked_list->insert(2);
$linked_list->insert(3);
$linked_list->insert(4);

$linked_list->display();

返回结果:
Total book titles: 4 
1 2 3 4

不同类型的链表

到此我们已经实现了一种最简单的单向链表类 LinkedList 这里还有其他几中链表

  • 双向链表
  • 环向列表
  • 多链表
双向列表
双向链表
环向列表
环向列表

多链表

多链表

链表的插入,删除,查找

前面了解了链表的插入和遍历操作,下面来看一下链表的其他操作

  • 在第一个节点处插入
  • 节点的搜索
  • 在特殊节点前插入
  • 在特殊节点后插入
  • 删除第一个节点
  • 删除最后一个节点
  • 搜索和删除一个节点
  • 链表的反转
  • 获取第N个位置的元素

下面依次讨论

在第一个节点处插入

节点搜索

   public function insertAtFirst(string $data = NULL) { 
        $newNode = new ListNode($data); 
        if ($this->_firstNode === NULL) {             
            $this->_firstNode = &$newNode;             
        } else { 
            $currentFirstNode = $this->_firstNode; 
            $this->_firstNode = &$newNode; 
            $newNode->next = $currentFirstNode;            
        } 
        $this->_totalNode++; 
        return TRUE; 
    } 

在特殊节点前插入

public function insertBefore(string $data = NULL, string $query =NULL)
    {   
        $newNode = new ListNode($data);
        if ($this->_firstNode && $this->_firstNode->data = $query) {
            $newNode->next = $this->_firstNode;
            $this->_firstNode-> &$newNode;
        }

        if ($this->_firstNode !== NULL) {       
            $previous = NULL;
            $currentNode = $this->_firstNode;
            while ($currentNode !== NULL) {
                if ($currentNode->data === $query) {
                    $previous->next = &$newNode;
                    $newNode->next = $currentNode;
                    $this->_totalNode++;
                    break;
                }
                $previous = $currentNode;
                $currentNode = $currentNode->next;
            }
        }
    }

在特定节点后插入

    public function insertAfter(string $data = NULL, string $query =NULL)
    {   
        $newNode = new ListNode($data);
        if ($this->_firstNode !== NULL) {
            $currentNode = $this->_firstNode;
            while ($currentNode !== NULL) {
                if ($currentNode->data === $query) {
                    $newNode->next = $currentNode->next;
                    $currentNode->next = &$newNode;
                    $this->_totalNode++;
                    break;
                }
                $currentNode = $currentNode->next;
            }
        }
    }

链表的反转

    public function reverse() { 
        if ($this->_firstNode !== NULL) { 
            if ($this->_firstNode->next !== NULL) { 
                $reversedList = NULL; 
                $next = NULL; 
                $currentNode = $this->_firstNode; 
                while ($currentNode !== NULL) { 
                    $next = $currentNode->next; 
                    $currentNode->next = $reversedList; 
                    $reversedList = $currentNode; 
                    $currentNode = $next; 
                } 
                $this->_firstNode = $reversedList; 
            } 
        } 
    } 

获取第N个位置的元素

    public function getNthNode(int $n = 0)
    {
        $count = 1;
        if ($this->_firstNode !== NULL) {
            $currentNode = $this->_firstNode;
            while ($currentNode !== NULL) {
                if ($count === $n) {
                    return $currentNode;
                }
                $count++;
                $currentNode = $currentNode->next;
            }
        }
    }

理解链表的算法复杂度

操作 时间复杂度:最差 事件复杂度:平均
在首,尾插入 O(1) O(1)
在首尾删除 O(1) O(1)
搜索 O(n) O(n)
访问 O(n) O(n)

使链表可迭代

上面我们使用 while 遍历链表的每一个节点.那我们可以通过链表本身进行迭代吗?PHP 有非常直观的 Iterator(迭代器) 接口

  • Current
  • Next
  • Key
  • Rewind
  • Valid

上面的方法可以通过 php 手册查询,不再赘述.下面我们将通过类 LinkedList 来实现上述接口来实现直接进行节点的迭代.为了在迭代的过程中追踪当前节点和当前的位置,类 LinkedList 需要额外的两个属性

private $_currentNode = NULL;  // 当前节点
private $_currentPosition = 0; // 当前位置

下面实现 Iterator(迭代器)的5个方法

    #pragram make implements Iterator
    public function current()
    {
        return $this->_currentNode->data;
    }

    public function next()
    {
        $this->_currentPosition++;
        $this->_currentNode = $this->_currentNode->next;
    }    

    public function key() { 
        return $this->_currentPosition; 
    } 

    public function rewind() { 
        $this->_currentPosition = 0; 
        $this->_currentNode = $this->_firstNode; 
    } 

    public function valid() { 
        return $this->_currentNode !== NULL; 
    } 

哈哈,现在我们有了列表的迭代方法了,意味着我们可以使用 foreach 或者其他过程函数来进行迭代:例如下面:

foreach ($BookTitles as $title) { 
    echo $title . "\n"; 
}
for ($BookTitles->rewind(); $BookTitles->valid(); $BookTitles->next()) { 
    echo $BookTitles->current() . "\n"; 
}

双向列表

后面双向列表和多向列表的内容和算法我觉得不需要在赘述了,有空在写吧

使用 PHP SplDoublyLinkedList

如果我们使用内置的类,我们不需要去实现双向列表.官方手册中清楚的列出了双向列表的接口,这里也不再介绍了,下面就举个例子:







©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,053评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,527评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,779评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,685评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,699评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,609评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,989评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,654评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,890评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,634评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,716评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,394评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,976评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,950评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,191评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,849评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,458评论 2 342

推荐阅读更多精彩内容