數據結構與算法_渡劫5_鏈表

  • 入門級書籍:《大話數據結構》(第二章 算法 & 第三章 線性表)的學習筆記
  • 以下所有代碼均已上傳至github,傳送門:github.com/Lebhoryi/Algorithms
  • 複製的代碼前面只有三個空格,玄學問題,具體的規範代碼看github

一、概念

  • 算法:算法就是解決問題的技巧和方式。

      官方(@.@):解決特定問題求解步驟的描述,在計算機中表現爲指令的優先序列,並且每條指令表示一個或多個操作。
    
  • 算法的特性:輸入輸出、有窮性、確定性、可行性

  • 算法的要求: 正確性、可讀性、健壯性、時間效率和存儲量低

  • 算法效率的度量方法:

    • 事後統計方法(不靠譜)

    • 事前統計方法(推薦)

  • 算法時間複雜度

    • 最好壞情況

    • 平均情況

  • 算法空間複雜度

二、線性表

1. 定義

定義:0個或者多個數據元素的序列。

  • 快捷判斷:假如有兩個及以上的數據元素,則第一個元素無前驅,最後一個元素無後繼

  • 特殊情況:複雜線性表中,一個元素可以有多個數據項組成。例如班級裏的人是數據元素,每個人的各科成績是數據項。

學號 姓名 語文 數學
1 張三 78 98
2 李四 87 90
3 王五 90 99

2. 線性表的抽象數據類型

  • 抽象數據類型(重溫一下概念):將數據類型和相關操作捆綁在一起

  • 線性表的操作:

    1. 創建和初始化

    2. 查找數據

    3. 插入數據

    4. 刪除數據


擼代碼時間

# coding=utf-8
'''
@ Summary: 有線性表A和B,實現AUB
@ Update:  

@ file:    2.實現交集操作.py
@ version: 1.0.0

@ Author:  [email protected]
@ Date:    19-9-24 下午3:18
'''

def union(la, lb):
   for i in lb:
       if i not in la:
           la.append(i)
   return la

if __name__ == "__main__":
   la = [1, 2, 3]
   lb = [2, 3, 4]
   result = union(la, lb)
   print(result)

3. 線性表的順序存儲結構的騷操作

  • 獲得元素
# coding=utf-8
'''
@ Summary: 從線性表中獲得元素
@ Update:  

@ file:    3.獲得元素.py
@ version: 1.0.0

@ Author:  [email protected]
@ Date:    19-9-24 下午3:33
'''

def get_elem(i, l):
   if not l:
       return None
   return l[i-1]

if __name__ == "__main__":
   l = [1, 2, 3]
   i = 1
   result = get_elem(i, l)
   print(result)

  • 插入和刪除操作

print(l.insert(i,n)) 的輸出是None,因此分開寫

# coding=utf-8
'''
@ Summary: 在線性表中插入和刪除一個元素
@ Update:  

@ file:    2-3.線性表-插入操作.py
@ version: 1.0.0

@ Author:  [email protected]
@ Date:    19-9-24 下午3:39
'''

def list_insert(l, i, new_elem):
   if not l or i < 0 or i > len(l):
       return None
   if len(l) <= max_lenth:
       l.insert(i, new_elem)
   return l

'''
python list 刪除的常用三種方法:

   1. del list[3]
   2. list.pop(index)  # 刪除索引指向的元素
   3. list.remove(elem)  # 刪除指定的元素
'''

def list_del(l, i):
   if not l or i < 0 or i > len(l):
       return None
   if len(l) <= max_lenth:
       l.pop(i-1)
   return l

if __name__ == "__main__":
   l = [1, 2, 3]
   i, new_elem = 1, 5
   global max_lenth
   max_lenth = 7
   result = list_insert(l, i, new_elem)
   print(result)
   result2 = list_del(l, i)
   print(result2)


結論:線性表的順序存儲結構,在讀數據時,不管是哪個位置,時間複雜度都是O(1);而插入或刪除時,時間複雜度都是O(n)。

4. 順序表的鏈式存儲結構

  • 定義:用一組任意的存儲單元存儲線性表的數據元素

  • 結點:數據元素,包含數據域和指針域兩部分

    • 頭結點:單鏈表第一個結點之前,設定一個頭結點,順序是第0位。該數據域爲空或者存儲鏈表的長度,指針域指向第一個結點的數據域

    • 頭指針:指向鏈表的第一個結點,假若有頭結點,則指向頭結點,鏈表可以沒有頭結點,但是一定要有頭指針

三、單鏈表的相關操作

