【数据结构与算法】第二章 数组、链表、栈和队列(基础)

第二章 数组、链表、栈和队列(基础)

表、栈、队列都属于线性的数据结构

这一章是基于数组及对数组的优化来讲。

 

 

一、普通数组

int[] arr = new int[10];

这是一个普通数组的基本结构,也是java语言中最基础的数据结构。数组也分为一维数组和多维数组。我们这次主要来讲一维数组。

1、解决数据存储的问题。它是带有一组操作对象的集合。(由于java是强类型的,所以数组中只能存同一类型的元素值)

2、它能通过索引进行快速定位。(这是最大的优点。可以把查找的时间复杂度O(1)常数级别)

3、容量必须在初始化时定义好,超过容量则数组越界。

4、除非在数组尾添加和删除元素,否则对数组添加、删除元素的成本过高。因为要进行元素移动。

例子:

对于数组不再多说,工作中常用到。下面来结合java标准库说下对于普通数组的优化。

 

二、动态数组ArrayList

由于普通数组初始化时必须指定容量大小,如果我们事先无法准确的预估数组元素的大小,这样程序执行过程中,可能会导致数组容量不够用的问题。这时我们需要使用动态数据。也就是说当数组元素的个数等于数组容量的时候进行扩容。

1、扩容(添加元素)

最后的结果:

 

当capacity已经满了时候,就调用扩容,这里扩容2倍。

java的arrayList默认是1.5倍。

 

 

2、缩容(删除元素)

其实这样写太过于简单粗暴,因为二分之一是一个临界值,如果你缩容完之后,马上又添加一个元素,这时候还需要进行扩容。最好的办法就是让这个临界值比起到比较的作用。

if (size == data.length /4){

    resize(data.length /2)

}

3、复杂度分析

查询元素:O(1)

修改元素:O(1)

添加元素:尾节点O(1),其它位置O(n)

删除元素:尾节点O(1),其它位置O(n)

 

三、链表

链表基于Node节点的数据结构,node节点是自定义的。

也就是说链表和数组是一个级别的。都是最底层的结构

数组:静态的数据结构

链表:动态的数据结构(指在运行时刻才能确定所需内存空间大小的数据结构)

1、链表解决的是数组添加和删除元素效率的问题。

2、但是链表牺牲了数组可以索引查找的高效

 

对于java标准库来说,基于双向链表且有虚拟头和尾节点实现的。

 

1、单链表和双链表的区别

单链表结构中仅包含下一个节点。

双链表结构中包含上一个节点和下一个节点。

优缺点:

提高了添加和删除元素的效率。

但增加了前一个节点地址的维护成本。

 

2、虚拟头节点和虚拟尾节点

虚拟尾节点对于链表来说是有很大意义的,由于链表不能通过索引快速定位最后一个元素,假如我们需要在链表尾进行查询、添加、修改、删除元素,这时虚拟尾节点可以帮助我们快速定位。

虚拟头节点的作用更大程度是是为了简单程序代码的实现。方便对头节点的添加和删除而已。

上面是一个添加节点的代码,可以看出,对于添加节点分两种不同的情况,在头节点添加还是在中间节点添加。如果有了虚拟头节点,那么就变成了一种情况。所有的添加都是在中间节点操作。

 

3、代码实现

1)定义Node节点

public class Node<T> {

    public T value;

    public Node<T> prev;

    public Node<T> next;

 

    public Node(T value, Node<T> prev, Node<T> next) {

        this.value = value;

        this.prev = prev;

        this.next = next;

    }

}

2)初始化链表

private Node<T> beginMarker;

private Node<T> endMarker;

private int size;

 

public MyLinkedList() {

    beginMarker = new Node<T>(null, null, null);

    endMarker = new Node<T>(null, beginMarker, null);

    beginMarker.next = endMarker;

    this.size = 0;

}

 

3)查询节点

public Node<T> getNode(int idx) {

    Node<T> currentNode = beginMarker.next;

    for (int i = 0; i < idx; i++) {

        currentNode = currentNode.next;

    }

    return currentNode;

}

 

4)添加节点

public void add(int idx, T value) {

    if (idx < 0 || idx > size) {

        throw new IllegalArgumentException("索引越界");

    }

    Node currentNode = getNode(idx);

    Node<T> newNode = new Node<T>(value, currentNode.prev, currentNode);

    newNode.prev.next = newNode;

    currentNode.prev = newNode;

    size++;

}

 

5、删除节点

public void remove(int idx) {

    if (idx < 0 || idx >= size) {

        throw new IllegalArgumentException("索引越界");

    }

    Node currentNode = getNode(idx);

    currentNode.prev.next = currentNode.next;

    currentNode.next.prev = currentNode.prev;

    size--;

}

 

4、复杂度分析:

查询元素:头尾节点O(1)、中间节点O(n)

修改元素:头尾节点O(1)、中间节点O(n)

添加元素:头尾节点O(1)、中间节点O(n)

删除元素:头尾节点O(1)、中间节点O(n)

 

三、栈(O(1))

根据栈的结构我们可以看出,它主要解决的对数据结构的一端进行查询、添加、删除元素的操作。解决了快速定位的问题。所以说我们实现的ArrayList、LinkedList都可以做为栈的实现。

  • arrayList不用说,这能通过索引快速定位,但必须把数组尾作为栈顶进行操作,才能使得时间复杂度都在O(1)(限制条件)。
  • linkedList,我们实现的是带虚拟头尾节点的双向链表,所以把链表头或链表尾当作栈顶都可以。时间复杂度一样。
  • 在java标准库中 public class Stack<E> extends Vector<E> 可以看出是基于Vector 实现的,而 Vector底层是数组

 

1、 栈的基本实现

2、代码实现

这是基于动态数组。实现起来还是比较简单的。

 

 

四、队列(O(1))

 

 

1、它是一种后进先出的数据结构

2、只能是一端入队在另一端出队。

3、不能对队中元素进行任何修改(但后期会说到优先队列)

 

我们现在实现的ArrayList和 LinkedList都可以做为队列的实现但是实现方式也不同:

1、ArrayList实现,由于Queue要求从一端入队,从另一端出队。对于数组实现是在数组尾入队,在数据头出队。但是出队的时候就会有问题,删除数组头元素是不是后面的元素都要进行上移。最好的方式就是作循环列队,不进行删除,重复使用

2、使用LinkedList实现,这种是最理想的实现,我们的LinkedList是带有虚拟头尾节点的双向链表,在链表头尾添加和删除的时间复杂度都是O(1)。

3、java标准库中Queue的实现是LinkedList。

 

代码实现:

public synchronized T push(T value) {

    super.add(0, value);

    return value;

}

 

public synchronized T pop() {

    T value = peek();

    super.remove(0);

    return value;

}

 

public synchronized T peek() {

    int size = super.size();

    if (size == 0) {

        throw new EmptyStackException();

    }

    return super.get(0);

}

 

五、总结

 

1、 ArrayList动态扩容是对普通数组扩容的优化。(查询快、添加和删除慢)

2、 LinkedList是对数组添加和删除元素效率的提升,但失去了索引查找的特性(添加和删除快、查询慢)

3、Stack是基于数组实现的,解决的是后进先出的问题。它的操作局限在栈顶元素。(操作单一)

4、Queue是基于LinkedList实现的。解决的是先进先出排队的问题。只能在队尾添加,队首删除。(操作单一)

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值