链表(一)
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)对链表进行频繁的插入和删除操作会导致频繁的内存申请和释放,容易造成内存碎片。
该篇博客是自己的学习博客,水平有限,如果有哪里理解不对的地方,希望大家可以指正!