Java笔记16——Java常见数据结构

Java学习——Java常见数据结构


一、数据结构

数据存储的常用结构有:栈、队列、数组、链表和二叉树。

(1). 栈

栈:stack,又称堆栈, 栈(stack)是限定仅在表尾进行插入和删除操作的线性表。我们把允许插入和删除的一端称为栈顶,另一端称为栈底,不含任何数据元素的栈称为空栈。栈又称为先进后出的线性表。

简单的说:采用该结构的集合,对元素的存取有如下的特点
先进后出(即,存进去的元素,要在后它后面的元素依次取出后,才能取出该元素)。例如,子弹压进弹夹,先压进去的子弹在下面,后压进去的子弹在上面,当开枪时,先弹出上面的子弹,然后才能弹出下面的子弹。

栈的入口、出口的都是栈的顶端位置。

进栈与出栈过程图(先进后出):
在这里插入图片描述
压栈:存元素。
弹栈:取元素。

(2). 队列

队列:queue,简称队, 队列是一种特殊的线性表,是运算受到限制的一种线性表,只允许在表的一端进行插入,而在另一端进行删除元素的线性表。
队尾(rear)是允许插入的一端。队头(front)是允许删除的一端。空队列是不含元素的空表。

简单的说,采用该结构的集合,对元素的存取有如下的特点:

  • 先进先出(即,存进去的元素,要在后它前面的元素依次取出后,才能取出该元素)。例如,小火车过山洞,车头先进去,车尾后进去;车头先出来,车尾后出来。
  • 队列的入口、出口各占一侧。

例如,下图中的左侧为入口,右侧为出口(先进先出):
在这里插入图片描述

(3). 数组

数组:Array,是有序的元素序列,数组是在内存中开辟一段连续的空间,并在此空间存放元素。就像是一排出租屋,有100个房间,从001到100每个房间都有固定编号,通过编号就可以快速找到租房子的人。

简单的说,采用该结构的集合,对元素的存取有如下的特点:

查找元素快:通过索引,可以快速访问指定位置的元素

增删元素慢

  • 指定索引位置增加元素:需要创建一个新数组,将指定新元素存储在指定索引位置,再把原数组元素根据索引,复制到新数组对应索引的位置。
  • 指定索引位置删除元素:需要创建一个新数组,把原数组元素根据索引,复制到新数组对应索引的位置,原数组中指定索引位置元素不复制到新数组中。

在这里插入图片描述

数组常用方法:引用
在这里插入图片描述

(4). 链表

链表 [Linked List]:链表是由一组不必相连(不必相连:可以连续也可以不连续)的内存结构(节点),按特定的顺序链接在一起的抽象数据类型。

补充: 抽象数据类型(Abstract Data Type [ADT]):表示数学中抽象出来的一些操作的集合。
内存结构:内存中的结构,如:struct、特殊内存块…等等之类;


数组和链表的区别和优缺点:

  • 数组是一种连续存储线性结构,元素类型相同,大小相等

数组的优点:

  • 存取速度快

数组的缺点:

  1. 事先必须知道数组的长度
  2. 插入删除元素很慢
  3. 空间通常是有限制的
  4. 需要大块连续的内存块
  5. 插入删除元素的效率很低

链表是离散存储线性结构

  • n 个节点离散分配,彼此通过指针相连,每个节点只有一个前驱节点,每个节点只有一个后续节点,首节点没有前驱节点,尾节点没有后续节点。

链表优点:

  1. 空间没有限制
  2. 插入删除元素很快

链表缺点:

  • 存取速度很慢

链表常用的有 3 类: 单链表、双向链表、循环链表。

链表的核心操作集有 3 种:插入、删除、查找(遍历)

3类链表结构总图:
在这里插入图片描述

单链表

单链表 [Linked List]:由各个内存结构通过一个 Next 指针链接在一起组成,每一个内存结构都存在后继内存结构(链尾除外),内存结构由数据域和 Next 指针域组成。

