Java学习笔记(十四):数据结构基础(一)

数据结构基础

计算机科学中,数据结构是一种数据组织、管理和存储的格式,它可以帮助我们实现对数据高效的访问和修改。更准确地说,数据结构是数据值的集合,可以体现数据值之间的关系,以及可以对数据进行应用的函数和操作。
通俗地说,我们需要去学习在计算机中如何去更好地管理我们的数据,才能让我们对我们的数据结构控制更加灵活。

线性表

线性表是最基本的一种数据结构,它是表示一组相同类型数据的有限序列,你可以把它与数据进行参考,但它并不是数组,线性表是一种表接口,它能够支持数据的插入、删除、更新、查询等,同时数组可以随机存放在数组中任意位置,而线性表只能依次有序排列,不能出现空隙,因此我们需要进一步的设计。

顺序表

将数据以此存储在连续的整块物理空间中,这种存储结构称为顺序存储结构,而以这种方式实现的线性表,我们称为顺序表。
同样的,表中的一个个体称之为元素,元素左边的元素称为前驱,右边的元素称为后驱。
顺序表

借助数组实现,然后对方法进行封装,实现增删改查。

目标:以数组为底层,编写以下抽象类的具体实现
先定义一个抽象类,再继承实现

/**
 * 线性表抽象类
 * @param <E> 存储的元素类型
 */
public abstract class AbstractList<E> {
    /**
     * 获取表的长度
     * @return顺序表的长度
     */
    public abstract int size();

    /**
     *添加一个元素
     * @param e 元素
     * @param index 要添加的位置
     */
    public abstract void add(E e, int index);

    /**
     * 移除指定位置的元素
     * @param index 位置
     * @return 移除的元素
     */
    public abstract E remove(int index);

    /**
     * 获取指定位置的元素
     * @param index 位置
     * @return 元素
     */
    public abstract E get(int index);

}

public class ArrayList<E> extends AbstractList<E> {
    //底层数组
    private Object[] arr = new Object[1];          //初始大小为20,后面会动态增长
    //长度
    private int size = 0;


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

    @Override
    public void add(E e, int index) {
        //位置是否合法(添加异常)
        if(index > size) throw new IllegalArgumentException("非法的插入位置");

        //如果要扩容,就扩容
        if (size >= arr.length){
            Object[] arr = new Object[this.arr.length + 10];            //arr为局部变量
            for (int i = 0; i < this.arr.length; i++) arr[i] = this.arr[i];  //把原来的arr搬过来
            this.arr = arr;
        }

        //把后面的元素往右移动
        int i = size - 1;       //最后一个元素开始
        while (i >= index){     //一直到目标位置
            arr[i+1] = arr[i];
            i--;
        }
        arr[index] = e;         //插入操作
        size ++;                //加入后size增长一位
        //把数据放进去

    }

    @Override
    public E remove(int index) {
        //位置是否合法(添加异常)
        if(index > size - 1) throw new IllegalArgumentException("非法的删除位置");
        //无需考虑扩容

        //前移后面的元素
        int i = index;
        E e = (E) arr[index];           //用Object存的,因此要进行强制类型转换(一定是安全的)
        while(i < size - 1){
            arr[i] = arr[i+1];  //前移后面的元素
            i++;
        }
        size--;                 //减少容量

        return e;
    }

    @Override
    public E get(int index) {
        //异常处理
        if (index >= size ) throw new IndexOutOfBoundsException("无法访问到下标位置");
        return (E) arr[index];
    }
}

public class Main {
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();
        list.add("A", 0);
        list.add("B", 0);
        list.remove(0);
        System.out.println(list.get(0));

    }
}

链表

数据分散的存储在物理空间中,通过一根线保存着它们之间的逻辑关系,这种存储结构称为链式存储结构。
实际上,就是每一个结点存放一个元素和一个指向下一个结点的引用(C语言里面是指针,Java就是对象的引用,代表下一个结点对象)
在这里插入图片描述
头节点:不存储数据

在这里插入图片描述

public class LinkedList<E> extends AbstractList<E> {


    private Node<E> head = new Node<>(null);            //头结点

    private int size;

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

    @Override
    public void add(E e, int index) {
        //位置是否合法(添加异常)
        if(index > size) throw new IllegalArgumentException("非法的插入位置");
        //链表不需要扩容,因为一直在动态扩容
        //插入元素
        /**
         * e1 -> e2 -> e3
         * 插入e0
         * e0 -> e2, e1 -> e0即可
         */
        //定位前驱节点
        Node<E> node = head, temp;
        for(int i = 0; i < index; i++){             //根据index数找到存储节点的前驱节点
            node = node.next;
        }
        temp = node.next;                           //暂时存放
        node.next = new Node<>(e);
        node.next.next = temp;
        size++;
    }

    @Override
    public E remove(int index) {            //删除的思路是直接把前驱结点指向后驱
        //位置是否合法(添加异常)
        if(index > size) throw new IllegalArgumentException("非法的插入位置");

        //
        Node<E> node = head, temp;
        for(int i = 0; i < index; i++){
            node = node.next;               //由于索引是从0开始,因此这里找到的是前驱结点
        }
        temp = node.next;
        node.next = node.next.next;         //前驱结点直接指向后驱结点
        size--;
        return temp.e;
    }

    @Override
    public E get(int index) {
        //位置是否合法(添加异常)
        if(index > size) throw new IllegalArgumentException("非法的插入位置");
        Node<E> node = head.next;
        for(int i = 0 ; i < index; i++){
            node = node.next;
        }

        return node.e;
    }

