编程语言
首页 > 编程语言> > 数据结构与算法之美——单链表复习

数据结构与算法之美——单链表复习

作者:互联网

1、课程内容

详情可参考“极客时间”上的《数据结构与算法之美》课程:07 | 链表(下):如何轻松写出正确的链表代码? (geekbang.org)

2、课后练习

代码:

结点

package dataStruct;

/**
 * @ClassName Node
 * @Version 1.0
 * @Author Wulc
 * @Date 2022-01-28 10:54
 * @Description 链表结点
 */
public class Node<T> {
    //数据信息
    public T data;
    //下一个节点引用
    public Node next;

    public Node(T data) {
        this.data = data;
    }
    
    //添加节点
    public void add(T data) {
        if (this.next == null) {
            this.next = new Node(data);
        } else {
            this.next.add(data);
        }
    }

    //添加节点
    public void add(Node node) {
        if (this.next == null) {
            this.next = node;
        } else {
            this.next.add(node);
        }
    }
}

链表:

package dataStruct;

import java.util.ArrayList;
import java.util.List;

/**
 * @ClassName ListNode
 * @Version 1.0
 * @Author Wulc
 * @Date 2022-01-28 10:54
 * @Description 链表
 */
public class ListNode<T> {
    //根结点位置
    public int foot;
    //链表长度
    public int length;
    //当前结点
    public Node root;
    //头结点
    public Node head;

    public ListNode() {
        this.head = new Node("Head");
    }

    //判断链表是否为空
    public boolean isEmpty() {
        if (length == 0 || this.root == null) {
            return true;
        } else {
            return false;
        }
    }

    //判断链表是否包含某个数值
    public boolean contain(T data) {
        Node cur = this.head.next;
        while (cur != null) {
            if (cur.data.equals(data)) {
                return true;
            }
            cur = cur.next;
        }
        return false;
    }

    //判断链表是否包含某个结点
    public boolean contain(Node node) {
        Node cur = this.head.next;
        while (cur != null) {
            if (cur == node) {
                return true;
            }
            cur = cur.next;
        }
        return false;
    }

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

    //添加结点
    public void addNode(T data) {
        if (this.isEmpty()) {
            this.root = new Node(data);
            //头结点的下一个结点为链表的第一个结点
            this.head.next = this.root;
        } else {
            this.root.add(data);
        }
        this.length++;
    }

    //添加结点
    public void addNode(Node node) {
        if (this.isEmpty()) {
            this.root = node;
            //头结点的下一个结点为链表的第一个结点
            this.head.next = this.root;
        } else {
            this.root.add(node);
        }
        this.length++;
    }

    //输出链表
    public Object[] print() {
        Object obj[] = new Object[this.length];
        int i = 0;
        //从第一个链表结点开始遍历
        this.root = this.head.next;
        while (this.root != null) {
            obj[i++] = this.root.data;
            this.root = this.root.next;
        }
        this.root = this.head.next;
        return obj;
    }

    //单链表反转:只要把链表上每个结点的next指针指向其前驱结点即可完成单链表反转
    public void reserve() {
        Node pre = null, next = null;
        this.root = this.head.next;
        while (this.root != null) {
            next = this.root.next;
            if (next == null) {
                //确保头结点的next指向链表的第一个结点
                this.head.next = this.root;
            }
            this.root.next = pre;
            pre = this.root;
            this.root = next;
        }
    }

    //链表中环的检测一:快慢指针法,慢指针和快指针如果相遇了,则有环,如果不相遇则无环
    public boolean ifHasLoopV() {
        //p1是慢指针,一次步进一格
        Node p1 = this.head.next;
        //p2是快指针,一次步进二格
        Node p2 = this.head.next.next;
        int i = 0;
        while (p1 != null && p2 != null) {
            if (p1 == p2) {
                return true;
            }
            if (p2.next.next == null) {
                return false;
            }
            p1 = p1.next;
            p2 = p2.next.next;
        }
        return true;
    }

    //链表中环的检测一:足迹法,遍历链表,如果重复遍历了某个结点,则有环
    public boolean ifHasLoopV1() {
        List<Node> list = new ArrayList<>();
        Node cur = this.head.next;
        while (cur != null) {
            if (list.contains(cur)) {
                return true;
            }
            list.add(cur);
            cur = cur.next;
        }
        return false;
    }

    //两个有序的链表合并(降序),将当前链表与参数链表合并成一个新的链表
    public ListNode mergeSortedLinkList(ListNode listNode) {
        ListNode tListNode = new ListNode();
        this.root = this.head.next;
        while (this.root != null && listNode.root != null) {
            if (this.root.data.toString().compareTo(listNode.root.data.toString()) < 0) {
                tListNode.addNode(new Node(this.root.data));
                this.root = this.root.next;
            } else {
                tListNode.addNode(new Node(listNode.root.data));
                listNode.root = listNode.root.next;
            }
        }
        while (this.root != null) {
            tListNode.addNode(new Node(this.root.data));
            this.root = this.root.next;
        }
        while (listNode.root != null) {
            tListNode.addNode(new Node(listNode.root.data));
            listNode.root = listNode.root.next;
        }
        return tListNode;
    }

