鏈表(一)
1 鏈表的出現背景
線性表的順序存儲有兩個大的缺點:一是鏈表長度有限,只能存儲有限的數據元素,當我們無法確定數據元素數量時順序存儲將無法滿足我們的需求;二是插入刪除操作的時間復雜度為O(n)。為了解決上述問題,所以有了線性表鏈式存儲的的出現。
2 鏈表是什麽?
用一組任意的存儲單元存儲線性表的數據,並通過指針將各個內存單元從前往後串聯起來。因為除了要存儲數據元素信息外,還要存儲直接後繼內存單元的地址,所以在一個內存單元中包含兩部分信息的模型稱為結點。
3 鏈表的特點
1)只要內存大小滿足需求,鏈表長度沒有限制;
2)當知道要插入或刪除的結點後,插入刪除操作的時間復雜度為O(1)。
4 單鏈表的實現及關鍵點
4.1 關鍵點
1)“哨兵”節點的創建可以有利於統一插入和刪除第一個結點的操作;
2)插入刪除操作時特別註意指針賦值的順序,防止指針丟失;
3)刪除操作完成後謹記釋放內存,防止內存泄漏;
4)重點提防邊界條件處理。
4.2 單鏈表的實現
有“哨兵”結點:
1 #ifndef SIGNALLINKLIST_H 2 #define SIGNALLINKLIST_H 3 4 typedef int ElemType; 5 typedef struct Node { 6 ElemType m_data; //數據元素信息 7 Node* m_next; //後繼節點地址 8 }*LinkList; 9 10 class SignalLinkList 11 { 12 private: 13 LinkList m_head; //鏈表頭指針 14 15 public: 16 SignalLinkList(); 17 ~SignalLinkList(); 18 void ClearList(); //清空鏈表 19 bool InsertNode(int i, ElemType elem); //往鏈表第i個結點前插入結點20 bool DeleteNode(int i, ElemType* pElem); //刪除鏈表中的第i個結點,並將數據元素信息返回 21 void VisitList() const; //遍歷鏈表 22 bool IsEmpty() const { return !m_head->m_next; } 23 }; 24 25 #endif
1 SignalLinkList::SignalLinkList() 2 { 3 m_head = new Node; 4 m_head->m_next = nullptr; 5 } 6 7 SignalLinkList::~SignalLinkList() 8 { 9 ClearList(); 10 delete m_head; 11 } 12 13 void SignalLinkList::ClearList() //清空鏈表 14 { 15 Node *pWorkNode = m_head->m_next, *pDeleteNode = nullptr; 16 17 while (pWorkNode) 18 { 19 pDeleteNode = pWorkNode; 20 pWorkNode = pWorkNode->m_next; 21 delete pDeleteNode; 22 } 23 m_head->m_next = nullptr; 24 } 25 26 bool SignalLinkList::InsertNode(int i, ElemType elem) //往鏈表第i個結點前插入結點 27 { 28 Node* pWorkNode = m_head; 29 int j = 1; 30 31 while (j < i && pWorkNode) //遍歷到第i-1個結點或者遍歷完所有節點 32 { 33 pWorkNode = pWorkNode->m_next; 34 ++j; 35 } 36 if (!pWorkNode || j > i) 37 return false; 38 39 //前插操作 40 Node* pNewNode = new Node; 41 pNewNode->m_data = elem; 42 pNewNode->m_next = pWorkNode->m_next; 43 pWorkNode->m_next = pNewNode; 44 45 return true; 46 } 47 48 bool SignalLinkList::DeleteNode(int i, ElemType* pElem) //刪除鏈表中的第i個結點,並將數據元素信息返回 49 { 50 if (!m_head->m_next) //當前鏈表為空鏈 51 return false; 52 53 Node* pWorkNode = m_head; 54 int j = 1; 55 while (j < i && pWorkNode) //遍歷到第i-1個結點或者遍歷完所有節點 56 { 57 pWorkNode = pWorkNode->m_next; 58 ++j; 59 } 60 if (!pWorkNode || j > i) 61 return false; 62 Node* pDeleteNode = pWorkNode->m_next; 63 pWorkNode->m_next = pDeleteNode->m_next; 64 *pElem = pDeleteNode->m_data; 65 delete pDeleteNode; 66 67 return true; 68 } 69 70 void SignalLinkList::VisitList() const //遍歷鏈表 71 { 72 Node* pWorkNode = m_head->m_next; 73 74 std::cout << "The element of list: "; 75 while (pWorkNode) 76 { 77 std::cout << pWorkNode->m_data << ‘ ‘; 78 pWorkNode = pWorkNode->m_next; 79 } 80 std::cout << std::endl; 81 }
測試代碼(在Visual Studio 2017上運行):
#include "pch.h" #include "SignalLinkList.h" #include <iostream> using namespace std; int main() { SignalLinkList list; //往鏈表最後一個數據元素後面插入數據 list.InsertNode(1, 1); list.InsertNode(2, 2); list.InsertNode(3, 3); list.VisitList(); //往鏈表第一個數據元素前插入數據 list.InsertNode(1, 0); //往鏈表中間插入數據元素 list.InsertNode(4, 10); list.VisitList(); ElemType elem; //刪除第一個數據元素 list.DeleteNode(1, &elem); //刪除最後一個數據元素 list.DeleteNode(4, &elem); //刪除中間的數據元素 list.DeleteNode(2, &elem); list.VisitList(); list.ClearList(); if (list.IsEmpty()) cout << "List is empty." << endl; else cout << "List isn‘t empty" << endl; return 0; }
測試結果:
無哨兵結點插入刪除操作:
1 bool SignalLinkList::InsertNode(int i, ElemType elem) //往鏈表第i個結點前插入結點 2 { 3 Node* pNewNode = new Node; 4 pNewNode->m_data = elem; 5 6 if (!m_head || 1 == i) //如果當前鏈表為空鏈或插入的為第一個結點 7 { 8 pNewNode->m_next = m_head; 9 m_head = pNewNode; 10 11 return true; 12 } 13 14 Node* pWorkNode = m_head; 15 int j = 1; 16 while (j < i - 1 && pWorkNode) 17 { 18 pWorkNode = pWorkNode->m_next; 19 ++j; 20 } 21 if (j > i - 1 || !pWorkNode) 22 return false; 23 pNewNode->m_next = pWorkNode->m_next; 24 pWorkNode->m_next = pNewNode; 25 26 return true; 27 } 28 29 bool SignalLinkList::DeleteNode(int i, ElemType* pElem) //刪除鏈表中的第i個結點,並將數據元素信息返回 30 { 31 if (!m_head) //鏈表為空 32 return false; 33 34 if (1 == i) //如果刪除第一個結點 35 { 36 *pElem = m_head->m_data; 37 Node* pDeleteNode = m_head; 38 m_head = m_head->m_next; 39 40 return true; 41 } 42 43 Node* pWorkNode = m_head; 44 int j = 1; 45 while (j < i - 1 && pWorkNode) //遍歷到第i-1個結點或者遍歷完所有節點 46 { 47 pWorkNode = pWorkNode->m_next; 48 ++j; 49 } 50 if (!pWorkNode || j > i) 51 return false; 52 Node* pDeleteNode = pWorkNode->m_next; 53 pWorkNode->m_next = pDeleteNode->m_next; 54 *pElem = pDeleteNode->m_data; 55 delete pDeleteNode; 56 57 return true; 58 }
測試時其它方法也應該做一些修改。
5 數組與鏈表的對比
1)數組的存取操作時間復雜度為O(1),插入刪除操作時間復雜度為O(n)。鏈表的存取復雜度時間復雜度為O(n),插入刪除操作時間復雜度為O(1);
2)數據簡單易用,在實現上使用的是連續的內存空間,可以借助CPU緩存機制,預讀取數組中的數據,訪問效率高。而鏈表在內存中並不是連續存儲的,所以對CPU緩存不友好,不能有效預讀;
3)數組的大小固定,一經聲明就要占用整塊內存空間,但是如果聲明的數組過大,很有可能因內存不足而導致內存分配失敗。而鏈表本身沒有大小限制,不用一次申請所有所需的空間;
4)對鏈表進行頻繁的插入和刪除操作會導致頻繁的內存申請和釋放,容易造成內存碎片。
鏈表(一)