    private static class Node<E>{
        private E e;                //一个元素
        private Node<E> next;       //一个对象(指针)

        public Node(E e){
            this.e = e;
        }
    }
}

public class Main {
    public static void main(String[] args) {
        AbstractList<String> list = new LinkedList<>();
        list.add("A", 0);
        list.add("B", 1);
        list.add("C", 2);
        System.out.println(list.remove(1));
        System.out.println(list.get(1));
    }
}

比较顺序表和链表的优异
顺序表优缺点:
1、访问速度快,随机访问性能高
2、插入和删除的效率地下,极端情况下需要变更整个表
3、不易扩充,需要复制并重新创建数组

链表优缺点:
1、插入和删除效率高,只需要改变连接点的指向即可。
2、动态扩充容量,无需担心容量问题。
3、访问元素需要依此寻找,随机访问元素效率低下。

链表只能指向后面,能不能指向前面?双向链表!

栈遵循先入后出原则,只能在线性表的一段添加和删除元素。我们可以把栈看作一个杯子,杯子只有一个口进出,最低处的元素只能等到上面的元素离开杯子后,才能离开。
在这里插入图片描述
向栈中插入一个元素时,称为入栈,移除栈顶元素称为出栈,我们需要尝试实现以下抽象类型

本次借助线性表完成

public abstract class AbstractStack<E> {
    /**
     * 出栈操作
     * @return 栈顶元素
     */
    public abstract E pop();

    /**
     * 入栈造作
     * @param e 元素
      */
    public abstract void push(E e);

}

public class ArrayStack<E> extends AbstractStack<E>{
    //底层数组
    private Object[] arr = new Object[20];          //初始大小为20,后面会动态增长
    //长度
    private int size = 0;           //不仅是长度,也是栈顶指针

    @Override
    public void push(E e) {                //没有index因为位置只能从最上面插入
        //如果要扩容,就扩容
        if (size >= arr.length){
            Object[] arr = new Object[this.arr.length + 10];            //arr为局部变量
            for (int i = 0; i < this.arr.length; i++) arr[i] = this.arr[i];  //把原来的arr搬过来
            this.arr = arr;
        }
        arr[size] = e;
        size++;
    }

    @Override
    public E pop() {
        return (E) arr[(size--)-1];         //都不用去掉原来的数,因为指针变了,下一个进栈直接覆盖原来的数。
    }
}

public class Main {
    public static void main(String[] args) {
        ArrayStack<String> stack = new ArrayStack<>();
        stack.push("A");
        stack.push("B");
        stack.push("C");
        System.out.println(stack.pop());
        stack.push("D");
        System.out.println(stack.pop());
        System.out.println("debug");
    }
}


其实,我们在JVM在处理方法调用时,也是一个栈操作。
如果用递归不恰当的话,会一直压栈,最后爆栈。

队列

队列同样也是受限制的线性表,不过队列就像我们排队一样,只能从队尾开始排,从队首出。
在这里插入图片描述

public abstract class AbstractQueue <E>{
    /**
     * 进队操作
     * @param e 元素
     */
    public abstract void offer(E e);

    /**
     * 出队操作
     * @return 元素
     */
    public abstract E poll();

}

public class ArrayQueue<E> extends AbstractQueue<E>{

    //底层数组
    private Object[] arr = new Object[4];
    //队尾队首下标
    private int head = 0, tail = 0;

    @Override
    public void offer(E e) {
        int next = (tail+1) % arr.length;       //防止tail追上head一圈的情况,到了最后把tail置零
        if (next == head) return;
        arr[tail] = e;                  //把数扔到队尾对应下标
        tail = next;                    //置数
    }

    @Override
    public E poll() {
        E e = (E) arr[head];
        head = (head + 1) % arr.length;
        return e;
    }
}

public class Main {
    public static void main(String[] args) {
        ArrayQueue<String> queue = new ArrayQueue<>();
        queue.offer("A");
        queue.offer("B");
        queue.offer("C");
        queue.offer("D");       //队满了,进不去了
        queue.poll();
        queue.poll();               //排出两个人
        queue.offer("E");
        queue.offer("F");
        System.out.println("debug");
    }
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
数据结构与算法是计算机科学中非常重要的概念。在Java中,有许多常见的数据结构和算法的实现。其中,数据结构包括数组、链表、栈、队列、跳表、散列表、二叉树、堆、图和Trie树。而算法包括递归、排序、二分查找、哈希算法和字符串匹配算法。\[1\] 在Java中,我们可以使用散列表(哈希表)来实现数据结构。而在字符串匹配算法中,有四种常见的算法:暴力匹配算法(BF算法)、RK算法、BM算法和KMP算法。这些算法都有各自的特点和适用场景。\[2\] 另外,在Java开发中,排序是一种常见的需求。我们可以使用一些常见的排序算法来对数据元素进行排序,比如按照日期对订单进行排序,按照价格对商品进行排序等等。在Java的开发工具包(JDK)中,已经提供了许多数据结构和算法的实现,比如List、Set、Map和Math等。我们可以借鉴JDK的方式,将算法封装到某个类中,并进行API的设计和实现。\[3\] 综上所述,数据结构与算法在Java中有着广泛的应用,通过学习和使用这些概念和实现,我们可以更好地解决问题和优化程序。 #### 引用[.reference_title] - *1* *2* [Java数据结构和算法学习笔记](https://blog.csdn.net/zth13015409853/article/details/121946203)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] - *3* [数据结构与算法(Java篇)笔记--Comparable接口](https://blog.csdn.net/csh1807266489/article/details/126782378)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值