1. 鏈表的特點
- 鏈表是一種非線性、非順序的物理結構,是由若干個節點組成。
- 鏈表采用的是“見縫插針”的存儲方法,不要求內存連續,靠next指針關聯起來。
- 鏈表的物理存儲方式為隨機存儲,訪問方式為順序訪問。
- 查找節點的時間復雜度為O(n),插入、刪除節點的時間復雜度為O(1)。
- 鏈表適用於寫操作多,讀操作少的場景。
1 //單向鏈表節點的數據結構 2 struct SingleListNode 3 { 4 int nData;//當前節點的數據 5 Node* pNext;//指向下一個節點的指針 6 }; 7 8 //雙向鏈表節點的數據結構 9 struct DobuleListNode 10 { 11 int nData;//當前節點的數據 12 Node* pPre;//指向上一個節點的指針 13 Node* pNext;//指向下一個節點的指針 14 };
2. 鏈表的基本操作
鏈表的操作方式主要分為增、刪、改、查,下面以單鏈表為例,分別以最簡單的方式介紹基本用法。
首先定義一個單鏈表數據結構:
1 struct SingleListNode 2 { 3 int nVal; 4 SingleListNode* pNext; 5 SingleListNode():pNext(NULL){} 6 SingleListNode(int nTempValue):nVal(nTempValue),pNext(NULL){} 7 }; 8 9 class SingleList 10 { 11 public: 12 SingleList():m_pHead(NULL),m_nSize(0){} 13 ~SingleList(){} 14 public: 15 SingleListNode* Find(int nIndex); 16 void Insert(int nIndex,SingleListNode* pNode); 17 SingleListNode* Remove(int nIndex); 18 void PrintList(); 19 private: 20 SingleListNode* m_pHead; 21 int m_nSize; 22 };
2.1 查找
鏈表節點的查找不能通過索引快速定位,只能從頭節點開始查找。
1 SingleListNode* SingleList::Find(int nIndex) 2 { 3 if(nIndex < 0 || nIndex >= m_nSize) 4 { 5 printf("SingleList::Find:failed! the index is out of range. index is %d \n",nIndex); 6 return NULL; 7 } 8 9 if(NULL == m_pHead) 10 { 11 printf("SingleList::Find:failed! the head node is null. \n"); 12 return NULL; 13 } 14 15 SingleListNode* pRet = m_pHead; 16 for(int i = 0;i < nIndex;i++) 17 { 18 if(pRet) 19 { 20 pRet = pRet->pNext; 21 } 22 } 23 return pRet; 24 }
2.2 更新
首先從列表中查找待更新的節點,直接把舊數據替換為新值即可。
2.3 刪除
刪除分為三種情況:
- 尾部刪除:將倒數第二個節點的next指針只為空。
- 頭部刪除:將鏈表頭節點設為原頭節點的next指針。
- 中間刪除:將前置節點的next指針指向待刪除節點的next指針。中間刪除和尾部刪除可以采用同一段代碼實現。
1 SingleListNode* SingleList::Remove(int nIndex) 2 { 3 SingleListNode* pRet = NULL; 4 if(nIndex < 0 || nIndex >= m_nSize) 5 { 6 printf("SingleList::Remove:failed! the index is out of range, size is %d .\n ",m_nSize); 7 return pRet; 8 } 9 10 if(NULL == m_pHead) 11 { 12 printf("SingleList::Remove:failed! the head node is null. \n"); 13 return NULL; 14 } 15 16 if(nIndex == 0) 17 { 18 pRet = m_pHead; 19 m_pHead = m_pHead->pNext; 20 } 21 else 22 { 23 SingleListNode* pPreNode = Find(nIndex - 1); 24 if(pPreNode == NULL) 25 { 26 printf("SingleList::Remove:failed! the pPre node is null.\n "); 27 return pRet; 28 } 29 30 pRet = pPreNode->pNext; 31 if(pRet) 32 { 33 pPreNode->pNext = pRet->pNext; 34 m_nSize--; 35 } 36 } 37 38 return pRet; 39 }
2.4 插入
- 尾部插入:將尾節點的next指針指向新插入的節點。
- 頭部插入:首先將帶插入的節點的next指針頭節點,然后將新插入的節點設為鏈表頭節點(注意順序不能反)。
- 中間插入:首先將新節點的next指針指向插入位置的節點,然后將原始插入位置節點的前置節點的next指針指向新節點(注意順序不能反)。中間插入和尾部插入可以采用同一段代碼實現。
1 void SingleList::Insert(int nIndex,SingleListNode* pNode) 2 { 3 if(NULL == pNode) 4 { 5 printf("SingleList::Insert:failed! the pNode is null.\n "); 6 return; 7 } 8 9 if(nIndex < 0 || nIndex > m_nSize) 10 { 11 printf("SingleList::Insert:failed! the index is out of range, size is %d .\n ",m_nSize); 12 return; 13 } 14 15 if(nIndex == 0) 16 { 17 //first 18 pNode->pNext = m_pHead; 19 m_pHead = pNode; 20 } 21 else 22 { 23 SingleListNode* pPre = Find(nIndex - 1); 24 if(pPre == NULL) 25 { 26 printf("SingleList::Insert:failed! the pPre node is null.\n "); 27 return; 28 } 29 30 pNode->pNext = pPre->pNext; 31 pPre->pNext = pNode; 32 } 33 m_nSize++; 34 }
3. 鏈表的應用
常見的面試題中考察鏈表應用的場景有:
- 兩數相加
- 判斷鏈表中是否有環
- 刪除鏈表的倒數第N個節點
- 反轉鏈表(單鏈表)
- 回文鏈表
- 合並兩個有序鏈表
等等,后邊會單獨出一篇文章介紹面試中常見的鏈表算法題。