玩转数据结构笔记(4)

296 阅读3分钟

链表

相对于数组(动态)而言,链表是真正的动态数据结构。 数据存在节点中,各个节点通过指针连接。

与数组对比

链表不需要处理扩容缩容,各个节点的空间很分散,通过指针连接, 因此失去了随机访问(通过索引访问)的能力。

而数组,每次添加元素开辟的空间是相邻的,所以可以通过索引访问,但是不是真的动态。

虚拟头节点

在链表添加元素时,我们需要判断链表是否为空,如果为空,则直接创建一个新的节点作为头节点,如果不为空,就需要头节点添加next元素。其实我们一直判断的是否存在头节点,既然这样,我们可以通过创建链表的同时,创建一个虚拟的头节点,它只是为了统一头节点特殊的代码逻辑。有了虚拟头节点,不管是什么时候,我们都可以直接执行head.next 添加元素了。

删除元素

删除元素时,我们需要拿到当前的头节点,然后一直next,直到我们想要删除的元素的上一个元素,用这个元素的next 直接连接 这个元素的next的next。

删除2这个元素时,我们通过next 遍历到 “2”的上一个元素 “1”,让“1”的next 指向想要删除元素“2”的下一个元素“3”,就实现了删除“2”这个元素。

Node cur = dummyHead;
// 这里的 判断是伪代码
while(cur.next.e != delE){
    cur = cur.next;
}
//现在 cur就是将要删除的前一个元素
Node delN = cur.next; // delN就是删除的元素
cur.next = delN.next // 将删除元素的前一个元素的next绕过删除的元素指向删除元素的next 
delN.next = null; //处理下删除元素的next 我们返回的是这个元素的值
size--; // 链表的长度别忘了 -1
return delN.e; // 返回删除元素的值

时间复杂度

增加元素:

addFirst:O(1)

addLast:O(n) 添加到尾部,需要遍历到尾部

add(index):O(n/2)->O(n)

删除元素:

delFirst:O(1)

delLast:O(n) 删除尾部,需要遍历到尾部

del(index):O(n/2)->O(n)

修改元素:

setFirst:O(1)

setLast:O(n) 修改尾部,需要遍历到尾部

set(index):O(n/2)->O(n)

查找元素:同上

链表实现栈

public class LinkedListStack<E> implements Stack<E> {

    private LinkedList<E> l;

    LinkedListStack(){
        this.l = new LinkedList<E>();
    }

    @Override
    public int getSize() {
        return l.getSize();
    }

    @Override
    public boolean isEmpty() {
        return l.isEmpty();
    }

    @Override
    public void push(E e) {
        l.addFirst(e);
    }

    @Override
    public E pop() {
        return l.removeFirst();
    }

    @Override
    public E peek() {
        return l.getFirst();
    }
}

尾指针链表实现队列

栈是栈顶进栈顶出。

队列是队尾进队首出。

栈只涉及到头部的操作,所以是O(1)复杂度。

队列,头部可以添加可以删除,访问尾部势必要遍历到最后,所有是O(n)复杂度。

既然这样,和数组实现循环队列一样,我们需要给链表的尾部元素也添加一个指针,这样可以直接获取到尾部元素执行添加元素。

头部可以删除可以添加,尾部可以添加,这样就可以实现队列。

@Override
    public void enqueue(E e) {
        if(tail == null){
            tail = new Node(e);
            head = tail;
        }else{
            tail.next = new Node(e);
            tail = tail.next;
        }
        size ++;
    }

    @Override
    public E dequeue() {
       if(isEmpty()){
           throw new IllegalArgumentException("Queue is empty. dequeue failed!");
       }
       Node h = head;
       head = head.next;
       h.next = null;
       if(head == null){
           tail = null;
       }
       size--;
       return h.e;
    }