1. 單鏈表的元素查找

# coding=utf-8
'''
@ Summary: 整表生成和刪除
@ Update:  

@ file:    2-7.線性表-鏈表生成和刪除.py
@ version: 1.0.0

@ Author:  [email protected]
@ Date:    19-9-25 下午10:12
'''


class Node(object):
   def __init__(self, val):
       self.val = val
       self.next = None

class Link(object):

   @staticmethod
   def c_link(vals):
       if not vals:
           return None

       # 鏈表可以沒有頭結點但是不能沒有頭指針,下面這行看不懂的去看下頭結點

       head = Node(len(vals))  # 創建頭節點
       move = head
       for val in vals:
           tmp = Node(val)
           # 頭插法,新元素永遠插在第一個節點,頭結點之後
           # move2 = head.next
           # head.next = tmp
           # tmp.next = move2
           # 尾插法,新元素永遠插在最後一個節點
           move.next = tmp
           move = tmp
       return head, len(vals)

   @staticmethod
   def p_link(link):
       if not link:
           return None
       link = link.next
       while link:
           print(link.val, end=" ")
           link = link.next
       print()

   @staticmethod
   def d_link(link):
       if not link:
           return None
       link.next = None
       return link


if __name__ == "__main__":
   link_a = [1, 2, 1, 4, 3]
   link_a, l_link = Link.c_link(link_a)
   Link.p_link(link_a)
   # Link.p_link(Link.d_link(link_a))

2. 單鏈表的元素插入


# coding=utf-8
'''
@ Summary: 單鏈表的元素插入
@ Update:  

@ file:    2-5.線性表-鏈表元素插入.py
@ version: 1.0.0

@ Author:  [email protected]
@ Date:    19-9-25 下午6:45
'''

class ListNode(object):
   def __init__(self, val):
       self.val = val
       self.next = None

class Link(object):

   @staticmethod
   def c_link(vals):
       # creat link
       if not vals:
           return None
       head = ListNode(len(vals))
       move = head
       for val in vals:
           tmp = ListNode(val)
           move.next = tmp
           move = tmp
       return head.next

   @staticmethod
   def p_link(ln):
       # print link
       if not ln:
           return None
       while ln:
           print(ln.val, end=" ")
           ln = ln.next
       print()

   @staticmethod
   def l_link(link):
       if not link:
           return None
       count = 0
       while link:
           count += 1
           link = link.next
       return count


def insert_elem(ln, node, s, lenth_link):
   """單鏈表中插入元素

   :param ln: node
   :param a: node
   :param s: int
   :return: node
   """
   if not ln or s < 0 or s > lenth_link:
       return None
   cur = ln
   count = 1
   while count < s:
       cur = cur.next
       count += 1
   node = ListNode(node)
   node.next = cur.next
   cur.next = node
   return ln


if __name__ == "__main__":
   link_a = [1, 2, 3, 4]
   node_b, s = 5, 2  # 在第二個節點的位置插入5

   link_a = Link.c_link(link_a)
   lenth_link = Link.l_link(link_a)
   Link.p_link(link_a)

   link_b = insert_elem(link_a, node_b, s, lenth_link)
   Link.p_link(link_b)

3. 單鏈表的元素刪除


# coding=utf-8
'''
@ Summary: 單鏈表中刪除元素
@ Update:  

@ file:    2-6.線性表-鏈表元素刪除.py
@ version: 1.0.0

@ Author:  [email protected]
@ Date:    19-9-25 下午8:59
'''

class Node(object):
   def __init__(self, val):
       self.val = val
       self.next = None

class Link(object):
   @staticmethod
   def create_link(vals):
       if not vals:
           return None
       head = Node(len(vals))
       move = head
       for val in vals:
           tmp = Node(val)
           move.next = tmp
           move = tmp
       return head.next

   @staticmethod
   def p_link(link):
       if not link:
           return None
       while link:
           print(link.val, end=" ")
           link = link.next
       print()

   @staticmethod
   def l_link(link):
       if not link:
           return None
       count = 0
       while link:
           count += 1
           link = link.next
       return count


def del_link(ln, i, lenth_link):
   if not ln or i < 0 or i > lenth_link:
       return None
   cur = ln
   count = 1
   # 找打要刪除的節點的上一個節點,pre.next = pre.next.next
   while count < i-1:
       cur = cur.next
       count += 1
   # print(cur.val)
   cur.next = cur.next.next
   return ln


