数据结构与算法实战(三)基础数据结构

数据结构与算法实战(三)基础数据结构

一、数组

数组最大优点:快速查询

1、二次封装属于我们的数组类

public class Array {
    
    private int[] data;
    private int size;
    
    //构造函数,传入数组的容量capacity构造Array
    public Array(int capacity){
        data = new int[capacity];
        size = 0;
    }
    //无参构造函数,传入数组的容量capacity = 10
    public Array(){
        this(10);
    }
    
    //获取数组中的元素个数
    public int getSize(){
        return size;
    }
    //获取数组的容量
    public int getCapacity(){
        return data.length;
    }

    //返回数组是否为空
    public boolean isEmpty(){
        return size == 0;
    }
}

2、向数组中添加元素

向数组末添加元素
public void addLast(int e){
    if(size == data.length)
        throw new IllegalArgumentException("AddLast failed,Array is already full");

    data[size] = e;
    size++;
}
向指定位置添加元素
//向指定位置添加元素
public void add(int index,int e){
    if(size == data.length)
        throw new IllegalArgumentException("AddLast failed,Array is already full");
    
    if(index < 0 || index > size)
        throw new IllegalArgumentException("AddLast failed,Required index >= 0 and index <= size");

    for (int i = size - 1; i >= index; i--) {
        data[i + 1] = data[i];
    }
    data[index] = e;
    size++;
}
向数组头添加元素
//向数组头添加元素
public void addFirst(int e) {

    add(0, e);
}
重写toString方法
@Override
public String toString() {
    StringBuilder res= new StringBuilder().append(String.format("Array : size = %d, capacity = %d \n",size,data.length));
    res.append('[');
    for (int i = 0; i <size; i++) {
        res.append(data[i]);
        if(i != size-1)
            res.append(", ");
    }
    res.append("]");
    return res.toString();
}

3、查询和修改元素

//获取index索引位置的元素
int get(int index){
    if(index < 0 || index >= size)
        throw new IllegalArgumentException("Get failed.Index is illegal");
    return data[index];
}

//修改index索引位置的元素为e
void set(int index, int e){
    if(index < 0 || index >= size)
        throw new IllegalArgumentException("Get failed.Index is illegal");
    data[index] = e;
}

4、包含、搜索和删除元素

//查找数组在是否含有元素e
public boolean contains(E e){
    for (int i = 0; i < size; i++) {
        if(data[i].equals(e) )
            return true;
    }
    return false;
}

//查找数组中元素e所在的索引,如果不存在元素e,则返回-1
public int find(E e){
    for (int i = 0; i < size; i++) {
        if (data[i].equals(e))
            return i;
    }
    return -1;
}

//从数组中删除index位置的元素,返回删除元素
public E remove(int index){
    if(index < 0 || index >= size)
        throw new IllegalArgumentException("Remove failed.Index is illegal");

    E ret = data[index];

    for(int i = index + 1; i < size; i ++){
        data[i-1] = data[i];
    }
    size --;

    if(size <= data.length / 2)
        resize(data.length / 2);
    return ret;
}

// 从数组中删除第一个元素,返回删除元素
public E removeFirst(){
    return remove(0);
}

//从数组中删除最后一个元素,返回删除元素
public E removeLast(){
    return remove(size-1);
}

//从数组中删除元素e
public void removeElement(E e){
    int index = find(e);
    if(index != -1)
        remove(index);
}
//动态数组
  private void resize(int newCapacity) {

        E[] newData = (E[]) new Object[newCapacity];
        for (int i = 0; i < size; i++) {
            newData[i] = data[i];
        }
        data = newData;
    }

5、动态添加元素

//向指定位置添加元素
public void add(int index, E e) {

    if (index < 0 || index > size)
        throw new IllegalArgumentException("AddLast failed,Required index >= 0 and index <= size");

    if (size == data.length)
        resize(2 * data.length);

    for (int i = size - 1; i >= index; i--) {
        data[i + 1] = data[i];
    }
    data[index] = e;
    size++;
}

private void resize(int newCapacity) {

    E[] newData = (E[]) new Object[newCapacity];
    for (int i = 0; i < size; i++) {
        newData[i] = data[i];
    }
    data = newData;
}

6、分析动态数组的时间复杂度

添加操作 O(n)

addLast(e) : O(1)

addFirst(e) : O(n)

add(index , e) : O(n)

删除操作 O(n)

removeLast(e) : O(1)

removeFirst(e) : O(n)

remove(index , e) : O(n)

