保證一週更兩篇吧,以此來督促自己好好的學習!代碼的很多地方我都給予了詳細的解釋,幫助理解。好了,幹就完了~加油!
聲明:本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
若有還可以改進、優化的地方,還請小夥伴們批評指正!