二、链表(链表数据操作)

一、链表数据增加

链表结构之中所有的操作方法都是在Link接口中定义的,每当进行数据增加的时候,链表内部都一定要自动的将数据封装在Node类中,只有将数据封装在Node类中才可以确定链表节点的先后顺序。

1.1在ILink接口中追加一个新的方法,实现数据的增加:

interface ILink { //建立链表的公共操作标准
    void add(T data);
}

1.2在LinkImpl子类之中进行此方法的覆写,所有的数据只有保存在Node类里面才可以成为一个完整的节点,才可以进行节点先后顺序的配置。

  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);
        }
    }

1.3Node类负责所有非根节点的数据存放:

   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类处理
            }
        }


    }

在整个链表结构之中,根节点是进行后续一切节点操作的根源,这个根节点一定要控制得当,有了根节点就意味着拥有了后续全部的节点的操作能力。

二、获取链表元素个数

链表之中会保存有多个内容,但是为了清楚的知道当前链表里面所保存的数据长度,则可以在每一次进行数据增加的时候进行一个个数的统计操作。

2.1在Link接口中追加有一个获取当前个数的方法:

 int size();

2.2在LinkImpl类中实现统计,每一次节点保存成功后进行统计的记录;

private int count;//统计元素个数
this.count++:

2.3测试代码

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来动态决定。

3.1在Link接口中追加新的方法:

boolean isEmpty();

3.2在LinkImpl子类里面实现此方法:

@Override
    public boolean isEmpty() {
      //  return this.count==0;   //建议使用下面的方式 调用size()方法
        return this.size()==0;
    }

3.3测试代码

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完成。

4.1在Link接口中追加一个获取数据的方法:

  Object[] toArray(); //数据转为对象数组

4.2在LinkImpl类中追加一个控制的脚标:foot,在外部类定义的时候所有的Node内部类实例可以共享一个变量。同时在LinklImpl类中追加一个数组

private int foot; //索引角标
private Object[] returnData; //返回数组

4.3数据的获取应该交由Node类完成,Node类中应该追加有一个遍历的操作方法,遍历所有的节点,并且将数据取出保存在外部类的数组之中;

    public void toArrayNode() {
            LinkImpl.this.returnData[LinkImpl.this.foot++] = this.data; //获取当前节点数据
            if (this.next != null) {
                this.next.toArrayNode(); //递归调用
            }
        }

4.4在LinkImpl类中覆写toArray()方法:

    @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;  //返回处理结果
    }

4.5测试代码

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

那么此时的链表已经成为了一个基本的动态数组。

五、根据索引获取链表数据

链表是动态数组对象,对象数组里面是可以根据索引来获取指定的数据的,所以现在也使用此机制实现,如果要进行索引的查询,那么就必须进行索引的判断。

5.1在Ilink接口里面追加一个新的方法:

public T get(int index); //根据索引获取元素

5.2在Node类中追加有节点的索引查询的方法:

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;
                }
            }
        }

5.3在LinkImpl类中进行get()方法覆写

   @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类处理

    }

5.4测试代码

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为链表长度;

六、修改链表数据

链表中的数据都有自己的索引编号,那么就可以根据索引编号来实现数据的修改操作了。

6.1在Ilink接口中追加一个链表数据修改的方法

    T set(int index,T newData); //根据索引修改数据 并返回原始数据

6.2数据修改的时候就是要定位到指定索引的Node对象,随后进行内容的替换即可,那么可以在Node类中追加一个处理方法:

   //根据索引修改元素值
        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;
                }

            }
        }

6.3在LinkImpl子类里面覆写set()方法

   @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);
    }

6.4测试代码

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 ,在链表里面可以保存各种对象,所以链表中也可以进行数据查找的实现。

7.1在Ilink中追加新方法contains()

    boolean contains(T data);   //查找某元素是否存在

7.2此时需要查找的是一个对象,而对象判断相等的标准做法只有一个Object.equals()方法,在Node类中实现查找操作。

 //查找指定元素是否存在
        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;
                }

            }
        }

7.3在LinkImpl子类中复写contains()方法:

 @Override
    public boolean contains(T data) {
        if(this.size()==0||data==null){
            return false;
        }else {
            return this.root.containsNode(data);
        }

    }

7.4测试代码

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)”。

八、数据删除

链表中的数据有很多,需要考虑数据删除的问题,而且引入链表这一概念主要也是为了方便数据删除的处理,对于链表数据的删除需要考虑两种形式:

  • 形式一:要删除的不是根节点数据

二、链表(链表数据操作)_第1张图片
LinkImpl所维护的只是根节点 所以要删除非根节点 的操作一定是在Node类中完成

  • 形式二:要删除的数据是根节点数据
    二、链表(链表数据操作)_第2张图片
    这种删除根节点的情况应该交由LinkImpl去完成

8.1在Ilink接口里面追加一个新的方法:

void remove(T data);

8.2 在Node类中实现子节点的删除控制

      //删除指定元素
        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);
                }
            }

        }

8.3 在LinkImpl类中实现根节点控制

  @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-- 
    }

8.4测试代码

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属性重置。

9.1在Ilink接口中追加一个清空方法:

    void clear();   //清空链表

9.2清空操作只需要将根节点设置为null即可

  @Override
    public void clear() {
        this.root=null; //清空所有引用
        this.count=0;
    }

9.3测试代码

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");

    }
}

你可能感兴趣的:(常见数据结构(Java版))