    //删除链表倒数第 n 个结点:快慢指针法
    public void removeLastN(int n) {
        if (n == length) {
            this.head.next = this.root.next;
            this.length--;
        }
        if (n < length) {
            Node p1 = this.head;
            Node p2 = this.head;
            int i = 0;
            while (i < n) {
                p2 = p2.next;
                i++;
            }
            while (p2 != null) {
                p1 = p1.next;
                p2 = p2.next;
                if (p2.next == null) {
                    this.root = p1;
                    //删除倒数第n个结点
                    this.root.next = p1.next.next;
                    this.length--;
                    break;
                }
            }
        }
    }

    //求链表的中间结点
    public Node getMiddleNode() {
        int i = 0;
        this.root = this.head.next;
        if (this.length % 2 == 1) {
            while (i++ < this.length / 2) {
                this.root = this.root.next;
            }
        } else {
            while (i++ < this.length / 2 - 1) {
                this.root = this.root.next;
            }
        }
        return this.root;
    }

}

3、总结

因为临近过年,比较闲,于是就用公司给的点数去买了极客时间上的《数据结构与算法之美》的课程。旨在巩固一下基础,提升代码能力和逻辑思维能力。

除了大二上数据结构课程时手动写过一些链表,之后在做课设,毕设,以及实际开发中,几乎没怎么用过链表。在java中链表结构常见的也就是LinkedList、LinkedHashMap、LinkedHashSet这些。比较笼统的来说加了Linked说明底层的结构添加了链表。

具体一点来说

比如LinkedList和ArrayList

LinkedList的底层数据结构是链表,没有固定大小,无需扩容。插入,删除速度快。

ArrayList的底层数据结构是数组,数组默认长度为10,当存储容量达到三分之二时,自动扩容1.5倍空间(即申请一个原来大小1.5倍的空间,并把原来里面的内容全部拷贝到新的空间去)。因此ArrayList对插入删除操作不太友好,但得益于底层的数组结构有下标索引加成,因此查询速度快。

LinkedHashMap和HashMap

LinkedHashMap是继承自HashMap,但多了一个“双向链表”。因为多了一个“双向链表”,因此LinkedHashMap可以按照元素的插入顺序进行输出(只是输出顺序可以按照插入顺序进行输出),但LinkedHashMap和HashMap的存储结构元素顺序都是一样的,都是按照key进行升序排序,因为LinkedHashMap的put方法同HashMap的put方法。

HashMap的结构是散列表+链表,如下:

 HashMap.put()底层实现

 LinkedHashMap.put其实和HashMap.put()底层实现原理一样,只不过LinkedHashMap多了一个双向链表存放entry了。

 LinkedHashSet和HashSet

首先说明一下HashSet中的值是不可重复的,HashSet底层其实也是用HashMap存储元素的。因为HashSet的add方法调用了HashMap的put方法,HashSet.add的值就是HashMap.put的key,因为HashMap的key不能重复,所以HashSet.add的值就不会重复了。

HashMap保存数据时会根据key去计算一个hashCode值用于决定在散列表中的存储位置,hash值是系统计算的,有一定随机性的,因此HashSet.add的值(相当于hashMap中的key)保存在内存中也是根据hashCode的值无序存放的。

当然这里有个问题就是既然hashSet的值是以key的形式存在hashMap中,但是hashMap中的key是有序的,而hashSet以hashMap key存放在hashMap中的值是无序的?其实这个问题很好解释,就是hashSet只是借用了hashMap的存储结构,但不会去使用到hashMap中相应的key排序优化的算法。因此在hashSet中还是根据hash code进行索引的。

LinkedHashSet继承HashSet,底层使用父类HashSet的LinkedHashMap来保存所有元素,因此LinkedHashSet和LinkedHashMap一样可以按照输入顺序有序输出。

 

4、参考资料

一文读懂链表反转(迭代法和递归法) - 你是风儿 - 博客园

java实现单链表_congge-CSDN博客_java链表实现

常见链表操作-链表中环的检测(JAVA实现)_wanf425的专栏-CSDN博客_链表中环的检测

搞定面试官(Java)—LinkedList插入更快?ArrayList遍历更快? - 知乎

迭代器是什么 - html中文网

HashSet添加元素过程_cai_ing的博客-CSDN博客_hashset添加元素

浅谈LinkedHashSet(哈希链表)_橙子的博客-CSDN博客_linkedhashset数据结构

java的LinkedHashSet是怎样实现存取有序的, 底层原理是什么_百度知道 (baidu.com)

标签:Node,结点,单链,之美,next,链表,数据结构,root,public
来源: https://blog.csdn.net/weixin_42032770/article/details/122733975