修改操作(支持随机访问) O(1)

set(index , e)

查询操作

get(index) O(1)

contains(e) O(n)

find(e) O(n)

二、栈

  • 栈是一种线性结构
  • 相比数组,栈对应的操作是数组的子集
  • 只能从栈顶添加或取出元素
  • 后进先出(LIFO)

栈的实现

先定义一个接口

public interface Stack<E> {
    
    int getSize();
    boolean isEmpty();
    void push(E e);
    E pop();
    E push();
}

基于之前说过的动态数组实现栈结构

//基于动态数组,实现栈
public class ArrayStack<E> implements Stack {

    Array<E> array;

    public ArrayStack(int capacity){
        array = new Array<>(capacity);
    }

    public ArrayStack(){
        array = new Array<>();
    }

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

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

    public int getCapacity(){
        return array.getCapacity();
    }

    @Override
    public void push(Object o) {
        array.addLast((E) o);
    }

    @Override
    public E pop() {
        return array.removeLast();
    }

    @Override
    public E peek() {
        return array.getLast();
    }

    @Override
    public String toString() {

        StringBuilder res = new StringBuilder();
        res.append("Stack :");
        res.append("[");
        for (int i = 0; i < array.getSize(); i++) {
            res.append(array.get(i));
            if(i != array.getSize() -1)
                res.append(", ");
        }
        res.append("]  top");

        return res.toString();
    }
}

注意:栈的五个基本操作都是O(1)的时间复杂度

栈的应用

20. 有效的括号

难度简单1947

给定一个只包括 '('')''{''}''['']' 的字符串,判断字符串是否有效。

有效字符串需满足:

  1. 左括号必须用相同类型的右括号闭合。
  2. 左括号必须以正确的顺序闭合。

注意空字符串可被认为是有效字符串。

示例 1:

输入: "()"
输出: true

示例 2:

输入: "()[]{}"
输出: true

示例 3:

输入: "(]"
输出: false
class Solution {
    public boolean isValid(String s) {

        Stack<Character> stack = new Stack<>();
        for (int i = 0; i < s.length(); i++) {
            char c = s.charAt(i);
            if(c == '(' || c == '{' || c == '[')
                stack.push(c);
            else{
                if(stack.isEmpty())
                    return false;

                char topChar = stack.pop();
                if(c == ')' && topChar != '(')
                    return false;
                if(c == ']' && topChar != '[')
                    return false;
                if(c == '}' && topChar != '{')
                    return false;
            }
        }
        return stack.isEmpty();
    }

三、队列

  • 也是一种线性结构
  • 相比数组,队列的操作是数组的子集
  • 只能从队尾添加元素,只能从队首取出元素
  • 先进先出

数组队列的实现

先定义一个接口

public interface Queue<E> {
    
    int getSize();
    boolean isEmpty();
    void enqueue();
    E dequeue();
    E getFront();
    
}
public class ArrayQueue<E> implements Queue {

    private Array<E> array;

    public ArrayQueue(int capacity){
        array = new Array<>(capacity);
    }

    public ArrayQueue(){
        array = new Array<>();
    }

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

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

    public int getCapacity(){
        return array.getCapacity();
    }

    @Override
    public void enqueue(Object e) {
        array.addLast((E) e);
    }

    @Override
    public E dequeue() {
        return array.removeFirst();
    }

    @Override
    public E getFront() {
        return array.getFirst();
    }
    @Override
    public String toString() {

        StringBuilder res = new StringBuilder();
        res.append("Queue :");
        res.append("front [");
        for (int i = 0; i < array.getSize(); i++) {
            res.append(array.get(i));
            if(i != array.getSize() -1)
                res.append(", ");
        }
        res.append("] tail");

        return res.toString();
    }
ArrayQueue的复杂度分析

除了dequeue() 为O(n) ,其余都为O(1)

数组队列的问题

删除首元素是O(n)的复杂度

由此引出循环队列

循环队列

front指向首元素,tail指向尾元素的后一个位置

起始的时候front == size 队列为空,然后一直向后添加元素a,b…,直到添加到f的时候满了,现在删除a,b两元素,front指向c,然后计算(tail+1) % capacity ,这是tail新指向的索引

在这里插入图片描述

当(tail+1) % capacity == front 时,队列满

capacity中会浪费一个空间

代码实现
public class LoopQueue<E> implements Queue{

    private E[] data;
    private int front , tail;
    private int size;

    public LoopQueue(int capacity){
        data = (E[]) new Object[capacity+1];
        front = 0;
        tail = 0;
        size = 0;
    }

