数据结构它描述的是数据和数据之间的关系。数据结构要三大要素:逻辑结构,描述数据和数据之间的关系,分为线性结构和非线性结构两种,所谓线性结构指的就是这种数据结构描述的数据之间存在这样的关系,除了首元素和微元素,任何元素都存在一个唯一前驱和唯一后继(前驱通俗的说就是这个元素的前一个元素,后继就是这个元素的后一个元素),而非线性结构中的数据元素之间就不存在这种关系,而是用父节点子节点的关系来描述,也就是说前驱后继不存在唯一性;存储结构,所谓存储结构,实际上指的就是怎么它数据之间的逻辑结构在计算机内存中表示出来,以方便我们对这些数据进行运算,存储结构分两种,顺序结构和链式结构,顺序结构指的是使用一块连续的内存,通过数组索引来表示数据之间的逻辑关系线性或者非线性,链式结构能够有效利用碎片内存,它不需要连续的空间,但是却不具备顺序表的随机访问能力,所谓随机访问指的是访问这片内存中任意位置的元素其时间复杂度都是O(1),链式存储结构的描述暗含这样一个事实,当我们采用链式结构来存储数据结构时,它必然需要一个数据节点来辅助我们实现数据结构,而这个数据节点至少包含数据域(存放我们真正想要的数据)和一个指针域(存放相邻节点的地址);算法,实际上这就是广义的算法了,就是指我们能够在这个数据结构上所采取的操作,例如插入、删除、清空数据结构等等。
或者你也可以把数据结构看成是一个容器,它里面容纳了某种类型的数据,数据之间存在着线性或者非线性的逻辑关系,存储结构则昭示着在计算机的世界里这个容器是以何种形式存在的。
基于顺序存储结构和链式存储结构实现的线性数据结构(线性表),我们把它做个比较:首先,顺序表它使用的是连续的内存,它提供了随机访问能力,但是它会有内存分配失败的风险(因为没有空闲的满足要求大小的内存可以被分配了,虽然这种情况出现的概率比较低,至少我现在为止还没遇到过,可能是因为数据量不够),而链表,它可以有效利用碎片内存,但是因为指针域的存在,无疑会多占用一部分空间(32位总线的机器,地址大小就是4字节)。尾部插入,顺序表的时间复杂度是O(1),链表的话,要看具体实现了。查询操作上,顺序表因为随机访问的特性,访问任意位置上的元素,其时间复杂度都是O(1),而链表,访问任何一个元素都需要从头结点(一般链表的实现会考虑带一个头结点,它不是真正意义上的数据节点,只是为了在编码实现过程中能排除首元素节点的特殊性,首元素节点才是真正意义上的第一个数据节点)开始遍历,直到对应的索引,所以它的时间复杂度是O(1)。任意位置上的插入,假设在x位置插入,对顺序表来说,我们需要将x位置以及其后的所有元素都要逐个移动顺序表大小减去x个位置,其时间复杂度是O(n),空出了位置之后的插入操作时间复杂度是O(1),那么总的插入操作时间复杂度是O(n),对于链表来说,插入的时候首先我们要找到这个索引所指示的节点,这个要从头结点开始遍历,其时间复杂度是O(1),而插入动作本身的时间复杂度为O(1),因为插入实际上只是改变了链表的指向关系,那么总的时间复杂速度是O(n);尽管如此,链表在任意位置的插入效率是要高于顺序表的,很简单,移动操作显然要比较操作更耗时,尤其是当你需要在中间位置插入一个子数据结构的时候,这种差异就越是明显。对于删除操作,它和插入时间复杂度考虑是差不多的,同样的,任意位置上的删除操作,链式表的性能是优于顺序表的。
STL里顺序表的代表就是vector,而链式表就是list了。在数据结构选择时,没有必要的理由,那么就使用vector。当你需要使用list的时候,你要问自己,你真的需要操作中间位置的元素吗?有没有可能把中间位置的元素“变”到尾部(比如交换)来完成操作,因为通常来说,我们使用数据结构时,尾部的插入和索引访问时使用频率最高的操作。
下面是基于链式存储结构实现的单链表,欢迎指针,文字描述,可以参考注释,这里尤其需要注意的是友元函数和Linux下template的写法,它和Windows很不一样。
数据节点的实现:
/**********************************************************************************/ /*链表的节点描述 */ /*链表就是靠每个节点间的指针指向来表示线性关系中的前驱和后继关系 */ /*每个链表的节点,必须包含指针域(指向下一节点的指针)和数据域(结点包含的数据) */ /*struct和class的区别:C++对C中的struct进行了扩充 */ /*它们的相同点是:struct和class都可以有成员函数(包括构造函数和析构函数) */ /* 都能实现继承和多态 */ /*区别是:struct在继承时,它的默认继承方式是private方式的 */ /* struct的成员和方法,它的默认访问控制权限是private的 */ /**********************************************************************************/ #ifndef CHAINNODE_H_ #define CHAINNODE_H_ template <typename T> struct ChainNode { ChainNode<T> *pNextNode{nullptr}; //指向链表中下一个结点的指针,名称中的p表示指针,这就是所谓的指针域 T element; //数据域,表示的是节点中存储的数据 ChainNode() = default; explicit ChainNode(const T& element) { this->element = element; //这里使用了this->element =element 这样的语法,这是因为形参名和类的成员有了同样的名字,使用this能区别谁是形参谁是类的成员 }; //构造函数 ChainNode(const T& element,ChainNode<T> *pNextNode) { this->element = element; this->pNextNode = pNextNode; } }; #endif ~
链表的实现
1 root@121.43.188.163's password: 2 Last login: Tue Jan 15 23:28:51 2019 from 112.64.61.14 3 4 Welcome to Alibaba Cloud Elastic Compute Service ! 5 6 [root@CentOs64-7 ~]# ls 7 MyProject mysql plantuml Python 8 [root@CentOs64-7 ~]# cd MyProject/ 9 [root@CentOs64-7 MyProject]# ls 10 include library source 11 [root@CentOs64-7 MyProject]# cd source/ 12 [root@CentOs64-7 source]# ls 13 Arithmetic DataStructrue Experiment tcpip 14 C++Primer DesignPattern MultiThread 15 [root@CentOs64-7 source]# cd DataStructrue/ 16 [root@CentOs64-7 DataStructrue]# ls 17 ChainList 18 [root@CentOs64-7 DataStructrue]# cd ChainList/ 19 [root@CentOs64-7 ChainList]# ls 20 ChainList.h ChainNode.h main.cpp main.out 21 [root@CentOs64-7 ChainList]# vim ChainNode.h 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 [root@CentOs64-7 ChainList]# vim ChainList.h 68 69 70 } 71 //找到给定位置的前驱 72 auto pPreElement = findPreElement(iPosition); 73 //用给定的参数构造新的结点 74 ChainNode<T> *pNewNode = new (std::nothrow) ChainNode<T>(element,pPreElement->pNextNode); 75 pPreElement->pNextNode = pNewNode; 76 ++m_iSize; 77 } 78 79 template <typename T> 80 void ChainList<T>::erase(const int iPosition) 81 { 82 //删除指定位置的元素 83 if( iPosition < 0 || iPosition >= m_iSize) 84 return; 85 //如果给定的索引不是一个合法索引,那么先找到这个元素,删除的时候给定的索引是不能等于m_iSize,如果等于的话,意味着删除的是最后一个元素,这是不合理的 86 auto pPreElement = findPreElement(iPosition); 87 pPreElement->pNextNode->element.~T(); 88 auto pTemp = pPreElement->pNextNode; 89 pPreElement->pNextNode = pPreElement->pNextNode->pNextNode; 90 delete pTemp; 91 pTemp = nullptr; 92 --m_iSize; 93 } 94 95 template <typename T> 96 void ChainList<T>::clear() 97 { 98 //清除数据结构中保存的数据 99 auto pNode = m_pHeaderNode->pNextNode; 100 ChainNode<T> *pNextDelNode {nullptr}; 101 while(pNode != nullptr) 102 { 103 pNextDelNode = pNode->pNextNode; 104 pNode->element.~T(); 105 delete pNode; 106 pNode = pNextDelNode; 107 --m_iSize; 108 } 109 if(nullptr == m_pHeaderNode) 110 { 111 std::cout << "头节点已经被删除了" << std::endl; 112 } 113 m_pHeaderNode->pNextNode = nullptr; 114 } 115 116 template <typename T> 117 ChainList<T>::~ChainList() 118 { 119 clear(); 120 delete m_pHeaderNode; 121 m_pHeaderNode = nullptr; 122 } 123 124 template <typename T> 125 std::ostream& operator<<(std::ostream &out,const ChainList<T> &myList) 126 { 127 if(true == myList.empty()) 128 return out; 129 auto pNode = myList.m_pHeaderNode->pNextNode; 130 while(pNode != nullptr) 131 { 132 out << pNode->element << " "; 133 pNode = pNode->pNextNode; 134 } 135 out << std::endl; 136 } 137 #endif