前面已經總結過了鏈表中的單向鏈表、環形鏈表,接下來我們繼續談談鏈表中的另一種之雙向鏈表。
單向鏈表和環形鏈表都屬於擁有方向性的鏈表,只能單向遍歷,萬一不幸其中有一個鏈表斷裂,那麼後面的鏈表數據便會遺失而無法復原。因此我們可以將兩個不同方向的鏈表結合起來,除了存放數據的字段,他還有兩個指針變量,其中一個指針指向後面的節點,另一個指針則指向前面的節點,這樣的鏈表被稱爲雙向鏈表(Double Linked List).每個節點很輕鬆的能夠找到其前後節點,同時任意的節點都可以找到其他節點,而不需要經過反轉或對比節點等處理。
優點:有兩個指針分別指向節點前後兩個節點,所以能夠輕鬆的找到前後節點,同時從雙向鏈表的任意節點都可以找到其他任意節點,不需要經過反轉或對比等處理。
缺點:由於雙向鏈表中每個節點都有兩個連接,因此在加入或刪除節點時,需要更多的時間來調整指針,另外每個指針均含有兩個指針變量,所以較爲浪費空間。
環形雙向鏈表:如果最後一個節點的右指針指向首節點(頭部節點),而首節點的左指針指向尾節點,這樣的鏈表就會環形雙向鏈表。
3.3.1 雙向鏈表的建立與遍歷
例子:設計一個python程序,可以讓用戶輸入數據來新增學生數據節點,並建立一個雙向鏈表。當用戶輸入結束後,可遍歷此鏈表並顯示其內容。
# CH03-12.py
class student: def __init_(self): self.name='' self.Math=0 self.Eng=0 self.no='' self.rlink=None self.llink=None head=student() head.llink=None head.rlink=None ptr=head # 設置存取指針開始的位置 select=0 while True: select=int(input('(1)新增 (2)離開 =>')) if select==2: break new_data=student() new_data.name=input('姓名: ') new_data.no=input('學號: ') new_data.Math=eval(input('數學成績: ')) new_data.Eng=eval(input('英語成績: ')) # 輸入節點結構中的數據 ptr.rlink=new_data new_data.rlink = None # 下一個元素的next先設置爲None new_data.llink=ptr # 存取指針設置爲新元素所在的位置 ptr=new_data ptr = head.rlink # 設置存取指針從鏈表頭的右指針字段所指的節點開始 print() while ptr!= None: print('姓名:%s\t學號:%s\t數學成績:%d\t英語成績:%d' \ %(ptr.name,ptr.no,ptr.Math,ptr.Eng)) ptr = ptr .rlink # 將ptr移往右邊下一個元素 # 結果 (1)新增 (2)離開 =>1 姓名: daniel 學號: 1001 數學成績: 100 英語成績: 100 (1)新增 (2)離開 =>1 姓名: marry 學號: 1002 數學成績: 99 英語成績: 96 (1)新增 (2)離開 =>2 姓名:daniel 學號:1001 數學成績:100 英語成績:100 姓名:marry 學號:1002 數學成績:99 英語成績:96
例子:設計一個python程序,鮮香右遍歷所建立雙向鏈表並輸出所有學生的數據節點,再向左遍歷所有節點並輸出信息。
# C03-13.py
select=0 class student: def __init_(self): self.name='' self.Math=0 self.Eng=0 self.no='' self.rlink=None self.llink=None head=student() head.llink=None head.rlink=None ptr=head # 設置存取指針開始的位置 select=0 while True: select=int(input('(1)新增 (2)離開 =>')) if select==2: break; new_data=student() new_data.name=input('姓名: ') new_data.no=input('學號: ') new_data.Math=eval(input('數學成績: ')) new_data.Eng=eval(input('英語成績: ')) # 輸入節點結構中的數據 ptr.rlink=new_data new_data.rlink = None # 下一個元素的next先設置爲None new_data.llink=ptr # 存取指針設置爲新元素所在的位置 ptr=new_data print('-----向右遍歷所有節點-----') ptr = head.rlink # 設置存取指針從鏈表頭的右指針字段所指的節點開始 while ptr!=None: print('姓名:%s\t學號:%s\t數學成績:%d\t英語成績:%d' \ %(ptr.name,ptr.no,ptr.Math,ptr.Eng)) if ptr.rlink==None: break ptr = ptr .rlink # 將ptr移往右邊下一個元素 print('-----向左遍歷所有節點-----') while ptr != None: print('姓名:%s\t學號:%s\t數學成績:%d\t英語成績:%d' \ %(ptr.name,ptr.no,ptr.Math,ptr.Eng)) if(ptr.llink==head): break ptr = ptr .llink # 結果 (1)新增 (2)離開 =>1 姓名: danile 學號: 1002 數學成績: 100 英語成績: 100 (1)新增 (2)離開 =>1 姓名: marry 學號: 1003 數學成績: 99 英語成績: 99 (1)新增 (2)離開 =>1 姓名: jasmine 學號: 1001 數學成績: 100 英語成績: 100 (1)新增 (2)離開 =>2 -----向右遍歷所有節點----- 姓名:danile 學號:1002 數學成績:100 英語成績:100 姓名:marry 學號:1003 數學成績:99 英語成績:99 姓名:jasmine 學號:1001 數學成績:100 英語成績:100 -----向左遍歷所有節點----- 姓名:jasmine 學號:1001 數學成績:100 英語成績:100 姓名:marry 學號:1003 數學成績:99 英語成績:99 姓名:danile 學號:1002 數學成績:100 英語成績:100
3.3.2 在雙向連鏈表中插入新節點
1)將新節點加入到雙向鏈表的第一個節點之前:將新節點的右指針指向原鏈表的第一個節點,接着將原鏈表第一個節點的左指針指向新節點,將原鏈表表頭指針指向新節點
2)將新節點加入雙向鏈表的末尾:將原鏈表的最後一個節點的右指針指向新節點,將新節點的左指針指向原鏈表的最後一個節點,並將新節點的右指針指向None.
3)將新節點加入到鏈表中ptr節點之後:首先將ptr節點的右指針指向新節點,再將新節點的左指針指向ptr節點,接着將ptr節點的下一個節點的左指針指向新節點,最後將新節點的右指針指向ptr節點的下一個節點。
例子:設計一個python程序,建立員工數據的雙向鏈表,並且允許可以在鏈表頭部、尾部、鏈表中間3種不同的位置插入節點,最後離開時,列出鏈表所有節點的信息。
# CH03-14.py
class employee: def __init__(self): self.num=0 self.salary=0 self.name='' self.llink=None # 左指針字段 self.rlink=None # 右指針字段 def findnode(head,num): ptr=head while ptr!=None: if ptr.num==num: return ptr ptr=ptr.rlink return ptr def insertnode(head,ptr,num,salary,name): newnode=employee() newhead=employee() newnode.num=num newnode.salary=salary newnode.name=name if head==None: # 雙向鏈表是空的 newhead.num=num newhead.salary=salary newhead.name=name return newhead else: if ptr==None: head.llink=newnode newnode.rlink=head head=newnode else: if ptr.rlink==None: # 插入鏈表末尾的位置 ptr.rlink=newnode newnode.llink=ptr else: # 插入中間節點的位置 newnode.rlink=ptr.rlink ptr.rlink.llink=newnode ptr.rlink=newnode newnode.llink=ptr return head llinknode=None newnode=None position=0 data=[[1001,32367],[1002,24388],[1003,27556],[1007,31299], \ [1012,42660],[1014,25676],[1018,44145],[1043,52182], \ [1031,32769],[1037,21100],[1041,32196],[1046,25776]] namedata=['Allen','Scott','Marry','John','Mark','Ricky', \ 'Lisa','Jasica','Hanson','Amy','Bob','Jack'] print('員工編號 薪水 員工編號 薪水 員工編號 薪水 員工編號 薪水') print('-------------------------------------------------------') for i in range(3): for j in range(4): print('[%2d] [%3d] ' %(data[j*3+i][0],data[j*3+i][1]),end='') print() head=employee() # 建立鏈表頭 if head==None: print('Error!! 內存分配失敗!!') sys.exit(0) else: head.num=data[0][0] head.name=namedata[0] head.salary=data[0][1] llinknode=head for i in range(1,12): # 建立鏈表 newnode=employee() newnode.num=data[i][0] newnode.name=namedata[i] newnode.salary=data[i][1] llinknode.rlink=newnode newnode.llink=llinknode llinknode=newnode while True: print('請輸入要插入其後的員工編號,如輸入的編號不在此鏈表中,') position=int(input('新輸入的員工節點將視爲此鏈表的鏈表頭,要結束插入過程,請輸入-1:')) if position==-1: # 循環中斷條件 break else: ptr=findnode(head,position) new_num=int(input('請輸入新插入的員工編號:')) new_salary=int(input('請輸入新插入的員工薪水:')) new_name=input('請輸入新插入的員工姓名:') head=insertnode(head,ptr,new_num,new_salary,new_name) print('\t員工編號 姓名\t薪水') print('\t==============================') ptr=head while ptr!=None: print('\t[%2d]\t[ %-10s]\t[%3d]' %(ptr.num,ptr.name,ptr.salary)) ptr=ptr.rlink # 結果 員工編號 薪水 員工編號 薪水 員工編號 薪水 員工編號 薪水 ------------------------------------------------------- [1001] [32367] [1007] [31299] [1018] [44145] [1037] [21100] [1002] [24388] [1012] [42660] [1043] [52182] [1041] [32196] [1003] [27556] [1014] [25676] [1031] [32769] [1046] [25776] 請輸入要插入其後的員工編號,如輸入的編號不在此鏈表中, 新輸入的員工節點將視爲此鏈表的鏈表頭,要結束插入過程,請輸入-1:1046 請輸入新插入的員工編號:1050 請輸入新插入的員工薪水:45000 請輸入新插入的員工姓名:marry 請輸入要插入其後的員工編號,如輸入的編號不在此鏈表中, 新輸入的員工節點將視爲此鏈表的鏈表頭,要結束插入過程,請輸入-1:-1 員工編號 姓名 薪水 ============================== [1001] [ Allen ] [32367] [1002] [ Scott ] [24388] [1003] [ Marry ] [27556] [1007] [ John ] [31299] [1012] [ Mark ] [42660] [1014] [ Ricky ] [25676] [1018] [ Lisa ] [44145] [1043] [ Jasica ] [52182] [1031] [ Hanson ] [32769] [1037] [ Amy ] [21100] [1041] [ Bob ] [32196] [1046] [ Jack ] [25776] [1050] [ marry ] [45000]
3.3.3 在雙向鏈表中刪除節點
刪除同插入類似,同樣有三種情況:
1)刪除雙向鏈表的第一個節點:將鏈表指針head指向原鏈表的第二個節點,再將新鏈表的左指針指向None.
2)刪除雙向鏈表的最後一個節點:將原鏈表最後一個節點之前的一個節點的右指針指向None.
X.llink.rlink = None
3)s刪除雙向鏈表的中間節點X:將X節點的前一個節點的右指針指向X節點的下一個節點,再將X節點的下一個節點的左指針指向X節點的上一個節點。
例子:設計一個Python程序,建立一個員工數據的雙向鏈表,並且允許在鏈表頭部、尾部和鏈表中間3種不同位置刪除借點情況。最後離開時,列出鏈表所有節點的數據字段的內容。
# CH03-15.py
class employee: def __init__(self): self.num=0 self.salary=0 self.name='' self.llink=None # 左指針字段 self.rlink=None # 右指針字段 def findnode(head,num): ptr=head while ptr!=None: if ptr.num==num: return ptr ptr=ptr.rlink return ptr def deletenode(head,del_node): if head==None: # 雙向鏈表是空的 print('[鏈表是空的]') return None if del_node==None: print('[錯誤:不是鏈表中的節點]') return None if del_node==head: head=head.rlink head.llink=None else: if del_node.rlink==None: # 刪除鏈表末尾的節點 del_node.llink.rlink=None else: # 刪除中間節點 del_node.llink.rlink=del_node.rlink del_node.rlink.llink=del_node.llink return head llinknode=None newnode=None position=0 data=[[1001,32367],[1002,24388],[1003,27556],[1007,31299], \ [1012,42660],[1014,25676],[1018,44145],[1043,52182], \ [1031,32769],[1037,21100],[1041,32196],[1046,25776]] namedata=['Allen','Scott','Mary','John','Mark','Ricky', \ 'Lisa','Jasica','Hanson','Amy','Bob','Jack'] print('員工編號 薪水 員工編號 薪水 員工編號 薪水 員工編號 薪水') print('-------------------------------------------------------') for i in range(3): for j in range(4): print('[%2d] [%3d] ' %(data[j*3+i][0],data[j*3+i][1]),end='') print() head=employee() # 建立鏈表頭 if head==None: print('Error!! 內存分配失敗!!') sys.exit(0) else: head.num=data[0][0] head.name=namedata[0] head.salary=data[0][1] llinknode=head for i in range(1,12): # 建立鏈表 newnode=employee() newnode.num=data[i][0] newnode.name=namedata[i] newnode.salary=data[i][1] llinknode.rlink=newnode newnode.llink=llinknode llinknode=newnode while True: position=int(input('\n請輸入要刪除的員工編號,要刪除插入過程,請輸入-1:')) if position==-1: # 循環中斷條件 break else: ptr=findnode(head,position) head=deletenode(head,ptr) print('\t員工編號 姓名\t薪水') print('\t==============================') ptr=head while ptr!=None: print('\t[%2d]\t[ %-10s]\t[%3d]' %(ptr.num,ptr.name,ptr.salary)) ptr=ptr.rlink # 結果 員工編號 薪水 員工編號 薪水 員工編號 薪水 員工編號 薪水 ------------------------------------------------------- [1001] [32367] [1007] [31299] [1018] [44145] [1037] [21100] [1002] [24388] [1012] [42660] [1043] [52182] [1041] [32196] [1003] [27556] [1014] [25676] [1031] [32769] [1046] [25776] 請輸入要刪除的員工編號,要刪除插入過程,請輸入-1:1001 請輸入要刪除的員工編號,要刪除插入過程,請輸入-1:-1 員工編號 姓名 薪水 ============================== [1002] [ Scott ] [24388] [1003] [ Mary ] [27556] [1007] [ John ] [31299] [1012] [ Mark ] [42660] [1014] [ Ricky ] [25676] [1018] [ Lisa ] [44145] [1043] [ Jasica ] [52182] [1031] [ Hanson ] [32769] [1037] [ Amy ] [21100] [1041] [ Bob ] [32196] [1046] [ Jack ] [25776]
到此爲止,鏈表的總結就結束了,總的來說三種鏈表結構(單向鏈表、環形鏈表、雙向鏈表)均有着統一的建立與遍歷、查找、刪除操作;單向和環形還有鏈接操作。單向還有獨有的反轉操作,並且可以更高效的表示多項式;環形具有高效表示稀疏矩陣的特點。
單向鏈表 | 環形鏈表 | 雙向鏈表 | |
建立與遍歷 | Yes | Yes | Yes |
插入 | Yes | Yes | Yes |
刪除 | Yes | Yes | Yes |
鏈接 | Yes | Yes | No |
反轉 | Yes | No | No |
else | 多項式的鏈表表示 | 稀疏矩陣的鏈表表示 | No |