Python數據結構與算法(六、鏈表)

保證一週更兩篇吧,以此來督促自己好好的學習!代碼的很多地方我都給予了詳細的解釋,幫助理解。好了,幹就完了~加油!
聲明:本python數據結構與算法是imooc上liuyubobobo老師java數據結構的python改寫,並添加了一些自己的理解和新的東西,liuyubobobo老師真的是一位很棒的老師!超級喜歡他~
如有錯誤,還請小夥伴們不吝指出,一起學習~
No fears, No distractions.

鏈表(LinkList)

數據存儲在“節點(Node)”中,
節點包含的內容:數據和下一個節點的指針
鏈表額增刪改查的時間複雜度全部是O(n)級別,但是在鏈表頭的操作的時間複雜度是O(1)的
優點:真正的動態,不需要考慮容量的問題
缺點:喪失了隨機訪問的能力,因爲每一個節點通過next指針穿插起來,不是連續存儲的

和數組對比

數組最好用於索引有語意的情況。例如scores[2]
是線性結構
數組最大的優點:支持快速查詢

鏈表不適合用於索引有語意的情況
不是線性結構
鏈表最大的優點:真正的動態,不浪費存儲空間

實現

# -*- coding: utf-8 -*-
# Author:           Annihilation7
# Data:             2018-09-27
# Python version:   3.6

class Node:
    def __init__(self, elem_=None, next_=None):
        """
        節點類構造函數
        :param elem_: 節點所帶的元素,默認爲None
        :param next_: 指向下一個節點的標籤(在python中叫做標籤)
        """
        self.elem = elem_
        self.next = next_  # 都是共有的

    def printNode(self):
        """打印Node"""
        print(self.elem, end='  ')  # 就打印一下元素的值


