數據結構——鏈表


1.什么是鏈表

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

2.節點

節點維護變量data和next,分別用於存儲數據和指向下一個節點。

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#:

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即可

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. 其他

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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM