JAVA数据结构之顺序表、单向链表及双向链表的设计和API实现

一、顺序表

顺序表在内存中是数组的形式存储

类名 SequenceList
构造方法 SequenceList(int capacity):创建容量为capacity的SequenceList对象
成员方法 1. public void clear():空置线性表
2. public boolean isEmpty():判断线性表是否为空,是返回true,否返回false
3. public int length():获取线性表中元素的个数
4. public T get(int i):读取并返回线性表中的第i个元素的值
5. public void insert(int i,T t):在线性表的第i个元素之前插入一个值为t的数据元素
6. public void insert(T t):向线性表中添加一个元素t
7. public T remove(int i):删除并返回线性表中第i个数据元素。
8. public int indexOf(T t):返回线性表中首次出现的指定的数据元素的位序号,若不存在,则返回-1。
成员变量 1. private T[] eles:存储元素的数组
2.private int N:当前线性表的长度

JAVA数据结构之顺序表、单向链表及双向链表的设计和API实现_第1张图片

//用来构造顺序表
public class SequenceList<T> implements Iterable{

    //存储元素的数组
    private T[] eles;
    //当前线性表的长度
    private int N;
    //获得当前容量
    private int capacity;

    //构造方法
    public SequenceList(int capacity) {
        //强制类型转化
        this.eles = (T[])new Object[capacity];
        //初始化长度
        N = 0;
        //获取当前顺序表的容量
        this.capacity = capacity;
    }

    //空置线性表
    public void clear() {
        for (int index = 0 ; index<getLength();index++){
            eles[index] = null;
        }
        this.N = 0;
    }

    //判断线性表是否为空,是返回true,否返回false
    public boolean isEmpty() {
        return N==0;
    }

    //获取线性表中元素的个数
    public int getLength() {
        return N;
    }

    //获取线性表的容量
    public int getCapacity() {
        return capacity;
    }

    //读取并返回线性表中的第i个元素的值
    public T get(int i) {
        if (i <= 0 || i>=N){
            throw new RuntimeException("当前元素不存在");
        }
        return eles[i];
    }

    //在线性表的第i个元素之前插入一个值为t的数据元素
    public void insert(int i,T t) {
        //如果当前元素的数量等于当前数组的长度
        if(N==eles.length){
            //扩容
            resize(2*eles.length);
        }
        //将i索引处以及后面的元素全部向后移动
        for (int index = N;index>i;index--){
            eles[index] = eles[index-1];
        }
        eles[i] = t;
        N++;
    }

    //向线性表中添加一个元素t
    public void insert(T t) {
        //如果当前元素的数量等于当前数组的长度
        if(N==eles.length){
            //扩容
            resize(2*eles.length);
        }
        //先将N处赋值为t,随后N自增
        eles[N]=t;
        N++;
    }

    //删除并返回线性表中第i个数据元素
    public T remove(int i) {
        //记录i处索引的值,并且后面的值全部向前移动
        T current = eles[i];
        for (int index = i ; index<N-1; index++ ){
            eles[index] = eles[index+1];
        }
        //元素个数减一
        N--;
        //如果当前元素的数量等于当前(数组的长度/4)
        if(N<eles.length/4){
            //将顺序表的长度变为原来的一半
            resize(eles.length/2);
        }
        return current;
    }


    //根据参数的newSize,重新来设置数组的大小
    public void resize(int newSize){
        //定义一个临时数组,指向原数组,用来提供复制数组
        T[] temp = eles;
        //容量翻倍
        eles = (T[]) new Object[newSize];
        //把原数组的数据拷贝到新数组即可
        for (int i = 0 ; i<N;i++){
            eles[i] = temp[i];
        }
        //重新定义容量的大小
        capacity = newSize ;
    }

    //返回线性表中首次出现的指定的数据元素的位序号,若不存在,则返 回-1。
    public int indexOf(T t){
        if(t==null){
            throw new RuntimeException("查找的元素不合法");
        }
        for (int i = 0; i < N; i++) {
            if (eles[i].equals(t)){
                return i;
            }
        }
        return -1;
    }

    //实现遍历输出
    @Override
    public Iterator iterator() {
        return new SIterator();
    }

    private class SIterator implements Iterator{

        private int index;
        public SIterator(){
            this.index = 0;
        }
       @Override
        public boolean hasNext() {
            //表示还有下一个元素
            return index<N;
        }

        @Override
        public Object next() {
            return eles[index++];
        }
    }
}
  • 从以上代码可得知,顺序表查找元素很快,只需要进行一次遍历即可,时间复杂度为O(1);
  • 在插入或者删除元素的时候,我们要进行相应的元素移动,时间复杂度大,时 间复杂为O(n);

二、链表

链表分为两个类组成,分别为Node节点类和LinkList链表类

JAVA数据结构之顺序表、单向链表及双向链表的设计和API实现_第2张图片

2.1 单向链表


[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FWtSJUS9-1681547848794)(C:\Users\29973\AppData\Roaming\Typora\typora-user-images\image-20230413101025423.png)]

2.1.1 设计Node节点类

类名 Node
构造方法 Node(T t,Node next):创建Node对象
成员变量 T item:存储数据
Node next:指向下一个结点
//用来定义节点
public class Node<T> {
    //存储数据
    T item;
    //下一个节点
    Node next;
    
    public Node(T item, Node next) {
        this.item = item;
        this.next = next;
    }
}

2.1.2 设计LinkList链表类

类名 LinkList
构造方法 LinkList():创建LinkList对象
成员方法 1. public void clear():空置线性表
2. public boolean isEmpty():判断线性表是否为空,是返回true,否返回false
3. public int length():获取线性表中元素的个数
4. public T get(int i):读取并返回线性表中的第i个元素的值
5. public void insert(T t):往线性表中添加一个元素;
6. public void insert(int i,T t):在线性表的第i个元素之前插入一个值为t的数据元素。
7. public T remove(int i):删除并返回线性表中第i个数据元素。
8. public int indexOf(T t):返回线性表中首次出现的指定的数据元素的位序号,若不存在,则 返回-1。
成员内容类 private class Node:结点类
成员变量 1. private Node head:记录首结点
2.private int N:记录链表的长度
//实现链表的构造
public class LinkList<T> implements Iterable{
    //记录头节点
    private Node<T> head;
    //记录链表的长度
    private int N;

    //构造方法
    public LinkList(){
        //初始化头节点
        this.head = new Node<T>(null,null);
        //初始化元素个数
        this.N = 0;
    }

    //清空链表:原理是断开头节点的指向,并且将链表置零
    public void clear(){
        head.next = null;
    }

    //获取链表的长度
    public int length(){
        return N;
    }

    //判断链表是否为空
    public boolean isEmpty(){
        return N==0;
    }

    //获取指定位置i处的元素
    public T get(int i){
        Node n = head.next;
        for (int index = 0 ; index<i ; index++){
            n = n.next;
        }
        return (T) n.item;
    }

    //向链表中插入元素
    public void insert(T t){
        //找到当前的最后一个节点
        Node n = head;
        while (n.next!=null){
            n = n.next;
        }
        //创建节点,保存元素
        Node newNode = new Node(t,null);
        //让当前最后一个节点指向新节点
        n.next = newNode;
        //元素的个数++
        N++;
    }

    //向链表的指定位置插入元素
    public void insert(int i , T t){
        if (i<0 || i>=N){
            throw new RuntimeException("位置不合法");
        }
        //找到i-1和i节点
        Node preNode = head;// i-1 节点
        for (int index = 0 ; index<=i-1 ; index++){
            preNode = preNode.next;
        }
        Node currentNode = preNode.next;// i 节点
        //创建新节点newNode
        Node newNode = new Node(t,null);
        //让i-1节点的next节点指向创建的newNode
        preNode.next = newNode;
        //将newNode的next节点指向原本的i节点
        newNode.next = currentNode;
        //元素个数++
        N++;
    }

    //删除指定位置的索引,并且返回被删除的元素
    public T remove(int i){
        if (i<0 || i>=N){
            throw new RuntimeException("位置不合法");
        }
        //找到 i-1 、 i 、i+1 节点
        Node preNode = head ; //找到i-1
        for (int index = 0 ; index<=i-1 ; index++){
            preNode = preNode.next;
        }
        Node currentNode = preNode.next; //找到i
        Node nextNode = currentNode.next; //找到i+1
        //将i-1的next节点指向i+1即可
        preNode.next = nextNode ;
        //元素个数--
        N--;
        return (T) currentNode.item;
    }

    //查找元素t在链表中第一次出现的位置
    public int indexOf(T t){
        Node n = head ;
        //依次去除节点元素进行比较
        for (int i = 0;n.next!=null;i++){
            n = n.next;
            if (t.equals(n.item)){
                return i;
            }
        }
        return -1;
    }


    //实现遍历输出
    @Override
    public Iterator iterator() {
        return new LIterator();
    }

    public class LIterator implements Iterator{
        private Node n;
        public LIterator(){
            this.n = head;
        }
        @Override
        public boolean hasNext() {
            return n.next!=null;
        }

        //获取下一个元素的值
        @Override
        public Object next() {
            n = n.next;
            return n.item;
        }
    }
}

2.2 双向链表


[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2VkiR53v-1681547985087)(C:\Users\29973\AppData\Roaming\Typora\typora-user-images\image-20230413170710947.png)]

2.2.1 设计Node节点类

类名 Node
构造方法 Node(T t,Node next):创建Node对象
成员变量 T item:存储数据
Node next:指向下一个结点
Node pre:指向上一个结点
public class Node<T> {
    //下一个节点
    public Node next;
    //上一个节点
    public Node pre;
    //存储数据
    public T item;

    //构造函数
    public Node(Node pre, Node next, T item) {
        this.pre = pre;
        this.next = next;
        this.item = item;
    }
}

2.2.2 设计DoublyLinkList链表类

类名 DoublyLinkList
构造方法 DoublyLinkList():创建DoublyLinkList对象
成员方法 1. public void clear():空置线性表
2. public boolean isEmpty():判断线性表是否为空,是返回true,否返回false
3. public int length():获取线性表中元素的个数
4. public T get(int i):读取并返回线性表中的第i个元素的值
5. public void insert(T t):往线性表中添加一个元素;
6. public void insert(int i,T t):在线性表的第i个元素之前插入一个值为t的数据元素。
7. public T remove(int i):删除并返回线性表中第i个数据元素。
8. public int indexOf(T t):返回线性表中首次出现的指定的数据元素的位序号,若不存在,则返回-1。
9. public T getFirst():获取第一个元素
10. public T getLast():获取最后一个元素
成员内容类 private class Node:结点类
成员变量 1. private Node head:记录首结点
2. private Node end:记录尾结点
3.private int N:记录链表的长度
public class DoublyLinkList<T> implements Iterable{
    //记录头节点
    private Node<T> head;
    //记录尾节点
    private Node<T> end;
    //记录链表的长度
    private int N;

    //构造方法
    public DoublyLinkList(){
        //初始化头节点和尾节点
        this.head = new Node<T>(null,null,null);
        this.end = null;
        //所以这个地方头节点和尾节点不占用长度
        this.N = 0;
    }

    //清空链表:原理是断开头节点和尾节点的指向,并且将链表置零
    public void clear(){
        head.next = null;
        end = null;
        N = 0;
    }

    //获取链表的长度
    public int length(){
        return N;
    }

    //判断链表是否为空
    public boolean isEmpty(){
        return N==0;
    }

    //获取第一个元素
    public T getHead(){
        if (isEmpty()){
            return null;
        }
        return (T) head.next.item;
    }

    //获取最后一个元素
    public T getLast(){
        if (isEmpty()){
            return null;
        }
        return (T) end.item;
    }