单链表实现图示:
在这里插入图片描述
解析:
Data 数据 + Next 指针,组成一个单链表的内存结构 ;
第一个内存结构称为 链头,最后一个内存结构称为 链尾
链尾的 Next 指针设置为 NULL [指向空];
单链表的遍历方向单一(只能从链头一直遍历到链尾)。

单链表操作图:
在这里插入图片描述
在这里插入图片描述

双向链表

双向链表 [Double Linked List]:由各个内存结构通过指针 Next 和指针 Prev 链接在一起组成,每一个内存结构都存在前驱内存结构和后继内存结构(链头没有前驱,链尾没有后继),内存结构由数据域、Prev 指针域和 Next 指针域组成。

双向链表实现图示:
在这里插入图片描述
解析:
Data 数据 + Next 指针 + Prev 指针,组成一个双向链表的内存结构;
第一个内存结构称为链头,最后一个内存结构称为链尾;
链头的 Prev 指针设置为 NULL, 链尾的 Next 指针设置为 NULL
Prev 指向的内存结构称为 前驱, Next 指向的内存结构称为 后继;
双向链表的遍历是双向的,即如果把从链头的 Next 一直到链尾的[NULL] 遍历方向定义为正向,那么从链尾的 Prev 一直到链头 [NULL ]遍历方向就是反向

双向链表操作图:
在这里插入图片描述

循环链表

单向循环链表 [Circular Linked List] : 由各个内存结构通过一个指针 Next 链接在一起组成,每一个内存结构都存在后继内存结构,内存结构由数据域和 Next 指针域组成。

双向循环链表 [Double Circular Linked List] : 由各个内存结构通过指针 Next 和指针Prev 链接在一起组成,每一个内存结构都存在前驱内存结构和后继内存结构,内存结构由数据域、Prev 指针域和 Next 指针域组成。

循环链表的单向与双向实现图示:
在这里插入图片描述
解析:
循环链表分为单向、双向两种;
单向的实现就是在单链表的基础上,把链尾的 Next 指针直接指向链头,形成一个闭环;
双向的实现就是在双向链表的基础上,把链尾的 Next 指针指向链头,再把链头的 Prev 指针指向链尾,形成一个闭环;
循环链表没有链头和链尾的说法,因为是闭环的,所以每一个内存结构都可以充当链头和链尾。

循环链表操作图:

在这里插入图片描述

(5). 二叉树

二叉树是树的一种,每个节点最多可具有两个子树,即结点的度最大为 2(结点度:结点拥有的子树数)。
例如:
在这里插入图片描述
树的一些概念:
在这里插入图片描述

二叉树就是每个节点不能多于有两个儿子,上面的图就是一颗二叉树。
二叉树:二叉查找树(binary search tree)。
定义:当前根节点的左边全部比根节点小,当前根节点的右边全部比根节点大。
可以看出,这对我们来找一个数是非常方便快捷的。
一棵树至少会有一个节点(根节点)

树由节点组成,每个节点的数据结构是这样的:
在这里插入图片描述
因此,我们定义树的时候往往是 ->定义节点->节点连接起来就成了树 ,而节点的定义就是:一个数据、两个指针(如果有节点就指向节点、没有节点就指向 null)

二叉树的种类

斜树

所有结点都只有左子树,或者右子树。

左斜树:
在这里插入图片描述

满二叉树

所有的分支节点都具有左右节点。

满二叉树:
在这里插入图片描述

完全二叉树

若设二叉树的深度为 h,除第 h 层外,其它各层 (1~h-1) 的结点数都达到最大个数,第 h 层所有的结点都连续集中在最左边,这就是完全二叉树。

完全二叉树与非完全二叉树:
在这里插入图片描述