if __name__ == "__main__":
   link_a = [1, 3, 4, 5, 2]
   link_a = Link.create_link(link_a)
   lenth_link = Link.l_link(link_a)
   # Link.p_link(link_a)
   i = 3  # 刪除第二個節點
   link_a = del_link(link_a, i, lenth_link)
   Link.p_link(link_a)

4. 單鏈表的整表刪除和生成

# coding=utf-8
'''
@ Summary: 整表生成和刪除
@ Update:  

@ file:    2-7.線性表-鏈表生成和刪除.py
@ version: 1.0.0

@ Author:  [email protected]
@ Date:    19-9-25 下午10:12
'''


class Node(object):
   def __init__(self, val):
       self.val = val
       self.next = None

class Link(object):

   @staticmethod
   def c_link(vals):
       if not vals:
           return None
       head = Node(len(vals))
       move = head
       for val in vals:
           tmp = Node(val)
           # 頭插法,新元素永遠插在第一個節點,頭結點之後
           # move2 = head.next
           # head.next = tmp
           # tmp.next = move2
           # 尾插法,新元素永遠插在最後一個節點
           move.next = tmp
           move = tmp
       return head, len(vals)

   @staticmethod
   def p_link(link):
       if not link:
           return None
       link = link.next
       while link:
           print(link.val, end=" ")
           link = link.next
       print()

   @staticmethod
   def d_link(link):
       if not link:
           return None
       link.next = None
       return link


if __name__ == "__main__":
   link_a = [1, 2, 1, 4, 3]
   link_a, l_link = Link.c_link(link_a)
   Link.p_link(link_a)
   # Link.p_link(Link.d_link(link_a))

5. 單鏈表和順序存儲結構的優缺點

四、靜態鏈表

1. 定義

用數組來代替指針來描述單鏈表,讓數組的元素都是由兩個數據域組成,data和cur,其中數據域data用來存放數據元素,數據域cur相當於單鏈表中的next指針,存放該元素的後繼在數組中的下標,把這種用數組描述的鏈表叫做靜態鏈表,這種描述方法還有起名叫遊標實現法。

在c中和python、java中是不一樣的,c中的數組需要指定數組長度,存儲空間是固定的,不能動態的更改,否則會溢出,而在python或者java中不需要指定,系統自檢溢出,系統可以動態的增加空間。

2. 現狀

單鏈表使用的多,對於靜態鏈表掌握程度理解即可


Q:(騰訊面試題)快速找到未知長度單鏈表的中間節點

提示:走一步和走兩步


# coding=utf-8
'''
@ Summary: 快速找到未知長度單鏈表的中間節點
@ Update:  

@ file:    2-9.尋找鏈表中間節點.py
@ version: 1.0.0

@ Author:  [email protected]
@ Date:    19-9-26 下午6:49
'''

class Node(object):
   def __init__(self, val):
       self.val = val
       self.next = None

class Link(object):

   @staticmethod
   def c_link(vals):
       if not vals:
           return None
       head = Node(len(vals))
       move = head
       for val in vals:
           tmp = Node(val)
           move.next = tmp
           move = tmp
       return head.next

   @staticmethod
   def p_link(link):
       if not link:
           return None
       while link:
           yield link.val
           link = link.next
       print()

def find_mid_node(link):
   cur = link
   cur2 = link.next
   while cur2 and cur2.next:
       cur = cur.next
       cur2 = cur2.next.next
   return cur.val


if __name__ == "__main__":
   list_a = [1, 4, 2, 5, 3, 2, 2]
   link_a = Link.c_link(list_a)
   for val in Link.p_link(link_a):
       print(val, end=" ")
   result = find_mid_node(link_a)
   print(result)

四、循環鏈表

1. 定義

將單鏈表中終端節點的指針從空指針改爲指向頭結點,形成一個環,頭尾相連,稱循環鏈表

2. 循環鏈表和單鏈表的差異

循環鏈表和單鏈表的主要差異在於循環的判斷條件上,單鏈表是判斷p->next是否爲空,循環鏈表是判斷p->next不等於頭結點,則循環未結束。

3. 循環鏈表的有點

在單鏈表中採用頭結點時,可以用O(1)的時間訪問第一個結點,用O(n)的時間訪問到最後一個結點;採用指向終端結點的尾指針來表示循環鏈表時可以用O(1)的時間由鏈表指針訪問到最後一個結點


以下的代碼都是尾指針示例


# coding=utf-8
'''
@ Summary: 循環鏈表及操作
@ Update:  

@ file:    2-10.線性表-循環鏈表.py
@ version: 1.0.0

@ Author:  [email protected]
@ Date:    19-9-26 下午7:45
'''

