基本数据结构底层原理——线性存储和链式存储

线性存储

线性存储指的是顺序表,其底层原理通过数组来实现。数组中的元素是有序的,通过下标访问。对于需要频繁访问使用的元素,使用顺序表的效率非常之高。

顺序表的优缺点

优点:

  • 顺序表有序,访问顺序表中的数据很快。

缺点:

  • 由于底层由数组实现,每当分配的空间用完后都需要扩容,而对于未使用的空间,也是一种浪费。

  • 进行插入和删除操作的效率很低,因为可能需要挪动大量的元素。

模拟实现顺序表

public class MyArraylist {

    public int[] elem;
    public int usedSize;//0
    //默认容量
    private static final int DEFAULT_SIZE = 10;

    public MyArraylist() {
        this.elem = new int[DEFAULT_SIZE];
    }

    /**
     * 打印顺序表:
     * 根据usedSize判断即可
     */
    public void display() {
        for (int i = 0; i < this.usedSize; i++) {
            System.out.print(this.elem[i] + " ");
        }
        System.out.println();
    }

    // 新增元素,默认在数组最后新增
    public void add(int data) {
        //判断
        if (isFull()) {
            //扩容
            this.elem = Arrays.copyOf(this.elem, 2 * this.elem.length);
        }
        this.elem[this.usedSize] = data;
        this.usedSize++;
    }

    /**
     * 判断当前的顺序表是不是满的!
     *
     * @return true:满   false代表空
     */
    public boolean isFull() {
        return this.usedSize == this.elem.length;
    }


    private boolean checkPosInAdd(int pos) {
        //判断
        if (pos < 0 || pos > this.usedSize)
            return false;
        return true;//合法
    }

    // 在 pos 位置新增元素
    public void add(int pos, int data) {
        if (checkPosInAdd(pos)) {
            if (isFull()) {
                //扩容
                this.elem = Arrays.copyOf(this.elem, 2 * this.elem.length);
            }
            // 1 2 3 4 5  pos: 2 -> 99
            // 1 2  99 3 4 5
            for (int i = this.usedSize; i > pos; i--) {
                this.elem[i] = this.elem[i - 1];
            }
            this.elem[pos] = data;
            this.usedSize++;
        }

    }

    // 判定是否包含某个元素
    public boolean contains(int toFind) {
        for (int i = 0; i < this.usedSize; i++) {
            if (this.elem[i] == toFind) {
                return true;
            }
        }
        return false;
    }

    // 查找某个元素对应的位置
    public int indexOf(int toFind) {
        for (int i = 0; i < this.usedSize; i++) {
            if (this.elem[i] == toFind) {
                return i;
            }
        }
        return -1;
    }

    private void checkIndex(int pos) {
        if (pos < 0 || pos >= this.usedSize) {
            //该异常需要自定义
            throw new IndexOutOfException("位置输入不合法!");
        }
    }

    // 获取 pos 位置的元素
    public int get(int pos) {
        //判断
        try {
            checkIndex(pos);
        } catch (IndexOutOfException e) {
            e.printStackTrace();
        }
        return this.elem[pos];
    }

    private boolean isEmpty() {
        if (this.usedSize == 0) {
            return true;
        }
        return false;
    }

    // 给 pos 位置的元素设为【更新为】 value
    public void set(int pos, int value) {
        //判断
        try {
            checkIndex(pos);
        } catch (IndexOutOfException e) {
            e.printStackTrace();
        }
        this.elem[pos] = value;
    }

    /**
     * 删除第一次出现的关键字key
     *
     * @param key
     */
    public void remove(int key) {
        //查找位置
        int index = indexOf(key);
        if (index == -1) {
            System.out.println("没有找到这个数据!");
            return;
        }
        for (int i = index; i < this.usedSize - 1; i++) {
            this.elem[i] = this.elem[i + 1];
        }
        this.usedSize--;
    }

    // 获取顺序表长度
    public int size() {
        return this.usedSize;
    }

    // 清空顺序表
    public void clear() {
        this.usedSize = 0;
    }

}

链式存储

链式存储主要指链表,链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。

链表有一个个结点连接而成,每个结点都存储着一定的信息,它可以包含存放的数据信息,下一个结点的地址和上一个结点的地址。通过这些地址可以访问到下一个结点,从而实现链式访问。

根据链表中结点所包含的信息不同,链表可以是单向或者双向,可以是循环或者非循环。除此之外,链表可以带头或者不带头。因此链表可以分为8种,其中,以单向不带头非循环链表和双向不带头非循环链表为主。

链表的优缺点

优点:

链表不需要初始化,直接添加元素即可。

对于增删操作,只需更改前一个结点和后一个结点的指向即可。

缺点:

查找元素只能通过遍历来实现,耗时较多。对于单链表,不能后退,如果必须得到该结点和上一个结点,必须在遍历到上一个结点时记录下来。

