數據結構與算法全套精講(python版) (四)鏈表

1. 鏈表

首先要明白一點:之所以做元素互換時,只有python裏纔可以直接用 a,b=b,a   ——> 就是因爲做a=10時,a可以看做是一個輸入待定的函數,10並不是直接放入到了a裏面,二者只是一個指向關係

注:鏈表與之前的順序表對比

(因爲鏈表只能記錄頭節點,故要search到其中的元素,就要從頭開始遍歷往後找,故訪問元素的時間複雜度是O(n); 而順序表可以直接找到目標元素,故訪問元素的時間複雜度是O(1),但除了在尾部的情況外,做刪除或插入時,都要把操作點位置後的所有元素依次後移,故對應的爲O(n))

(1). 單鏈表 

class Node(object):
    def __init__(self,data):
        self.data = data
        self.next = None#指向待定

class SingleLinkList(object):#表頭只有data,表尾next爲None
    def __init__(self,node=None):#待接節點待定
        self.__head = node#頭節點爲私有屬性(必須雙下劃綫),限鏈表內使用

    def is_empty(self):
        return self.__head == None#頭空,則鏈表空

    def length(self):
        cur = self.__head#cur遊標,用來移動遍歷節點
        count = 0
        while cur != None:
            count += 1
            cur = cur.next
        return count#當鏈表空,則頭空,cur=None,不會循環,則count=0,符合

    def travel(self):#遍歷
        cur = self.__head
        while cur != None:
            print(cur.data, end=' ')
            cur = cur.next
        print('')

    def add_front(self,item):#鏈首加元素,頭不變
        node = Node(item)
        '鏈首本身沒有next,等號代表指向,self.__head本身指向的是原head後的第一個節點,現在要把該節點接到新的節點的後面'
        node.next = self.__head
        self.__head = node

    def append(self,item):#鏈表尾加元素
        node = Node(item)#待加節點
        if self.is_empty():
            self.__head=node
        else:
            cur = self.__head
            while cur.next != None:#找到表尾;當表爲空,cur=self.head=None ,沒cur.next,故需另做判斷
                cur = cur.next
            cur.next = node

    def insert(self,pos,item):#指定位置加元素
        if pos <= 0:
            self.add_front(item)#鏈首
        elif pos > (self.length()-1):
            self.append(item)#鏈尾
        else:
            pre = self.__head#遊標從頭節點起步
            count = 0
            """
            此處有個難點:比如pos=2,即要插入到第3個位置(不算頭結點),由於插入位置的前個節點的指向也需更改,
            故條件取到count < (pos-1) 而不是count < pos
            """
            while count < (pos-1):
                count += 1
                pre = pre.next
            #退出循環時,pre指向pos-1位置
            node = Node(item)
            node.next = pre.next#pre.next是被新節點佔去位置的原節點,將其放到新節點的後面
            pre.next = node

    def remove(self,item):#刪除節點
        '要考慮鏈首、尾、爲空'
        cur = self.__head
        pre = None #遊標cur的前個節點
        while cur != None:
            if cur.data == item:
                '先判斷該節點是否爲頭結點'
                if cur == self.__head:
                    self.__head = cur.next#當只有頭結點,將其刪除後,self.__head就該是None,而cur.next就是None
                else:
                    """
                    注意:pre本身原是無節點意義的,是因爲在判斷條件下會先經歷
                    pre = cur cur = cur.next 的位置後移,所以pre也有了和cur一樣的節點屬性!
                    """
                    pre.next = cur.next#把待刪除節點的下個節點指給待刪除節點的前個節點
                break#必須有!
            else:
                pre = cur
                cur = cur.next#pre與cur同時後移

    def search(self,item):#查找某節點是否存在
        cur = self.__head
        while cur != None:
            if cur.data == item:
                return True
            else:
                cur = cur.next
        return False

if __name__ == '__main__':
    ll = SingleLinkList()
    print(ll.is_empty())
    print(ll.length())

    ll.append(1)
    print(ll.is_empty())
    print(ll.length())

    ll.append(2)
    ll.append(3)
    ll.append(4)
    ll.add_front(8)#8 1 2 3 4

    ll.insert(-1, 9)#9 8 1 2 3 4
    ll.travel()
    ll.insert(2, 100)#9 8 100 1 2 3 4
    ll.travel()
    ll.insert(10, 200)#9 8 100 1 2 3 4 200
    ll.travel()

    ll.remove(9)#刪頭
    ll.travel()#8 100 1 2 3 4 200
    ll.remove(200)#刪尾
    ll.travel()#8 100 1 2 3 4
    ll.remove(1)
    ll.travel()#8 100 2 3 4

 

(2). 單向循環鏈表 

class Node(object):
    def __init__(self, data):
        self.data = data
        self.next = None  # 指向待定


