C++和python實現(xiàn)單鏈表及其原理
一、鏈表的基本概念
鏈表是數(shù)據(jù)元素的線性集合(Linear Collection
),物理存儲不連續(xù)。那么,這種設(shè)計的優(yōu)點是什么?缺點又是什么?
鏈表的基本結(jié)構(gòu):
鏈表是由一系列的“節(jié)點”組成在一起的集合,節(jié)點(Node)由數(shù)據(jù)域(data)和指針域(next)組成。
從功能上看,data負責存儲數(shù)據(jù),next負責存儲下一個節(jié)點的位置。當然,用更加嚴格的語句來講,next存儲的是其直接后繼的地址,關(guān)于直接后繼的定義見:
鏈表的分類:
常見的鏈表種類有:單向鏈表、雙向鏈表、單向循環(huán)鏈表、雙向循環(huán)鏈表,將會在后面文章中單獨介紹各種鏈表的結(jié)構(gòu)和代碼實現(xiàn),以及對應(yīng)的鏈表操作。
鏈表的基本操作:
鏈表的基礎(chǔ)操作包含:插入、刪除、查找、合并等,此外還有:反轉(zhuǎn)、排序、深度復(fù)制等。
鏈表的優(yōu)點:
- 插入和刪除快;
- 存儲空間不受限制,可動態(tài)申請擴充,不需事先開辟內(nèi)存;
鏈表的缺點:
- 相比于數(shù)組,鏈表的需要更多的存儲空間(需存儲指針);
- 存儲不連續(xù)導(dǎo)致其訪問時間長;
- 從反向訪問角度,單向鏈表難以反向訪問,擴展為雙向鏈表則需要額外存儲反向指針;
- 每次節(jié)點訪問都需要從頭部開始;
二、單鏈表
基本結(jié)構(gòu):
單鏈表的結(jié)構(gòu)含有四個概念:頭指針、頭結(jié)點、普通Node、尾結(jié)點,下面分別介紹:
- 頭指針指向頭結(jié)點,是單鏈表的表示,必不可少;
- 頭結(jié)點是單鏈表的第一個節(jié)點,其數(shù)據(jù)域內(nèi)容一般無效;
- 普通Node即用于數(shù)據(jù)存儲和直接后繼指針存儲的節(jié)點;
- 尾結(jié)點即鏈表中最后一個節(jié)點,其指針域next為空,其后無節(jié)點;
單鏈表的基本操作:
針對單鏈表常見的操作有:增、改、查、刪等,
常用的操作如下:
(1)增
對鏈表添加元素一般有三種方法:頭插法(add)、尾插法(append)、任意位置插入法(insert)。
(2)改
改動鏈表中某個節(jié)點的data
。
(3)查
查找分為按值查找和按位置查找兩種,前者表示按照值查找對應(yīng)位置,后者表示按位置查找對應(yīng)值;
(4)刪
刪除分為按值刪除和按位置刪除兩種;前者表示按照值刪除對應(yīng)節(jié)點,后者表示按照位置刪除對應(yīng)節(jié)點;
實現(xiàn)說明:
按照自己目前所看的資料,一般都會實現(xiàn)下面介紹的這些函數(shù),具體介紹放在python和C++實現(xiàn)中。
1.python實現(xiàn)
(1)節(jié)點設(shè)計
按照單鏈表的定義可知,節(jié)點包含數(shù)據(jù)域data
和指針域next
:
但是由于next和python的內(nèi)置函數(shù)next()
重名,所以指針域使用pointer
表示。
代碼如下:
class Node: ? ? def __init__(self, data): ? ? ? ? """ ? ? ? ? Args: ? ? ? ? ? ? data: data of node, any type? ? ? ? ? """ ? ? ? ? self.data = data ? ? ? ? self.pointer = None
(2)鏈表類:Single_Linked_List
上述Node類對象即為鏈表的基本組成結(jié)構(gòu),可以用于實現(xiàn)頭結(jié)點、普通節(jié)點和尾結(jié)點。
因此,鏈表類只需要提供頭指針:
class Single_Linked_List: ? ? def __init__(self, node=None): ? ? ? ? self.__head = node
(3)判斷鏈表是否為空:is_empty()函數(shù)
實際上,只需要判斷頭指針是否指向Node類對象(或是否等于None),就可判斷一個鏈表是否為空:
def is_empty(self): ? ? """判斷鏈表是否為空""" ? ? if self.__head == None: ? ? ? ? return True ? ? else: ? ? ? ? return False
(4)頭插法:add()函數(shù)
在鏈表頭進行節(jié)點插入是很常見的插入操作,這種方式使得“先插入的節(jié)點在鏈表尾部”。頭插法需要將頭指針指向新的節(jié)點,并讓新的節(jié)點指向原來的頭結(jié)點:
def add(self, data): ? ? """Add dnode into head ? ? """? ? ? # 創(chuàng)建新節(jié)點 ? ? node = Node(data) ? ? # 令新的節(jié)點指向原來的頭結(jié)點 ? ? node.pointer = self.__head ? ? # 令頭指針指向新的節(jié)點 ? ? self.__head = node
(5)尾插法:append()函數(shù)
如果想要鏈表節(jié)點次序和插入次序相同,就需要使用尾插法。在插入之前需要判斷鏈表是否為空,如果不為空才能進行插入(可以調(diào)用前面定義的is_empty()
函數(shù),但是下述代碼沒有)。
此外,還需要進行鏈表的遍歷操作,找到最后一個節(jié)點。單鏈表只能從表頭開始訪問,所以每次尾插都必須遍歷。
def append(self, data): ? ? """ append node into tail ? ? """ ? ? node = Node(data) ? ?? ? ? # 頭指針為空時即為首節(jié)點 ? ? if self.__head == None: ? ? ? ? self.__head = node ? ? # 頭指針不為空時進行遍歷 ? ? else: ? ? ? ? current = self.__head ? ? ? ? while current.pointer != None: ? ? ? ? ? ? current = current.pointer ? ? ? ? current.pointer = node
(6)在任意位置插入:insert()函數(shù)
前面介紹的頭插法和尾插法,其原理相對簡單,但是并不能完全滿足插入需求。如果知道目標插入的位置,可以采用insert()
函數(shù)實現(xiàn)任意位置的節(jié)點插入。
需要注意的是,在實現(xiàn)insert()
函數(shù)時必須考慮到“position
”參數(shù)可能出現(xiàn)的幾種情況。比如python中并沒有明確的類型要求,所以要檢查“position”是不是int類型。
對于核心的節(jié)點插入實現(xiàn)功能,需要找到目標插入位置對應(yīng)的節(jié)點,并使得這個節(jié)點指向新節(jié)點,讓新節(jié)點指向原位置節(jié)點的后一個節(jié)點。這個過程類似于鐵鏈中加入鐵環(huán)的過程,要保證新鐵環(huán)和原來的兩個鐵環(huán)相連接。
def insert(self, position, data): ? ? """在任意位置插入節(jié)點 ? ? Args: ? ? ? ? position:插入節(jié)點的位置,int ? ? ? ? data:插入節(jié)點的值 ? ? """ ? ?? ? ? if not isinstance(position, int): ? ? ? ? raise ValueError("expect type is 'int', but got {}".format(position.__class__)) ? ?? ? ? # 頭插法 ? ? if position <= 0: ? ? ? ? self.add(data) ? ? # 尾插法 ? ? elif position > self.get_length(): ? ? ? ? self.append(data) ? ?? ? ? else: ? ? ? ? current = self.__head ? ? ? ? current_position = 0 ? ? ? ? node = Node(data) ? ? ? ? # 目的:計算出插入位置 ? ? ? ? while current_position < position -1: ? ? ? ? ? ? current_position += 1 ? ? ? ? ? ? current = current.pointer ? ? ? ? # 首先:必須使得當前節(jié)點的pointer指針指向新建的node ? ? ? ? # 其次:必須保證新建的node的pointer指向當前節(jié)點的后一個節(jié)點 ? ? ? ? node.pointer = current.pointer ? ? ? ? current.pointer = node
(7)計算鏈表長度:get_length()函數(shù)
對于調(diào)用者和類內(nèi)部的其它函數(shù)來做,鏈表長度是一個非常有用的值。比如在插入函數(shù)insert()
中,需要判斷插入位置是不是大于鏈表長度。
計算鏈表長度的實現(xiàn)比較簡單,只需要遍歷鏈表的所有節(jié)點,并用計數(shù)器來統(tǒng)計節(jié)點的數(shù)目即可。
def get_length(self): ? ? """ 獲取鏈表的長度""" ? ? # 沒有任何node ? ? if self.__head == None: ? ? ? ? return 0 ? ? # 節(jié)點數(shù)統(tǒng)計 ? ? else: ? ? ? ? current = self.__head ? ? ? ? length = 0 ? ? ? ? while current != None: ? ? ? ? ? ? current = current.pointer ? ? ? ? ? ? length += 1 ? ? ? ? return length
(8)遍歷所有節(jié)點:traversal()函數(shù)
鏈表、樹、圖等結(jié)構(gòu)都需要遍歷操作,其中鏈表的遍歷比較簡單,只需要依次的訪問所有節(jié)點即可。
def traversal(self): ? ? current = self.__head ? ? i = 0 ? ? # 循環(huán)結(jié)束的條件依舊是節(jié)點的pointer指向不為空 ? ? while current != ?None: ? ? ? ? print("Element {} is {} ".format(i, current.data)) ? ? ? ? current = current.pointer ? ? ? ? i += 1
(9)搜索:search()函數(shù)
前面提到搜索有按值搜索和按位置搜索兩種,它們的原理和實現(xiàn)都十分相似,所以僅以按值搜索為例。
需要注意的是,insert()
函數(shù)需要判斷鏈表是否為空,并且需要考慮到目標值不在鏈表中的情況,分別對應(yīng)不同的返回值。
def search(self, data): ? ? """ 返回值為data的第一個節(jié)點""" ? ? if self.__head == None: ? ? ? ? return -1 ? ? else: ? ? ? ? current = self.__head ? ? ? ? current_position = 0 ? ? ? ? # 遍歷節(jié)點 ? ? ? ? while current != None: ? ? ? ? ? ? # 目標值搜索成功 ? ? ? ? ? ? if current.data == data: ? ? ? ? ? ? ? ? return current_position ? ? ? ? ? ? # 目標值搜索不到則繼續(xù)搜索 ? ? ?? ? ? ? ? ? ? else: ? ? ? ? ? ? ? ? current_position += 1 ? ? ? ? ? ? ? ? current = current.pointer ? ? # 目標值不存在于鏈表中 ? ? ? ?? ? ? return False
(10)刪除:delete()函數(shù)
上述的查找中以“按值查找”為例,這次刪除中同樣以“按值刪除”為例,“按位置刪除”的實現(xiàn)與之類似。
按值刪除,即刪除目標值對應(yīng)的目標節(jié)點。在進行遍歷時,需要記錄當前節(jié)點和當前節(jié)點的前一個節(jié)點。因為,一旦查找大目標值所在的目標節(jié)點,需要令目標節(jié)點的前一個節(jié)點指向目標節(jié)點的下一個節(jié)點,即完成節(jié)點的刪除。
def delete(self, data): ? ? """ 刪除值為data的第一個節(jié)點""" ? ? if self.is_empty(): ? ? ? ? return None ? ?? ? ? # 記錄當前節(jié)點和前一個節(jié)點 ? ? current = self.__head ? ? piror = None ? ? while current != None: ? ? ? ? # 查找成功分為兩種情況 ? ? ? ? if current.data == data: ? ? ? ? ? ? # 目標節(jié)點為頭結(jié)點 ? ? ? ? ? ? if current == self.__head: ? ? ? ? ? ? ? ? self.__head = self.__head.pointer ? ? ? ? ? ? ? ? return True ? ? ? ? ? ? ?# 目標節(jié)點不是頭結(jié)點 ? ? ? ? ? ? ?# 令目標節(jié)點的前一個節(jié)點指向目標節(jié)點的后一個節(jié)點 ?? ? ? ? ? ? ? else: ? ? ? ? ? ? ? ? piror.pointer = current.pointer ? ? ? ? ? ? ? ? return True ? ? ? ? ?# 更新當前節(jié)點和前一個節(jié)點 ? ? ? ? ?else: ? ? ? ? ? ? piror = current ? ? ? ? ? ? current = current.pointer ? ? return False ??
2.C++實現(xiàn)
前面的python
實現(xiàn)中已經(jīng)分析了各個函數(shù)的作用,以及對應(yīng)的實現(xiàn)過程。雖然python和C++的語法不同,但是核心過程是類似的,所以下面不再重復(fù)對過程的敘述。
(1)節(jié)點設(shè)計
由于C++的指針必須指定類型,所以需要使用空指針NULL
作為pointer
的值。
class Node{ public: ?? ?int data; ?? ?Node *pointer=NULL; };
(2)鏈表類:SingleLinkedList
遵循聲明和實現(xiàn)分類的策略,先對各個函數(shù)進行聲明。
class SingleLinkedList { public: ?? ?SingleLinkedList(); ?? ?bool isEmpty(); ?? ?int getLength(); ?? ?void add(int data); ?? ?void append(int data); ?? ?void insert(int position, int data); ?? ?void traversal(); ?? ?int search(int data); ?? ?void remove(int data); private: ?? ?Node *head; };
(3)判斷鏈表是否為空:isEmpty()函數(shù)
bool SingleLinkedList::isEmpty() { ?? ?// 頭結(jié)點不指向任何結(jié)點,為空 ?? ?if (head->pointer == NULL) { ?? ??? ?return true; ?? ?} ?? ?else { ?? ??? ?return false; ?? ?} }
(4)頭插法:add()函數(shù)
void SingleLinkedList::add(int data) { ?? ?// 當原列表僅有頭結(jié)點時,直接插入新節(jié)點即可 ?? ?if (head->pointer == NULL) { ?? ??? ?head->pointer = new Node; ?? ??? ?head->pointer->data = data; ?? ??? ? ?? ?} ?? ?// 當原列表頭結(jié)點后面含有后繼節(jié)點時 ?? ?// 令頭結(jié)點直接后繼為新節(jié)點 ?? ?// 并令新節(jié)點的直接后繼為原來頭結(jié)點的直接后繼 ?? ?else { ?? ??? ?// 臨時存儲頭結(jié)點的直接后繼 ?? ??? ?Node *temp = head->pointer; ?? ??? ?head->pointer = new Node; ?? ??? ?head->pointer->data = data; ?? ??? ?head->pointer->pointer = temp; ?? ?} }
(5)尾插法:append()函數(shù)
void SingleLinkedList::append(int data) { ?? ?Node *current = head->pointer; ?? ?// 找到列表的最后一個節(jié)點的位置current ?? ?// current的指針域為NULL ?? ?while (current->pointer!=NULL) ?? ?{ ?? ??? ?current = current->pointer; ?? ?} ?? ?// 令current的指針域指向新節(jié)點,完成插入 ?? ?current->pointer = new Node; ?? ?current->pointer->data = data; }
(6)在任意位置插入:insert()函數(shù)
void SingleLinkedList::insert(int position, int data) { ?? ?// 頭插法 ?? ?if (position <= 0) { ?? ??? ?add(data); ?? ?} ?? ?// 尾插法 ?? ?else if (position > getLength()){ ?? ??? ?append(data); ?? ?} ?? ?else { ?? ??? ?// 令頭指針所在的位置為0 ?? ??? ?int current_position = 0; ?? ??? ?Node *current = head; ?? ??? ?Node *prior = NULL; ?? ??? ?// 查找目標節(jié)點位置current,并記錄其直接前驅(qū)節(jié)點piror ?? ??? ?while (current_position<position) ?? ??? ?{ ?? ??? ??? ?// 更新當前節(jié)點和直接前驅(qū) ?? ??? ??? ?prior = current; ?? ??? ??? ?current = current->pointer; ?? ??? ??? ?current_position++; ?? ??? ?} ?? ??? ?// 目標位置的直接前驅(qū)prior指向新節(jié)點 ?? ??? ?// 新節(jié)點指向目標位置的節(jié)點 ?? ??? ?prior->pointer = new Node; ?? ??? ?prior->pointer->data = data; ?? ??? ?prior->pointer->pointer = current; ?? ?} };
(7)計算鏈表長度:getLength()函數(shù)
int SingleLinkedList::getLength() { ?? ?int counter = 0; ?? ?Node *current = head; ?? ?// 遍歷鏈表,直到最后一個元素 ?? ?while (current->pointer!=NULL) ?? ?{ ?? ??? ?counter++; ?? ??? ?current = current->pointer; ?? ?} ?? ?return counter; }
(8)遍歷所有節(jié)點:traversal()函數(shù)
void SingleLinkedList::traversal() { ?? ?Node *current; ?? ?// 指向頭結(jié)點的直接后繼 ?? ?current = head->pointer; ?? ?int counter = 1; ?? ?// 遍歷鏈表,輸出每個節(jié)點的值 ?? ?while (current!=NULL) ?? ?{ ?? ??? ?printf("Element in %d is %d \n", counter, current->data); ?? ??? ?counter++; ?? ??? ?current = current->pointer; ?? ?} }
(9)搜索:search()函數(shù)
int SingleLinkedList::search(int data) { ?? ?int current_position = 1; ?? ?Node *current = head->pointer; ?? ?while (current!=NULL) ?? ?{ ?? ??? ?// 搜索成功返回當前位置 ?? ??? ?if (current->data == data) { ?? ??? ??? ?return current_position; ?? ??? ?} ?? ??? ?// 繼續(xù)更新位置; ?? ??? ?current = current->pointer; ?? ??? ?current_position++; ?? ?} ?? ?// 搜索失敗,返回-1 ?? ?return -1; }
(10)刪除:remove()函數(shù)
void SingleLinkedList::remove(int data) { ?? ?Node *current = head->pointer; ?? ?Node *prior = head; ?? ?// 遍歷鏈表 ?? ?while (current!=NULL) ?? ?{ ?? ??? ?// 查找到目標位置 ?? ??? ?if (current->data == data) { ?? ??? ??? ?// 令目標位置的直接前驅(qū)指向目標節(jié)點的直接后繼 ?? ??? ??? ?prior->pointer = current->pointer; ?? ??? ??? ?break; ?? ??? ?} ?? ??? ?// 更新當前節(jié)點和其前驅(qū)節(jié)點 ?? ??? ?prior = current; ?? ??? ?current = current->pointer; ?? ?} }
總結(jié):
在使用python實現(xiàn)時,頭結(jié)點數(shù)據(jù)域data是有效的。這種方式使得代碼中需要很多的“if-else”判斷結(jié)構(gòu),增加了代碼的復(fù)雜性。
在使用C++實現(xiàn)時,頭結(jié)點數(shù)據(jù)域data是無效的,這種方式使得代碼更加簡潔。
到此這篇關(guān)于C++和python實現(xiàn)單鏈表及其原理的文章就介紹到這了,更多相關(guān)C++,python單鏈表內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- Python與C++中梯度方向直方圖的實現(xiàn)
- c++與python實現(xiàn)二分查找的原理及實現(xiàn)
- c++和python實現(xiàn)順序查找實例
- python?與c++相互調(diào)用實現(xiàn)
- python直接調(diào)用和使用swig法方調(diào)用c++庫
- 如何利用Python實現(xiàn)簡單C++程序范圍分析
- 如何在C++中調(diào)用python代碼你知道嗎
- C++調(diào)用python(執(zhí)行py文件)的全過程
- C++通過內(nèi)嵌解釋器調(diào)用Python及間接調(diào)用Python三方庫
- Python 調(diào)用 C++ 傳遞numpy 數(shù)據(jù)詳情
相關(guān)文章
VC實現(xiàn)將網(wǎng)址解析出所有ip地址的實例代碼
這篇文章主要介紹了VC實現(xiàn)將網(wǎng)址解析出所有ip地址的實例代碼,本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2021-04-04