链表的模拟实现(单链表)

// 1、无头单向非循环链表实现
public class SingleLinkedList {
    static class ListNode {
        public int val;
        public ListNode next;

        public ListNode() {
        }

        public ListNode(int val) {
            this.val = val;
        }

        public ListNode(int val, ListNode next) {
            this.val = val;
            this.next = next;
        }
    }
    public ListNode head;

    //头插法
    public void addFirst(int data) {
        ListNode node = new ListNode(data);
        node.next = head;
        head = node;
    }
    //尾插法
    public void addLast(int data) {
        ListNode node = new ListNode(data);
        if (head == null) {
            head = node;
            return;
        }
        ListNode cur = head;
        while (cur.next != null) {
            cur = cur.next;
        }
        cur.next = node;
    }
    private Boolean checkIndex(int index) {
        return index >= 0 && index <= size();
    }
    private ListNode findIndex(int index) {
        ListNode cur = head;
        int count = 0;
        while (count < index - 1) {
            cur = cur.next;
            count++;
        }
        return cur;
    }
    //任意位置插入,第一个数据节点为0号下标
    public boolean addIndex(int index,int data) {
        if (!checkIndex(index)) {
            return false;
        }
        if (index == 0) {
            addFirst(data);
            return true;
        }else if (index == size()) {
            addLast(data);
            return true;
        }
        ListNode cur = findIndex(index);
        ListNode node = new ListNode(data);
        node.next = cur.next;
        cur.next = node;
        return true;
    }
    //查找是否包含关键字key是否在单链表当中
    public boolean contains(int key) {
        ListNode cur = head;
        while (cur != null) {
            if (cur.val == key) {
                return true;
            }
            cur = cur.next;
        }
        return false;
    }
    private ListNode findPrevNode(int key) {
        ListNode cur = head;
        while (cur.next != null) {
            if (cur.next.val == key) {
                return cur;
            }
            cur = cur.next;
        }
        return null;
    }
    //删除第一次出现关键字为key的节点
    public void remove(int key) {
        if (head == null) {
            return;
        }
        if (head.val == key) {
            head = head.next;
            return;
        }
        ListNode cur = findPrevNode(key);
        if (cur == null) {
            return;
        }
        cur.next = cur.next.next;
    }

    //删除所有值为key的节点
    public void removeAllKey(int key) {
        if (head == null) {
            return;
        }
        ListNode p = head;
        ListNode q = head.next;
        while (q != null) {
            if (q.val == key) {
                p.next = q.next;
            }else {
                p = q;
            }
            q = q.next;
        }
        if (head.val == key) {
            head = head.next;
        }
    }
    //得到单链表的长度
    public int size() {
        int count = 0;
        ListNode cur = head;
        while (cur != null) {
            cur = cur.next;
            count++;
        }
        return count;
    }
    public void display() {
        ListNode cur = head;
        while (cur != null) {
            System.out.print(cur.val + " ");
            cur = cur.next;
        }
        System.out.println();
    }
    public void display(ListNode head) {
        ListNode cur = head;
        while (cur != null) {
            System.out.print(cur.val + " ");
            cur = cur.next;
        }
        System.out.println();
    }

        public void clear() {
        head = null;
    }
}

Java的数据结构题一般只涉及到单链表,而Java底层实现的链表是双向链表。

栈和队列

栈和队列底层由顺序表实现,栈和队列大体上是相似的,都具有以下特点:

  • 增删操作都在栈顶(队头)

  • 每次增删或者访问都只能操作一个元素。

二者的区别在于:

栈的逻辑是“先进后出“,而队列是”先进先出“;

栈的实现就像是往箱子里放东西,最先放进去的东西,等到要拿出来的时候,总是最后一个拿出来。

而队列的实现就像去食堂排队打饭,只有排你前面的人都打好饭才能轮到你。

栈和队列具有的功能较少,栈的功能有:push()->入栈,pop()->出栈,peek()->查看栈顶元素,empty()->判空,search->查找等,队列功能有:offer()->入队列,poll()->出队列,peek->查看队头元素等。

当然,除了普通队列以外,还存在双端队列Deque,双端队列不局限于先进先出,它可以在队头或者队尾进,也可以在队头或者队尾出,没有限制。

栈和队列的实现不仅仅可以通过顺序表,也可以通过链表实现,也就是所谓的链式栈和链式队列。而实际上,Java底层自己实现的链表中就包含了栈和队列的所有方法,也就是说链表不单单是链表,它也可以是栈,也可以是队列。

当然,为了方便识别所创建的链表是普通的链表,还是栈或者队列,通常需要更改变量名。

栈和队列有着相似的特点,因此栈可以模拟实现队列,队列也可以模拟实现栈:

用队列实现栈

用栈实现队列

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值