二叉树的一些性质
  1. 二叉树第 i 层上的结点数目最多为 2^(i-1) (i≥1)
  2. 深度为 h 的二叉树至多有 2^h-1 个结点(h≥1)
  3. 包含 n 个结点的二叉树的高度至少为 log2 (n+1)
  4. 在任意一棵二叉树中,若终端结点的个数为 n0,度为 2 的结点数为 n2,则 n0=n2+1。
二叉树的遍历方式

二叉树的遍历方式,一般分为先序遍历,中序遍历,后序遍历。
先序遍历

  • 先访问根节点,然后访问左节点,最后访问右节点(根->左->右)

中序遍历

  • 先访问左节点,然后访问根节点,最后访问右节点(左->根->右)

后序遍历

  • 先访问左节点,然后访问右节点,最后访问根节点(左->右->根)

在这里插入图片描述
图中二叉树的遍历:

先序遍历(根-左-右): 1-2-4-8-9-5-10-3-6-7

中序遍历:(左-根-右): 8-4-9-2-10-5-1-6-3-7

后序遍历(左-右-根): 8-9-4-10-5-2-6-7-3-1

二、单向链表与二叉树的java实现

(1). 单向链表

/*
* 单向链表节点
* */
public class Node {
    private Integer data;   //结点存放的数据
    private Node next;  //next指针

    public Node(Integer data) {
        this.data = data;
    }

    public Integer getData() {
        return data;
    }

    public void setData(Integer data) {
        this.data = data;
    }

    public Node getNext() {
        return next;
    }

    public void setNext(Node next) {
        this.next = next;
    }
}
/*
* 单向链表
* */
public class SingleLinkedList {
    private Node head;  //头结点
    private int size;   //链表长度

    public SingleLinkedList() {
    }

    //添加一个数据节点
    public boolean add(Integer data){
        Node node = new Node(data);
        if (head == null){  //如果头节点为空
            head = node;    //将该节点 设置为头节点
        }else {
            //从头节点开始遍历,知道查找到最后一个不为空的节点
            Node temp = head;
            while (temp.getNext() != null){
                temp = temp.getNext();
            }
            temp.setNext(node); //添加至最后一个节点的下一个节点
        }
        size++;
        return true;
    }

    //根据下标插入节点
    public boolean add(Integer data,int index){
        if (index > size){  //如果输入的下标超出链表长度,产生越界
            throw new RuntimeException("越界异常!(IndexOutOfBundException),  index:" + index + "此时链表长度:" + size);
        }
        Node node = new Node(data);
        if(index == 0){
            node.setNext(head); //将该节点添加到头节点之前
            head = node;    //将当前节点设为头节点
        }else{
            Node temp = head;
            for (int i = 0;i < index-1;i++){
                temp = temp.getNext();
            }
            //节点插入操作
            node.setNext(temp.getNext());   //将添加节点的下一个节点设置为index位置的下一个节点
            temp.setNext(node); //将index节点的前一个节点 设置为index位置的上一个节点
        }
        size++;
        return true;
    }

    //遍历并输出节点
    public void print(){
        Node node = head;
        while(node != null){
            System.out.println(node.getData() + " ");
            node = node.getNext();
        }
    }

    //默认删除链表最后一个节点
    public Integer remove(){
        if (head == null) {  //如果头节点都为空
            throw new RuntimeException("链表为空,不能进行删除操作!");
        }
        if (head.getNext() == null){ //头节点的下一个节点为空时,删除头节点
            Integer data = head.getData();
            size--; //链表长度减1
            head = null;
            return data;
        }

        //通过断开最后节点的联系从而达到删除最后一个节点的目的
        Node tFast = head;
        Node tLow = null;
        while (tFast.getNext() != null){
            tLow = tFast;
            tFast = tFast.getNext();
        }
        size--;
        tLow.setNext(null);
        return tFast.getData();
    }

