python資料結構學習筆記(七)

來源:互聯網
上載者:User

標籤:

 

  • 7 Linked Lists
    • 7.1 singly linked list
    • 7.2 circular linked lists
    • 7.4 the positional list ADT
    • 7.5 sorting a positional list
    • 7.6 鏈表與數組實現的序列比較
7 Linked Lists7.1 singly linked list

單向鏈表,就是一系列的結點組成一個線性集合。裡面每個結點儲存有一個元素的引用,還會指向鏈表的下一個元素. 最後一個元素指向None.

鏈表的第一個和最後一個結點稱為鏈表的頭部和尾部。從頭部開始不斷訪問下一個next可以遍曆這個鏈表。為了能訪問 鏈表,必須保證有頭部的引用。

鏈表的一個特點使可以很方便的在頭部和尾部增加新的結點。add_first在頭部增加一個新結點,並更新頭部指標。

add_first(L, e):    newest = Node(e)    newest.next = L.head    L.head = newest    L.size = L.size + 1

在尾部增加結點並更新tail

add_last(L, e):    newest = Node(e)    newest.next = None    L.tail.next = newest    L.tail = newest    L.size = L.size + 1

之前使用list實現的stack和queue在插入和刪除操作上有攤還O(1)的時間,但是在個別插入上可能時間比較長。 使用鏈表來實現則可以所有操作都達到O(1)的時間。

class Empty(Exception):    passclass LinkedStack:    class _Node:        """lightweight, nonpublic class for storing a singly linked node."""        __slots__ = ‘_element‘, ‘_next‘        def __init__(self, element, next):            self._element = element            self._next = next    def __init__(self):        self._head = None        self._size = 0    def __len__(self):        return self._size    def is_empty(self):        return self._size == 0    def push(self, e):        self._head = self._Node(e, self._head)        self._size += 1    def top(self):        if self.is_empty():            raise Empty(‘stack is empty‘)        return self._head._element    def pop(self):        if self.is_empty():            raise Empty(‘stack is empty‘)        answer = self._head._element        self._head = self._head._next        self._size -= 1        return answer

鏈表實現隊列的情況類似,需要注意的是隊列需要維護多一個成員_tail,因為隊列可以在末尾插入新的元素。 在空的隊列插入第一個元素的時候要注意同時還要修改_head來指向這第一個元素。同時如果刪除一個元素剛好 隊列為空白的時候也需要注意,修改_tail來指向None.

7.2 circular linked lists

將鏈表最後一個元素的next指向head,就可以形成一個環。

有時候在隊列中我們取出第一個元素,進行操作後又要放回隊列的末尾,實現不斷迴圈的重複操作,這時候 就可以使用迴圈鏈表來實現隊列。

隊列只需要維護一個tail引用,需要head的時候訪問tail.next即可。而需要取出隊列頭部的一個元素放到尾部的時候, 不需要真正的改變資料結構,只要將tail = tail.next就等於將第一個元素放到尾部。

下面用迴圈鏈表類比一個迴圈隊列:

class CircularQueue:    class _Node:        """lightweight, nonpublic class for storing a singly linked node."""        __slots__ = ‘_element‘, ‘_next‘        def __init__(self, element, next):            self._element = element            self._next = next    def __init__(self):        self._tail = None        self._size = 0    def __len__(self):        return self._size    def is_empty(self):        return self._size == 0    def first(self):            if self.is_empty():            raise Empty(‘queue is empty‘)        return head._element    def dequeue(self):        if self.is_empty():            raise Empty(‘queue is empty‘)        oldhead = self._tail._next        if self._size == 1:            self._tail == None        else:            self._tail._next = oldhead._next        self._size -= 1        return oldhead._element    def enqueue(self, e):        newest self._Node(e, None)        if self.is_empty():            newest._next = newest        else:            newest._next = self._tail._next            self._tail._next = newest        self._tail = newest        self._size += 1    def rotate(self):        if self._size > 0:            self._tail = self._tail._next

和普通隊列一樣,需要注意的是往空的隊列中插入第一個元素和刪除最後一個元素的情況。

7.4 the positional list ADT

前面的隊列只是支援在頭部或者尾部增刪元素,但是不能隨便在任意位置進行操作。對於鏈表來說,使用下標訪問 並不是那麼容易,因為很難通過一個下標來訪問一個結點,只能從頭部或者尾部開始逐個移動。

如果用一個結點的參考資料表示他的位置,則可以在特定位置前面或者後面插入元素,或者刪除某個特定結點。

下面重新抽象出一種隊列,可以根據引用來訪問特定位置的結點。定義positional list ADT。

前面的隊列中執行插入等是直接在提供給使用者對結點的操作,如果使用者給的參數結點出錯則可能造成無法預測的結果。 所以現在要想辦法將這些對結點的操作封裝起來,然後提供給使用者一些高層次的借口,比如在某個位置上的操作。

定義位置position為新的資料類型,p.element()返回這個位置上儲存的元素。然後之前list的操作中直接返回元素的都 改為只是返回一個position位置。 對於一個list L:

  • L.first(): 返回第一個元素的position,如果L為空白則返回None.
  • L.last()
  • L.before(p): 返回position p前一個位置的position,如果p是第一個位置則返回None.
  • L.after(p)
  • L.is_empty()
  • len(L)
  • iter(L): 返回一個遍曆elements的迭代器,如可以使用for e in L: process e等
  • L.add_first(e): 參數e是要插入的element,返回新元素的position
  • L.add_last(e)
  • L.add_before(p, e): 同樣返回position
  • L.add_after(p, e)
  • L.replace(p, e): 返回原來的element
  • L.delete(p): 返回被刪除的element

