1. 程式人生 > >第4章:資料和連結串列結構

第4章:資料和連結串列結構

  • 資料結構是表示一個集合中包含的資料的一個物件
  • 陣列資料結構
    • 陣列是一個數據結構
      • 支援按照位置對某一項的隨機訪問,且這種訪問的時間是常數
      • 在建立陣列時,給定了用於儲存資料的位置的一個數目,並且陣列的長度保持固定
      • 插入和刪除需要移動資料元素
      • 建立一個新的、較大的或較小的陣列,可能也需要移動資料元素
    • 支援的操作
      • 在給定位置訪問或替代陣列的一個項
      • 檢視陣列的長度
      • 獲取陣列的字串表示
    • 陣列操作及 Arrary 類方法

使用者的陣列操作

Array類中的方法

a = Array(10)  

__init__( capacity, fillValue = None ) 

len(a) 

__len__() 

str(a) 

__str__() 

for item in a: 

__iter__() 

a[index] 

__getitem__(index) 

a[index] = newItem 

__setitem__( index, newItem ) 

  • 程式碼

#!/usr/bin/env python

# -*- coding:utf-8 -*-

# Author:Lijunjie

   

"""

File: arrays.py

   

An array is like a list, but the client can use only [], len, iter, and str

   

To instantiate, use

   

<Varialbe> = Array( <capacity>, <optional fill value> )

   

The fill value is None by default.

"""

   

class Array( object ):

""" Represents an array """

 

def __init__( self, capacity, fillValue = None ):

""" Capacity is the static size of the array. fillValue is placed at each position. """

self._items = list()

for count in range( capacity ):

self._items.append( fillValue )

 

def __len__( self ):

""" -> The capacity of the array. """

return len( self._items )

 

 

def __str__( self ):

""" -> The string representation of the array """

return str( self._items )

 

 

def __iter__( self ):

"""Supports traversal with for loop."""

return iter( self._items )

 

 

def __getitem__( self, index ):

""" Subscript operator for access at index. """

return self._items[ index ]

 

 

def __setitem__( self, index, newItem ):

"""Subscript operator for replacement at index."""

self._items[index] = newItem

  

  • 隨機訪問與連續記憶體
    • 陣列索引是一個隨機訪問操作,這種訪問的時間是常數
    • 計算機為陣列項分配的是一段連續的記憶體單元
      •    

    • Python陣列索引操作的步驟
      • 獲取陣列記憶體塊的基本地址
      • 給這個地址加上索引,返回最終的結果
    • 常量時間的隨機訪問,可能是陣列最想要功能,但是這要求陣列必須用一段連續的記憶體來表示,這樣會導致陣列實現其它操作時,需要付出代價
  • 靜態記憶體與動態記憶體
    • 可以根據應用程式的資料需求來調整陣列長度
      • 在程式開始的時候建立一個具有合理預設大小的陣列
      • 當陣列大小不能儲存更多的資料時,建立一個新的,更大的陣列,並且從原陣列轉移資料項
      • 當陣列似乎存在浪費記憶體的時候,以一種類似的方式減小陣列的長度
  • 物理大小與邏輯大小
    • 物理大小
      • 陣列單元的總數,或者指建立陣列時,用來指定其容量的數字
    • 邏輯大小
      • 陣列當前可供應用程式使用的項的數目
  • 陣列的操作
    • 資料設定

DEFAULT_CAPACITY = 5

logicalSize = 0

a = Array[DEFAULT_CAPACITY]

  • 增加陣列的大小
    • 步驟
      • 建立一個新的,更大的陣列
      • 將舊的陣列複製到新的陣列
      • 將舊的陣列變數重新設定為新的陣列物件
    • 程式碼

if logicalSize == len( a ):

temp = Array( len( a ) + 1 )

for i in range( logicalSize ):

temp[i] = a[i]

a = temp

  • 時間效能
    • 當給陣列新增 n 項時,其整體效能:
    • 每次增加陣列大小時,可以將陣列的大小加倍,以提升時間效能,但是是以一定的記憶體浪費為代價的

temp = Array( len( a ) * 2 )

  • 空間效能
    • 線性
  • 減小陣列的大小
    • 步驟
      • 建立一個新的,更小的陣列
      • 將舊的陣列複製到新的陣列中
      • 將舊陣列變數設定為新的陣列物件
    • 程式碼
      • 觸發及操作
        • 陣列的邏輯大小小於或等於其物理大小的四分之一,並且物理大小至少是建立陣列時的預設容量兩倍的時候,將陣列的大小減小至其原來的二分之一

if logicalSize <= len(a) // 4 and len(a) >= DEFAULTCAPACTIY * 2:

