今天是中秋节,开学第三周周末了,这学期让我感觉最有趣的课是python和数据结构,数据结构的前几节课,老师讲了list链表的两种实现方式,分别是Arraybased List和LinkedList。我感觉了解数据结构的底层实现方式特别有趣,于是课下自己花了两天的课余时间尝试了LinkedList的实现,在这里分享一下自己的心得。
一. 简介
List, 是一个由多个结点连接而成的链表,像一个火车一样,由一节一节的车厢组成,每个车厢仅仅连接前后相邻的车厢,有了这样的连接,火车头就可以带着多节车厢一起前进。List是一个可增可删,可以在任意位置插入的结构,与固定大小的数组相比,更加便于实际操作。本文主要介绍LinkedList在c++中的实现。
二. Link
有了链表的基本概念,那么火车厢之间是如何连接在一起的呢?很简单,就是指针。每一个结点都储存有两个值:结点本身的数值和下一个结点的位置。所以我们只要做如下的定义:
template <class Elem> class Link {
public:
Elem element;
Link *next;
Link<Elem>(const Elem& value, Link<Elem>* nextval = nullptr)
:element(value),next(nextval)
{
}
Link<Elem>(Link<Elem>* nextval = nullptr)
:next(nextval)
{
}
};
我们当然不希望这个链表只能储存整数或者只能储存字符,所以我们将Link定义为模板类,Elem代表各种数据类型,如int,float,char,string……
另外,在这里我们要讲一下list和array的一个重要区别:list的元素的储存位置是分散的,而array的储存位置是连续的。
Array在初始化时,系统根据数组的大小分配了一块连续的储存区域,数组的大小再也不可以改变,这也正是array不能直接往数组中间插入数值的原因;
对于list,初始化时仅仅生成了第一个位置(也就是火车头),每次增加元素时,系统单独分配一个内存空间给这个新元素,相邻位置的元素会储存下这个新元素的内存地址(也就是Link类中的*next),所以,即使list的储存空间是分散的,仍然可以通过这些储存下的地址找到list中的元素。
三. 定义LinkedList类
有了Link这个基础的结构,我们就可以搭建LinkedList类了,给火车装上发动机,给火车上的工作人员分配好具体工作,保证火车稳定的开动。先来看看private的数据成员和初始化函数:
#include "Link.h"
class LinkedList :
{
private:
Link<Elem>* head;
Link<Elem>* tail;
Link<Elem>* fence;
int leftnum;
int rightnum;
public:
LinkedList() {
head = tail = fence = new Link<Elem>;
leftnum = 0;
rightnum = 0;
}
}
很直观,head是链表的第一个位置,tail是链表的最后一个位置,fence则代表当前位置,leftnum和rightnum代表当前位置前后的元素的数量。初始化一个list,没有传入元素,初始化后如图所示,如果没有看懂可以回顾一下上面Link类的初始化哦
有了这几个基础的数据成员,我们就可以实现大部分list链表的操作了。看,我们要实现的函数有,这么多!别怕,熟悉了基本的操作,就能举一反三了。
void clear(); //清空所有数据
bool insert(const Elem&); //在当前位置插入数据
bool append(const Elem&); //在链表尾部增加元素
bool remove(); //移除当前位置元素
void setStart(); //将当前位置设定为第一个位置
void setEnd(); //将当前位置设定为最后一个位置
void prev(); //将位置向前移一位
void next(); //将位置向后移一位
int leftLength() const; //返回前面的元素数量
int rightLength() const; //返回后面元素数量
bool setPos(int pos); //设置当前位置
bool getValue(Elem&) const; //获取当前的数值
void print() const; //打印整个链表
四.函数实现
先来看看insert函数的实现,不如再看一次初始化实例图:
1.当我们为链表插入第一个元素时的步骤是这样的:
a)先创建一个element为73,指向null的一个Link对象:
Link<int>* temp=new Link<int>(73,nullptr);
b)原本fence是指向null的,我们要让它指向这个新元素temp:
fence->next=temp;
c)然后,右边元素数加一,tail相应地要移到73的位置,如图
tail=temp;
rightnum++;
2.插入第一个元素之后,我们想要在fence处再插入一个数72:
a)同样地创建一个值为72的Link对象,但是需要注意的是,此时不是在队尾插入,而是在fence和tail之间插入,所以我们不能像刚才一样让新建的Link指向null,而是应该指向下一个,即73
Link<int>* temp=new Link<int>(72,fence->next);
b)fence指向72,和上面一样,tail仍然是73,无需修改:
fence->next=temp;
rightnum++;
3.经过上面两个例子,我们可以整理为通用的insert函数:
bool insert(const Elem& val) {
fence->next = new Link<Elem>(val, fence->next);
if (fence == tail) {
tail = fence->next;
}
rightnum++;
return true;
}
4.append函数也就可以实现了:
bool append(const Elem& val) {
tail->next = new Link<Elem>(val, nullptr);
tail = tail->next;
//也可以直接赋值: tail=tail->next=new Link<Elem>(val,nullptr);
rightnum++;
return true;
}
好了,详细介绍了insert函数之后,其他函数也不难实现了,我觉得完整代码直接放在文章里面显得太臃肿了,有需要的百度云自取:链接:https://pan.baidu.com/s/1RXCwgPrdvhuoRIPRb01GKQ 提取码:a8uy 。如果有问题可以在评论区讨论,有错误也烦请指正,谢谢!