一、什么是链表

动静的线性数据结构。

二、链表的增删改查

(一)非递归实现

<?phpclass LinkedList{    // protected Node $head;    protected Node $dummyHead; // 虚构头结点    private $size;    public function __construct()    {        // $this->head = null;        $this->dummyHead = new Node();        $this->size = 0;    }    public function getSize(): int    {        return $this->size;    }    public function isEmpty(): bool    {        return $this->size == 0;    }    // 在链表头增加新的元素    public function addFirst($e)    {        $this->add(0, $e);    }    // 在链表的 index (0-based) 地位增加新的元素 e    public function add($index, $e)    {        // 判断 index 的合法性        if ($index < 0 || $index > $this->size) {            // 留神 index 能够取到 size 的,因为能够在最初一个节点增加元素            throw new \Exception('Add failed. illegal index');        }        $prev = $this->dummyHead;        for ($i = 0; $i < $index; $i++) {            $prev = $prev->next;        }        // 程序很重要        $node = new Node($e);        $node->next = $prev->next;        $prev->next = $node;        // 更优雅的写法        // $prev->next = new Node($e, $prev->next);        $this->size ++;    }    // 在链表开端增加元素 e    public function addLast($e)    {        $this->add($this->size, $e);    }    // 取得链表中第 index 个地位的元素:    public function get($index)    {        if ($index < 0 && $index >= $this->size) {            throw new \Exception('Get failed, Illeal index');        }        $cur = $this->dummyHead->next;        for ($i = 0; $i < $index; $i++) {            $cur = $cur->next;        }        return $cur->e;    }    // 取得链表的第一个元素    public function getFrist()    {        return $this->get(0);    }    // 取得链表的最初一个元素    public function getLast()    {        return $this->get($this->size - 1);    }    // 批改链表的第 index 个地位的元素    public function set($index, $e)    {        if ($index < 0 && $index >= $this->size) {            throw new \Exception('Set failed, Illeal index');        }        $cur = $this->dummyHead->next;        for ($i = 0; $i < $index; $i++) {            $cur = $cur->next;        }        $cur->e = $e;    }    // 查找链表中是否存在元素 e    public function contains($e)    {        $cur = $this->dummyHead->next;        while ($cur != null) {            if ($e == $cur->e) {                return true;            }            $cur = $cur->next;        }        return false;    }    // 从链表中删除第 index 个元素,并返回删除元素的值    public function remove($index)    {        if ($index < 0 || $index >= $this->size) {            throw new \Exception('Delete failed, illegal index');        }        $prev = $this->dummyHead;        for ($i = 0; $i < $index; $i++) {            $prev = $prev->next;        }        $retNode = $prev->next;        $prev->next = $retNode->next;        $retNode->next = NULL;        $this->size--;        return $retNode->e;    }    // 从链表中删除第一个元素,返回删除元素    public function removeFirst()    {        return $this->remove(0);    }    // 从链表中删除最初一个元素,返回删除元素    public function removeLast()    {        return $this->remove($this->size - 1);    }    public function toString()    {        $cur = $this->dummyHead->next;        $ret = '';        while ($cur != null) {            $ret .= $cur->e . '->';            $cur = $cur->next;        }        $ret .= 'NULL';        return $ret;    }}class Node{    public $e;    public $next;    public function __construct($e = null, $next = null)    {        $this->e = $e;        $this->next = $next;    }}

1. 虚构头结点(dummy head)

为什么要应用虚构头结点?详情参考 链表问题:虚构节点dummy

因为头结点的上一个节点不存在,很多对于其余节点,须要用上上一个节点的操作对头结点就不适宜,因而,就须要独自思考头结点。

虚构头结点的目标就是打消头结点的特殊性,把头结点当做一个一般的节点来解决。即在头结点的后面加上了一个虚构头结点。

2. 增删

在链表两头增加和删除元素的要害:找到要增加或删除的节点的前一个节点

减少元素示意图:

删除元素示意图:

这也是用虚构头结点的起因:如果想要在头结点减少一个元素或者删除头结点,那么就须要找到头结点的前一个节点,然而头结点后面是没有节点的,比拟常见的做法是分状况探讨;更好的做法是应用虚构头结点这种技巧,把头结点也当做一般元素来解决,最终也不会对后果产生影响,防止了分类探讨。

值得一提的是,在减少或删除元素时遍历链表时,是从虚构头结点开始遍历的,而非链表的第一个元素开始,代码如下:

// 在链表的 index (0-based) 地位增加新的元素 epublic function add($index, $e){    ...    // 从 dummyHead 开始遍历    $prev = $this->dummyHead;    for ($i = 0; $i < $index; $i++) {        $prev = $prev->next;    }    $node = new Node($e);    $node->next = $prev->next;    $prev->next = $node;    $this->size ++;}// 从链表中删除第 index 个元素,并返回删除元素的值public function remove($index){    ...    // 从 dummyHead 开始遍历    $prev = $this->dummyHead;    for ($i = 0; $i < $index; $i++) {        $prev = $prev->next;    }    $retNode = $prev->next;    $prev->next = $retNode->next;    $retNode->next = NULL;    $this->size--;    return $retNode->e;}

为什么要从 dummyHead 开始遍历呢?就不得不再提一遍减少或者删除元素的重点了:找到要增加或删除的节点的前一个节点,即咱们要找到的前一个节点而非指标节点。

举个例子,在链表 L 中地位为 2 的中央增加一个节点 6,链表 L 如下:

index012345next
val123456null

当初在头结点后面加上一个虚构头结点:

indexdummyHead012345next
valnull123456null

遍历的指标节点是 L[1]

dummyHead -> next = L[0]L[0] -> next      = L[1]

两次就找到了指标节点的前一个节点,和待增加的地位 2 对立。

3. 改查

遍历找到指标节点即可。然而与增删有些许不同,改查并不是从 dummyHead 开始遍历,而是从链表的头结点开始

举个例子,获取 L 中索引为 2 的元素的值,L 链表如下:

index012345next
val123456null

如果从 dummyHead 开始遍历,循环两次,找到的是 L[1] 这个元素,那么如果是从头结点开始遍历,那么遍历两次就能够找到指标元素了。

L[0] -> next = L[1];L[1] -> next = L[2];

查和改的代码如下:

// 批改链表的第 index 个地位的元素public function set($index, $e){    ...    // 从头结点开始遍历    $cur = $this->dummyHead->next;    for ($i = 0; $i < $index; $i++) {        $cur = $cur->next;    }    $cur->e = $e;}// 取得链表中第 index 个地位的元素:public function get($index){    ...    // 从头结点开始遍历    $cur = $this->dummyHead->next;    for ($i = 0; $i < $index; $i++) {        $cur = $cur->next;    }    return $cur->e;}

三、翻转链表

  • 援用
  • 指针
  • 递归

第一版:

public function reverseList($head)    {        if ($head->next == null) {            return $head;        }        $reversedList = $this->reverseList($head->next);        $head->next = null;        $tail = $reversedList;        while ($tail->next != null) {            $tail = $tail->next;        }        $tail->next = $head;        return $reversedList;    }

改良:

<?php/** * Definition for a singly-linked list. * class ListNode { *     public $val = 0; *     public $next = null; *     function __construct($val) { $this->val = $val; } * } */class Solution {    /**     * @param ListNode $head     * @return ListNode     */    function reverseList($head) {        if ($head->next == null) {            return $head;        }        $reversedList = $this->reverseList($head->next);        $head->next->next = $head;        $head->next = null;        return $reversedList;    }}

三、递归

链表具备人造的递归性。

找到一种可视化的,可疾速失去递归后果的伎俩。

当初写累了,当前再补充这一块的内容。