'單向循環列表'
class SingleCycleLinkList(object):
    def __init__(self, node=None):  # 待接節點待定
        self.__head = node  # 頭節點爲私有屬性(必須雙下劃綫),限鏈表內使用
        if node:
            node.next = node  # 若鏈表在初始時就已經傳入了節點,則next指向自己

    def is_empty(self):
        return self.__head == None  # 頭空,則鏈表空

    def length(self):
        if self.is_empty():
            return 0  # 函數遇return則執行結束
        cur = self.__head  # 遊標
        count = 1  # 若非空,則至少有頭節點,計數從1起
        while cur.next != self.__head:  # 至尾結束
            count += 1
            cur = cur.next
        return count

    def travel(self):  # 遍歷
        if self.is_empty():  # 當空,cur = self.__head=None,但None不具備next屬性,故空鏈要單獨列出
            return
        cur = self.__head
        while cur.next != self.__head:
            print(cur.data, end=' ')
            cur = cur.next
        print(cur.data)  # 尾節點在循環退出時沒打出,故補上

    def add_front(self, item):  # 鏈首加元素,頭不變
        node = Node(item)
        '鏈首本身沒有next,等號代表指向,self.__head本身指向的是原head後的第一個節點,現在要把該節點接到新的節點的後面'
        if self.is_empty():
            self.__head = node
            node.next = node
            return
        cur = self.__head
        while cur.next != self.__head:
            cur = cur.next
        # 退出循環,cur此時爲尾節點
        node.next = self.__head  # node接到鏈首,此時self.__head更新後指向的是node了
        self.__head = node  # 頭結點接node
        cur.next = self.__head

    def append(self, item):  # 鏈表尾加元素
        node = Node(item)  # 待加節點
        if self.is_empty():
            self.__head = node
            node.next = node
        else:
            cur = self.__head
            while cur.next != self.__head:
                cur = cur.next
            node.next = self.__head
            cur.next = node

    def insert(self, pos, item):  # 指定位置加元素
        if pos <= 0:
            self.add_front(item)  # 鏈首
        elif pos > (self.length() - 1):
            self.append(item)  # 鏈尾
        else:
            pre = self.__head  # 遊標從頭節點起步
            count = 0
            """
            此處有個難點:比如pos=2,即要插入到第3個位置(不算頭結點),由於插入位置的前個節點的指向也需更改,
            故條件取到count < (pos-1) 而不是count < pos
            """
            while count < (pos - 1):
                count += 1
                pre = pre.next
            # 退出循環時,pre指向pos-1位置
            node = Node(item)
            node.next = pre.next  # pre.next是被新節點佔去位置的原節點,將其放到新節點的後面
            pre.next = node

    def remove(self, item):  # 刪除節點
        '要考慮鏈首、尾、爲空'
        if self.is_empty():
            return
        cur = self.__head
        pre = None  # 遊標cur的前個節點
        while cur.next != self.__head:
            if cur.data == item:
                '先判斷該節點是否爲頭結點'
                if cur == self.__head:  # =是指向,==不是
                    # 當待刪爲頭結點,則先找尾結點
                    rear = self.__head
                    while rear.next != self.__head:
                        rear = rear.next  # 後移, 直至找到尾節點
                    self.__head = cur.next  # cur此時就是頭結點,把頭指向鏈首cur.next,即鏈首成了新的頭結點了
                    rear.next = self.__head  # 尾指向新的頭

                else:  # 中間節點
                    """
                    注意:pre本身原是無節點意義的,是因爲在判斷條件下會先經歷
                    pre = cur cur = cur.next 的位置後移,所以pre也有了和cur一樣的節點屬性!
                    """
                    pre.next = cur.next  # 把待刪除節點的下個節點指給待刪除節點的前個節點
                '此處不能像之前用break!因爲break只是退出了while循環,而此處需要函數結束'
                return

            else:
                pre = cur
                cur = cur.next  # pre與cur同時後移
        # 退出循環,cur此時爲尾節點
        if cur.data == item:
            if cur == self.__head:  # 只有頭結點
                self.__head = None
            else:
                pre.next = cur.next  # cur的前節點直接指向cur的後節點

    def search(self, item):  # 查找某節點是否存在
        if self.is_empty():
            return False
        cur = self.__head
        while cur.next != self.__head:
            if cur.data == item:
                return True
            else:
                cur = cur.next
        # 退出循環,cur此時爲尾節點
        if cur.data == item:
            return True
        return False


