链表 Java实现

刚开始学习数据结构一般都是从链表开始,我这里将我当初学习链表的种种代码以及心得作以总结,如有错误欢迎指正。

刚开始学习链表时也就是我刚开始学习数据结构,我就在那念叨,数组不就行了,整个链表麻烦不麻烦。这里说的是并不是数组不好,只能说是各自有各自的优点把,我就从数组和链表的区别开始。

数组和链表

数组有‘1好2不好’

1好是,数组的内存是绝对连续的,因此数组的随机访问操作非常的快,时间复杂度是O(1) O(1)O(1),为常量时间,例如arr[20]和arr[2000]的访问花费的时间是一样的。

2不好是 :
a.数组在定义的时候,必须指定其大小,实际应用中,当数组元素满了以后,要进行扩容,扩容的代码虽然简单,如arr = Arrays.copyOf(arr, arr.length*2)就能够达到2倍扩容的效果,但是当数组的元素数量比较大的话,扩容需要经过开辟更大块内存,拷贝数据,GC回收原来的旧内存等步骤,其效率就比较低了,扩容花费的时间也长了。当扩容一段时间后,数组内存空间特别大,但是随着删除操作,最后只有少量的有效元素(如10000个数组元素空间,却只存储了10个有效元素),内存就被浪费了,这时候就得缩容(如ArrayList数组集合的缩容实现),其效率也比较低。

b.在数组中,插入元素或者删除元素,都需要经过大量数据的移动。插入操作会引起插入点后面的元素都向后进行移动;删除操作会引起删除点后面的元素都向前进行移动,这两个操作的时间花费都是O(n) O(n)O(n),是线性时间,说明随着数组的元素数量越多,插入删除操作的效率越低。

区别于数组,那么链表就有‘1不好2好’:

2好是:
a.链表的每一个元素节点都是独立new出来的,也就是说链表中所有元素节点内存并不是连续的,而是上一个节点记录了下一个节点的地址,这样内存的使用效率就非常的高,在存储数据量比较大的时候,不需要大片连续的内存空间,因此只要当前JVM可用内存足够大,链表就可以无限生成新的节点,不存在大块内存开辟,大量数据拷贝,GC回收旧内存的内存扩容问题,这一点要比数组优秀!

b.在链表中插入数据,只需要生成新的节点接入链表中就可以,不涉及其它数据节点的移动;在链表中删除数据,只需要把待删除节点从原链表中卸载下来就可以,也不涉及其它数据节点的移动,也就是数据插入和删除的时间复杂度是O(1) O(1)O(1),常量时间,速度很快(这个指的是插入删除操作本身花费的时间,往往在链表中做插入和删除操作的时候,首先会遍历链表,找到合适的位置,那么链表搜索的时间是O(n) O(n)O(n),为线性时间)。

1不好是,因为链表中节点的内存不是连续的,所以就不能像数组那样支持元素的随机访问(就是通过指定下标,访问对应的元素的值),当在链表中访问一个元素节点时,总是要从头节点开始,一个个往后遍历,链表元素越多,遍历的时间就越长,因此链表遍历搜索的时间是O(n) O(n)O(n),为线性时间,没有数组随机访问O(1) O(1)O(1)的效率好。

结论:所以一般插入删除操作多,用链表;随机访问多,用数组。当然还需要具体情况具体分析。数组和链表各操作时间
在这里插入图片描述
这里我们就要上代码了
这里我展示的是带头节点的单链表

/**
 * 带头节点的单链表的实现
 * @param <T>
 */
class Link<T extends Comparable<T>>{

    /**
     * 指向单链表的头节点,其地址域中记录了链表第一个节点的地址
     */
    HeadEntry<T> head;

    /**
     * 初始化链表,生成头节点被head指向
     */
    public Link(){
        this.head = new HeadEntry<>(0, null, null);
    }

    /**
     * 单链表的头插法
     * @param val
     */
    public void insertHead(T val){
        Entry<T> node = new Entry<>(val, this.head.next);
        this.head.next = node;
        this.head.cnt += 1; // 更新头节点中链表节点的个数
    }

    /**
     * 单链表的尾插法
     * @param val
     */
    public void insertTail(T val){
        Entry<T> node = head;
        while(node.next != null){
            node = node.next;
        }
        node.next = new Entry<>(val, null);
        this.head.cnt += 1; // 更新头节点中链表节点的个数
    }

    /**
     * 单链表中删除所有值是val的节点
     * @param val
     */
    public void remove(T val){
        Entry<T> pre = head;
        Entry<T> cur = head.next;

        while(cur != null){
            if(cur.data == val){
                // val节点的删除
                pre.next = cur.next;
                cur = pre.next; // 重置cur,继续向后删除链表中所有值为val的节点
                this.head.cnt -= 1; // 更新头节点中链表节点的个数
            } else {
                pre = cur;
                cur = cur.next;
            }
        }
    }

    /**
     * 打印单链表的所有节点元素的值
     */
    public void show(){
        Entry<T> cur = this.head.next;
        while (cur != null) {
            System.out.print(cur.data + " ");
            cur = cur.next;
        }
        System.out.println();
    }

