数据结构思维笔记(四)LinkedList

1.链式数据结构

在链表中,每个节点包含列表中下一个节点的引用. 其它的链表结构包括树和图,其中节点可以包含多个其它节点的引用。


简单的节点Demo

public class ListNode {

    public Object node;
    public ListNode next;

    //init Node
    public ListNode() {
        this.node = null;
        this.next = null;
    }

    //usually use as the head
    public ListNode(Object node) {
        this.node = node;
        this.next = null;
    }


    //normal node
    public ListNode(Object node, ListNode next) {
        this.node = node;
        this.next = next;
    }


    @Override
    public String toString() {
        return "ListNode{" + "node=" + node + ", next=" + next + '}';
    }
    
}

ListNode提供了几个构造函数,可以让你为datanext提供值,或将它们初始化为默认值,null.


创造一个链表

ListNode node1 = new ListNode(1);
ListNode node2 = new ListNode(2);
ListNode node3 = new ListNode(3);

node1.next = node2;
node2.next = node3;
node3.next = null;

ListNode node0 = new ListNode(0,node1);


2.LinkedList初始化结构

构造内嵌函数Node

//内嵌函数(Nested Function set the Node)
    private class Node {
        public E data;
        public Node next;

        public Node() {
            this.data = null;
            this.next = null;
        }

        public Node(E data) {
            this.data = data;
            this.next = null;
        }

        public Node(E data, Node next) {
            this.data = data;
            this.next = next;
        }

    }

初始化结构

public class MyLinkedList<E> implements List<E> {

    private int size;            // keeps track of the number of elements
    private Node head;           // reference to the first node

    public MyLinkedList() {
        head = null;
        size = 0;
    }
}

存储元素数量不是必需的,并且一般来说,保留冗余信息是有风险的,因为如果没有正确更新,就有机会产生错误。它还需要一点点额外的空间。

但是如果我们显式存储size,我们可以实现常数时间的size方法;否则,我们必须遍历列表并对元素进行计数,这需要线性时间。

因为我们显式存储size明确地存储,每次添加或删除一个元素时,我们都要更新它,这样一来,这些方法就会减慢,但是它不会改变它们的增长级别,所以很值得。


3.LinkedList方法划分

Add方法

    @Override
    public boolean add( E element) {
        if (head == null) {
            head = new Node(element);
        } else {
            Node node = head;
            for (; node.next != null; node = node.next) {}      //loop until the next one
            node.next = new Node(element);
        }
        size++;
        return true;
    }

indexOf方法

	@Override
    public int indexOf(Object target) {
        Node node = head;
        for (int i=0; i<size; i++) {
            if ( equals(target,node.data) ) {
                return i;
            }
            node = node.next;
        }
        return -1;
    }

那么这种方法的增长级别是什么?

  • 每次在循环中,我们调用了equals,这是一个常数时间(它可能取决于targetdata大小,但不取决于列表的大小)。循环中的其他操作也是常数时间。
  • 循环可能运行n次,因为在更糟的情况下,我们可能必须遍历整个列表。

所以这个方法的运行时间与列表的长度成正比(换言之,是线性的)。


Add双参方法

	@Override
    public void add(int index, E element) {
        if (head == null) {
            Node node = new Node(element);
        } else {
            Node node = getNode(index-1);
            node.next = new Node(element,node.next);
        }
        size++;
    }

辅助方法

	private Node getNode(int index){
        if (index<0 || index>=size) {
            throw new IndexOutOfBoundsException();
        }
        Node node = head;
        for (int i=0; i<index; i++) {
            node = node.next;
        }
        return node;
    }
  • getNode类似indexOf,出于同样的原因也是线性的。
  • add中,getNode前后的一切都是常数时间。

所以放在一起,add是线性的。


Remove方法

 @Override
    public E remove(int index) {
        E element = get(index);
        if (index == 0) {
            head = head.next;
        } else {
            Node node = getNode(index-1);
            node.next = node.next.next;
        }
        size--;
        return element;
    }

remove使用了get查找和存储index处的元素。然后它删除包含它的Node

如果index==0,我们再次处理这个特殊情况。否则我们找到节点index-1(这个真的是心机,得防止它漏过)并进行修改,来跳过node.next并直接链接到node.next.next。这有效地从列表中删除node.next,它可以被垃圾回收。

当人们看到两个线性操作时,他们有时会认为结果是平方的,但是只有一个操作嵌套在另一个操作中才适用。如果你在一个操作之后调用另一个,运行时间会相加。如果它们都是O(n)的,则总和也是O(n)的。

所以Remove()是线性的


4.ArrayList和LinkedList的对比
MyArrayListMyLinkedList
add(末尾)1n
add(开头)n(剩余的都往后)1
add(一般)nn
get / set1n
indexOf / lastIndexOfnn
isEmpty / size11
remove(末尾)1n
remove(开头)n1
remove(一般)nn
  • MyArrayList的优势操作是,插入末尾,移除末尾,获取和设置
  • MyLinkedList的优势操作是,插入开头,以及移动开头。(链式结构头容易获取
    要用哪种看具体的需求


原书链接:https://wizardforcel.gitbooks.io/think-dast/content/5.html
GitHub链接(提供源码):https://github.com/huoji555/Shadow/tree/master/DataStructure

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值