if __name__ == '__main__':
    ll = SingleCycleLinkList()
    print(ll.is_empty())
    print(ll.length())

    ll.append(1)
    print(ll.is_empty())
    print(ll.length())

    ll.append(2)
    ll.append(3)
    ll.append(4)
    ll.add_front(8)  # 8 1 2 3 4

    ll.insert(-1, 9)  # 9 8 1 2 3 4
    ll.travel()
    ll.insert(2, 100)  # 9 8 100 1 2 3 4
    ll.travel()
    ll.insert(10, 200)  # 9 8 100 1 2 3 4 200
    ll.travel()

    ll.remove(9)  # 刪頭
    ll.travel()  # 8 100 1 2 3 4 200
    ll.remove(200)  # 刪尾
    ll.travel()  # 8 100 1 2 3 4
    ll.remove(1)
    ll.travel()  # 8 100 2 3 4

 

(3). 雙向鏈表

class Node(object):
    def __init__(self,data):
        self.data = data
        self.prev = None#前驅節點
        self.next = None#後置節點

'雙向鏈表'
class DoubleLinkList(object):
    def __init__(self,node=None):#待接節點待定
        self.__head = node#頭節點爲私有屬性(必須雙下劃綫),限鏈表內使用

    def is_empty(self):
        return self.__head == None#頭空,則鏈表空

    def length(self):
        cur = self.__head#cur遊標,用來移動遍歷節點
        count = 0
        while cur != None:
            count += 1
            cur = cur.next
        return count#當鏈表空,則頭空,cur=None,不會循環,則count=0,符合

    def travel(self):#遍歷
        cur = self.__head
        while cur != None:
            print(cur.data, end=' ')
            cur = cur.next
        print('')

    """
    由於雙向鏈表的以上前4個屬性及search()與單鏈表一樣,所以可以直接用面向對象的繼承方法,寫作如下:
    class DoubleLinkList(SingleLinkList)
    則以上5個屬性就無需再寫了,因爲直接繼承過來了
    """

    def add_front(self,item):#鏈首加元素,頭不變
        node = Node(item)
        '鏈首本身沒有next,等號代表指向,self.__head本身指向的是原head後的第一個節點,現在要把該節點接到新的節點的後面'
        node.next = self.__head
        self.__head = node
        node.next.prev = node#比單鏈多一個node的下個節點對node的指向

    def append(self,item):#鏈表尾加元素
        node = Node(item)#待加節點
        if self.is_empty():
            self.__head=node
        else:
            cur = self.__head
            while cur.next != None:#找到表尾;當表爲空,cur=self.head=None ,沒cur.next,故需另做判斷
                cur = cur.next
            cur.next = node
            node.prev = cur#比單鏈多一個cur的下個節點node對cur的指向

    def insert(self,pos,item):#指定位置加元素
        if pos <= 0:
            self.add_front(item)#鏈首
        elif pos > (self.length()-1):
            self.append(item)#鏈尾
        else:#此處與單鏈不同
            cur = self.__head#遊標從頭節點起步
            count = 0
            while count < pos:
                count += 1
                cur = cur.next
            #退出循環時,cur指向pos位置
            node = Node(item)
            """
            以下四字因操作先後順序不同,也可改成如下:
            node.next = cur
            node.prev = cur.prev
            cur.prev = node
            node.prev.next = node
            """
            node.next = cur
            node.prev = cur.prev
            cur.prev.next = node
            cur.prev = node

    def remove(self,item):#刪除節點
        '要考慮鏈首、尾、爲空'
        cur = self.__head
        while cur != None:
            if cur.data == item:
                '先判斷該節點是否爲頭結點'
                if cur == self.__head:
                    self.__head = cur.next#當只有頭結點,將其刪除後,self.__head就該是None,而cur.next就是None
                    if cur.next:#當只有cur這一個節點的時候,cur.next爲None,是沒有prev的
                        cur.next.prev = None
                else:
                    cur.prev.next = cur.next#把待刪除節點的下個節點指給待刪除節點的前個節點
                    if cur.next:
                        cur.next.prev = cur.prev
                break#必須有!
            else:
                cur = cur.next#pre與cur同時後移

    def search(self,item):#查找某節點是否存在
        cur = self.__head
        while cur != None:
            if cur.data == item:
                return True
            else:
                cur = cur.next
        return False

if __name__ == '__main__':
    ll = DoubleLinkList()
    print(ll.is_empty())
    print(ll.length())

    ll.append(1)
    print(ll.is_empty())
    print(ll.length())

    ll.append(2)
    ll.append(3)
    ll.append(4)
    ll.add_front(8)#8 1 2 3 4

    ll.insert(-1, 9)#9 8 1 2 3 4
    ll.travel()
    ll.insert(2, 100)#9 8 100 1 2 3 4
    ll.travel()
    ll.insert(10, 200)#9 8 100 1 2 3 4 200
    ll.travel()

    ll.remove(9)#刪頭
    ll.travel()#8 100 1 2 3 4 200
    ll.remove(200)#刪尾
    ll.travel()#8 100 1 2 3 4
    ll.remove(1)
    ll.travel()#8 100 2 3 4

 

 

 

 

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