鏈表是一種很重要的數據結構,它由兩部分組成,第一個部分是我們要儲存的數據,第二個部分是指向下一個儲存單元的指針。鏈表在使用中有順序表無法比擬的靈活性,免去了儲存空間不夠,又有可能浪費的尷尬。
單鏈表有一個頭指針pHead,當我們沒有數據要儲存的時候它指向NULL,當我們有數據的時候它指向第一塊儲存單元。儲存單元裏面有兩個部分,前面的部分是我們要儲存的數據data,後面的部分是指向下一個儲存單元的指針pNext,當後面沒有儲存單元的時候就指向NULL。那麼我們儲存的數據在內存中並不是連續儲存的,而是在內存中跳躍式儲存的。要使用的時候再直接申請一塊空間。
下面是鏈表的定義
typedef struct ListNode { DataType data; struct ListNode *pNext; }SListNode, *PSListNode;
可以看到單鏈表的兩個成員。爲了使用方便我們直接typedef重命名。
單鏈表有幾種基本操作,比如插入數據,刪除數據,那我下面實現了一下。
首先,我寫了一個申請新單元的函數
PSListNode ByeNode(DataType data) { PSListNode pNewNode = (PSListNode)malloc(sizeof(SListNode)); if (NULL != pNewNode) { pNewNode->data = data; pNewNode->pNext = NULL; } return pNewNode; }
它可以爲我們直接在內存中申請一塊新的空間並且返回它的地址。
第一個實現就是我們從尾部插入數據
void PushBack(PSListNode* pHead, DataType data) { PSListNode pNode = NULL; PSListNode pNewNode = NULL; assert(pHead); if (NULL == *pHead) { *pHead = ByeNode(data); } else { pNode = *pHead; while (NULL != pNode->pNext) { pNode = pNode->pNext; } pNewNode = ByeNode(data); pNode->pNext = pNewNode; } }
這裏我們傳的參數是二級指針,因爲我們是要改變它指針的指向。假如我們直接傳遞一級指針,那麼我們並不能改變它的指向,相當於我們函數中操作了半天,其實都是在操作一個臨時的指針變量,只不過他跟我們的頭指針的指向是一樣的,最後什麼也沒有返回,頭指針什麼變化都沒有。
接下來就是我們從尾部刪除數據的實現
void PopBack(PSListNode* pHead) { /*PSListNode pPerNode = NULL; PSListNode pCurNode = NULL; assert(pHead); if (NULL == *pHead) { return; } else { pCurNode = *pHead; pPerNode = pCurNode; while (NULL != pCurNode->pNext) { pPerNode = pCurNode; pCurNode = pCurNode->pNext; } if (pCurNode==pPerNode) { *pHead = NULL; free(pCurNode); pCurNode = NULL; pPerNode = NULL; } else { pPerNode->pNext = NULL; free(pCurNode); pCurNode = NULL; } }*/ PSListNode pPerNOde = *pHead; PSListNode pCurNode = *pHead; assert(pHead); if (NULL == *pHead) { return; } else { if (NULL == pCurNode->pNext) { return; } else { while (NULL!=pCurNode->pNext) { pPerNOde = pCurNode; pCurNode = pCurNode->pNext; } pPerNOde->pNext = NULL; free(pCurNode); pCurNode = NULL; } } }
註釋中的代碼是我剛開始的時候寫的,我發現他的邏輯不是很清晰,在第二個部分中我把鏈表中只有一個節點的情況單列了出來,邏輯比之前清晰了很多。
那我們也可以在鏈表的頭部插入數據
void PushFront(PSListNode* pHead, DataType data) { PSListNode NewNode = NULL; assert(pHead); if (NULL == *pHead) { *pHead = ByeNode(data); } else { NewNode = ByeNode(data); if (NULL == NewNode) { return; } else { NewNode->pNext = (*pHead); *pHead = NewNode; } } }
思路有了之前的兩個函數做鋪墊想起來並不難。申請一塊新的空間之後,讓它的pNext指向我們之前的第一塊空間。然後改變我們的頭指針的指向,讓它指向我們的新空間。這裏注意我們申請空間是有可能失敗的,所以要判斷一下。
當然還有從頭部刪除
void PopFront(PSListNode* pHead) { assert(pHead); if (NULL == *pHead) { return; } else { PSListNode pCurNode = *pHead; pCurNode = pCurNode->pNext; free(*pHead); *pHead = pCurNode; pCurNode = NULL; } }
千萬不要忘記free空間之後要給指針賦空,否則會形成野指針。
還有就是尋找我們鏈表中的元素
PSListNode Find(PSListNode pHead, DataType data) { if (NULL == pHead) { return NULL; } else { PSListNode pNode = pHead; /*while (data != pNode->data) { if (NULL == pNode->pNext) { return NULL; } pNode = pNode->pNext; } return pNode;*/ while (NULL != pNode) { if (data == pNode->data) return pNode; pNode = pNode->pNext; } return NULL; } }
註釋掉的代碼是我第一次寫的,後來我發現它的邏輯有點問題,我可以更簡單的實現它的功能。
最後返回我要找的數據的位置,假如沒有找到那麼就返回空。
打印我鏈表中的元素
void PrintList(PSListNode pHead) { PSListNode pNode = pHead; while (NULL!=pNode) { printf("%d ", pNode->data); pNode = pNode->pNext; } printf("\n"); }
刪除我的任意位置的節點
void Erase(PSListNode* pHead, PSListNode pos) { PSListNode pCurNode = pos; PSListNode pPerNode = NULL; assert(pHead); if (NULL == *pHead) { return; } else { pPerNode = *pHead; while (pPerNode->pNext != pCurNode) { pPerNode = pPerNode->pNext; } pPerNode->pNext = pCurNode->pNext; free(pCurNode); pCurNode = NULL; } }
在我的鏈表的任意位置插入一個節點
void Insert(PSListNode* pHead, PSListNode pos, DataType data) { PSListNode ptmpNode = *pHead; PSListNode pNode = *pHead; assert(pHead); if (NULL == *pHead) { *pHead = ByeNode(data); } else { while (pos != pNode) { if (NULL == pNode) { return; } pNode = pNode->pNext; } ptmpNode = pNode->pNext; pNode->pNext = ByeNode(data); pNode = pNode->pNext; pNode->pNext = ptmpNode; } }