利用PHP实现常用的数据结构之链表(小白系列文章五)

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);

你可能感兴趣的:(链表,小白,数据结构与算法,php)