数据结构、手写双向非循环链表

本文详细介绍了数据结构在软件开发中的重要性,包括顺序存储、链式存储、索引存储和散列存储等存储分类,以及线性结构、树形结构和图状结构的逻辑分类。重点剖析了数组、链表、栈和队列等典型数据结构,并对比了ArrayList和LinkedList的性能差异。
摘要由CSDN通过智能技术生成

1.数据结构

1.引入

数据是软件开发的核心。在软件开发过程中通常都是对数据新增删除修改查看的操作。

如何对数据进行合理的存储,如何有效的提升数据操作的效率,都是软件开发过程中的重中之重,所以使用合适的数据结构是非常重要的。

2.简介

数据结构(Data Structure):计算机存储数据、操作数据的方式。这些数据按照特定结构组成一个集合。

数据结构是一种宏观概念,里面包含多种具体的数据结构。

 每一种数据结构都有着它们自己的优点、又有着它们自己的缺点。

3.分类

数据结构可以按照存储结构进行划分,也可以从逻辑上进行分类。

1.存储分类

数据存储结构分类是按照数据在计算机中存储的方式进行分类的,主要分为:顺序存储结构链式存储结构、索引存储结构、散列存储结构。

1.顺序存储结构

数据存储到一块连续的空间。Java中数组就是顺序存储结构的一种。

优点:地址连续,遍历效率高。

缺点:中间插入或删除数据时效率低。

2.链式存储结构

数据存储到非连续空间中。每个数据存储后面数据的地址,形成链式结构。

优点:插入和删除数据效率相对较高。

缺点:遍历效率低。丧失随机访问能力。

3.索引存储结构

除了存储的数据外,额外还有索引表存储数据的索引信息。MySQL数据库、MongoDB等存储工具都支持索引。

4.散列表(Hash)存储结构

散列表存储结构是对数据做计算,直接得到数据存储时内存地址。使用顺序结构和链式结构共同实现。

2.逻辑分类

数据结构从逻辑上分为:线性结构、树形结构、图状结构。

逻辑分类产生的数据结构,只是程序员认为的结构,存储到计算机中还是按照存储分类进行存储的。

逻辑分类按照前一个数据和后一个数据数量对应关系进行的分类。

1.线性结构

数据和数据之间定义了线性关系。除首位和尾位不相连以外,里面内容都是有顺序的。而且是1对1的关系,也就是说一个数据的后面只能跟一个数据,一个数据的前面只能有一个数据。

数组、链表、栈、队列都是线性结构的表现。

2.树形结构

数据之间具有层次关系。数据是1对多的关系,也就是说一个数据后面可以有多个数据,这个数据前面只能有一个数据。

树形结构有很多种:普通树、二叉树、二叉搜索树、平衡二叉搜索树、红黑树、B树等。

3.图形结构

数据之间是网状关系。数据是多对多的关系,也就是说一个数据后面可以有多个数据,这个数据前面也可以有多个数据。

2.数据结构-数组回顾

1.介绍

Java中数组是对线性结构,也是顺序存储结构的具体实现。

Java中数组会在内存中开辟大小固定,地址连续的空间,数组中的数据具有从前往后的顺序。

2.特点

1.长度固定,创建完成后,长度不允许改变。

2.在堆内存中的地址为连续的;

3.存储相同类型的数据

4.0<=下标<数组长度

5.遍历效率高,可以随机访问

6.中间删除,插入元素效率低。

3.数据结构-链表

1.介绍

1.链表的存储分类为链式结构,非连续的空间。

2.链表第一个节点称为头节点,最后一个节点称为尾节点。

2.分类

1.单向链表

        如果前一个结点存储了后一个节点的地址,后一个节点没有存储前一个节点的位置这种链表称为单向链表。

 2.双向链表

如果前一个节点存储了后一个节点地址,后一个节点也存储前一个节点地址,这种链表称为双向链表。

 3.循环链表

 4.总结

链表分类:单向非循环链表、单向循环链表、双向非循环链表、双向循环链表

3.链表中概念强调

1.只包含一个节点

 2.特点

1. 存储空间不连续。

2. 中间新增、删除元素效率高于数组。

3. 遍历效率低,丧失随机访问的能力。

4.小节实战案例 - 手写双向非循环链表

非循环双向链表:

  1. 首尾不相连。

  2. 每个节点需要存储上个节点和下个节点的地址, 头节点上个节点地址为null, 尾节点下个节点地址为null。

优势:

链表优势在于中间的添加和删除效率高。

劣势:

链表不具备随机访问的能力。所以每次获取元素的值都从头或从尾一个一个找,可以判断要查询元素的索引值,如果要查询索引值超过最大索引的一半,从后往前找。如果没有超过最大索引的一半,从前往后找。

添加节点的方式:

  1. 头插法: 新添加的节点作为头节点。

  2. 尾插法: 新添加的节点作为尾节点。

1.添加数据(尾插)

先定义List接口

public interface List {
    //1.添加
    boolean add(Object o);

    //2.获取元素
    Object get(int index);

    //3.获取个数
    int size();

    //4.修改
    boolean set(Object o, int index);

    //5.删除,根据第几个删除
    boolean remove(int index);

    //6.删除,根据元素值删除
    boolean remove(Object o);
}

 实现:

public class LinkedList implements List{
    private int count;
    private Node head;
    private Node tail;

