链表
链表是一种线性的数据结构,但是其内存空间和数组不同,是不连续的,因此需要上一个节点有保存下一个节点位置的信息。
Node节点类的设计
Node节点类主要包含两个重要变量,一个是保存当前节点的数据,另外一个是保存下一个节点的位置信息(即位于哪一个内存块上)。位置信息采用指针来实现。具体设计的代码如下:
template<typename T>
class Node{
public:
T value;
Node<T> *next;
Node(T _value, Node<T> *_next = NULL){
value = _value;
next = _next;
}
};
利用Node节点类实现链表
链表的数据成员
链表类中的主要信息只有一个,就是头节点,以供外部访问。另外一些辅助的变量可以包括back尾部指针,以及size大小等信息。
List链表类的主要数据成员如下:
template<typename T>
class List{
private:
Node<T>* head;//链表的头部
Node<T>* back;//链表的尾部
int size = 0;
};
链表作为一种数据结构,最为主要的功能应当包括:查询,添加,删除,修改。
链表的建立
链表节点的添加,也就是链表建立/节点插入的一个过程。对于链表的建立,如果在上一步中,采用了head和back两个指针的数据成员的设计,那么在建立链表时,就能够有两种方式。分别是通过向链表的头部插入节点来建立链表和通过向链表的尾部追加一个节点来建立链表。
通过向链表的头部插入节点来建立链表
由于我们设计了head指针,因此这个操作很简单,首先我们需要建立一个新的节点,之后我们通过调整节点next指针的指向和head指针的指向来实现节点的插入。
具体的操作方法如下:
void push_forward(T _value){ //反向插入节点
size++;
if (!head && !back){ //如果是空链表
head = new Node<T>(_value);//next默认指向NULL
back = head; //只有一个节点 链表的头部和尾部指向同一节点
}
else{ //当链表非空的时候
head = new Node<T>(_value, head);//将链表的头部指向新的节点 链表的尾部不需要进行移动
}
}
通过向链表的尾部插入节点来建立链表
不同于常规的单链表,由于我们添加了一个back指针,这就使得向链表的尾部添加元素变得十分简单,只需要进行与上面相似的操作即可,但是需要注意的一点是,要考虑特殊情况,当原来的链表是空的时候,需要单独对头节点进行相应的处理。
具体的实现方法如下:
void push_back(T _value){//正向插入节点
size++;
if (!head && !back){//如果是空链表
head = new Node<T>(_value);//新建一个节点 链表的头部指向这个节点
back = head; //链表的尾部指向第一个节点
}
else{ //如果链表非空
back->next = new Node<T>(_value);//尾部的所指向的下一个位置新建一个节点 这个节点的下一个指向NULL
back = back->next;//更新链表的尾部指针 //头部指针不需要修改
}
}
删除链表中的元素
对于数组元素的删除,容易知道,它的效率很低下,主要是因为采用数组来存放数据,数据所占据的空间是连续的,如果删除其中的一个,那么想要保持数组原来的连续性,就得将大量的数据进行移动,而这个移动的过程的开销是很大的,带来了不必要的浪费。
而链表的一个优点就是,可以在O(1)的时间内删除头节点或者尾节点,对于处于中间的数据,删除它的平均时间复杂度为O(n/2)。(这里假设我们采用的是从头至尾遍历找点然后删除),但即使是这样,相比较于数组,如果采用同样的方法,它的时间复杂度会是链表的6倍左右。
具体的思路是,从链表的头部开始寻找,直到找到了目标元素,直接将该元素删除,同时需要调整前一个节点的next指针的指向。具体实现如下:
void del(T targetValue){
if (isempty()) return;//如果链表为空 那么直接返回
else{
Node<T>* pre = NULL; //前驱指针
Node<T>* cur = head; //当前指针
while (cur){
if (cur->value == targetValue) return;//如果找到了 直接返回
pre = cur;//更新前驱指针
cur = cur->next;//更新当前指针
}
//删除节点
if (!cur) reutrn;//如果没有找到 直接返回
if (cur == head){
delete cur;
head = NULL;
back = NULL;
}//如果删除的是头节点
else if(cur == back){
delete cur;
back = pre;
pre->next = NULL;
}//如果删除的是尾部节点
else{
Node<T>* p = cur->next;
delete cur;
pre->next = p;
}//如果要删除的值在中间
size--;//跟新size的大小
}
}
查询链表中的元素
链表中的元素的查询的效率较为低下。由于链表的存储空间不是连续的,因此无法像数组那样,直接通过下标在O(1)的时间内访问某个指定的元素。对于链表的元素的查询,其方法为从头节点开始逐个进行比较,直至找到指定元素,时间复杂度为O(n)。具体的实现如下:
void find(T targetValue){
Node<T>* p = head;
while (p){
if (p->value == targetValue){
//do something...
break;
}//如果找到了
p = p->next;
}
}