    public LoopQueue(){
        this(10);
    }

    public int getCapacity(){
        return data.length - 1; //数组中会有一个位置被有意识的浪费掉
    }

    @Override
    public int getSize() {
        return size;
    }

    @Override
    public boolean isEmpty() {
        return tail == front;
    }

    @Override
    public void enqueue(Object e) {

        if((tail+1) % data.length == front)
            resize(getCapacity() * 2);

        data[tail] = (E)e ;
        tail = (tail + 1) % data.length;
        size ++;
    }

    private void resize(int newCapacity) {
        E[] newData = (E[]) new Object[newCapacity + 1];
        for (int i = 0; i < size; i++) {
            newData[i] = data[(i + front) % data.length];
        }
        data = newData;
        front = 0;
        tail = size;
    }

    @Override
    public E dequeue() {
        if(isEmpty())
            throw new IllegalArgumentException("Cannot dequeue from an empty queue");
        E ret = data[front];
        data[front] = null;
        front = (front + 1) % data.length;
        size --;

        if(size == getCapacity() / 4 && getCapacity()/2 != 0)
            resize(getCapacity()/2);

        return ret;
    }

    @Override
    public E getFront() {
        if(isEmpty())
            throw new IllegalArgumentException("Queue is empty");
        return data[front];
    }

    @Override
    public String toString() {
        StringBuilder res= new StringBuilder().append(String.format("Queue : size = %d, capacity = %d \n",size,getCapacity()));
        res.append("front [");
        for (int i = front; i != tail; i = (i+1) % data.length) {
            res.append(data[i]);
            if((i+1) % data.length != tail )
                res.append(", ");
        }
        res.append("] tail");
        return res.toString();
    }

    public static void main(String[] args) {
        LoopQueue<Integer> queue = new LoopQueue<>();
        for (int i = 0; i < 10; i++) {
            queue.enqueue(i);
            System.out.println(queue);

            if(i % 3 == 2){
                queue.dequeue();
                System.out.println("1111"+queue); }
        }
    }
}

数组队列和循环队列性能比较

import java.util.Random;

public class Main {

    public static void main(String[] args) {

        int opCount = 200000;

        ArrayQueue<Integer> arrayQueue = new ArrayQueue<>();
        double time1 = testQueue(arrayQueue,opCount);
        System.out.println("ArrayQueue , time:  "+ time1 + "s");

        LoopQueue<Integer> loopQueue = new LoopQueue<>();
        double time2 = testQueue(loopQueue,opCount);
        System.out.println("LoopQueue , time:  "+ time2 + "s");

    }

    //测试使用队列q运行opCount个入队、出队操作所需要的时间,单位:秒
    private static double testQueue(Queue<Integer> q, int opCount){

        long startTime = System.nanoTime();

        Random random = new Random();
        for (int i = 0; i < opCount; i++) {
            q.enqueue(random.nextInt(Integer.MAX_VALUE));
        }
        for (int i = 0; i < opCount; i++) {
            q.dequeue();
        }

        long endTime = System.nanoTime();

        return (endTime - startTime)/1000000000.0;

    }
}

在这里插入图片描述

155. 最小栈

设计一个支持 pushpoptop 操作,并能在常数时间内检索到最小元素的栈。

  • push(x) —— 将元素 x 推入栈中。
  • pop() —— 删除栈顶的元素。
  • top() —— 获取栈顶元素。
  • getMin() —— 检索栈中的最小元素。
import java.util.Stack;
/**
 * 解题思路:
 *          使用一个栈,每输入一个数字,入栈两次,第一次是自身元素,第二次是入栈元素和栈顶元素之间的最小值
 *          保持栈顶就是整个栈中元素的最小值
 *          例如输入2,3,1。 在栈中就是 2,2,3,2,1,1
 */
public class MinStack {

    private Stack<Integer> stack;
    /** initialize your data structure here. */
    public MinStack() {
        stack = new Stack<>();
    }

    public void push(int x) {

        if(stack.isEmpty()){
            stack.push(x);
            stack.push(x);
        }else {
            int temp = stack.peek();
            stack.push(x);
            if(x > temp){
                stack.push(temp);
            }else {
                stack.push(x);
            }
        }
    }

    public void pop() {

        stack.pop();
        stack.pop();
    }

    //获取栈顶元素
    public int top() {

        return stack.get(stack.size() - 2);
    }

    public int getMin() {

        return stack.peek();
    }
}


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值