何谓链表,为何使用链表的数据结构
链表是一种线性表,也就是说,它还是有顺序的,比如下标1, 2, 3...通常情况下,提起线表自然想到的存储方式就是连续的一片内存,比如C++中的数组或者STL的vector,这种存储方式便于连续读取和存储,访问也很方便,只要知道第一个元素的位置,再走n步(步长为数据长度),就是第n+1个元素的位置。对于链表,其存储位置并不是连续的。所以为了把随机位置的数据连起来,链表需要存储数据,还要存储数据之间是怎么连在一起的,也就是相邻元素的地址信息。
可见链表的存储是比较麻烦的,同时,链表的与遍历有关的运行往往会很慢。设想,如果想要获取第5000个元素,数组只要在第一个位置地址基础上延后5000;而对于链表,你可能需要从第一个位置一直找下去,直到走5000步。但是链表在增删元素上是灵活的,只要随机划一片内存,加一个元素,然后后改变相邻元素的指针关系就可以了,增删元素的耗时都是一个常数;相形之下,数组要笨重地多,你要把数组从中间剖开,后续的元素都要受到影响,需要复制重建。
链表的实现
链表并不是一个全新的概念,事实上,在STL标准库里面就有list链表容器了。但是为了便于理解,自己来亲手写一个链表的结构也很有意思。
template <class T>
// 链表节点结构体
struct listNode {
listNode(const T& t) {
data = t;
previous = NULL;
next = NULL;
}
T data;
listNode<T>* previous;
listNode<T>* next;
};
如上,即为一个双向链表的一个节点的内容,包括数据,向前指针和向后指针。值得注意的是,链表和链表节点是不同结构,这里我们定义的并不是链表,而是描述一个链表节点的结构体。而对于链表类,我们有如下的类的定义。
template <class T>
class List {
public:
List();
List(const List& L);
~List();
void print();
void push_back(T t);
void insert(T t, int position);
void delete(int position);
protected:
listNode<T>* firstNode;
listNode<T>* lastNode;
int listSize;
};
这个简单的链表的成员包括第一个节点位置firstNode,最后一个节点位置lastNode, 以及链表总长度listSize. 同时该链表还包括最基本的成员函数,比如构造,复制析构等。还有将所有链表内容打印的print函数,在尾部加入元素的push_back函数,随机位置增删的insert以及delete函数。
现在我们就来看一下这几个是怎么实现的吧!
//构造函数
template <class T>
List<T>::List() {
firstNode = NULL;
lastNode = NULL;
listSize = 0;
}
//复制构造函数
template <class T>
List<T>::List(const List<T>& L) {
listNode<T>* tempNode1;
listNode<T>* sourceNode;
listNode<T>* duplicateNode;
//如果为空
if (L.listSize == 0) {
firstNode = NULL;
lastNode = NULL;
listSize = 0;
return;
}
//如果非空
firstNode = new listNode<T>(L.firstNode->data);
// sourceNode复制来源
sourceNode = L.firstNode;
// duplicateNode新建节点
duplicateNode = firstNode;
while (sourceNode->next != NULL) {
sourceNode = sourceNode->next;
duplicateNode->next = new listNode<T>(sourceNode->data);
tempNode1 = duplicateNode;
duplicateNode = duplicateNode->next;
duplicateNode->previous = tempNode1;
}
lastNode = duplicateNode;
listSize = L.listSize;
}
// 析构函数
template <class T>
List<T>::~List() {
// 从头到尾逐步删除节点
while (firstNode != NULL) {
listNode<T>* tempNode = firstNode->next;
delete firstNode;
firstNode = tempNode;
}
}
// push_back()函数, 在尾部加一个节点
template <class T>
void List<T>::push_back(T t) {
listNode<T>* newNode;
newNode = new listNode<T>(t);
if (listSize == 0) {
lastNode = firstNode = newNode;
}
else {
lastNode->next = newNode;
lastNode = newNode;
}
listSize++;
}
// insert函数, 在链表中加一个节点
template <class T>
void List<T>::insert(T t, int position) {
listNode<T>* newNode;
listNode<T>* tempNode;
newNode = new listNode<T>(t);
if (position == 0) {
firstNode->previous = newNode;
newNode->next = firstNode;
firstNode = newNode;
listSize++;
return;
}
else if (position == listSize) {
lastNode->next = newNode;
newNode->previous = lastNode;
lastNode = newNode;
listSize++;
return;
}
if (position > listSize / 2) {
tempNode = lastNode;
for (int i = 0; i < listSize - position; i++) {
tempNode = tempNode->previous;
}
}
else {
tempNode = firstNode;
for (int i = 0; i < position - 1; i++) {
tempNode = tempNode->next;
}
}
listSize++;
newNode->next = tempNode->next;
tempNode->next->previous = newNode;
newNode->previous = tempNode;
tempNode->next = newNode;
return;
}
// delete 函数
template <class T>
void List<T>::del(int position) {
listNode<T>* tempNode;
if (position == 0) {
tempNode = firstNode->next;
tempNode->previous = NULL;
delete firstNode;
firstNode = tempNode;
listSize--;
return;
}
else if (position == listSize - 1) {
tempNode = lastNode->previous;
tempNode->next = NULL;
delete lastNode;
lastNode = tempNode;
listSize--;
return;
}
// 依据位置靠近头部还是尾部选择
if (position < listSize / 2) {
tempNode = firstNode;
for (int i = 0; i < position - 1; i++) {
tempNode = tempNode->next;
}
}
else {
tempNode = lastNode;
for (int i = 0; i < listSize - position - 1; i++) {
tempNode = tempNode->previous;
}
}
listSize--;
tempNode->previous->next = tempNode->next;
tempNode->next->previous = tempNode->previous;
delete tempNode;
}
// print 函数, 打印链表的全部内容
template <class T>
void List<T>::print() {
listNode<T>* tempNode;
tempNode = firstNode;
for (int i = 0; i < listSize; i++) {
std::cout << std::to_string(tempNode->data) << std::endl;
tempNode = tempNode->next;
}
return;
}
链表的测试
对上述构建的链表类进行了简单测试, 基本实现了上述目标功能. 现在回看,上述链表实现存在着一些不足。比如头结点,加入头结点使得代码更加简洁, 就不需要再判断链表是否为空或者特殊操作位置是否在链表的头或者尾了。
新手指南
对于新手而言, 通过C++实现数据结构里面的链表,最难理解的可能就是指针, 而不能理解指针就无法理解链表.
我们知道,程序运行过程中,数据存储在内存中,而指针就是这些内存的门牌号. 比如上述实现方法里面, listNode
上面程序中有指向结构体的指针,通过该指针能够访问结构体的成员,但是不能用"a.b", 而是使用了"a->b". 二者的用法是非常类似的.