    //向链表中插入元素(默认是尾部)
    public void insert(T t){
        //1. 如果链表为空
        if (isEmpty()){
            //1.1 创建新的节点
            Node newNode = new Node(head, null, t);
            //1.2 让新的的节点称之为尾节点
            end = newNode;
            //1.3 让头节点指向尾节点
            head.next = end;
        }else {
        //2. 如果链表不为空
            Node oldNode = end;
            //2.1 创建新的节点
            Node newNode = new Node(oldNode,null,t);
            //2.2 让当前的尾节点指向新节点
            oldNode.next = newNode;
            //2.3 让新节点成为尾节点
            end = newNode;
        }
        N++;
    }

    //向链表的指定位置插入元素
    public void insert(int i , T t){
        if (i<0 || i>=N){
            throw new RuntimeException("位置不合法");
        }

        //找到 i-1 的节点
        //假如在 3 位置插入新节点,我们就是遍历从0-2,恰好可以找到2节点
        Node preNode = head;
        for (int index = 0 ; index<=i-1 ; index++){
            preNode = preNode.next;
        }
        //找到 i 位置节点
        Node currentNode = preNode.next;
        //创建新节点
        Node newNode = new Node(null,null,t);
        //修改指向
        preNode.next = newNode;
        newNode.next = currentNode;
        currentNode.pre = newNode;
        newNode.pre = preNode;
        //元素个数加一
        N++;
    }

    //删除指定位置的索引,并且返回被删除的元素
    public T remove(int i){
        if (i<0 || i>=N){
            throw new RuntimeException("位置不合法");
        }
        //找到i位置的节点
        Node node = head;
        for (int index = 0 ;index<=i ; index++){
            node = node.next;
        }
        //找到 i+1 位置节点
        Node nextNode = node.next;
        //找到 i-1 位置节点
        Node preNode = node.pre;
        //修改指向
        preNode.next = nextNode;
        nextNode.pre = preNode;
        N--;
        return (T) node.item;
    }

    //获取指定位置i处的元素
    public T get(int i){
        Node node = head;
        for (int index = 0 ; index<=i ; index++){
            node  = node.next;
        }
        return (T) node.item;
    }

    //查找元素t在链表中第一次出现的位置
    public int indexOf(T t){
        Node node = head;
        for (int index = 0 ; node.next != null ; index++){
            node  = node.next;
            if (t.equals(node.item)){
                return index;
            }
        }
        return -1;
    }

    @Override
    public Iterator iterator() {
        return new DIterator();
    }

    private class DIterator implements Iterator{
        private Node n;

        public DIterator() {
            this.n = head;
        }

        @Override
        public boolean hasNext() {
            return n.next != null;
        }

        @Override
        public Object next() {
            n = n.next;
            return n.item;
        }
    }
}

2.2.3 总结和提醒


总结

  • 每一次查询,都需要从链表的头部开始,依次向后查找,随着数据元素N的增多,比较的元素越多,时间复杂度为O(n)
  • 对于插入和移除节点,随着数据元素N的增多,查找的 元素越多,时间复杂度为O(n);
  • 相比较顺序表,链表插入和删除的时间复杂度虽然一样,但仍然有很大的优势,因为链表的物理地址是不连续的, 它不需要预先指定存储空间大小,或者在存储过程中涉及到扩容等操作,同时它并没有涉及的元素的交换。

提醒

其中对于双向链表遍历的边界值条件,如下代码

//获取指定位置i处的元素
public T get(int i){
    Node node = head;
    for (int index = 0 ; index<=i ; index++){
        node  = node.next;
    }
    return (T) node.item;
}

我们需要获取到i处的元素,因为我们是从头节点head开始遍历,所以其实我们的跳数也是从头节点开始计算,假设我们需要定位到2号节点,从head节点开始计算,index设置为0,我们刚好要移动3次。即要定位i处的元素,边界值判定条件恰好为i

JAVA数据结构之顺序表、单向链表及双向链表的设计和API实现_第3张图片

参考

黑马程序员Java数据结构与java算法全套教程

你可能感兴趣的:(Java,java,数据结构,链表)