package demo7;
interface ILink<E>{ //设置泛型避免安全隐患
public void add(E e); //增加数据
public int size(); //获取数据个数
public boolean isEmpty(); //判断是否为空集合
public Object[] toArray(); //将集合元素以数组形式返回
public E get(int index); //根据索引获取数据
public void set(int index, E data); //修改索引数据
public boolean contains(E data); //判断数据是否存在
public void remove(E e); //数据删除
public void clean(); //清空集合
}
class LinkImpl<E> implements ILink<E>{
private class Node{ //保存节点的数据关系
private E data; //保存的数据
private Node next; //保存下一个引用
private int count; //保存数据的个数
public Node(E data) { //有数据的情况下才有意义
this.data = data;
}
//在现在定义的Node类之中并没有出现setter与getter方法,
//是因为内部类中的私有属性也方便外部类直接访问
//第一次调用:this = LinkImpl.root
//第二次调用:this = LinkImpl.root.next
//第二次调用:this = LinkImpl.root.next.next
//...
public void addNode(Node newNode) { //保存新的Node数据
if(this.next == null) { //当前节点的下一个节点为null
this.next = newNode; //保存当前节点
}else {
this.next.addNode(newNode); //如果不是继续往后判断
}
}
//在Node类中递归获取数据
//第一次调用:this = LinkImpl.root
//第二次调用:this = LinkImpl.root.next
//第三次调用:this = LinkImpl.root.next.next
//...
public void toArrayNode() {
LinkImpl.this.returnData[LinkImpl.this.foot ++] = this.data; //将外部类中的数据放进数组当前位置
if(this.next != null) { //还有下一个数据
this.next.toArrayNode();
}
}
//在Node中索引数据
public E getNode(int index) {
if(LinkImpl.this.foot ++ == index) { //索引相同
return this.data; //返回当前数据
}else {
return this.next.getNode(index); //返回下一条索引数据
}
//这一特点和数组是很相似的,但是需要注意的是,数组获取一个数据的时间复杂度为1
//而链表获取数据的时间复杂度为n;
}
//在Node中修改指定索引数据
public void setNode(int index, E data) {
if(LinkImpl.this.foot ++ == index) { //索引相同
this.data = data; //返回当前数据
}else {
this.next.setNode(index, data); //返回下一条索引数据
}
//这种操作的时间复杂度也是n,因为依然需要进行数据的遍历处理。
}
//在Node中进行一次判断数据是否存在
public boolean containsNode(E data) {
if(data.equals(this.data)) { //对象比较(将不为空的数据放在比较的前面,防止程序空指针中断)
return true;
}else {
if(this.next == null) { //没有后续节点
return false; //无索引
}else {
return this.next.containsNode(data); //继续向下判断
}
}
//由于整个链表没有null数据的存在,所以整个的程序在判断的时候直接使用每一个的节点数据发出equals的方法调用即可。
}
//在Node中进行数据的删除(非根节点)
public void removeNode(Node previous, E data) {
if(this.data.equals(data)) {
previous.next = this.next; //空出当前节点
}else {
if(this.next != null) {
this.next.removeNode(this, data); //向后继续删除
}
}
//完善LinkImpl中子类的remove方法
}
}
//------------------------------以下为Link类中定义的成员-----------------------
private Node root; //保存根元素
private int count; //保存数据的个数
private int foot = 0; //操作数组的脚标
private Object[] returnData; //返回的数据保存
//------------------------------以下为Link类中定义的方法-----------------------
@Override
public void add(E e) {
if(e == null) { //方法调用直接结束
return ;
}else {
//数据本身是不具有关联特性的,只有Node类有,要想实现关联处理就必须将数据包装在Node类中
Node newNode = new Node(e);
if(this.root == null) { //现在没有根节点
this.root = newNode; //第一个节点作为根节点
}else {
this.root.addNode(newNode); //将新节点保存在合适的位置
}
this.count ++; //对数据计数
}
}
@Override
public int size() { //数据统计功能
return this.count;
}
//只是对于数据保存中的一个辅助功能
@Override
public boolean isEmpty() { //判断链表是否为空集合
//return this.root == null; //判断根节点是否为空
return this.count == 0; //判断计数器是否为空
//使用根节点或者是长度判断其本质相同
}
@Override
public Object[] toArray() { //将数据以数组的形式返回
if(this.isEmpty()) { //空集合
return null; //没有数据
}
this.foot = 0; //脚标清零
this.returnData = new Object[this.count]; //根据已有长度开辟数组长度
this.root.toArrayNode(); //利用Node类进行递归数据获取
return this.returnData;
//集合的数据一般要返回时肯定要以对象数组的形式返回
}
@Override
public E get(int index) { //引用索引查询数据
if(index >= this.count) { //索引应该在指定的范围之内
return null;
} //索引数据的获取应该由Node类完成
this.foot = 0; //重置索引的下标
return this.root.getNode(index);
}
@Override
public void set(int index, E data) {
if(index >= this.count) { //索引应该在指定的范围之内
return ; //方法结束
} //索引数据的获取应该由Node类完成
this.foot = 0; //重置索引的下标
this.root.setNode(index, data);
}
@Override
public boolean contains(E data) { //在LinkImpl子类里面实现此方法
if(data ==null) {
return false; //没有数据
}
return this.root.containsNode(data); //把数据交给Node类判断
}
@Override //删除指定数据
public void remove(E data) { //在LinkImpl子类里面实现根节点的判断
if(this.contains(data)) { //判断数据是否存在
if(this.root.data.equals(data)) { //根节点为要删除节点
this.root = this.root.next; //根的下一个节点
}else { //交由Node类进行删除
this.root.next.removeNode(this.root, data);
}
this.count --;
}
//根节点数据删除
//如果现在根节点并不是要删除的节点,那么就需要进行后续节点判断,
//但是请一定要注意此时的根节点已经判断完成,判断应该从根节点的下一个开始判断
//在Node类中追加删除处理
//删除的逻辑是依靠引用来完成的
}
@Override
public void clean() { //根据根节点清空集合
this.root = null; //后续的所有节点都没有了
this.count = 0; //个数清零
}
}
public class JavaDemo1 {
public static void main(String[] args) {
ILink<String> all = new LinkImpl<>();
System.out.println("【数据追加之前】 数据个数:" + all.size() + "、是否为空集合:" + all.isEmpty());
//添加数据
all.add("Hello");
all.add("World");
all.add("fire");
//修改数据
all.set(1, "世界");
//删除数据
//all.remove("Hello"); //删除根节点数据
all.remove("世界"); //删除非根节点数据
//清空集合
//all.clean();
System.out.println("【数据追加之后】 数据个数:" + all.size() + "、是否为空集合:" + all.isEmpty() );
//获取链表中的所有数据
Object[] result = all.toArray();
if(result != null) {
for(Object temp : result) {
System.out.println(temp);
}
}
System.out.println("----------------- 数据获取的分割线 --------------------------------------");
System.out.println(all.get(0));
System.out.println(all.get(1));
System.out.println(all.get(4));
System.out.println("----------------- 数据获取的分割线 --------------------------------------");
System.out.println(all.contains("火"));
System.out.println(all.contains("Hello"));
}
//这些就是链表的基本功能,当然这只是一个最简单最基础的单向链表实现。
}