class LinkedList:
    def __init__(self):
        """
        鏈表構造函數
        """
        self._dummyhead = Node()        # 虛擬頭結點,作用巨大,把他當成不屬於鏈表的哨兵節點就好
        # 如果沒有dummyhead,在鏈表頭部插入與刪除操作將要特殊對待,因爲找不到待操作節點的前一個節點
        # 而有了虛擬頭結點後,就不存在這種情況了
        self._size = 0       # 容量

    def getSize(self):
        """
        獲得鏈表中節點的個數
        :return: 節點個數
        """
        return self._size

    def isEmpty(self):
        """
        判斷鏈表是否爲空
        :return: bool值,空爲True
        """
        return self.getSize() == 0

    def add(self, index, elem):
        """
        普適性的插入功能
        時間複雜度:O(n)
        :param index: 要插入的位置(注意index對於用戶來說也是從零開始的,這裏我沒做更改)
        :param elem: 待插入的元素
        """
        if index < 0 or index > self._size:     # 有效性檢查
            raise Exception('Add failed. Illegal index')
        prev = self._dummyhead      # 從虛擬頭結點開始,注意虛擬頭結點不屬於鏈表內的節點,當做哨兵節點來看就好了
        for i in range(index):      # 往後擼,直到待操作節點的前一個節點
            prev = prev.next
        prev.next = Node(elem, prev.next)
        # 先看等式右邊,創建了一個節點對象,攜帶的元素是elem,指向的元素就是index處的節點,即
        # 現在有一個新的節點指向了index處的節點
        # 並將它賦給index節點處的前一個節點的next,是的prev的下一個節點就是這個新節點,完成拼接操作
        # 可以分解成三句話:  temp = Node(elem); temp.next = prev.next; prev.next = temp
        # 畫個圖就很好理解啦
        self._size += 1 # 維護self._size

    def addFirst(self, elem):
        """
        將elem插入到鏈表頭部
        時間複雜度:O(1)
        :param elem: 要插入的元素
        """
        self.add(0, elem)       # 直接點用self.add

    def addLast(self, elem):
        """
        鏈表尾部插入元素elem
        時間複雜度:O(n)
        :param elem: 待插入的元素
        """
        self.add(self._size, elem)      # 調用self.add

    def remove(self, index):
        """
        刪除第index位置的節點
        時間複雜度:O(n)
        :param index: 相應的位置,注意從零開始
        :return: 被刪除節點的elem成員變量
        """
        if index < 0 or index >= self.getSize():    # index合法性檢查
            raise Exception('Remove falied. Illegal index')
        pre = self._dummyhead       # 同樣的,要找到待刪除的前一個節點,所以從dummyhead開始
        for i in range(index):      # 往後擼index個節點
            pre = pre.next
        retNode = pre.next          # 此時到達待刪除節點的前一個節點,並用retNode對待刪除節點進行標記,方便返回elem
        pre.next = retNode.next     # pre的next直接跨過待刪除節點直接指向待刪除節點的next,畫個圖就很好理解了
        retNode.next = None         # 待刪除節點的next設爲None,讓它完全從鏈表中脫離,使得其被自動回收
        self._size -= 1             # 維護self._size
        return retNode.elem         # 返回被刪除節點的elem成員變量

    def removeFirst(self):
        """
        刪除第一個節點(index=0)
        時間複雜度:O(1)
        :return: 第一個節點的elem成員變量
        """
        return self.remove(0)   # 直接調用self.add方法

    def removeLast(self):
        """
        刪除最後一個節點(index=self._size-1)
        時間複雜度:O(n)
        :return: 最後一個節點的elem成員變量
        """
        return self.remove(self.getSize() - 1)

    def removeElement(self, elem):
        """
        刪除鏈表的指定元素elem,這個方法實現的是將鏈表中爲elem的Node全部刪除哦,與數組只刪除最左邊的第一個是不一樣的!如果elem不存在我們什麼也不做
        :param elem: 待刪除的元素elem
        時間複雜度:O(n)
        """
        pre = self._dummyhead             # 老方法,被刪除元素的前一個記爲pre
        while pre.next:                   # 只要pre的next不爲空
            if pre.next.elem == elem:     # pre的next的elem和elem相等
                delNode = pre.next        # 記下pre的next的節點,準備略過它
                pre.next = delNode.next   # 略過pre.next直接將pre.next置爲pre.next.next
                delNode.next = None       # delNode的next置爲空,被當成垃圾回收
                self._size -= 1           # 維護self._size
                # 注意此時不要pre = pre.next,因爲這時候pre的next又是一個新的元素!也需要進行判斷的,所以刪除的是所有攜帶值爲elem的節點
            else:
                pre = pre.next            # 不相等就往後擼就完事了


    def get(self, index):
        """
        獲得鏈表第index位置的值
        時間複雜度:O(n)
        :param index: 可以理解成索引,但並不是索引!
        :return: 第index位置的值
        """
        if index < 0 or index >= self.getSize():    # 合法性檢查
            raise Exception('Get failed.index is Valid, index require 0<=index<=self._size-1')
        cur = self._dummyhead.next      # 初始化爲第一個有效節點
        for i in range(index):          # 執行index次
            cur = cur.next              # 往後擼,一直到第index位置
        return cur.elem

    def getFirst(self):
        """
        獲取鏈表第一個節點的值
        時間複雜度:O(1)
        :return: 第一個節點的elem
        """
        return self.get(0)      # 調用self.add方法

    def getLast(self):
        """
        獲取鏈表最後一個節點的值
        時間複雜度:O(n)
        :return: 最後一個節點的elem
        """
        return self.get(self.getSize() - 1)

    def set(self, index, e):
        """
        把鏈表中第index位置的節點的elem設置成e
        時間複雜度:O(n)
        :param index: 鏈表中要操作的節點的位置
        :param e:  將要設爲的值
        """
        if index < 0 or index >= self.getSize():        # 合法性檢查
            raise Exception('Set failed.index is Valid, index require 0<=index<=self._size-1')
        cur = self._dummyhead.next  # 從第一個元素開始,也就是dummyhead的下一個節點
        for i in range(index):      # 往後擼,直到要操作的節點的位置
            cur = cur.next
        cur.elem = e        # 設置成e即可

    def contains(self, e):
        """
        判斷鏈表的節點的elem中是否存在e
        時間複雜度:O(n)
        由於並不存在索引,所以只能從頭開始找,一直找。。。如果到尾還沒找到就是不存在
        :param e: 要判斷的值
        :return: bool值,存在爲True
        """
        cur = self._dummyhead.next  # 將cur設置成第一個節點
        while cur != None:      # 只要cur有效,注意這個鏈表的最後一個節點一定是None,因爲dummyhead初始化時next就是None,這個
            # 通過鏈表的這些方法只會往後移動,一直處於最末尾
            if cur.elem == e:   # 如果相等就返回True
                return True
            cur = cur.next      # 否則就往後擼
        return False            # 到頭了還沒找到就返回False

    def printLinkedList(self):
        """對鏈表進行打印操作"""
        cur = self._dummyhead.next
        print('表頭:', end=' ')
        while cur != None:
            cur.printNode()
            cur = cur.next
        print('\nSize:  %d' % self.getSize())