    @Override
    public boolean add(Object o) {
        return addLast(o);
    }
    //尾插
    public boolean addLast(Object o){
        try {
            Node oldNode=this.tail;
            if (oldNode!=null){
                Node newNode=new Node(oldNode,o,null);

                this.tail=newNode;
                oldNode.next=newNode;
            }else {
                Node newNode=new Node(null,o,null);
                this.tail=newNode;
                this.head=newNode;
            }
            count++;
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }
    
    
    public void checkIndex(int index){
        if (index<0||index>count-1){
            throw new RuntimeException("节点个数出现问题,节点数范围:0 ~ " + (count - 1));
        }
    }
    private class Node {
        Node pre;
        Object iteam;
        Node next;

        public Node(Node pre, Object iteam, Node next) {
            this.pre = pre;
            this.iteam = iteam;
            this.next = next;
        }
    }
}

2. 添加元素(头插)

public boolean addFirst(Object o){
        try {
            Node oldNode=this.head;
            if (oldNode!=null){
                Node newNode=new Node(null,o,oldNode);
                this.head=newNode;
                oldNode.pre=newNode;
            }else {
                Node newNode=new Node(null,o,null);
                this.head=newNode;
                this.tail=newNode;
            }
            count++;
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

3. 获取元素

// //获取节点中的元素值(节点从0开始计算)
    @Override
    public Object get(int index) {
        Node node=getNode(index);
        return node.iteam;
    }
    public Node getNode(int index){
        checkIndex(index);
        if (index<(count/2)){
            Node node=this.head;
            for (int i = 0; i < index; i++) {
                node=node.next;
            }
            return node;
        }else {
            Node node=this.tail;
            for (int i = index; i <count-1; i++) {
                node=node.pre;
            }
            return node;
        }
    }

4. 获取元素个数

自定义异常:

 public void checkIndex(int index){
        if (index<0||index>count-1){
            throw new RuntimeException("节点个数出现问题,节点数范围:0 ~ " + (count - 1));
        }
    }

 实现:

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

5.修改元素

@Override
    public boolean set(Object o, int index) {
        checkIndex(index);
        try {
            Node node=getNode(index);
            node.iteam=o;
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }


    }

6.删除元素

//通过下标删除
    @Override
    public boolean remove(int index) {
        checkIndex(index);
        Node node=getNode(index);
        Node next=node.next;
        Node pre=node.pre;
        try {
            if (node.pre==null&&node.next==null){
                this.head=null;
                this.tail=null;
                node=null;
            } else if (node.pre==null){
                this.head=next;
                next.pre=null;
                node=null;

            } else if (node.next==null) {
                this.tail=pre;
                pre.next=null;
                node=null;
            }else {
                pre.next=next;
                next.pre=pre;
                node=null;
            }
            count--;
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }
    //通过元素删除
    @Override
    public boolean remove(Object o) {
        for (int i = 0; i < count; i++) {
            if (get(i).equals(o)){
                Node node=getNode(i);
                Node pre=node.pre;
                Node next=node.next;
                if (pre==null&&next==null){
                    this.head=null;
                    this.tail=null;
                    node=null;
                } else if (pre==null) {
                    this.head=next;
                    next.pre=null;
                    node=null;
                } else if (next==null) {
                    this.tail=pre;
                    pre.next=null;
                    node=null;
                }else {
                    pre.next=next;
                    next.pre=pre;
                    node=null;
                }
                return true;
            }
        }
        return false;
    }

5.ArrayList和LinkedList

1.对比

ArrayList和LinkedList 存储的数据都是有序可重复的。

remove():

ArrayList: 删除元素后需要将删除元素后面所有的元素依次向前移动。

LinkedList: 修改节点中其它节点的引用。

get():

ArrayList: 直接通过索引值,取出数组指定脚标内容。

LinkedList: 需要通过头|尾节点,遍历寻找获取的元素。

add():

ArrayList: 直接按照下标添加到数组中,可能出现扩容。

LinkedList: 头插,尾插。

set():

修改区别不大。

中间插入:

ArrayList: 插入后把后面所有元素都后移,可能出现扩容现象。

LinkedList: 插入后修改前后节点指向。

2.结论

ArrayList 适合做查询(使用下标进行查询)。

LinkedList适合做插入、删除。

6.数据结构-栈(Stack)

1.介绍

栈(Stack)是一种存储受限的线性结构。其具体实现可以用链表或数组。

栈只允许从一侧进行操作数据,这侧称为栈顶(top)。另一侧称为栈底(bottom)。

往栈中放入元素的过程称为:入栈(push)。

从栈中删除元素的过程称为:出栈(pop)。

由于只允许一侧添加或删除数据,所以栈中数据满足:先进后出

 2.特点

1.先进后出

2.一侧操作数据

7.数据结构-队列

1.介绍

队列(queue)简称队,是一种运算受限的线性表,其限制是仅允许在一端进行插入,而在另一端进行删除(取出)。

在队列中把插入数据元素的一端称为 队尾(rear),删除数据元素的一端称为队首(front)。

向队尾插入元素称为进队或入队,新元素入队后成为新的队尾元素;从队列中删除元素称为离队或出队,元素出队后,其后续元素成为新的队首元素。

由于队列的插入和删除操作分别在队尾和队首进行,每个元素必然按照进入的次序离队,也就是说先进队的元素必然先离队,所以称队列为 先进先出表(First In First Out,简称FIFO)。

1.双端队列deque

double ended queue 通常读为"deck"

所谓双端队列是指两端都可以进行进队和出队操作的队列,如下图所示,将队列的两端分别称为前端和后端,两端都可以入队和出队。其元素的逻辑结构仍是线性结构

 输出受限的双端队列,即一个端点允许插入和删除,另一个端点只允许插入的双端队列。

 输入受限的双端队列,即一个端点允许插入和删除,另一个端点只允许删除的双端队列。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值