temp = Array( len(a) // 2 )

for i in range( logicalSize ):

temp[i] = a[i]

a = temp

  • 在陣列中插入一項
    • 步驟
      • 檢查可用空間,以判斷是否需要增加陣列大小
      • 從陣列的邏輯未尾開始,直到目標索引,每一項向後移動一個單元
      • 將新的項賦值給目標索引位置
      • 邏輯大小增加1
    • 程式碼

#Increase physical size of array if necessary

   

#shift items by one position

while index in range( logicalSize, targetIndex, -1 ):

a[index] = a[index-1]

 

#Add new item, and increase logical size

a[targetIndex] = newItem

logicalSize += 1 

  • 插入操作是線性的
  • 從陣列中刪除一項
    • 步驟
      • 從緊跟目標索引的位置開始,直至邏輯未尾,將每一項都往前移一位
      • 將邏輯大小減1
      • 檢查浪費空間,看是否有必要,更改陣列的物理大小
    • 程式碼

#shift items by one position

while index in range( targetIndex, logicalSize ):

a[index] = a[index+1]

 

#decrease logical size

logicalSize -= 1

   

#decrease physical size of array if necessary  

  • 時間效能為線性
  • 複雜度權衡:時間、空間和陣列
    • 陣列操作的執行時間

操作

執行時間

從第 i 個位置訪問

O(1),最好情況和最壞情況

在第 i 個位置訪問

O(1),最好情況和最壞情況

在邏輯未尾插入

O(1),平均情況

在邏輯未尾刪除

O(1),平均情況

在第 i 個位置插入

O(n),平均情況

在第 i 個位置刪除

O(n),平均情況

增加容量

O(n),最好情況和最差情況

減小容量

O(n),最好情況和最差情況

  • 裝載因子
    • 陣列的裝載因子等於其邏輯大小除以物理大小
  • 二維陣列 - - 網格
    • 使用陣列的陣列可以表示網格
      • 頂層的陣列長度等於網格中行的數目,頂層陣列的每一個單元格也是陣列,陣列的長度即為網格中列的長度
      • 要支援使用者使用雙下標,需要使用 __getitem__方法
    • Grid 類的定義

#!/usr/bin/env python

# -*- coding:utf-8 -*-

# Author:Lijunjie

   

"""

Defines a two-dimensional array

"""

   

from arrays import Array

   

class Grid( object ):

"""Represent a two dimensional array."""

 

def __init__( self, rows, columns, fillValue = None ):

self._data = Array( rows )

for row in range( rows ):

self._data[row] = Array( columns, fillValue )

 

 

def getHeight( self ):

"""Return the number of rows"""

return len( self._data )

 

 

def getWidth( self ):

"""Return the number of columns."""

return len( self._data[0] )

 

 

def __getitem__( self, index ):

"""Supports two-dimensional indexing with [row][column]"""

return self._data[index]

 

 

def __setitem__( self, index, newItem ):

"""Supoort two dimensional replacement by index."""

self._data[index] = newItem

 

 

def __str__( self ):

"""Return a string represention of a grid."""

result = ""

for row in range( self.getHeight() ):

for column in range( self.getWidth() ):

result += str( self._data[row][column] ) + " "

result += "\n"

 

return result

  • 雜亂的網格和多維陣列
    • 雜亂的網格有固定的行,但是每一行中列的數目不同
    • 需要時候,可以在網格的定義中新增維度
  • 連結串列結構
    • 連結串列結構是一個數據結構,它包含0個或多個節點。一個節點包含了一個數據項,以及到其它節點的一個或多個連結
    • 單鏈表結構和雙鏈表結構
      • 單鏈表結構的節點包含了一個數據項和到下一個節點的一個連結。雙鏈表結構中的結點還包括了到前一個節點的一個連結。
      • 單鏈表結構示意圖
      • 雙鏈表結構示意圖
      • 與陣列對比
        • 和陣列相同,連結結構表示了項的線性序列
        • 但是連結串列結構無法通過指定索引,立即訪問某一項。而是必須從結構的一段開始,沿著連結串列進行,直到達到想要的位置( 或項
        • 插入和刪除與陣列有很大不同
          • 一旦找到插入點或刪除點,就可以進行刪除與插入操作,而不需要在記憶體中移動資料項
          • 在每一次插入和刪除的過程中,連結串列結構會調整大小,並不需要額外的記憶體代價,也不需要複製資料項
    • 非連續性記憶體和節點
      • 陣列中的項必須儲存在連續的記憶體中,即陣列中項的邏輯順序是和記憶體中的物理單元序列緊密耦合的。
      • 連結串列結構將結構中的項的邏輯順序和記憶體中的順序解耦了,即計算機只要遵循連結串列結構中一個給定項的地址和位置的連結,就能在記憶體中找到它的單元在何處。這種記憶體表示方案,即叫做非連續性記憶體
      • 連結串列結構中的基本單元表示的是節點
        • 單鏈表結點
        • 雙鏈表結點
    • 單鏈表結點類
      • 類程式碼

#!/usr/bin/env python

# -*- coding:utf-8 -*-

# Author:Lijunjie

   

"""

linked node structure

"""

   

class Node( object ):

"""Represent a singly linked node."""

 

def __init__( self, data, next = None ):

"""Instantiates a Node with default next of None."""

self.data = data

self.next = next

  • 測試程式碼

#!/usr/bin/env python

# -*- coding:utf-8 -*-

# Author:Lijunjie

   

"""

File: testnode.py

   

Tests the Node class

"""

   

from node import Node

   

head = None

   

#Add five nodes to the beginning of the linked structrue

for count in range(1,10):

head = Node( count, head )

 

#Print the contents of the structrue

while head != None:

print( head.data )

head = head.next

  • 程式碼分析
    • 插入的項總是位於結構的開始處
    • 顯示資料的時候,按照與插入項相反的順序出現
    • 顯示完資料後,head 指標指向了None,節點實際上從連結串列結構中刪除了,對於程式來說,節點不再可用,並會在下一次垃圾回收的時候回收
      • 為了避免這種情況,遍歷連結串列時,可以使用一個臨時指標

       

  • 單鏈表結構上的操作
    • 遍歷
      • 使用臨時指標,進行遍歷

probe = head

while probe != None:

#<use or modify probe.data>

probe = probe.next 

  • 遍歷在時間上是線性的,不需要額外的開銷
  • 搜尋
    • 一個連結串列的順序搜尋和遍歷類似,都是從第1個節點開始並且沿著連結,直到遇到哨兵
    • 搜尋給定的項

probe = head

while probe != None and targetItem != probe.data:

#<use or modify probe.data>

probe = probe.next

   

if probe != None:

<targetItem has been found>

else:

<targetItem is not in the linked structure> 

  • 對於單鏈表結構,順序搜尋是線性的
  • 訪問連結串列中的第 i

#Assums 0 <= index < n

probe = head

while index > 0:

probe = probe.next

index -= 1

return probe.data

  • 替換
    • 替換操作也需要使用遍歷模型,包括替換一個給定的項,或替換一個給定的位置
    • 替換給定的項

probe = head

while probe != None and targetItem != probe.data:

#<use or modify probe.data>

probe = probe.next

   

if probe != None:

probe.data = newItem

return True

else:

return False

  • 替換第 i

#Assums 0 <= index < n

probe = head

while index > 0:

probe = probe.next

index -= 1

probe.data = newItem 

  • 在開始處插入
    • 程式碼

head = Node( newItem, head ) 

  • 此操作的時間和記憶體都是常數
  • 在末尾插入
    • 考慮兩情況
      • head指標為 None, 此時,將head指標設定為新的節點
      • head指標不為 None,此時,程式碼將檢索最後一個節點,並將其next指標指向新的節點
    • 程式碼

newNode = Node( newItem )

if head is None:

head = newNode

else:

probe = head

while probe.next != None:

probe = probe.next

probe.next = newNode 

  • 從開始處刪除
    • 假設結構中至少有一個節點
    • 程式碼

#Assume at least one node in the structure

removedItem = head.data

head = head.next

return removedItem 

  • 從末尾處刪除
    • 假設至少有一個節點
    • 考慮兩種情況
      • 只有一個節點,head 指標設定為None
      • 在最後一個節點前有其它節點。程式碼搜尋倒數第2個節點,並將其 next 指標設定為None
    • 程式碼

#Assume at least one node in the structure

removedItem = head.data

if head.next is None:

head = None

else:

probe = head

while probe.next.next != None:

probe = probe.next

removedItem = probe.data

probe.next = None

return removedItem

  • 在任何位置插入
    • 在一個連結串列的第 i 個位置插入一項,必須先找到位置為 i - 1 ( i < n) 或者 n - 1 ( i >= n )的節點。然後,需要考慮如下兩種情況。此處,還需要考慮 head 為空或者插入位置小於等於0的情況
      • 該節點的 next 指標為 None,因此,應將該項放在連結串列結構的未尾
      • 該節點的 next 指標不為None,因此,直接將新的項放在位置 i - 1 和 i 的節點之間
    • 程式碼

if head is None or index <= 0:

head = Node( newItem, head )

else:

#Serach for node at position index - 1 or the last position

probe = head

while index > 1 and probe.next != None:

probe = probe.next

index -=1

#Insert new node after node at position index - 1 or last position

probe.next = Node( newItem, probe.next )

  • 從任意位置刪除
    • 從一個連結串列結構中刪除第 i 項,具有以下3種情況
      • i <= 0 — — 使用刪除第 1 項的程式碼
      • 0 < i < n — — 搜尋位於 i -1 位置的節點,刪除其後面的節點
      • i >= n — — 刪除最後一個節點
    • 程式碼

#Assume that the linked structure has at least one node

if index <=0 or head.next == None:

removeItem = head.data

head = head.next

return removeItem

else:

#Search for node at position next - 1 or the next to last position

probe = head

while index > 1 and probe.next.next != None

probe = probe.next

index -= 1

removeItem = probe.next.data

probe.next = probe.next.next

return removeItem 

  • 複雜度權衡:時間、空間和單