Java链表的使用
前言
说明:
- 语言:Java
- 环境:IntelliJ IDEA
- JDK版本:1.8
- 源码:GitHub
- 链表的插入、查询、排序通常涉及算法,本文重点是探究链表,并非算法,因此代码只是以最通俗易懂的方式编写
在学习Java链表的使用之前,需要先了解Java引用类型的使用
int a = 10;
int b = a;
b++;
System.out.println(a);
上面这段代码的运行结果为:10
。在int b = a;
这个操作中,实际是将a的值赋给b,所以b++;
不会影响a的值,这是基本数据类型的特性。
//Employee是描述员工信息的实体类
Employee employee1 = new Employee(1,"xiaoming",30);
Employee employee2 = employee1;
employee2.seteName("liumei");
System.out.println(employee1.toString());
上面这段代码的运行结果为:Employee{eId=1, eName='liumei', eAge=30}
。这就说明,在Employee employee2 = employee1;
操作中,使得employee1和employee2共享相同的数据,因此两个变量任意一个改变数据,都会影响对方。
实际上,new
是在内存中划分一个空间来存储数据,而接收new
的employee1变量只是一个指针,指向这个内存空间,当把这个变量赋给变量employee2时,employee2也将是指向和employee1相同内存空间的指针,因此操作这两个变量,实际上都是对同一内存空间的操作。
单链表
特点:
- 拥有next指针,指向下一个节点
- 只能单向的从头结点为起点操作链表
- 在链表首部插入节点效率高,在链表尾部插入节点效率低
插入节点:
/**
* 在链表尾部插入数据
*/
public boolean addLast(Employee employee){
Node node = new Node(employee); //创建要插入的节点
Node temp = this.head; //创建辅助指针指向head节点
while (true){ //移动辅助指针到当前链表最后一个节点
if(temp.next==null){break;}
temp = temp.next;
}
temp.next = node; //将要插入的节点插入到链表尾部
this.length++;
return true;
}
/**
* 在链表首部插入数据
*/
public boolean addFirst(Employee employee){
Node node = new Node(employee); //创建要插入的节点
Node temp = this.head; //创建辅助指针指向head节点
if(isEmpty()){ //链表为空情况
this.head.next = node; //直接在head后插入节点
}else{ //链表不为空情况
temp = temp.next; //移动temp到第一个有效节点
this.head.next = node; //将要插入节点插入到head节点后
node.next = temp; //将其他节点连接到刚插入的节点上
}
this.length++;
return true;
}
/**
* 在index节点之后插入节点,索引从0开始
*/
public boolean addIndex(Employee employee,int index){
if(isEmpty()||index>=this.length||index<0){ return false;}//空链表、索引格式范围不正确则插入失败
Node node = new Node(employee); //创建要插入的节点
Node temp = this.head; //创建辅助指针指向head节点
for (int i = 0;i<=index;i++){ //将辅助指针移动到目标节点
temp = temp.next;
}
node.next = temp.next; //让新节点的next指向目标节点之后的节点
temp.next = node; //让辅助指针指向新节点
this.length++;
return true;
}
删除节点:
/**
* 删除最后一个节点
*/
public boolean deleteLast(){
if(isEmpty()){return false;}
Node temp = this.head; //创建辅助指针指向head节点
while (true){ //移动辅助指针到当前链表最后一个节点的前一个节点
if(temp.next.next==null){break;}
temp = temp.next;
}
temp.next = null; //断开与最后一个节点的的连接
this.length--;
return true;
}
/**
* 删除第一个节点
*/
public boolean deleteFirst(){
if(isEmpty()){return false;}
Node temp = this.head.next; //创建辅助指针指向第一个有效节点
this.head.next = temp.next; //将头节点连接到第一个有效节点之后的节点
this.length--;
return true;
}
/**
* 删除指定索引节点,索引从0开始
*/
public boolean deleteIndex(int index){
if(isEmpty()||index>=this.length||index<0){ return false;}//空链表、索引格式范围不正确则删除失败
Node temp = this.head; //创建辅助指针指向头结点
for (int i = 0;i
查询节点:
/**
* 查看index位置的数据,索引从0开始
*/
public Employee getEmployee(int index){
if(isEmpty()||index>=this.length||index<0){ return null;}//空链表、索引格式范围不正确则查询失败
Node temp = this.head; //创建辅助指针指向头结点
for (int i = 0;i<=index;i++){ //将辅助指针移动到目标节点
temp = temp.next;
}
return temp.data; //返回数据
}
修改节点:与查询类似,需要定制规则
双向链表
特点:
- 拥有next指针和previous指针,next指针指向下一个节点,previous指针指向前一个节点
- 允许双向操作链表
- 双向链表通常拥有头结点和尾结点,这样从双端插入数据效率都很高
- 由于允许双向操作链表,所以查询效率高于单链表
插入节点:
/**
* 在链表尾部插入数据
*/
public boolean addLast(Employee employee){
Node node = new Node(employee); //创建要插入的节点
if(isEmpty()){ //如果链表为空
this.head.next = node; //将head和last节点连接到node
this.last.pre = node;
node.next = this.last; //将node的next和pre连接到last和head
node.pre = this.head;
}else{ //如果链表不为空(注意顺序)
node.next = this.last; //将node连接到last和当前最后一个节点
node.pre = this.last.pre;
this.last.pre.next = node; //将last和当前最后一个节点连接到node
this.last.pre = node;
}
this.length++;
return true;
}
/**
* 在链表首部插入数据
*/
public boolean addFirst(Employee employee){
Node node = new Node(employee); //创建要插入的节点
if(isEmpty()){ //如果链表为空
this.head.next = node; //将head和last节点连接到node
this.last.pre = node;
node.next = this.last; //将node的next和pre连接到last和head
node.pre = this.head;
}else{ //如果链表不为空(注意顺序)
node.next = this.head.next; //将node连接到head和当前第一个节点
node.pre = this.head;
this.head.next.pre = node; //将head和当前第一个节点连接到node
this.head.next = node;
}
this.length++;
return true;
}
/**
* 在index节点之后插入节点,索引从0开始
*/
public boolean addIndex(Employee employee,int index){
if(isEmpty()||index>=this.length||index<0){return false;}//空链表、索引格式范围不正确则插入失败
Node node = new Node(employee);;
Node temp;
if((index+1)<=this.length/2){ //如果要插入的位置小于链表总长度一半,则在前半段插入
temp = this.head;
for (int i = 0;i<=index;i++){
temp = temp.next;
}
node.next = temp.next;
node.pre = temp;
temp.next.pre = node;
temp.next = node;
}else{ //如果要插入的位置大于链表总长度的一半,则在后半段插入
temp = this.last;
for (int i = 0;i<=this.length-(index+1);i++){
temp = temp.pre;
}
node.next = temp.next;
node.pre = temp;
temp.next.pre = node;
temp.next = node;
}
this.length++;
return true;
}
删除节点:
/**
* 删除最后一个节点
*/
public boolean deleteLast(){
if(isEmpty()){return false;}
this.last.pre.pre.next = this.last; //将最后一个节点的前一个节点的next指向last
this.last.pre = this.last.pre.pre;//将last的pre指向最后一个节点的前一个节点
this.length--;
return true;
}
/**
* 删除第一个节点
*/
public boolean deleteFirst(){
if(isEmpty()){return false;}
this.head.next.next.pre = this.head;//将第一个节点的后一个节点的pre指向head
this.head.next = this.head.next.next;//将head的next指向第一个节点的后一个节点
this.length--;
return true;
}
/**
* 删除指定索引节点,索引从0开始
*/
public boolean deleteIndex(int index){
if(isEmpty()||index>=this.length||index<0){return false;}//空链表、索引格式范围不正确则删除失败
Node temp;
if((index+1)<=this.length/2){ //如果要插入的位置小于链表总长度一半,则在前半段删除
temp = this.head;
for (int i = 0;i<=index;i++){
temp = temp.next;
}
temp.pre.next = temp.next;
temp.next.pre = temp.pre;
}else{ //如果要插入的位置大于链表总长度的一半,则在后半段删除
temp = this.last;
for (int i = 0;i<=this.length-(index+1);i++){
temp = temp.pre;
}
temp.pre.next = temp.next;
temp.next.pre = temp.pre;
}
this.length--;
return true;
}
查找节点:
/**
* 查看index位置的数据,索引从0开始
*/
public Employee getEmployee(int index){
if(isEmpty()||index>=this.length||index<0){return null;}//空链表、索引格式范围不正确则删除失败
Node temp;
if((index+1)<=this.length/2){ //如果要插入的位置小于链表总长度一半,则在前半段查找
temp = this.head;
for (int i = 0;i<=index;i++){
temp = temp.next;
}
return temp.data;
}else{ //如果要插入的位置大于链表总长度的一半,则在后半段查找
temp = this.last;
for (int i = 0;i<=this.length-(index+1);i++){
temp = temp.pre;
}
return temp.data;
}
}
修改节点:与查询类似,需要定制规则
循环链表
特点:
- 拥有next指针,指向下一个节点
- 链表的最后一个节点的next指向第一个节点,形成循环
- 为了实现环的完整性,循环链表通常不需要头节点
- 只能进行一个方向的循环
循环链表的增删改查需要根据需求确定,普通实现可类比将最后一个节点的next连接到首节点的单链表。实际上需要根据需求定制在环的什么位置插入,在什么位置删除,以及判定循环一圈和循环终止的条件
双向循环链表
特点:
- 拥有next指针和previous指针,next指针指向下一个节点,previous指针指向前一个节点
- 链表的最后一个节点的next指向第一个节点,第一个节点的previous指正指向最后一个节点,形成循环
- 为了实现环的完整性,循环链表通常不需要头节点
- 可以进行双向的循环
双向循环链表的增删改查需要根据需求确定,普通实现可类比将最后一个节点的next连接到首节点,首节点的previous连接在最后一个节点的双向链表。实际上需要根据需求定制在环的什么位置插入,在什么位置删除,以及判定循环一圈和循环终止的条件