    //通过下标查找并删除该节点
    public Integer remove(Integer index){
        if (index >= size) {  //删除的下标大于链表长度时
            throw new RuntimeException("越界异常!(IndexOutOfBundException),  index:" + index + "此时链表长度:" + size);
        }
        if (index == 0){ //头节点的下一个节点为空时,删除头节点
            Integer data = head.getData();
            size--;
            head = null;
            return data;
        }

        //根据下标查找删除节点
        Node tFast = head;
        Node tLow = null;
        for (int i = 0;i < index;i++){
            tLow = tFast;
            tFast = tFast.getNext();
        }
        tLow.setNext(tFast.getNext());  //将index的上一个节点与index的下一个节点关联
        tFast.setNext(null);    //将该index节点断开与下个节点的联系
        size--;
        return tFast.getData();
    }

    public Integer get(Integer index){
        if(index >= size || index < 0){
            throw new RuntimeException("越界异常!(IndexOutOfBundException),  index:" + index + "此时链表长度:" + size);
        }
        Node node = head;
        for (int i =0;i < index;i++) {
            node = node.getNext();
        }
        return node.getData();
    }

    public Node getHead() {
        return head;
    }

    public void setHead(Node head) {
        this.head = head;
    }

    public int getSize() {
        return size;
    }
}

我们编写一个测试类进行测试:

public class MainTest {
    public static void main(String[] args) {
        SingleLinkedList list = new SingleLinkedList();
        list.add(1);
        list.add(2);
        list.add(3);
        list.add(4);
        list.add(6,4);
        System.out.println("输出现在链表存有的所有数据");
        list.print();
        list.remove(3);
        System.out.println("--------remove操作后-------");
        list.print();
        System.out.println("链表长度:" + list.getSize());
        System.out.println("-------输出下标为2的结点的数据---------");
        System.out.println(list.get(2));
    }
}

结果:
在这里插入图片描述

(2).二叉树

/*
* 二叉树
*
* */
public class TreeNode {
    private Integer data;   //数据
    private TreeNode left;  //左节点
    private TreeNode right; //右节点

    public TreeNode(Integer data) {
        this.data = data;
    }

    public Integer getData() {
        return data;
    }

    public void setData(Integer data) {
        this.data = data;
    }

    public TreeNode getLeft() {
        return left;
    }

    public void setLeft(TreeNode left) {
        this.left = left;
    }

    public TreeNode getRight() {
        return right;
    }

    public void setRight(TreeNode right) {
        this.right = right;
    }
}
/*
* 二叉排序树
* */
public class BinarySortTree {
    private TreeNode root;  //根节点

    /**
     * 添加数据
    */
    public boolean add(Integer data){
        if (root == null){  //当二叉树根结点为空时
            this.root = new TreeNode(data); //设为根结点
        }else {
            TreeNode temp = root;
            TreeNode parentNode = null;
            while (temp != null){   //通过循环找出添加数据的位置
                parentNode = temp;  //parentNode代指当前结点temp的父节点
                //分情况判断新进结点置于何处
                if(data < temp.getData()) {  //当添加数据比根结点的小,那就进入根结点的左边
                    temp = temp.getLeft();  //此时的结点为该结点的左节点(进入左结点当中比较)
                    if (temp == null) { //当该结点为空时,说明没有可比较对象,直接在此处新建结点
                        parentNode.setLeft(new TreeNode(data));
                        return true;
                    }
                }else { //当添加数据不小于根结点的数据时 ,那就进入根结点的右边
                    temp = temp.getRight();
                    if (temp == null) {
                        parentNode.setRight(new TreeNode(data));
                        return true;
                    }
                }
            }
        }
        return false;
    }

    /**
     * 根据key的数值查找数据
     * @return
     */
    public TreeNode get(Integer key){
        TreeNode temp = root;
        while(temp != null){
            if(temp.getData() > key){   //当查找数据小于于该结点数据时
                temp = temp.getLeft();  //结点向左遍历
            }else if(temp.getData() < key){ //当查找数据大于该结点数据时
                temp = temp.getRight(); //结点向右遍历
            }else {
                return temp;    //查找成功,返回temp对象
            }
        }return null;
    }

