本期算法我们主要讲解链表的知识,可能将会持续一段时间。 在学习链表的算法前我们首先开始对链表有一定的了解,接下来我们先浅谈链表的基本知识概念。
我们会经常用到java中的自带的链表(LinkedList),同时和链表类似的线性结构的是数组类型,而在大厂的面试题中链表会经常出一些更加有难度的题目。本期我们主要从最浅显的链表基本知识讲解。
简介
链表之所以可以有链表的特点,与每个节点的特点分不开,每个节点不仅包含当前节点的属性信息,还包括它指定的下一个节点的信息,在C,C++中我们使用指针来展现这一特点,虽然JAVA不提供指针,但是可以使用另外一种更加方便的对象的引用来展现这一特点。我们可以简单的用如下的代码来表示链表的结构:
class Node
{
Object data;
Node next;//指向下一个结点
}
Object为所有类的父类,因此可以表示各种各样的对象来满足链表的节点表示的需求。
单链表是一个线性表。根据上面的链表的特点我们可以知道,链表特点决定他在存储中可以不受在存储介质中的位置的影响,只需要每个节点存放有可以知道下个节点的位置信息便可以了,因此可以大大的利用碎片的存储空间,在添加新的节点或者删除一个节点时候也仅仅只需要改变节点的指向位置便可以了,操作的时间复杂度仅为O(1)。但是这个特点会限制随机查找的速度。除此之外,链表还是很多算法的基础,最常见的哈希表就是基于链表来实现的。与链表对标的是数组,数组的结构结构决定他可以方便的查询数据,可以进行数据的随机查询,时间复杂度为O(1)。但是这种便利性是有一定代价的,在数据的存储上就需要一块连续的空间来进行存储,而且对数据的删除与添加也需要一定的代价,时间复杂度为O(n);
下面我们查看各种结构(包括数组,双链表,单链表,队列,栈)的增删改查的时间复杂度:
操作
链表的定义
public class Link {
Node head = null; // 头节点
/**
* 节点类的定义,data代表节点的值,next为链表节点指向的下一个节点
*/
class Node {
Node next = null;// 节点的引用,指向下一个节点
int data;// 节点的对象,即内容
public Node(int data) {
this.data = data;
}
}
}
添加节点
/**
* 插入数据,向链表的结尾插入数据
*/
public void add(int d) {
Node newNode = new Node(d);// 实例化一个节点
if (head == null) {
head = newNode;
return;
}
Node tmp = head;
while (tmp.next != null) {
tmp = tmp.next;
}
tmp.next = newNode;
}
步骤:
- 初始化一个节点实例
- 找到链表最后一个节点,并将最后一个节点的next指针指向需要加入的节点。
/**
* 插入数据,向链表的中间插入数据
*/
public void add(T t,int index){
if (index <0 || index >size){
throw new IllegalArgumentException("index is error");
}
if (index == 0){
this.addFirst(t);
return;
}
Node preNode = this.head;
//找到要插入节点的前一个节点
for(int i = 0; i < index-1; i++){
preNode = preNode.next;
}
Node node = new Node(t);
//要插入的节点的下一个节点指向preNode节点的下一个节点
node.next = preNode.next;
//preNode的下一个节点指向要插入节点node
preNode.next = node;
}
步骤:
- 使用给定值初始化新结点
- 将需要添加的节点的“next”字段链接到指定位置的下一个结点;
- 将指定位置中的“next”字段链接到初始化的节点;
图例如下:
删除节点
//删除链表中的某个元素
public void remove(T t){
if(head == null){
System.out.println("链表为空");
return;
}
//要删除的元素与头结点的元素相同
while(head != null && head.T.equals(t)){
head = head.next;
}
/**
*对头结点之后的结点进行判别
*/
Node cur = this.head;
while(cur != null && cur.next != null){
if(cur.next.T.equals(t)){
cur.next = cur.next.next;
}
else cur = cur.next;
}
}
步骤:
- 找到 需要删除节点的上一个结点 prev 及其下一个结点 next;
- 接下来链接 prev 到 cur 的下一个节点 next。
此时整体步骤上图所示,但是具体实现时候要分情况讨论:1.删除节点为头节点。2.删除节点不是头节点。具体实现如上代码所示,图例如下:
参考图例:
https://zhuanlan.zhihu.com/p/29627391