利用PHP實現經常使用的數據結構之鏈表(小白系列文章五)

tips:由於涉及指針,咱們用引用來模擬,因此讀者應該有面向對象的知識貯備。

引子

你能夠把鏈表簡單理解爲動態數組,它不須要一塊一塊的開闢空間,同時,你又要注意,它存在的主要意義或者說使用場景主要是」指針功能「,它可以指來指去,對一些應用特別是內存管理起到了關鍵做用。php

引文

由於涉及內存,經常會有一些程序的邊界限制,須要programer擁有必定嚴密的邏輯去保證代碼的魯棒性和健壯性,因此這個知識點是面試的常考點。下面咱們看看PHP的單鏈表實現(附常考題目實現):面試

<?php

/**
 *  PHP 單鏈表
 *  author:entner
 *  time  :2017-8-14
 *    email :1185087164@qq.com
 */


/**
 * TODO:構建鏈表節點
 */
Class Node{
    public $data;
    public $next;

    public function __construct($val,$nex){
        $this->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 "<pre>";
// print_r($result);
print_r($pre);
相關文章
相關標籤/搜索