链表(一)

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)对链表进行频繁的插入和删除操作会导致频繁的内存申请和释放,容易造成内存碎片。

 

该篇博客是自己的学习博客,水平有限,如果有哪里理解不对的地方,希望大家可以指正!

posted @ 2019-04-08 22:21  zpchya  阅读(206)  评论(0编辑  收藏  举报