**仅作为个人笔记记录一下,有任何不足之处,欢迎大神们多多指正!!!**
1. 定义:
双向链表也叫双链表,是链表的一种,它的每个数据结点中都有两个指针,分别指向直接后继和直接前驱。所以,从双向链表中的任意一个结点开始,都可以很方便地访问它的前驱结点和后继结点。
2. 数据格式示意图:
大致就是那么个意思,每个节点都保存着前节点、后节点的地址值以及data值,头结点、尾结点比较特殊,头结点的前节点地址为null、尾结点的后节点地址值为null。
3. 实现代码:
<?php
/**
* Class Node
* 节点类
*/
class Node{
public $previous = null; //前驱指针
public $val = null; //节点的val
public $next = null; //后驱指针
/**
* Node constructor.
* @param $val
*/
public function __construct($val){
$this->val = $val;
}
/**
* 重写toString
* @return string
*/
public function __toString(){
// TODO: Implement __toString() method.
return "[{$this->val}]";
}
}
/**
* Class linkedList
*
* 节点操作类
*/
class linkedList{
private $headNode; //头指针
private $tailNode; //尾指针
private $size; //链表长度
private static $instance = null; //linkedList操作实例
/**
* linkedList constructor.
*/
private function __construct(){
$this->size = 0;
}
/**
* @return linkedList
*/
public static function instance(){
if(self::$instance instanceof self){
return self::$instance;
}
self::$instance = new self();
return self::$instance;
}
#region 1. 添加节点 - 默认是从后向前添加
/**
* 新增节点
* - 插入规则:如果index存在,则插入的节点替换已存在的index索引节点,被替换的节点后移
* @param mixed $element 元素
* @param null|int $index
* @return bool
* @throws Exception
*/
public function add($element, $index = null){
$node = new Node($element);
//检测越界
$this->_checkRange($index);
//添加节点
if(is_null($index)){ //index传null,表示从末尾添加
if($this->tailNode instanceof Node){ //尾结点也是一个Node,即链表中已经有了元素
$node->previous = $this->tailNode;
$this->tailNode->next = $node;
$this->tailNode = $node;
}else{ //尾结点不是Node,null表示链表中没有元素
$this->headNode = $this->tailNode = $node;
}
}else{
$nodeOld = $this->get($index); //当前索引上的老节点
//替换为参数节点
if($nodeOld instanceof Node){ //如果被替换的节点是Node的实例
$node->previous = $nodeOld->previous;
$node->next = $nodeOld;
$nodeOld->previous = $node;
}
//判断是否为头节点 or 尾结点
if($node->previous === null){
$this->headNode = $node;
}
if($node->next === null){
$this->tailNode = $node;
}
}
$this->size ++;
return true;
}
#endregion
#region 2. 删除节点
/**
* 删除节点
* @param int $index 节点的索引值
* @return bool
* @throws Exception
*/
public function remove($index){
//检测索引值有没有越界
$this->_checkRange($index);
$node = $this->headNode;
$countIndex = 0; //计数索引
while ($node instanceof Node){
if($countIndex == $index){ //找到了指定索引的节点
//前驱存在
if($node->previous instanceof Node ){
if( $node->next instanceof Node){ //后驱存在
$node->previous->next = $node->next;
}else{ //后驱不存在,尾节点置为null
$node->previous->next = null;
$this->tailNode = null;
}
}
//后驱存在
if($node->next instanceof Node){
if($node->previous instanceof Node){
$node->next->previous = $node->previous ; //前驱存在
}else{
$node->next->previous = null; //前驱不存在
$this->headNode = $node->next; //头节点置为下一个节点
}
}
unset($node);
$this->size --;
return true;
}
$node = $node->next; //指针向下移动一位,指向下一个节点
$countIndex ++; //计数索引 +1
}
return false;
}
#endregion
#region 3. 获取节点
/**
* 从头开始找指定索引的节点
* @param int $index 索引值
* @return Node|null
*/
public function get($index){
if(!is_numeric($index)) return null;
//检测索引值有没有越界
$this->_checkRange($index);
$node = $this->headNode;
$countIndex = 0; //计数索引
while ($node instanceof Node){
if($countIndex == $index){
return $node;
}
$node = $node->next;
$countIndex ++;
}
return null;
}
#endregion
#region 4. 格式化展示链表数据
/**
* 格式化展示链表
* @return string
* ⇋ ⇌ ⇆
*/
public function formatShow(){
$str = "";
$node = $this->headNode;
$countIndex = 0; //计数索引
$connector = " ⇆ "; //连接符
while ($node instanceof Node){
$str .= "[{$countIndex}:{$node->val}]{$connector}";
$node = $node->next;
$countIndex ++; //+1
}
return trim($str, $connector);
}
#endregion
#region 5. 获取链表元素格式
/**
* @return int
*/
public function size(){
return $this->size;
}
#endregion
#region 6. 获取节点的索引值(如果有值相同的节点,则返回匹配到的第一个节点索引值)
/**
* @param $element
* @return int
*/
public function indexOf($element){
$node = $this->headNode;
$countIndex = 0;
while ($node instanceof Node){
if($node->val == $element){
return $countIndex;
}
$node = $node->next;
$countIndex ++;
}
return -1; //没找到,返回-1
}
#endregion
/**
* 检测范围
* @param int|null $index 传递传索引值
* @throws Exception
*/
private function _checkRange($index){
//检测索引值有没有越界
if($index && $index >= $this->size) throw new Exception("indexOutOfBounds: index越界!");
}
}
//-----------------------------------------------------------------------------------------------------------------
// Debug调试
//-----------------------------------------------------------------------------------------------------------------
try{
linkedList::instance()->add("val_1");
linkedList::instance()->add("val_2");
linkedList::instance()->add("val_3");
linkedList::instance()->add("val_4");
linkedList::instance()->add("val_5");
linkedList::instance()->add("val_6");
linkedList::instance()->add("val_x");
linkedList::instance()->add("val_x");
linkedList::instance()->add("val_x");
$linkedString = linkedList::instance()->formatShow();
echo "size : ",linkedList::instance()->size(),PHP_EOL;
echo "linkedString : " , $linkedString,PHP_EOL;
echo "获取指定索引的节点:", linkedList::instance()->get(1),PHP_EOL;
echo "删除指定索引的节点:", linkedList::instance()->remove(4),PHP_EOL;
echo "size : ",linkedList::instance()->size(),PHP_EOL;
echo "linkedString : " , linkedList::instance()->formatShow(),PHP_EOL;
echo "查找节点索引 : " , linkedList::instance()->indexOf("val_x"),PHP_EOL;
if(error_get_last() !== null){
throw new Exception(json_encode(error_get_last()));
}
}catch (Exception $exception){
$errMsg = [
'code' => $exception->getCode(),
'msg' => $exception->getMessage(),
'trace' => $exception->getTrace(),
'trace_as_string' => $exception->getTraceAsString(),
];
print_r($errMsg);
}