前言
前一篇文章我们了解了什么是数据结构以及为什么要学习数据结构。还记得选择数据结构的原则吗?那就是“因地制宜,适合的就是最好的”。今天我们就将正式敲开数据结构的大门。和上一篇文章一样,我们以一句诗作为引子来引出今天的主题,这句诗就是“千里姻缘一线牵”。其中的“线”就是我们今天的主题,说到线我们可能会想到故事发展的时间线,时间就有先后顺序,而且顺序很重要,比如说有时候你会听到这样一句话“并不是你不够优秀,而是她先遇到了他“。也许这个时候你就能体会到顺序的重要。在数据结构中也有一种数据结构非常讲究出场的顺序,那就是线性表。线性表可以说是数据结构世界里的绝世好男人,它不仅讲究顺序,而且还永远都不会出轨。他的感情经历如图所示就像一条线一样,从头到尾不会分叉。
如同现实社会一样,有暖男就会有渣男,数据结构里的的渣男叫做二叉树,不是劈腿就是在劈腿的路上,不过这又是另一段精彩的故事。不过今天还是专注于专一的线性表。
线性表的表示
我们前面说到线形表的两个特性,一个是讲究顺序,一个是专一不分叉。这是线性表的逻辑结构,逻辑都是抽象的,所以我们可以把逻辑结构看成一个类,两个特性就是类的属性。真正把线形表存储到计算机的结构我们称之为存储结构,我们可以把存储结构看成类的实例,一个类可以有多个不同的实例。我们今天就了解一下最常见的两种线形表的实例顺序表和单链表。
顺序表
想象这样一个场景,一天晚上闲来无事睡不着,你就开始和你下铺的室友开始吹牛b,互相吹嘘自己的情史,说第一任女朋友是刘亦菲,第二任女朋友是杨幂,第三任女友刘诗诗,第四任女朋友是欧阳娜娜。也许你自己都没发现,说着说着,你就发现你的脑子里出现了这样一张表。
后来你总感觉有点不对劲,直到看到你和女朋友屏保,才发现原来把现女友忘了,赶紧把现女友加上,表就变成了这样。
后来你想想,你和杨幂也就暧昧了几天,应该不算你女朋友,她在这张表不配拥有姓名,所以你决定把她删了。这是你发现一个问题,虽然你只是删除了杨幂,可杨幂之后的人序号都要改变,她之后的人都受到了影响。
看着这张表,忽然你的脑海里晃过一个熟悉的身影,那是一个穿着校服扎着马尾的女孩,是她让你知道了喜欢一个人是什么样的感觉,所以你毫不犹豫的加上了她的名字。从回忆中醒来,你发现了和删除同样的问题,当你加入一个人的时候,她后面的人都会受到影响。
实现
当我们将其用代码实现结果如下,这里采用的是java语言,其他语言实现方式略有不同,但是思想是一样的。
public class SequenceList<T> {
private final int maxSize = 10;
private T[] listArray;
private int length;
// 创建顺序表
public SequenceList() {
this.length = 0;
this.listArray = (T[]) new Object[maxSize];
}
// 创建顺序表
public SequenceList(int n) {
if(n < 0 || n > maxSize) {
System.out.println("Error");
System.exit(1);
} else {
this.length = 0;
this.listArray = (T[]) new Object[n];
}
}
//表背增加表项
public void add(T object) {
listArray[length] = object;
length++;
}
//插入表项
public boolean insert(T object, int position) {
if(position < 0 || position > length + 1) {
System.out.println("error");
return false;
} else if(length == listArray.length) {
T[] p = (T[]) new Object[length * 2];
for(int i = 0; i < length + 1; i++) {
p[i] = listArray[i];
}
listArray = p;
}
for (int i = length + 1 ; position < i; i--) {
listArray[i - 1] = listArray[i];
i--;
}
listArray[position] = object;
return true;
}
//移除表项
public boolean remove( int position) {
if(isEmpty()||position < 0 || position > length) {
System.out.println("error");
return false;
}
for ( int i = position; i < length + 1; i++) {
listArray[i] = listArray[i + 1];
}
return true;
}
//判断顺序表是否为空
public boolean isEmpty() {
return length == 0;
}
//清空顺序表
public void clear() {
length = 0;
}
//更新表项
public boolean update(T object, int position) {
if(isEmpty() || position < 0 || position > length) {
System.out.println("error");
return false;
}
listArray[position] = object;
return true;
}
//遍历表项
public void nextOrder() {
for(int i = 0; i < length; i++) {
System.out.println(listArray[i]);
}
}
}
单链表
当你在思考问题的时候,你室友这一边也陷入了深深的回忆,那是2012年的第一场雪,来的没有比以往时候更晚一些,那是一个穿着鹅黄色羽绒服留着齐刘海的女孩,她的名字叫做娜扎(此处略去一万字),娜扎之后是热巴(此处略去两万字),热巴之后又遇到了。。。(此处略去十万字)。
我们注意到顺序表的实现是在数组的基础上做了一些改造,而单链表是建立在节点的基础上,节点的结构如下,其中数据域中存储数据,在我们的例子中就是“娜扎”等等,指针是指向下一个节点,例如我们例子中的“娜扎之后”等等。
你室友的经历随着一声叹息连成了一张链表如下图所示。
当你的室友想删去娜扎时,结果图就变成了这样,我们注意到,虽然删去了娜扎,但是好像娜扎后面的节点并没有受到影响。
当增加一个节点时,结果如下,我们发现,增加一个节点对于后面的节点也没有影响,只是前面一个节点需要把指针指向增加的节点。
实现
由于没有直接可用的节点的类型,所以我们需要自定义节点类型如下
public class Node<T> {
T data;
Node<T> next;
public Node(Node<T> n) {
next = n;
}
public Node(T obj, Node<T> n) {
data = obj;
next = n;
}
public T getData() {
return data;
}
public Node<T> getNext() {
return next;
}
}
单链表的代码如下
public class LinkList<T> {
private Node<T> head;
private int length;
//创建单链表
public LinkList() {
length = 0;
head = new Node<T>(null);
};
//获取头节点
public Node<T> getHead() {
return head;
}
//在链表尾增加节点
public void add(T object) {
Node<T> p, q;
p = head;
q = head.next;
while (q != null) {
p = q;
q = q.next;
}
p.next = new Node<T>(object, null);
length++;
}
//插入节点
public boolean insert(T object, int position) {
if(isEmpty() || position > length) {
System.out.println("error");
return false;
}
Node<T> p, q;
int i = 1;
p = head;
q = head.next;
while (i < position) {
p = q;
q = q.next;
i++;
}
p.next = new Node<T>(object, q);
length++;
return true;
}
//删除节点
public boolean remove(int position) {
if(isEmpty() || position > length) {
System.out.println("error");
return false;
}
Node<T> p, q;
p = head;
q = head.next;
int i = 1;
while (i < position) {
p = q;
q = q.next;
i++;
}
p.next = q.next;
length--;
return true;
}
//清除链表
public void clear() {
length = 0;
head.next = null;
}
//查找节点
public T search(int position) {
if(isEmpty() || position > length) {
System.out.println("error");
return null;
}
Node<T> p, q;
int i = 1;
p = head;
q = head.next;
while (i < position) {
p = q;
q = q.next;
i++;
}
return q.data;
}
//遍历节点
public void nextOrder() {
if(isEmpty()) {
System.out.println("error");
}
Node<T> p, q;
int i = 1;
p = head;
q = head.next;
while (i < length + 1) {
p = q;
System.out.println(p.data);
q = q.next;
i++;
}
}
//修改节点
public void modify(T object, int position) {
if(isEmpty() || position > length) {
System.out.println("error");
}
Node<T> p, q;
int i = 1;
p = head;
q = head.next;
while (i < position) {
p = q;
q = q.next;
i++;
}
q.data = object;
}
//判断节点是否为空
public boolean isEmpty() {
return length == 0;
}
}
总结
从上面的例子我们可以看出,顺序表的增删很麻烦,只要有一个表项的增加或者删除,后面的表项都需要改变,而对于单链表,增删很方便,对于后面的节点不会有影响。但是对于节点查找,单链表很麻烦,必须要从第一个节点开始遍历,直到找到为止,而对于顺序表而言,查找很方便,只需要给定序号,就可以直接找到,如给定5我们就可以直接找到欧阳娜娜。还是回到我们的原则,因地制宜,没有最好的数据结构,只有最合适的数据结构。
彩蛋
唐朝有个文人叫韦固,小时候经常到河边去玩,一天晚上,他见一个慈祥的老人在月光下翻阅书信,一边看,一边用一根红线绳把两块石头系在一起。韦固看见后非常奇怪,随口问道:“老伯伯,你系石头干什么?”老人说:“我在给当婚的人牵线呢!这一对石头,就是世上一对夫妻呀!“韦固好奇地问:”那我的妻子是谁呢?”老人说:“就是村头看菜园子的女孩儿。”
韦固很生气,心想,那丫头又穷又丑,我可不要,不如害死她算了。第二,他路过菜园,看看旁边没有人,拾了一块石头向女孩砸过去,女孩”扑通”一声倒在地上,韦固也吓得逃往外乡。
十几年后,韦固做了大学士,给他提亲的人非常多,但没有一个称心如意的。一天,韦固到张员外家作客,看见张员外的外甥女美貌出众,心里便十分喜欢;姑娘看韦固仪表堂堂,心里也有几分爱意。张员外看在眼里,喜在心上,当下托媒人定了婚事,选了吉期。到了大喜的日子。韦固将小姐娶到府上。洞房花烛夜,韦固细细端详爱妻,发现额角有一块小疤,就问她是怎么回事。小姐说:“小时候家里穷,有一天,我正在菜园里拾菜,不知哪个野小子打了我一石头,因此留下了这个疤。”韦固听后,心里十分吃惊,就把月下老人的话告诉了妻子,他这才相信缘分是拆不散的。
这就是千里姻缘一线牵的由来,也希望单身的各位都能尽快找到属于自己的姻缘。
点赞就是最大的支持,更多学习资料和文章可以关注微信公众号QStack。