    /**
     * 获取链表节点的个数
     * @return
     */
    public int size() {
        return this.head.cnt;
    }

    /**
     * 单链表的节点类型
     * @param <T>
     */
    static class Entry<T>{
        T data; // 链表节点的数据域
        Entry<T> next; // 下一个节点的地址

        public Entry(T data, Entry<T> next) {
            this.data = data;
            this.next = next;
        }
    }

    /**
     * 头节点的类型,添加了int cnt成员变量,记录链表中节点的个数
     * @param <T>
     */
    static class HeadEntry<T> extends Entry<T>{
        int cnt; // 用来记录节点的个数

        public HeadEntry(int cnt, T data, Entry<T> next) {
            super(data, next);
            this.cnt = cnt;
        }
    }
}

接下来我展示的是当时在学习链表时的一些题
链表的逆置

/**
 * 逆置单链表,思想是从链表第二个节点开始采用头插法重新链接节点
 */
public void reverse(){
    if(this.head.next == null){
        return;
    }

    // 从链表第二个节点开始逆置
    Entry<T> cur = this.head.next.next;
    this.head.next.next = null;
    Entry<T> post = null;

    while(cur != null){
        post = cur.next;
        cur.next = head.next;
        head.next = cur;
        cur = post;
    }
}

判断链表是否有环,并判断入环节点

/**
 * 判断链表是否有环,如果有,返回入环节点的值,没有环,返回null
 * @return
 */
public T getLinkCircleVal(){
    Entry<T> slow = this.head.next;
    Entry<T> fast = this.head.next;

    // 使用快慢指针解决该问题
    while(fast != null && fast.next != null){
        slow = slow.next;
        fast = fast.next.next;
        if(slow == fast){
            break;
        }
    }

    if(fast == null){
        return null;
    } else {
        /**
         * fast从第一个节点开始走,slow从快慢指针相交的地方开始走,
         * 它们相遇的时候,就是环的入口节点
         */
        fast = this.head.next;
        while(fast != slow){
            fast = fast.next;
            slow = slow.next;
        }
        return slow.data;
    }
}

合并两个单链表

/**
 * 合并两个有序的单链表
 * @param link
 */
public void merge(Link<T> link){
    Entry<T> p = this.head;
    Entry<T> p1 = this.head.next;
    Entry<T> p2 = link.head.next;

    // 比较p1和p2节点的值,把值小的节点挂在p的后面
    while(p1 != null && p2 != null){
        if(p1.data.compareTo(p2.data) >= 0){
            p.next = p2;
            p2 = p2.next;
        } else {
            p.next = p1;
            p1 = p1.next;
        }
        p = p.next;
    }

    if(p1 != null){ // 链表1还有剩余节点
        p.next = p1;
    }

    if(p2 != null){ // 链表2还有剩余节点
        p.next = p2;
    }
}

双向链表的实现

/**
 * 双向链表的实现
 * @param <T>
 */
class Link<T extends Comparable<T>>{
    /**
     * 指向双向链表的头节点,其地址域中记录了链表第一个节点的地址
     */
    private Entry<T> head;

    /**
     * 构造函数,生成head指向的头节点,头节点不存数据
     */
    public Link(){
        head = new Entry<>(null, null, null);
    }

    /**
     * 双向链表的头插法
     * @param val
     */
    public void insertHead(T val){
        Entry<T> node = new Entry<>(val, this.head.next, this.head);
        this.head.next = node;
        if(node.next != null){
            node.next.pre = node;
        }
    }

    /**
     * 双链表的尾插法
     * @param val
     */
    public void insertTail(T val){
        Entry<T> cur = this.head;
        while(cur.next != null){
            cur = cur.next;
        }
        cur.next = new Entry<>(val, null, cur);
    }

    /**
     * 双向链表删除所有值为val的节点
     * @param val
     */
    public void remove(T val){
        Entry<T> cur = this.head.next;
        while(cur != null){
            if(cur.data.compareTo(val) == 0){
                cur.pre.next = cur.next;
                if(cur.next != null){
                    cur.next.pre = cur.pre;
                }
            }
            cur = cur.next;
        }
    }

    /**
     * 打印双向链表所有节点元素的值
     */
    public void show(){
        Entry<T> cur = this.head.next;
        while(cur != null){
            System.out.print(cur.data + " ");
            cur = cur.next;
        }
        System.out.println();
    }

    /**
     * 链表节点类型定义
     * @param <T>
     */
    static class Entry<T>{
        T data;
        Entry<T> next; // 存储后一个节点的地址
        Entry<T> pre; // 存储前一个节点的地址

        public Entry(T data, Entry<T> next, Entry<T> pre) {
            this.data = data;
            this.next = next;
            this.pre = pre;
        }
    }
}

发布了21 篇原创文章 · 获赞 8 · 访问量 731
展开阅读全文

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 大白 设计师: CSDN官方博客

分享到微信朋友圈

×

扫一扫,手机浏览