链表结构之中所有的操作方法都是在Link接口中定义的,每当进行数据增加的时候,链表内部都一定要自动的将数据封装在Node类中,只有将数据封装在Node类中才可以确定链表节点的先后顺序。
interface ILink { //建立链表的公共操作标准
void add(T data);
}
private Node root; //根节点
//==============以上为内部的节点关系类==================
@Override
public void add(T data){
if(data==null){ //排除掉所有的空元素
return;
}
Node newNode = new Node(data); // 将数据封装在节点中
if(this.root==null){ // 没有根节点
this.root=newNode;
}else { //此时根节点存在
this.root.addNode(newNode);
}
}
private class Node {
private T data;
private Node next;
public Node(T data) {
this.data = data;
}
//第1次调用方法:this=LinkImpl.root;
//第2次调用方法:this=LinkImpl.root.next;
//第3次调用方法:this=LinkImpl.root.next.next;
//...
public void addNode(Node newNode){
if (this.next==null){ //当前节点之后有空余
this.next = newNode;
}else {
this.next.addNode(newNode); //交由Node类处理
}
}
}
在整个链表结构之中,根节点是进行后续一切节点操作的根源,这个根节点一定要控制得当,有了根节点就意味着拥有了后续全部的节点的操作能力。
链表之中会保存有多个内容,但是为了清楚的知道当前链表里面所保存的数据长度,则可以在每一次进行数据增加的时候进行一个个数的统计操作。
int size();
private int count;//统计元素个数
this.count++:
public class TestDemo { //主类
public static void main(String[] args) {
ILink link = new LinkImpl<>();
System.out.println(link.size());
link.add("hello");
link.add("you");
link.add("guys");
System.out.println(link.size());
}
}
程序执行结果:
0
3
链表之中如果不保存有任何的数据就认为它是一个空的链表,空链表的判断可以依据长度或者根节点是否为null来动态决定。
boolean isEmpty();
@Override
public boolean isEmpty() {
// return this.count==0; //建议使用下面的方式 调用size()方法
return this.size()==0;
}
public class TestDemo { //主类
public static void main(String[] args) {
ILink link = new LinkImpl<>();
System.out.println(link.isEmpty());
link.add("hello");
link.add("you");
link.add("guys");
System.out.println(link.isEmpty());
}
}
程序执行结果:
true
false
以上的操作实现了链表数据的存储,但是链表数据存储之后其主要的目的是为了进行内容的获取,本次获取的内容是将所有的数据转为对象数组的形式得到,考虑到实际的情况,本次的处理使用Object完成。
Object[] toArray(); //数据转为对象数组
private int foot; //索引角标
private Object[] returnData; //返回数组
public void toArrayNode() {
LinkImpl.this.returnData[LinkImpl.this.foot++] = this.data; //获取当前节点数据
if (this.next != null) {
this.next.toArrayNode(); //递归调用
}
}
@Override
public Object[] toArray() {
if(this.size()==0){ //没有元素则返回空
return null;
}
this.foot=0; //角标清零
this.returnData = new Object[this.size()];
this.root.toArrayNode(); //交给Node类负责处理
return this.returnData; //返回处理结果
}
public class TestDemo { //主类
public static void main(String[] args) {
ILink link = new LinkImpl<>();
link.add("hello");
link.add("you");
link.add("guys");
Object result[] = link.toArray();
for(Object s:result){
System.out.println(s);
}
}
}
程序执行结果:
hello
you
guys
那么此时的链表已经成为了一个基本的动态数组。
链表是动态数组对象,对象数组里面是可以根据索引来获取指定的数据的,所以现在也使用此机制实现,如果要进行索引的查询,那么就必须进行索引的判断。
public T get(int index); //根据索引获取元素
public T getNode(int index) {
if (LinkImpl.this.foot++ == index) { //索引相同 返回当前数据
return this.data; //返回当前数据
}else{
if(this.next!=null){
return this.next.getNode(index);
}else { //到最后了
return null;
}
}
}
@Override
public T get(int index) {
if(index>=this.size()||index<0){
return null;
}
this.foot=0; //foot清0
return this.root.getNode(index); //交由Node类处理
}
public class TestDemo { //主类
public static void main(String[] args) {
ILink link = new LinkImpl<>();
link.add("hello");
link.add("you");
link.add("guys");
System.out.println(link.get(0));
System.out.println(link.get(3));
}
}
程序执行结果:
hello
null
在使用数组进行索引位置数据获取的时候,是直接进行定位,所以其时间复杂度为“O(1)”。但是在使用链表的时候时间复杂度为O(n)了,n为链表长度;
链表中的数据都有自己的索引编号,那么就可以根据索引编号来实现数据的修改操作了。
T set(int index,T newData); //根据索引修改数据 并返回原始数据
//根据索引修改元素值
public T setNode(int index, T newData) {
if (LinkImpl.this.foot++ == index) {
T temp = this.data; //返回数据
this.data = newData;
return temp;
} else {
if (this.next != null) {
return this.next.setNode(index, newData);
} else {
return null;
}
}
}
@Override
public T set(int index, T newData) {
if (index >= this.size()||index<0) {
return null;
}
this.foot = 0;
return this.root.setNode(index,newData);
}
public class TestDemo { //主类
public static void main(String[] args) {
ILink link = new LinkImpl<>();
link.add("hello");
link.add("you");
link.add("guys");
System.out.println(link.get(2));
System.out.println("修改索引位置为2的元素"+link.set(2,"gays"));
System.out.println(link.get(2));
}
}
程序执行结果:
guys
修改索引位置为2的元素guys
gays
修改和之前的索引查询其性能都是“O(n)”,因为都需要进行依次遍历的处理操作。
在字符串里面存在有一个contains()方法,该方法主要功能是查找指定的内容是否存在,并且返回值类型为boolean ,在链表里面可以保存各种对象,所以链表中也可以进行数据查找的实现。
boolean contains(T data); //查找某元素是否存在
//查找指定元素是否存在
public boolean containsNode(T data){
if(this.data.equals(data)){ //数据判断相同
return true;
}else {
if(this.next!=null){
return this.next.containsNode(data);
}else {
return false;
}
}
}
@Override
public boolean contains(T data) {
if(this.size()==0||data==null){
return false;
}else {
return this.root.containsNode(data);
}
}
public class TestDemo { //主类
public static void main(String[] args) {
ILink link = new LinkImpl<>();
link.add("hello");
link.add("you");
link.add("guys");
System.out.println(link.contains("guys"));
System.out.println(link.contains("gg"));
}
}
程序执行结果:
true
false
此类查询的时间复杂度同样是“O(n)”,在数组未做任何优化的时候,同样的查询,它的时间复杂度也是“O(n)”。
链表中的数据有很多,需要考虑数据删除的问题,而且引入链表这一概念主要也是为了方便数据删除的处理,对于链表数据的删除需要考虑两种形式:
LinkImpl所维护的只是根节点 所以要删除非根节点 的操作一定是在Node类中完成
void remove(T data);
//删除指定元素
public void removeNode(Node previous,T data){
if(this.data.equals(data)){
previous.next=this.next; //空出当前节点
}else {
if(this.next!=null){ //还有后续节点
this.next.removeNode(this,data);
}
}
}
@Override
public void remove(T data) {
if (this.size() == 0 || data == null) {
return;
}
if (this.root.data.equals(data)) { //根节点
this.root = this.root.next; //修改根节点
} else { //后续判断可以从第二个节点开始
if (this.root.next != null) {
this.root.next.removeNode(this.root,data);
}
}
count--; //注意count--
}
public class TestDemo { //主类
public static void main(String[] args) {
ILink link = new LinkImpl<>();
link.add("hello");
link.add("you");
link.add("guys");
link.remove("hello");
System.out.println(Arrays.toString(link.toArray()));
System.out.println(link.get(0));
}
}
程序执行结果:
[you, guys]
you
删除节点就是修改节点引用关系 同时注意count值的变化 需要把count–
当进行清空处理时需要注意count属性重置。
void clear(); //清空链表
@Override
public void clear() {
this.root=null; //清空所有引用
this.count=0;
}
public class TestDemo { //主类
public static void main(String[] args) {
ILink link = new LinkImpl<>();
link.add("hello");
link.add("you");
link.add("guys");
link.clear();
System.out.println(Arrays.toString(link.toArray()));
System.out.println(link.size());
}
}
程序执行结果:
null
0
该链表为最简单的链表 ,没有任何优化设计
该链表内9个方法如下:
void add(T data); //增
int size(); //获取长度
boolean isEmpty(); //判空
Object[] toArray(); //数据转为对象数组
T get(int index); //根据索引获取元素
T set(int index, T newData); //根据索引修改数据 并返回原始数据
boolean contains(T data); //查找某元素是否存在
void remove(T data); //删除指定数据
void clear(); //清空链表
import java.util.Arrays;
/**
* 作者: kinggm520 Email:[email protected]
* 时间: 2019-12-23 21:39
*/
interface ILink { //建立链表的公共操作标准
void add(T data); //增
int size(); //获取长度
boolean isEmpty(); //判空
Object[] toArray(); //数据转为对象数组
T get(int index); //根据索引获取元素
T set(int index, T newData); //根据索引修改数据 并返回原始数据
boolean contains(T data); //查找某元素是否存在
void remove(T data); //删除指定数据
void clear(); //清空链表
}
class LinkImpl implements ILink {
private class Node {
private T data;
private Node next;
public Node(T data) {
this.data = data;
}
//第1次调用方法:this=LinkImpl.root;
//第2次调用方法:this=LinkImpl.root.next;
//第3次调用方法:this=LinkImpl.root.next.next;
//...
//增
public void addNode(Node newNode) {
if (this.next == null) { //当前节点之后有空余
this.next = newNode;
} else {
this.next.addNode(newNode);
}
}
//获取全部数据组成的对象数组
public void toArrayNode() {
LinkImpl.this.returnData[LinkImpl.this.foot++] = this.data; //获取当前节点数据
if (this.next != null) {
this.next.toArrayNode(); //递归调用
}
}
//根据索引获取对应元素
public T getNode(int index) {
if (LinkImpl.this.foot++ == index) { //索引相同 返回当前数据
return this.data; //返回当前数据
} else {
if (this.next != null) {
return this.next.getNode(index);
} else { //到最后了
return null;
}
}
}
//根据索引修改元素值
public T setNode(int index, T newData) {
if (LinkImpl.this.foot++ == index) {
T temp = this.data; //返回数据
this.data = newData;
return temp;
} else {
if (this.next != null) {
return this.next.setNode(index, newData);
} else {
return null;
}
}
}
//查找指定元素是否存在
public boolean containsNode(T data) {
if (this.data.equals(data)) { //数据判断相同
return true;
} else {
if (this.next != null) {
return this.next.containsNode(data);
} else {
return false;
}
}
}
//删除指定元素
public void removeNode(Node previous, T data) {
if (this.data.equals(data)) {
previous.next = this.next; //空出当前节点
} else {
if (this.next != null) { //还有后续节点
this.next.removeNode(this, data);
}
}
}
}
//==============以上为内部的节点关系类==================
private Node root; //根节点
private int count; //统计元素个数
private int foot; //索引角标
private Object[] returnData; //返回数组
@Override
public void add(T data) {
if (data == null) { //排除掉所有的空元素
return;
}
Node newNode = new Node(data); // 将数据封装在节点中
if (this.root == null) { // 没有根节点
this.root = newNode;
} else { //此时根节点存在
this.root.addNode(newNode); //交由Node类处理
}
this.count++;
}
@Override
public int size() { //获取链表内存储元素的个数
return this.count;
}
@Override
public boolean isEmpty() {
// return this.count==0; //建议使用下面的方式 调用size()方法
return this.size() == 0;
}
@Override
public Object[] toArray() {
if (this.size() == 0) { //没有元素则返回空
return null;
}
this.foot = 0; //角标清零
this.returnData = new Object[this.size()];
this.root.toArrayNode(); //交给Node类负责处理
return this.returnData; //返回处理结果
}
@Override
public T get(int index) {
if (index >= this.size() || index < 0) {
return null;
}
this.foot = 0; //foot清0
return this.root.getNode(index); //交由Node类处理
}
@Override
public T set(int index, T newData) {
if (index >= this.size() || index < 0) {
return null;
}
this.foot = 0;
return this.root.setNode(index, newData);
}
@Override
public boolean contains(T data) {
if (this.size() == 0 || data == null) {
return false;
} else {
return this.root.containsNode(data);
}
}
@Override
public void remove(T data) {
if (this.size() == 0 || data == null) {
return;
}
if (this.root.data.equals(data)) { //根节点
this.root = this.root.next; //修改根节点
} else { //后续判断可以从第二个节点开始
if (this.root.next != null) {
this.root.next.removeNode(this.root, data);
}
}
count--;
}
@Override
public void clear() {
this.root = null; //清空所有引用
this.count = 0;
}
}
public class TestDemo { //主类
public static void main(String[] args) {
ILink link = new LinkImpl<>();
link.add("hello");
link.add("you");
link.add("guys");
}
}