三、測試

import linkedlist       # Linkedlist寫在這個py文件中
import numpy as np
np.random.seed(7)

test = linkedlist.LinkedList()
print(test.getSize())
print(test.isEmpty())
test.addFirst(6)
for i in range(13):
    test.addLast(np.random.randint(11))
test.printLinkedList()
test.add(10, 'annihilation7')
test.printLinkedList()
print(test.getSize())
print(test.get(2))
print(test.getLast())
print(test.getFirst())
test.set(0, 30)
test.printLinkedList()
print(test.contains(13))
print(test.remove(8))
test.printLinkedList()
print(test.removeFirst())
test.printLinkedList()
print(test.removeLast())
test.printLinkedList()

print('刪除全部爲7的元素:')
test.removeElement(7)
test.printLinkedList()
print(test.getSize())

四、輸出

0
True
表頭: 6  4  9  6  3  3  7  7  9  7  8  9  10  10
Size:  14
表頭: 6  4  9  6  3  3  7  7  9  7  annihilation7  8  9  10  10
Size:  15
15
9
10
6
表頭: 30  4  9  6  3  3  7  7  9  7  annihilation7  8  9  10  10
Size:  15
False
9
表頭: 30  4  9  6  3  3  7  7  7  annihilation7  8  9  10  10
Size:  14
30
表頭: 4  9  6  3  3  7  7  7  annihilation7  8  9  10  10
Size:  13
10
表頭: 4  9  6  3  3  7  7  7  annihilation7  8  9  10
Size:  12
刪除全部爲7的元素:
表頭: 4  9  6  3  3  annihilation7  8  9  10
Size:  9
9

五、典型習題

習題(leetcode-203)

Remove all elements from a linked list of integers that have value val.
Example:
Input: 1->2->6->3->4->5->6, val = 6
Output: 1->2->3->4->5
就是刪除鏈表中值爲val的所有節點。
其實就是我們上面實現的removeElement方法,很簡單~~~~

解決方案

# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, x):
#         self.val = x
#         self.next = None
class Solution:
    def removeElements(self, head, val):
        """
        :type head: ListNode
        :type val: int
        :rtype: ListNode
        """
        dummyhead = ListNode(-1)        # 創建虛擬頭結點,此時就不需要針鏈表頭的刪除激進行特殊的處理了,非常方便
        dummyhead.next = head

        pre = dummyhead
        while pre.next:				# pre下一個Node有效
            if pre.next.val == val:
                pre.next = pre.next.next        # 等於val就略過下一個節點
            else:
                pre = pre.next      # 否則往後擼
        return dummyhead.next       # 注意dummyhead.next纔是鏈表頭

六、總結

鏈表的所有操作只有在表頭的操作其時間複雜度纔是O(1)的,正好與數組相反,數組只有在尾部的操作的時間複雜度才O(1)的。
鏈表和數組既然在功能上如此相近,在性能上誰優誰劣?請看後面的章節吧O(∩_∩)O

若有還可以改進、優化的地方,還請小夥伴們批評指正!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章