1. 程式人生 > >鏈表(一)

鏈表(一)

vat 釋放 圖片 大小限制 nullptr sign 順序 讀取 分享

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)對鏈表進行頻繁的插入和刪除操作會導致頻繁的內存申請和釋放,容易造成內存碎片。

鏈表(一)