數據結構--python第三章鏈表之雙向鏈表

前面已經總結過了鏈表中的單向鏈表、環形鏈表,接下來我們繼續談談鏈表中的另一種之雙向鏈表。

單向鏈表和環形鏈表都屬於擁有方向性的鏈表,只能單向遍歷,萬一不幸其中有一個鏈表斷裂,那麼後面的鏈表數據便會遺失而無法復原。因此我們可以將兩個不同方向的鏈表結合起來,除了存放數據的字段,他還有兩個指針變量,其中一個指針指向後面的節點,另一個指針則指向前面的節點,這樣的鏈表被稱爲雙向鏈表(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