目录
一、链表的简单理解
在我上一篇文章详细讲解了顺序表,我们可以知道顺序表在内存中是顺序存储的,而我们这篇文章即将讲解的链表却和他刚好相反,链表是在内存中随机存储,这也侧面说明了,链表这个东西很灵活,它可以合理利用空间很多的空间,并且可以通过一些“秘密信号”来实现各自的联系。
1、链表长什么样子?
链表是由一个个结点来组成的,这些结点就和火车车厢一样,一个接着一个的连接下去,如下图就是一个链表
2、结点是什么?
链表是由一个结点构成的就如下图,链表中每个数据的存储都由以下两部分组成:
- 数据元素本身,其所在的区域称为数据域;
- 指向直接后继元素的指针,所在的区域称为指针域;
而我们的链表就是依靠着一个个结点链接而成,根据上图我们不难发现一个结点必不可少两个部分,一个来存数据,一个来存下一个结点的地址。通过以上的了解我们差不多对链表有了一个初步的了解,接下来我们就开始用代码构建一个属于我们自己的链表。
二、链表的代码实现
1、结点的构建
一个链表的构建离不开一个个结点的组成,所以构建链表的第一步当然是打基础,建立结点,我们可以创建一个类来实现一个结点的创建,然后通过构造方法赋值的方式来给结点赋值,当然这个引用必须得是这个结点类型的引用,因为我们的链表基本都是通过结点指向下一个结点实现的
class NodeList{
public int date;
public NodeList next;
public NodeList(int num){
this.date = num;
}
}
2、链表的构建
结点有了我们就可以构建一个链表了,首先,一个链表必须要有头节点,假如没有这个头节点就会出现群龙无首的现象,链表数据全都丢掉,因此这个头节点很重要,然后就是初始化链表,其实和节点的构建差不都,链表这边我也是用一个构造方法来定义一个链表的开端,只不过结点的构造方法是对结点赋值,而链表初始化是构建一个结点并且对其进行赋值。
public NodeList head;
public MyLinkedList(int num){
this.head = new NodeList(num);
}
3.查看链表长度
查找链表的长度我们需要构建一个哨兵结点来遍历整个链表,当哨兵结点为null时,返回一个size值。
//得到单链表的长度
public int size(){
int count = 0;
NodeList cur = this.head;
while(cur != null){
count++;
cur = cur.next;
}
return count;
}
4、查找是否指定元素是否在单链表当中
这个需要构建一个以布尔类型作为返回值的方法,首先我们还是建立一个哨兵结点,遍历整个链表,假如某一个结点存储的值与指定元素相同,返回True,遍历完链表还没有找到就返回False
//查找是否包含关键字key是否在单链表当中
public boolean contains(int key){
NodeList cur = this.head;
while(cur != null){
if(cur.date == key){
return true;
}
cur = cur.next;
}
return false;
}
5、在链表中指定位置插入元素
这个与顺序表一样,有三种情况
(1)、尾部插入
这个是最简单的一种插入方式,直接把最后的结点的next指向新结点就可以了
//尾插法
public void addLast(int num){
NodeList nodeList = new NodeList(num);
if(this.head == null){
this.head = nodeList;
}else{
NodeList cur = this.head;
while(cur.next != null){
cur = cur.next;
}//cur == null
cur.next = nodeList;
}
}
(2)、头部插入
头部插入我们需要考虑头指针的问题,我们大概可以用两步来解决:
第一步,把新结点的next指针指向原先链表的头指针。
第二步,把新结点变为新的头指针。
//头插法
public void addFirst(int num){
NodeList nodeList = new NodeList(num);//先建立一个节点
nodeList.next = this.head;
this.head = nodeList;
}
(3)、中间插入
中间插入同样分为两个步骤:
第一步、新结点的next指针,指向插入位置的指针
第二部、插入位置前置结点的next指针,指向新结点
/**
* 找到index-1位置的节点的地址
* @param index
* @return
*/
public NodeList findIndex(int index){
NodeList cur = this.head;
while(index - 1 != 0){
cur = cur.next;
index--;
}
return cur;
}
/**
*
* @param index 插入位置
* @param num 插入元素
* 我们可以先判断然后寻找然后插入
*/
//任意位置插入,第一个数据节点为0号下标
public void addIndex(int index,int num) {
if (index < 0 || index > size()) {
System.out.println("erro");
return;
}
if(index == size()){
addFirst(num);
return;
}
if(index == size()){
addLast(num);
return;
}
NodeList nodeList = new NodeList(num);
NodeList cur = findIndex(index);
nodeList.next = cur.next;
cur.next = nodeList;
}
6、在链表指定元素第一次出现的删除
删除元素和增加元素大同小异,也是三种方法
(1)、尾部删除
尾部删除最为简单,把倒数第二个结点的next指向直接指向为空即可
(2)、头部删除
把链表原先的头节点设置为原先头结点的next指针即可
(3)、中间删除
把要删除的结点的前置机结点的next指针,指向要删除元素的next指针即可
前提是我们需要构建一个方法来寻找要删除元素的上一个结点的下标,这样就可以利用得到结点的next直接指向删除元素的next
/**
* 找到 要删除的关键字的前驱
* @param key
* @return
*/
public NodeList searchPerv(int key){
NodeList cur = this.head;
while(cur != null){
if(cur.next.date == key){
return cur;
}//注意是cur.next
cur = cur.next;
}
return null;
}
//删除第一次出现关键字为key的节点
public void remove(int key){
if(this.head == null){
System.out.println("erro");
return;
}
if(this.head.date == key){
this.head = this.head.next;
return;
}
NodeList cur = searchPerv(key);
NodeList del = cur.next;
cur.next = del.next;
}
7、链表的打印
这个就很简单,我们只需要遍历链表,依次打印结点上的元素即可
(1)、打印整个链表
public void display(){
NodeList cur = this.head;//cur来遍历链表
while(cur != null){
System.out.print(cur.date+" ");
cur = cur.next;
}
System.out.println();
}
(2)、从给定位置开始打印链表
/**
* 从指定头节点开始进行打印
* @param newHead 给定的头节点
*/
public void display2(NodeList newHead){
NodeList cur = newHead;
while(cur != null){
System.out.print(cur.date+" ");
cur = cur.next;
}
System.out.println();
}
8、清空链表
这个清空链表会出现一个比较棘手的问题,就是假如我们从头节点开始释放把每一个引用释放null,但是当我们第一次释放完结点引用时会发现整个链表后面全都丢了,所有这时候我们就需要设定一个哨兵结点,在每次释放之前我们记录头节点的位置,然后等头结点释放完毕之后,我们就不必丢失之前的位置了
public void clear(){
while (this.head != null) {
NodeList curNext = head.next;
this.head.next = null;
this.head = curNext;
}
}
三、总结
通过这篇文章我们可以发现其实数据结构并没有所谓的好与坏,链表与顺序表也是如此,可以说他俩各有千秋,各有长处,关于链表与顺序表的性能我做了如下表的总结
我们不难发现顺序表的优势在于查找与更新一个元素, 而链表的优势在于插入和删除。
对于需要不断查找和更新频率相对较多的组合,我们选用顺序表更好。
对于需要不断插入删除的组合,我们选择链表最好。
好了,这篇文章到这里就结束了,很高兴能在利物浦即将决赛的前夜完成这一篇文章,希望利物浦能夺冠!!!我是banni,我们下期再见😁😁😁😁😁