class Node(object):
   def __init__(self, val):
       self.val = val
       self.next = None

   def __repr__(self):   # 這個函數將內容‘友好’地顯示出來,否則會顯示對象的內存地址
       return str(self.val)


class CLink(object):
   @ staticmethod
   def c_link(vals):
       # 創建循環鏈表
       if not vals:
           return None
       # 創建頭結點
       head = Node(len(vals))
       move = head  # 給定一個變量,用做節點向前移動
       for i in range(len(vals)):
           tmp = Node(vals[i])
           if i == 0:  # 首節點循環
               move.next = tmp
               tmp.next = tmp
               move = tmp
           else:
               tmp.next = move.next
               move.next = tmp
               move = tmp
       # return head
       # 循環鏈表用尾節點比頭結點友好,據說
       rear = head
       rear.next = move
       return rear

   @ staticmethod
   def p_link(link):
       # 打印循環鏈表
       if not link:
           return None
       cur = link.next.next  # cur爲第一個節點,接着爲當前節點
       while cur.next != link.next.next:
           # print(cur, link.next)
           yield cur.val
           cur = cur.next
       yield cur.val
       print()

   @ staticmethod
   def l_link(link):
       # lenth of link
       if not link:
           return None
       cur, count = link.next.next, 1
       # 判斷條件是最後一個指向是否是第一個節點
       while cur.next != link.next.next:
           count += 1
           cur = cur.next
       return count

   @ staticmethod
   def add_node(link, node, s, l_link):
       if not link or not node or s < 0:
           return None
       s = s % l_link if s > l_link else s
       node = Node(node)
       cur = link.next.next
       if s == l_link:
           # cur指向第一個節點,最後一個節點指向cur
           node.next = cur
           link.next.next = node
       else:
           count = 1
           while count < s:
               # 找到要插入的cur位置
               cur = cur.next
               count += 1
           # cur指向下一個節點,當前節點指向cur
           node.next = cur.next
           cur.next = node
       return link

   @ staticmethod
   def del_node(link, s, l_link):
       if not link or s < 0:
           return None
           # 存在s>l_link的情況,需要對s做一步處理
       s = s % l_link if s > l_link else s
       cur = link.next.next
       count = 1
       while count < s:
           cur = cur.next
           count += 1
       # 當前cur指向下下一個節點,跳過下一個節點
       cur.next = cur.next.next
       return link

   @ staticmethod
   def find_node(link, s, l_link):
       if not link or s < 0:
           return None
       s = s % l_link if s > l_link else s
       cur = link.next.next
       count = 1
       while count < s:
           cur = cur.next
           count += 1
       return cur.val


if __name__ == "__main__":
   list_a = [1, 2, 1, 5, 3, 7]
   link_a = CLink.c_link(list_a)
   [print(node, end=" ") for node in CLink.p_link(link_a)]

   # 插入節點,在尾部插入節點的複雜度爲O(1)
   lenth_link = CLink.l_link(link_a)
   node, s = 666, 10
   link_a = CLink.add_node(link_a, node, s, lenth_link)
   [print(node, end=" ") for node in CLink.p_link(link_a)]


   # delete node
   link_a = CLink.del_node(link_a, s, lenth_link)
   [print(node, end=" ") for node in CLink.p_link(link_a)]

   # find node
   node_a = CLink.find_node(link_a, s, lenth_link)
   print(node_a)


  • 手撕代碼練習1-1:約瑟夫問題

  • 手撕代碼練習1-2:強化版約瑟夫問題

  • 手撕代碼練習2:鏈表相接

  • 手撕代碼練習3:判斷鏈表是否有環

  • 手撕代碼練習4:魔術師發牌問題

  • 手撕代碼練習5:拉丁方陣

上述練習題的代碼已經上傳至github,傳送門☞:github.com/Lebhoryi/Algorithms

五、雙向鏈表

1. 定義

在單鏈表的基礎上,新增一個指向前驅節點的指針域

2. 循環鏈表

雙向鏈表也可以是循環表:

3. 操作

  • 插入操作

  • 刪除操作


雙向鏈表保存有前後指針,因此插入和刪除都很方便,時間效率提高但是空間存儲變大,用空間換取時間

4. 代碼練習time

  • 練習1(代碼已經上傳,地址同上):

一句話輸出字母:


[chr(i) for i in range(65, 91)]


對於執行較慢的程序,可以通過消耗更多的內存(空間換時間)來

進行優化;而消耗過多內存的程序,可以通過消耗更多的時間(時間換空間)來降低內存的消耗。

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