數據結構——鏈表

1.什麼是鏈表

 鏈表是一種物理存儲結構上非連續、非順序的存儲結構,數據元素的邏輯順序是經過鏈表中的指針連接次序實現的。node

2.節點

節點維護變量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)

3.鏈表類

正式構建一個鏈表類函數

鏈表類應該具備如下屬性或方法:性能

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

3.1 獲取鏈表元素個數

因爲咱們維護了一個屬性,因此直接返回count便可code

class LinkList<T>:
{
    ...
    public int Count { get { return count; } }
}

在Python中咱們重寫__len__方法

def __len__(self):
    return self.count

3.2 獲取指定位置節點

類比索引功能,設定頭節點位置爲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

3.3 向指定位置插入一個節點

向位置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

3.4 訪問/修改一個節點的值

前面已經實現了查找節點的訪問,如今只用提供一個接口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

3.5 刪除節點

實現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

4 性能分析和優化思路

實際上,咱們實現的增刪改查大多基於GetIndexNode方法,時間複雜度隨元素的位置而定,若是操做在鏈表頭,那麼時間複雜度O(1),若是操做在鏈表尾,時間複雜度則O(n),總體來看增刪改查的時間複雜度均爲O(n)。

與數組對比,因爲數組是一個在內存連續存放的數據結構,因此數組支持隨機訪問(任意索引訪問時間複雜度均爲O(1))。在隨機讀寫性能上數組會比鏈表有更好的表現,可是鏈表也有優勢,鏈表是一個實現了自動擴容的數據結構,咱們徹底能夠不去關心一個鏈表能容納多少元素,而數組則每每來經過擴容來實現動態數組,也會形成空間和時間的浪費。

實際上,鏈表通常只在尾部添加元素,咱們徹底能夠再維護一個foot變量指向鏈表尾來優化效率。

5. 其餘

上面咱們實現了一個最基礎的鏈表,實際上鍊表有不一樣的許多形式,還有雙向鏈表、循環鏈表等等,這些鏈表都是基於單向鏈表發展出來的,有興趣的朋友能夠親自一一實現。

相關文章
相關標籤/搜索