    public TreeNode getRoot() {
        return root;
    }

    public void setRoot(TreeNode root) {
        this.root = root;
    }
}

我们将二叉树的三种遍历方式写在了MainTest.java中

import java.util.LinkedList;
import java.util.Queue;

public class MainTest {
    public static void main(String[] args) {
        TreeNode root = new TreeNode(1);
        TreeNode node2 = new TreeNode(2);
        TreeNode node3 = new TreeNode(3);
        TreeNode node4 = new TreeNode(4);
        TreeNode node5 = new TreeNode(5);
        TreeNode node6 = new TreeNode(6);
        TreeNode node7 = new TreeNode(7);

        root.setLeft(node2);
        root.setRight(node3);
        node2.setLeft(node4);
        node2.setRight(node5);
        node3.setLeft(node6);
        node3.setRight(node7);

        System.out.println("--------广度遍历--------");
        printByLayer(root);
        System.out.println("--------深度遍历--------");
        System.out.println("先序遍历:");
        pre_print(root);
        System.out.println("中序遍历:");
        mid_print(root);
        System.out.println("后序遍历:");
        last_print(root);
    }

/*
* 二叉树 深度遍历
*
* */

    //先序遍历: 根-左-右
    public static void pre_print(TreeNode root){
        if (root != null) { //当当前结点不为空时,停止遍历
            //根结点
            System.out.println(root.getData());
            //左结点
            if (root.getLeft() != null) {
                pre_print(root.getLeft());  //将该结点设为根节点递归调用,(利用递归实现先序遍历的实现方法)
            }
            //右节点
            if (root.getRight() != null) {
                pre_print(root.getRight());
            }
        }
    }

    //中序遍历: 左-根-右
    public static void mid_print(TreeNode root) {
        if (root != null) {
            //输出左孩子
            if (root.getLeft() != null) {    //同理,利用递归
                mid_print(root.getLeft());
            }
            //输出根节点
            System.out.println(root.getData());
            //输出右孩子
            if (root.getRight() != null) {
                mid_print(root.getRight());
            }
        }
    }

    //后序遍历: 左-右-根
    public static void last_print(TreeNode root){
        if (root != null) {
            //输出左孩子
            if (root.getLeft() != null) {    //同理,利用递归
                last_print(root.getLeft());
            }
            //输出右孩子
            if (root.getRight() != null) {
                last_print(root.getRight());
            }
            //输出根节点
            System.out.println(root.getData());
        }
    }

/*
* 二叉树广度遍历,按层遍历
*
* */
    public static void printByLayer(TreeNode root){
        if(root == null){
            return ;
        }
        //创建一个队列 存储树结点
        Queue<TreeNode> queue = new LinkedList<>();
        queue.add(root);    //先存入根结点
        while (!queue.isEmpty()){
            //将队列的数据弹出,存入树结点中
            TreeNode temp = queue.poll();
            System.out.println(temp.getData());
            //管理该结点的左右孩子,按照先进先出顺序存入队列
            if(temp.getLeft() != null){
                queue.add(temp.getLeft());
            }if (temp.getRight() != null){
                queue.add(temp.getRight());
            }
        }
    }
}

MainTest.java运行结果:
在这里插入图片描述

再写一个测试类Test.java对二叉树TreeNode.java类进行测试:

//import MainTest.java中的中序遍历方法

public class Test {
    public static void main(String[] args) {
        BinarySortTree tree = new BinarySortTree();
        tree.add(14);
        tree.add(25);
        tree.add(10);
        tree.add(18);
        tree.add(1);
        tree.add(15);
        tree.add(29);
        mid_print(tree.getRoot());  //调用中序遍历输出二叉树
        TreeNode node = tree.get(18);
        if (node != null){
            System.out.println("查找成功:" + node.getData());
        }else
            System.out.println("查找失败!");
    }
}

总结

常见数据结构介绍及解析 ,( ̄︶ ̄)↗ !!!
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值