tips:因为涉及指针,我们用引用来模拟,所以读者应该有面向对象的知识贮备。
引子
你可以把链表简单理解为动态数组,它不需要一块一块的开辟空间,同时,你又要注意,它存在的主要意义或者说使用场景主要是”指针功能“,它能够指来指去,对一些应用特别是内存管理起到了关键作用。
引文
因为涉及内存,常常会有一些程序的边界限制,需要programer拥有一定严密的逻辑去保证代码的鲁棒性和健壮性,所以这个知识点是面试的常考点。下面我们看看PHP的单链表实现(附常考题目实现):
data = $val; $this->next = $nex; }}/** * TODO:构建单链表 */Class SingleLinkList{ /* 头插法创建链表 n为节点总数 */ public function headInsert($n){ /* 新建一个头节点 */ $head = new Node(null,null); for($i=$n;$i>0;$i--){ $newNode = new Node($i,null); $head->data = $newNode->data; #新建节点赋值给头节点 $newNode->next = $head->next; #将头节点的后继节点作为新建节点的后继节点,相当于在原头节点和头节点的后继节点中间添加了一个新节点 $head->next = $newNode; #将新建节点作为头节点的后继节点,这时候原本头节点的后继节点已经改变了 } return $head; } /* 尾插法创建链表 */ public function rearInsert($n){ /* 新建一个尾节点 */ $rear = new Node(null,null); for($j=0;$j<$n;$j++){ $newNode = new Node($j,null); $rear->data = $newNode->data; //$newNode = $rear->next; $rear->next = $newNode; $rear = $newNode; } return $rear; } /** * TODO:读取链表中第i个数据 * @param $list object 待插入的链表 * @param $i int 节点序号 */ public function readIThNode($list,$i){ /* 如果链表为空或者i小等于0 */ if($list == null || $i<=0){ echo "输入参数不合法"; return ; } /* */ $p = $list->next; #设置p指向第一个节点(即头节点的后继节点)) $j=0; #计时器必须初始化 while($p && $j<$i ){ $p = $p->next; ++$j; } /* 第i步 */ if($p == null){ #说明链表已经结束,不存在i节点,过滤掉i大于链表长度的情况(因为节点是散列的,事先并不知道其长度) echo "i长度大于链表长度" ; exit; }else{ $e = $p->data; #第i个节点存在 ,返回 return $e; } } /** * TODO:在链表的第i个位置之前插入节点e * @param $list object 待插入的链表 * @param $i int 节点序号 * @param $e object 待插入的节点 */ public function Insert($list,$i,$e){ if($e == null){ echo "待插入节点为空"; exit; } $p = $list->next; #设置p指向第一个节点 $j=0; #计时器必须初始化 while($p && $j<$i ){ $p = $p->next; #保证节点在向后移动 ++$j; } /* 第i步 */ if($p == null){ #说明链表已经结束,不存在i节点,过滤掉i大于链表长度的情况(因为节点是散列的,事先并不知道其长度) echo "不存在i节点" ; exit; }else{ /* 标准的插入语句(头插法) */ $e->next = $p->next; $p->next = $e; return $list; } } /** * TODO:删除链表的第i个节点,并返回该节点的值 * @param $list object 待插入的链表 * @param $i int 节点序号 */ public function Delete($list,$i){ if($list == null || $i<=0){ echo "输入参数不合法"; exit; } $p = $list->next; #设置p指向第一个节点 $j=0; #计时器必须初始化 while($p && $j<$i ){ $p = $p->next; #保证节点在向后移动 ++$j; } /* 第i步 */ if($p == null){ #说明链表已经结束,不存在i节点,过滤掉i大于链表长度的情况,以为若i大于链表长度,则上面循环会跳出直接进入判断然后返回 echo "不存在i节点" ; exit; }else{ /* 标准的删除语句 */ $q = $p->next; $p->next = $q->next; $e = $q->data; unset($q); return $e; } } /** * TODO:删除整张链表 * @param $list object 待插入的链表 */ public function DeleteAll($list){ if($list == null ){ echo "输入参数不合法"; exit; } $p = $list->next; #设置p指向第一个节点 while($p != null ){ $q = $p->next; #保证节点在向后移动 unset($p); $p = $q; } } /** * Question1:输出倒数第K个节点 * @param $head object 链表 * @param $k int 序号 */ function FindKthToTail($head, $k){ /* 如果链表为空或者k不合法 返回null */ if($head == null || $k<=0){ return null; } /* 这里采用了复杂度为O(n)的算法,需要准备两个节点 */ $behind = $head; #指向链表的第一个节点 /* 算法思路:准备两个指针,假如第一个指针走到n-1(即链表末尾),第二个指针走到倒数k的位置,两者之间相差(n-1)-(n-k) = k-1 */ for($i=0;$i<$k-1;$i++){ /* 让第一个指针先走k-1个单位,如果不为空,则指针向后移动 */ /* 注意:这里有一个隐藏的条件,就是链表的长度有可能小于k,我们不不遍历完整个链表是无法知道其长度的 */ if($head->next != null){ $head = $head->next; }else{ return ; } } /* 当第一个指针走到k-1且还不为空,这时让第二个指针开始走,当第一个指针走到n-1的时候,第二个指针也走到了倒数第k的位置,即所求 */ while($head->next != null){ $head = $head->next; $behind = $behind->next; } return $behind; } /** * Question2:反转链表 * @param $head object 链表 */ public function ReverseList($pHead){ /* 如果链表为空,返回null */ if($pHead == null){ return null; } $pre = $pHead; #前一节点 ,这里是根节点 $cur = $pre->next; #当前节点 2 例:1->2->3 $next = null; #后一节点 /* 链表存在且不为空 */ while(!$cur){ $next = $cur->next; #用一个变量暂时存储后一节点,因为一旦前面反转,就断链了 $cur->next = $pre; #将前一节点作为当前节点的后一节点,是为反转 #指针后移 $pre = $cur; $cur = $next; } return $pre;}}$object = new SingleLinkList();$result = (new SingleLinkList)->headInsert(4);$pre = $object->ReverseList($result);//$behind = $object->FindKthToTail($result,1);// $e = $object->readIThNode($result,2);// echo $e;// $newNode = new Node(6,null);// $newList = $object->Insert($result,2,$newNode);// $e = $object->Delete($result,2); echo "";// print_r($result);print_r($pre);