数据结构与算法图解(二)

190 阅读4分钟

队列

队列,就是首先加入队列的,将会首先从队列移出。因此计算机科学家都用缩写“FIFO”(first in, first out)先进先出,来形容它。

与栈类似,队列也有3个限制(但内容不同)。

1)只能在末尾插入数据(这跟栈一样)。

2)只能读取开头的数据(这跟栈相反)。

3)只能移除开头的数据(这也跟栈相反)。

image.png

image.png

队列也是处理异步请求的理想工具——它能保证请求按接收的顺序来执行。此外,它也常用于模拟现实世界中需要有序处理事情的场景,例如飞机排队起飞、病人排队看医生。

`以下是Java实现队列的示例代码:

 


public class Queue {

    private int[] data;

    private int maxSize;

    private int front;

    private int rear;

 

    public Queue(int maxSize) {

        this.maxSize = maxSize;

        this.data = new int[maxSize];

        this.front = 0;

        this.rear = -1;

    }

 

    public void enqueue(int value) {

        if (isFull()) {

            throw new RuntimeException("Queue is full");

        }

        data[++rear] = value;

    }

 

    public int dequeue() {

        if (isEmpty()) {

            throw new RuntimeException("Queue is empty");

        }

        return data[front++];

    }

 

    public int peek() {

        if (isEmpty()) {

            throw new RuntimeException("Queue is empty");

        }

        return data[front];

    }

 

    public boolean isEmpty() {

        return front > rear;

    }

 

    public boolean isFull() {

        return rear == maxSize - 1;

    }

}

 

队列是一种先进先出(FIFO,First In First Out)的数据结构,队列的基本操作包括enqueue、dequeue、peek、isEmpty和isFull。在实现中,我们使用一个数组来存储队列内元素,以及两个指针front和rear分别指向队列头部和尾部。其中,enqueue()方法实现元素的入队操作,dequeue()方法实现元素的出队操作,peek()方法返回队列头部元素但不出队,isEmpty()方法检查队列是否为空,isFull()方法检查队列是否已满。需要注意的是,在队列定义中,我们使用rear指向队列尾部,而实际存储在数组中时,rear可能会超过数组上界,这时我们可以通过判断rear和数组长度是否相等来确定队列是否已满。

链表

每个结点除了保存数据,它还保存着链表里的下一结点的内存地址。这份用来指示下一结点的内存地址的额外数据,被称为链。链表如下图所示。

image.png

链表相对于数组的一个好处就是,它可以将数据分散到内存各处,无须事先寻找连续的空格子。

 

读取链表的时间复杂度为O(N)。这跟读取数组的O(1)相比,的确是一大劣势。

 

链表的查找效率跟数组一样。记住,所谓查找就是从列表中找出某个特定值所在的索引。对于数组和链表来说,它们都是从第一格开始逐个格子地找,直至找到。如果是最坏情况,即所找的值在列表末尾,或完全不在列表里,那就要花O(N)步。

 

插入数组的最坏情况:当插入位置为索引0时,因为需要先将插入位置右侧的数据都右移一格,所以会导致O(N)的时间复杂度。然而,若是往链表的表头进行插入,则只需一步,即O(1)。

 

链表的插入效率为O(N),与数组一样。计算机得先找到索引1的结点,让结点1的链指向新的结点。这个过程就是之前所说的读取链表,其效率为O(N)。接着通过第一个链访问下一个结点。

image.png 删除链表的最后一个结点,其实际的删除动作只需1步——令倒数第二的结点的链指向null。然而,要找出倒数第二的结点,得花N步,因为我们依然只能从第一个结点顺着链往下一个个地找。

双向链表

用链表来做队列的底层也可以,要是采用双向链表这一链表的变种,就能使队列的插入和删除都为O(1)。

双向链表跟链表差不多,只是它每个结点都含有两个链——一个指向下一结点,另一个指向前一结点。此外,它还能直接访问第一个和最后一个结点

image.png

二叉树

树也是基于结点的数据结构,但树里面的每个结点,可以含有多个链分别指向其他多个结点。 二叉树存在的目的是解决既要保持顺序,又要快速查找、插入和删除的场景。 二叉树是一种遵守以下规则的树:

1)每个结点的子结点数量可为0、1、2。

2)如果有两个子结点,则其中一个子结点的值必须小于父结点,另一个子结点的值必须大于父结点

image.png

查找

二叉树的查找算法先从根结点开始。树的查找必须从根开始。

(1) 检视该结点的值。

(2) 如果正是所要找的值,太好了!

(3) 如果要找的值小于当前结点的值,则在该结点的左子树查找。

(4) 如果要找的值大于当前结点的值,则在该结点的右子树查找。

二叉树查找的时间复杂度是O(log N)。可见二叉树查找跟有序数组的二分查找拥有同样的效率。