鏈表是一種物理存儲結構上非連續、非順序的存儲結構,數據元素的邏輯順序是經過鏈表中的指針連接次序實現的。node
節點維護變量data和next,分別用於存儲數據和指向下一個節點。python
C#:數組
class Node<T> { private T data; private Node<T> next; public T Data { get { return data; } set { data = value; } } public Node<T> Next { get { return next; } set { next = value; } } public Node(T item) { data = item; } public Node(T item, Node<T> nextNode) { data = item; next = nextNode; } }
Python:數據結構
class Node: def __init__(self, data, next=None): self.data = data self.next = next def __repr__(self): return str(self.data)
正式構建一個鏈表類函數
鏈表類應該具備如下屬性或方法:性能
count | 元素數量 |
head | 頭節點指針 |
insert | 向指定位置插入元素 |
get/set | 獲取/修改節點值 |
remove | 刪除指定元素 |
定義好一個類:優化
C#:spa
class LinkList<T>: { private Node<T> head;//頭節點 private int count;//計數器 }
Python:指針
class LinkList: def __init__(self): self._head = None self.count = 0
因爲咱們維護了一個屬性,因此直接返回count便可code
class LinkList<T>: { ... public int Count { get { return count; } } }
在Python中咱們重寫__len__方法
def __len__(self): return self.count
類比索引功能,設定頭節點位置爲0,下一節點位置+1
實現一個函數,返回指定位置的Node對象,兩種直觀解決思路:
迭代,經過一個while或循環來遍歷至指定位置
遞歸,鏈表是一種自然適合遞歸的數據結構,經過遞歸也能夠輕鬆找到目標節點
C#(迭代實現):
private Node<T> GetIndexNode(int index) { //獲取指定索引的Node對象 if (index > count) { throw new IndexOutOfRangeException(); } Node<T> target = head; for (int i = 0; i < index; i++) { target = target.Next; } return target; }
Python(遞歸實現):
def _getnode(self, index: int): # 返回指定索引的節點 def getnode(node: Node, offset: int): '''遞歸函數 :param node: 節點 :param offset: 偏移量 :return: ''' if not node or offset < 0: raise IndexError('超出索引範圍') if offset == 0: return node return getnode(node.next, offset-1) return getnode(self._head, index)
Tips:使用遞歸實現可能會出現棧溢出,python默認調用棧容量1000
向位置i插入一個節點,只要找到目標位置前一個節點(i-1),把i-1節點的next指向新節點,再把新節點的next節點指向i+1節點便可
特別要注意若是向0位置插入節點,要修改head的指向
C#:
public void Insert(T item, int i) { if (i == 0) { Node<T> newNode = new Node<T>(item,head); head = newNode; } else { Node<T> preNode = GetIndexNode(i - 1); Node<T> newNode = new Node<T>(item, GetIndexNode(i)); preNode.Next = newNode; } count++; }
Python:
def insert(self, item, index): if index > self.size: raise IndexError if index == 0: node = Node(item, self._head) self._head = node else: prev = self._getnode(index - 1) node = Node(item, prev.next) prev.next = node self.count += 1
前面已經實現了查找節點的訪問,如今只用提供一個接口get/set節點存儲的值便可
C#:
public T GetItem(int i) { return GetIndexNode.Data; }
Python:
def __getitem__(self, item): if not isinstance(item,int): raise IndexError return self._getnode(item).data def __setitem__(self, key, value): if not isinstance(key,int): raise IndexError node = self._getnode(key) node.data = value
實現pop和remove兩個方案,分別使用索引位置和節點值來刪除一個節點
C#:
public T Pop(int i) { if (i == 0) { Node<T> target = head; head = target.Next; count--; return target.Data; } T response = GetIndexNode(i).Data; Node<T> preNode = GetIndexNode(i - 1); Node<T> nextNode = GetIndexNode(i).Next; preNode.Next = nextNode; count--; return response; } public void Remove(T item) { if (head.data = item) { head = head.next; } else { Node<T> prev = Node(None,head); while (prev.next != None) { Node<T> cur = prev.next; if (cur.data == item) { prev.next = cur.next; break; } } count--; }
Python:
def pop(self, index: int): # 根據索引位置刪除鏈表節點 prev = self._getnode(index - 1) target = prev.next prev.next = target.next self.count -= 1 return target.data def remove(self, item): # 根據節點值刪除鏈表節點 prev = Node(None, self._head) while prev.next != None: if prev.next.data == item: self.count -= 1 prev.next = prev.next.next return prev = prev.next
實際上,咱們實現的增刪改查大多基於GetIndexNode方法,時間複雜度隨元素的位置而定,若是操做在鏈表頭,那麼時間複雜度O(1),若是操做在鏈表尾,時間複雜度則O(n),總體來看增刪改查的時間複雜度均爲O(n)。
與數組對比,因爲數組是一個在內存連續存放的數據結構,因此數組支持隨機訪問(任意索引訪問時間複雜度均爲O(1))。在隨機讀寫性能上數組會比鏈表有更好的表現,可是鏈表也有優勢,鏈表是一個實現了自動擴容的數據結構,咱們徹底能夠不去關心一個鏈表能容納多少元素,而數組則每每來經過擴容來實現動態數組,也會形成空間和時間的浪費。
實際上,鏈表通常只在尾部添加元素,咱們徹底能夠再維護一個foot變量指向鏈表尾來優化效率。
上面咱們實現了一個最基礎的鏈表,實際上鍊表有不一樣的許多形式,還有雙向鏈表、循環鏈表等等,這些鏈表都是基於單向鏈表發展出來的,有興趣的朋友能夠親自一一實現。