現在用position把元素封裝起來,要訪問第一個元素可以通過L.first().element()

遍曆整個list的方法:

cursor = data.first()while cursor is not None:    print cursor.element()    cursor = data.after(cursor)

因為cursor等於最後一個position的時候,after(cursor)返回None,所以用來作為判斷結束的條件。

為了判斷兩個position是否相等,給position加上__eq____ne__兩個方法。

  • 驗證position是否合法

為了方便判斷一個postion是否屬於一個list,在position中維護一個包含這個position的list的引用,還有 這個position指向的那個結點的引用。_validate函數接受一個position參數,判斷該position是否屬於當前list, 屬於則返回對應的結點node.

_make_position函數則通過一個結點得到對應的position

class Empty(Exception):    passclass _DoublyLinkedBase:    class _Node:        __slots__ = ‘_element‘, ‘_prev‘, ‘_next‘        def __init__(self, element, prev, next):            self._element = element            self._prev = prev            self._next = next    def __init__(self):        self._header = self._Node(None, None, None)        self._trailer = self._Node(None, None, None)        self._header._next = self._trailer        self._trailer._prev = self._header        self._size = 0    def __len__(self):        return self._size    def is_empty(self):        return self._size == 0    def _insert_between(self, e, predecessor, successor):        newest = self._Node(e, predecessor, successor)        predecessor._next = newest        successor._prev = newest        self._size += 1        return newest    def _delete_node(self, node):        predecessor = node._prev        successor = node._next        predecessor._next = successor        successor._prev = predecessor        self._size -= 1        element = node._element        node._prev = node._next = node._element = None        return elementclass PositionalList(_DoublyLinkedBase):    """a sequential container of elements allowing positional access."""    class Position:        def __init__(self, container, node):            self._container = container            self._node = node        def element(self):            return self._node._element        def __eq__(self, other):            return type(other) is type(self) and other._node is self._node        def __ne__(self, other):            return not (self == other)    def _validate(self, p):        if not isinstance(p, self.Position):            raise TypeError(‘p must be proper Position type‘)        if p._container is not self:            raise ValueError(‘p does not belong to this container‘)        if p._node._next is None:            raise ValueError(‘p is no longer valid‘)        return p._node    def _make_position(self, node):        if node is self._header or node is self._trailer:            return None        else:            return self.Position(self, node)    def first(self):        return self._make_position(self._header._next)    def last(self):        return self._make_position(self._trailer._prev)    def before(self, p):        node = self._validate(p)        return self._make_position(node._prev)    def after(self, p):        node = self._validate(p)        return self._make_position(node._next)    def __iter__(self):        cursor = self.first()        while cursor is not None:            yield cursor.element()            cursor = self.after(cursor)    def _insert_between(self, e, predecessor, successor):        node = _DoublyLinkedBase._insert_between(self, e, predecessor, successor)        return self._make_position(node)    def add_first(self, e):        return self._insert_between(e, self._header, self._header._next)    def add_last(self, e):        return self._insert_between(e, self._trailer._prev, self._trailer)    def add_before(self, p, e):        original = self._validate(p)        return self._insert_between(e, original._prev, original)    def add_after(self, p, e):        original = self._validate(p)        return self._insert_between(e, original, original._next)    def delete(self, p):        original = self._validate(p)        return self._delete_node(original)  # inherited method returns element    def replace(self, p, e):        """        :param p, e: replace the element at position p with e        :return: return the element formerly at position p.        """        original = self._validate(p)        old_value = original._element        original._element = e        return old_value
7.5 sorting a positional list

下面要使用插入排序的演算法來對positional list排序。回顧插入排序的演算法,從左邊第一個元素開始設定一個標誌 marker,表示包括marker左邊的都是已經有序的。然後每次將marker右邊一個元素加進來,逐個和marker前面的元素 比較,找到合適的位置並將其插入,直到整個list都是有序的。每次取marker右邊的一個元素為pivot,然後用一個變數 walk從marker開始向左遍曆,直到walk的前一個元素的值小於等於pivot的值(從小到大排序),最後將pivot插入到 walk和前一個元素之間。

def insertion_sort(L):    if len(L) > 1:        marker = L.first()        while marker != L.last():            pivot = L.after(marker)            value = pivot.element()            if value > marker.element():  # pivot is already sorted.                marker = pivot            else:                walk = marker                while walk != L.first() and L.before(walk).element() > value:                    walk = L.before(walk)                L.delete(pivot)                L.add_before(walk, value)
7.6 鏈表與數組實現的序列比較

數組序列的優勢

  • 數組可以在O(1)的時間實現下標訪問,相比這下鏈表就不能實現下標訪問,而訪問任意一個元素需要從頭開始遍曆尋找。
  • 實際操作中對某個位置的操作可能比鏈表要快,因為只要改變該位置儲存內容就行,而鏈表通常還要更新相鄰的指標
  • 使用的記憶體更少,最壞的情況是數組剛剛擴充的時候記憶體利用率只有一半,但是鏈表由於每個元素要多存一個或者兩個指標而需要兩到三倍的空間。

鏈表序列優勢

  • 鏈表的增刪查改執行時間是O(1),相比之下數組有些操作只是攤還時間為O(1)
  • 支援任意位置O(1)時間的插入刪除操作,只要給出相對位置的引用。而數組刪除某個位置的元素則比較慢,需要將後面的元素都向前移動一個位置。

python資料結構學習筆記(七)

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.