鏈表是一種物理存儲單元上非連續、非順序的存儲結構,數據元素的邏輯順序是經過鏈表中的指針連接次序實現的。鏈表由一系列結點(鏈表中每個元素稱爲結點)組成,結點能夠在運行時動態生成。每一個結點包括兩個部分:一個是存儲數據元素的數據域,另外一個是存儲下一個結點地址的指針域。php
相比於線性表順序結構,操做複雜。因爲沒必要須按順序存儲,鏈表在插入的時候能夠達到O(1)的複雜度,比另外一種線性表順序錶快得多,可是查找一個節點或者訪問特定編號的節點則須要O(n)的時間,而線性表和順序表相應的時間複雜度分別是O(logn)和O(1)。node
使用鏈表結構能夠克服數組鏈表須要預先知道數據大小的缺點,鏈表結構能夠充分利用計算機內存空間,實現靈活的內存動態管理。可是鏈表失去了數組隨機讀取的優勢,同時鏈表因爲增長告終點的指針域,空間開銷比較大。鏈表最明顯的好處就是,常規數組排列關聯項目的方式可能不一樣於這些數據項目在記憶體或磁盤上順序,數據的存取每每要在不一樣的排列順序中轉換。鏈表容許插入和移除表上任意位置上的節點,可是不容許隨機存取。鏈表有不少種不一樣的類型:單向鏈表,雙向鏈表以及循環鏈表。數組
連接方式存儲的線性表簡稱爲鏈表(Linked List)。
鏈表的具體存儲表示爲:
① 用一組任意的存儲單元來存放線性表的結點(這組存儲單元既能夠是連續的,也能夠是不連續的)數據結構
② 鏈表中結點的邏輯次序和物理次序不必定相同。爲了能正確表示結點間的邏輯關係,在存儲每一個結點值的同時,還必須存儲指示其後繼結點的地址(或位置)信息(稱爲指針(pointer)或鏈(link))測試
鏈式存儲是最經常使用的存儲方式之一,它不只可用來表示線性表,並且可用來表示各類非線性的數據結構。this
/** * 構建鏈表節點 */ Class Node { public $e; public $next; public function __construct($e = null, $next = null) { $this->e = $e; $this->next = $next; } }
單鏈表中每一個結點的存儲地址是存放在其前趨結點next域中,而開始結點無前趨,故應設頭指針head指向開始結點。鏈表由頭指針惟一肯定,單鏈表能夠用頭指針的名字來命名。spa
終端結點無後繼,故終端結點的指針域爲空,即NULL。指針
/** * 在鏈表頭添加新的元素e * @param $e */ public function addFirst($e) { $this->head = new Node($e, $this->head); $this->size ++; } /** * 在鏈表的index(0-based)位置添加新的元素e * 在鏈表中不是一個經常使用的操做,練習用: * @param $index * @param $e * @throws Exception */ public function add($index, $e){ if($index < 0 || $index > $this->size) throw new Exception("Add failed. Illegal index."); if($index == 0) { $this->addFirst($e); } else { $prev = $this->head; for($i = 0 ; $i < $index - 1 ; $i ++) { $prev = $prev->next; } // $node = new Node($e); // $node->next = $prev->next; // $prev->next = $node; $prev->next = new Node($e, $prev->next); $this->size ++; } }
這裏咱們能夠看到,在添加的時候在鏈表頭部插入與在鏈表指定索引位置插入,咱們採用不一樣的插入方式,爲了能夠採用共同的方法,咱們在下面引入虛擬頭節點,$dummyHead,咱們定義$dummyHead爲存在與head以前的節點,這樣head節點也能夠看成普通節點來處理,就不須要再單獨作處理。code
定義文件名爲dummyLink.php對象
/** * 構建鏈表節點 */ Class Node { public $e; public $next; public function __construct($e = null, $next = null) { $this->e = $e; $this->next = $next; } } class LinkedList { public $dummyHead; private $size; public function __construct() { $this->dummyHead = new Node(); $this->size = 0; } /** * 獲取鏈表中的元素個數 * @return int */ public function getSize() { return $this->size; } /** * 返回鏈表是否爲空 * @return bool */ public function isEmpty() { return $this->size == 0; } /** * 在鏈表的index(0-based)位置添加新的元素e * 在鏈表中不是一個經常使用的操做,練習用: * @param $index * @param $e * @throws Exception */ public function add($index, $e) { if ($index < 0 || $index > $this->size) throw new \Exception("Add failed. Illegal index."); $prev = $this->dummyHead; for ($i = 0; $i < $index; $i++) { $prev = $prev->next; } $prev->next = new Node($e, $prev->next); $this->size++; } /** * 在鏈表頭添加新的元素e * @param $e */ public function addFirst($e) { $this->add(0, $e); } /** * 在鏈表末尾添加新的元素e * @param $e * @throws Exception */ public function addLast($e) { $this->add($this->size, $e); } /** * 得到鏈表的第index(0-based)個位置的元素 * @param $index * @return mixed * @throws Exception */ public function get($index) { if ($index < 0 || $index >= $this->size) throw new \Exception("Get failed. Illegal index."); $cur = $this->dummyHead->next; for ($i = 0; $i < $index; $i++) $cur = $cur->next; return $cur->e; } /** * 得到鏈表的第一個元素 * @return mixed * @throws Exception */ public function getFirst() { return $this->get(0); } /** * 得到鏈表的最後一個元素 * @return mixed * @throws Exception */ public function getLast() { return $this->get($this->size - 1); } /** * 修改列表中某一個索引位置的值 * @param $index * @param $e * @throws Exception */ public function set($index, $e) { if ($index < 0 || $index >= $this->size) throw new \Exception("Set failed. Illegal index."); $cur = $this->dummyHead->next; for ($i = 0; $i < $index; $i++) $cur = $cur->next; $cur->e = $e; } /** * 查找鏈表中是否有元素e * @param $e * @return bool */ public function contains($e) { $cur = $this->dummyHead->next; while ($cur != null) { if ($cur->e == $e) { return true; } $cur = $cur->next; } return false; } /** * 從鏈表中刪除index(0-based)位置的元素, 返回刪除的元素 * @param $index * @return mixed */ public function remove($index){ if ($index < 0 || $index >= $this->size) throw new \Exception("Remove failed. Index is illegal."); $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; } /** * 從鏈表中刪除第一個元素, 返回刪除的元素 * @return mixed */ public function removeFirst(){ return $this->remove(0); } /** * 從鏈表中刪除最後一個元素, 返回刪除的元素 * @return mixed */ public function removeLast(){ return $this->remove($this->size - 1); } /** * 從鏈表中刪除元素e * @param $e */ public function removeElement($e){ $prev = $this->dummyHead; while($prev->next != null){ if($prev->next->e == $e) break; $prev = $prev->next; } if($prev->next != null){ $delNode = $prev->next; $prev->next = $delNode->next; $delNode->next = null; } } public function __toString() { $res = ''; for($cur = $this->dummyHead->next ; $cur != null ; $cur = $cur->next) $res .= $cur->e."->"; $res.="NULL".PHP_EOL; return $res; } }
$list = new LinkedList(); $arr = [3,6,4,9,8]; foreach ($arr as $v) { $list->addLast($v); } $list->add(2,15); //指定位置插入 var_dump($list->dummyHead); //查看對象形式的鏈表結構 echo $list.PHP_EOL; var_dump($list->get(2)); //獲取索引2位置的結果 $list->removeLast(); //移除最後一個元素 echo $list; //查看最後結果
測試結果:
在知乎上專門查詢了一下鏈表的應用,找到兩個比較好的整理以下
當數據量不大時(好比只有一萬個數據),順序表在全部方面的表現全都優於鏈表。就連在插入和刪除時也是如此。由於鏈表插入新的結點要構造對象,這是很是耗時的;而在刪除時,同於現代的計算機進行復制操做的效率極高,由於表現不比鏈表差。鏈表刪除時還要執行析構操做,因此會慢很多。當順序表長度大於必定的值時,插入和刪除操做速度就會變得不如鏈表。鏈表的缺點主要在於按元素序號隨機訪問時效率低下。一些其它數據結構,好比圖和樹,在形式上也相似鏈表。(固然也有基於順序表的實現)
相對於ArrayList,LinkedList插入是更快的。由於LinkedList不像ArrayList,不須要在數組裝滿的時候要將全部的數據從新裝入一個新的數組,這是ArrayList最壞的一種狀況,時間複雜度是O(n),而LinkedList中插入或刪除的時間複雜度僅爲O(1)。ArrayList在插入數據時還須要更新索引(除了插入數組的尾部)。 我以爲在如下場景LinkedList比ArrayList有優點:1) 你的應用不會隨機訪問數據。由於若是你須要LinkedList中的第n個元素的時候,你須要從第一個元素順序數到第n個數據,而後讀取數據。2) 你的應用更多的插入和刪除元素,更少的按索引讀取數據(若是隻是遍歷,區別不大)。由於插入和刪除元素不涉及重排數據,因此它要比ArrayList要快。