鏈表是一種很重要的數據結構,它由兩部分組成,第一個部分是咱們要儲存的數據,第二個部分是指向下一個儲存單元的指針。鏈表在使用中有順序表沒法比擬的靈活性,免去了儲存空間不夠,又有可能浪費的尷尬。數據結構
單鏈表有一個頭指針pHead,當咱們沒有數據要儲存的時候它指向NULL,當咱們有數據的時候它指向第一塊儲存單元。儲存單元裏面有兩個部分,前面的部分是咱們要儲存的數據data,後面的部分是指向下一個儲存單元的指針pNext,當後面沒有儲存單元的時候就指向NULL。那麼咱們儲存的數據在內存中並非連續儲存的,而是在內存中跳躍式儲存的。要使用的時候再直接申請一塊空間。
ide
下面是鏈表的定義
函數
typedef struct ListNode { DataType data; struct ListNode *pNext; }SListNode, *PSListNode;
能夠看到單鏈表的兩個成員。爲了使用方便咱們直接typedef重命名。指針
單鏈表有幾種基本操做,好比插入數據,刪除數據,那我下面實現了一下。內存
首先,我寫了一個申請新單元的函數
it
PSListNode ByeNode(DataType data) { PSListNode pNewNode = (PSListNode)malloc(sizeof(SListNode)); if (NULL != pNewNode) { pNewNode->data = data; pNewNode->pNext = NULL; } return pNewNode; }
它能夠爲咱們直接在內存中申請一塊新的空間而且返回它的地址。class
第一個實現就是咱們從尾部插入數據
變量
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; } }
這裏咱們傳的參數是二級指針,由於咱們是要改變它指針的指向。假如咱們直接傳遞一級指針,那麼咱們並不能改變它的指向,至關於咱們函數中操做了半天,其實都是在操做一個臨時的指針變量,只不過他跟咱們的頭指針的指向是同樣的,最後什麼也沒有返回,頭指針什麼變化都沒有。List
接下來就是咱們從尾部刪除數據的實現鏈表
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; } }