数组(Array)
定义
优势
劣势
应用场景
Java代码部分
链表(Linked List)
定义
优势
劣势
应用场景
Java代码部分
栈(Stack)
定义
特点
优势
劣势
应用场景
Java代码部分(使用栈)
Java代码部分(使用链表)
队列(Queue)
定义
特点
优势
应用场景
Java代码部分(使用数组)
Java代码部分(使用链表)
二叉树(Binary Tree)
定义
特点
优势
劣势
应用场景
Java代码部分
二叉查找树(Binary Search Tree)
定义
特点
优势
劣势
Java代码部分
堆(Heap)
定义
特点
优势
劣势
应用场景
Java部分代码(最大堆)
散列表/哈希表(Hashing)
定义
特点
优势
劣势
常用的Hash数据结构
应用场景
Java代码部分(查找唯一的字符串)
Java部分代码(计算一个字符串的字符)
图(Graph)
定义
特点
优势
劣势
应用场景
Java代码部分
数组是由相同类型的元素(element)的集合所组成的数据结构,分配一块连续的内存来存储。利用元素的索引(index)可以计算出该元素对应的存储地址。
最简单的数据结构类型是一维数组。例如,索引为0到9的32位整数数组,可作为在存储器地址2000,2004,2008,...2036中,存储10个变量,因此索引为i的元素即在存储器中的2000+4×i地址。数组第一个元素的存储器地址称为第一地址或基础地址。
二维数组,对应于数学上的矩阵概念,可表示为二维矩形格
1. 可随机访问数组内容(寻址容易)
2. 易于排序和遍历
3. 可替换多个变量
1. 数组的长度固定
2. 插入与删除难
3. 若数组的长度很长,数组内元素少,则空间被浪费(容量高,占用率低,空间浪费)
4. 需要连续的内存来分配
1. 用于线性方式储存信息
2. 需要频繁搜索的场景
// 数组类型 : 8大基础类型
int[] intArray1, intArray2, intArray3;
byte[] byteArray1;
short[] shortsArray1;
boolean[] booleanArray1;
long[] longArray1;
float[] floatArray1;
double[] doubleArray1;
char[] charArray1;
// 一维数组的创建与初始化
// 创建方式1 : 已知数组长度
intArray1 = new int[20];
// 创建方式2 : 已知数组元素
intArray2 = new int[]{1,2,3,4,5,6,7,8,9,10};
// 或者
intArray2 = {1,2,3,4,5,6,7,8,9,10};
// 创建方式3 : 已知数组长度和元素
intArray3 = new int [5] {1,2,3,4,5};
// 二维数组
// 创建方式1
int intArrayOfArray1[][]={{1,2,3},{4,5,6}};
// 创建方式2
int[][] intArrayOfArray2 = new int[4][2];
intArrayOfArray2[i][j] = x; // 分别赋值x常量
// 创建方式3 : 动态申请第二维数组的长度
int[][] intArrayOfArray3 = new int[5][];//五行的长度
for(int i=0; i < intArrayOfArray3.length; i++){
intArrayOfArray3[i] = new int[i+1]; //列的长度每次都变化。每次都要重新申请空间(长度)
for(int j=0; j < intArrayOfArray3[i].length; j++)
intArrayOfArray3[i][j] = i + j;
}
链表是一种线性表,但是并不会按线性的顺序存储数据,而是在每一个节点里存到下一个节点的指针(Pointer)
1. 链表长度可动态分配
2. 无空间浪费,因为容量和大小总是相等
3. 易于插入和删除,因为只需要进行一次链条操作(将指针链接到新的节点上即可)
4. 高效的内存分配
1. 如果头节点丢失,链表也会丢失(寻址困难)
2. 无法随机访问链表内容
1. 适用于内存有限的地方
2. 适用于需要频繁插入和删除的应用程序
import java.util.*;
// 新建链表 : 节点和下一个节点
class LLNode{
int data;
LLNode next;
LLNode(int data)
{
this.data=data;
this.next=null;
}
}
class Demo{
LLNode head; // 头结点
// 头部插入新的节点
LLNode insertInBeg(int key,LLNode head)
{
LLNode ttmp=new LLNode(key);
if(head==null)
head=ttmp;
else
{
ttmp.next=head;
head=ttmp;
}
return head;
}
// 尾部插入新的节点
LLNode insertInEnd(int key,LLNode head)
{
LLNode ttmp=new LLNode(key);
LLNode ttmp1=head;
if(ttmp1==null)
head=ttmp;
else
{
while(ttmp1.next!=null)
ttmp1=ttmp1.next;
ttmp1.next=ttmp;
}
return head;
}
// 索引插入新的节点
LLNode insertAtPos(int key,int pos,LLNode head)
{
LLNode ttmp=new LLNode(key);
if(pos==1)
{
ttmp.next=head;
head=ttmp;
}
else
{
LLNode ttmp1=head;
for(int i=1;ttmp1!=null && i
栈只允许在有序的线性资料集合的一端(称为堆栈顶端,top)进行加入数据(push)和移除数据(pop)的运算。因而按照后进先出(LIFO, Last In First Out)的原理运作,堆栈常用一维数组或链表来实现
1. 使用Java的线性数据结构
2. 遵循LIFO : 最后进的元素最先出
3. 只有顶部的元素可以被访问
4. 插入和删除都是从顶部开始的。例如:一叠盘子、椅子等。
5. 4个主要操作 : 所有的操作都在恒定时间内进行,即O(1)。
push(ele) - 用来在顶部插入元素
pop() - 从堆栈中删除顶部的元素
isEmpty() - 如果堆栈是空的,返回true
peek() - 获取堆栈的顶部元素
1. 以后进先出的方式维护数据
2. 最后一个元素可以随时使用
3. 所有的操作都是O(1)的复杂性
1. 操作被限制在堆栈的顶部
2. 不太灵活
递归(Recursion)
解析(Parsing)
浏览器(Browser)
编辑器(Editors)
import java.util.*;
class Stack
{
int[] a;
int top;
Stack()
{
a=new int[100];
top=-1;
}
void push(int x)
{
if(top==a.length-1)
System.out.println("overflow");
else
a[++top]=x;
}
int pop()
{
if(top==-1)
{System.out.println("underflow");
return -1;
}
else
return(a[top--]);
}
void display()
{
for(int i=0;i<=top;i++)
System.out.print(a[i]+" ");
System.out.println();
}
boolean isEmpty()
{
if(top==-1)
return true;
else
return false;
}
int peek()
{
if(top==-1)
return -1;
return (a[top]);
}
}
public class Demo
{
public static void main(String args[])
{
Stack s=new Stack();
Scanner in= new Scanner(System.in);
do
{System.out.println("\n******** MENU *******");
System.out.println("\n1.PUSH");
System.out.println("\n2.POP");
System.out.println("\n3.PEEK");
System.out.println("\n4 IS EMPTY");
System.out.println("\n5.EXIT");
System.out.println("\n enter ur choice : ");
switch(in.nextInt())
{
case 1:
System.out.println("\nenter the value ");
s.push(in.nextInt());
break;
case 2:
System.out.println("\n popped element : "+ s.pop());
break;
case 3:
System.out.println("\n top element : "+ s.peek());
break;
case 4: System.out.println("\n is empty : "+ s.isEmpty());
break;
case 5: System.exit(0);
break;
default: System.out.println("\n Wrong Choice!");
break;
}
System.out.println("\n do u want to cont... ");
}while(in.nextInt()==1);
}
}
Output:
******** MENU *******
1.PUSH
2.POP
3.PEEK
4 IS EMPTY
5.EXIT
enter ur choice :
1
enter the value
12
do u want to cont...
1
******** MENU *******
1.PUSH
2.POP
3.PEEK
4 IS EMPTY
5.EXIT
enter ur choice :
1
enter the value
56
do u want to cont...
1
******** MENU *******
1.PUSH
2.POP
3.PEEK
4 IS EMPTY
5.EXIT
enter ur choice :
2
popped element : 56
do u want to cont...
1
******** MENU *******
1.PUSH
2.POP
3.PEEK
4 IS EMPTY
5.EXIT
enter ur choice :
4
is empty : false
do u want to cont...
1
******** MENU *******
1.PUSH
2.POP
3.PEEK
4 IS EMPTY
5.EXIT
enter ur choice :
2
popped element : 12
do u want to cont...
import java.util.*;
class LNode
{
int data;
LNode next;
LNode(int d)
{
data=d;
}
}
class Stack
{
LNode push(int d,LNode head){
LNode tmp1 = new LNode(d);
if(head==null)
head=tmp1;
else
{
tmp1.next=head;
head=tmp1;
}
return head;
}
LNode pop(LNode head){
if(head==null)
System.out.println("underflow");
else
head=head.next;
return head;
}
void display(LNode head){
System.out.println("\n list is : ");
if(head==null){
System.out.println("no LNodes");
return;
}
LNode tmp=head;
while(tmp!=null){
System.out.print(tmp.data+" ");
tmp=tmp.next;
}
}
boolean isEmpty(LNode head)
{
if(head==null)
return true;
else
return false;
}
int peek(LNode head)
{
if(head==null)
return -1;
return head.data;
}
}
public class Demo{
public static void main(String[] args)
{
Stack s=new Stack();
LNode head=null;
Scanner in=new Scanner(System.in);
do
{System.out.println("\n******** MENU *******");
System.out.println("\n1.PUSH");
System.out.println("\n2.POP");
System.out.println("\n3.PEEK");
System.out.println("\n4 IS EMPTY");
System.out.println("\n5 DISPLAY");
System.out.println("\n6.EXIT");
System.out.println("\n enter ur choice : ");
switch(in.nextInt())
{
case 1:
System.out.println("\nenter the value ");
head=s.push(in.nextInt(),head);
break;
case 2:
head=s.pop(head);
break;
case 3:
System.out.println("\n top element : "+ s.peek(head));
break;
case 4:
System.out.println("\n is empty : "+ s.isEmpty(head));
break;
case 5: s.display(head);
break;
case 6: System.exit(0);
break;
default: System.out.println("\n Wrong Choice!");
break;
}
System.out.println("\n do u want to cont... ");
}while(in.nextInt()==1);
}
}
Output
******** MENU *******
1.PUSH
2.POP
3.PEEK
4 IS EMPTY
5 DISPLAY
6.EXIT
enter ur choice :
1
enter the value
12
do u want to cont...
1
******** MENU *******
1.PUSH
2.POP
3.PEEK
4 IS EMPTY
5 DISPLAY
6.EXIT
enter ur choice :
1
enter the value
56
do u want to cont...
1
******** MENU *******
1.PUSH
2.POP
3.PEEK
4 IS EMPTY
5 DISPLAY
6.EXIT
enter ur choice :
5
list is :
56 12
do u want to cont...
1
******** MENU *******
1.PUSH
2.POP
3.PEEK
4 IS EMPTY
5 DISPLAY
6.EXIT
enter ur choice :
3
top element : 56
do u want to cont...
1
******** MENU *******
1.PUSH
2.POP
3.PEEK
4 IS EMPTY
5 DISPLAY
6.EXIT
enter ur choice :
4
is empty : false
do u want to cont...
1
队列是先进先出(FIFO, First-In-First-Out)的线性表。在具体应用中通常用链表或者数组来实现。队列只允许在后端(称为rear)进行插入操作,在前端(称为front)进行删除操作。
1. 线性数据结构
2. 遵循FIFO : 最先进入的元素最先出
3. 插入可以从后端进行。
4. 删除可以从前端进行。例如:售票处的排队,公共汽车站。
5. 4个主要操作 : 所有操作都在恒定时间内进行,即O(1)。
enqueue(ele) - 用来在顶部插入元素
dequeue() - 从队列中删除顶端的元素
peekfirst() - 获得队列的第一个元素
peeklast() - 获得队列中的最后一个元素
1. 以先进先出的方式维护数据
2. 从起点插入和从终点删除需要O(1)时间
1. 调度(Scheduling)
2. 保持播放列表(Maintaining playlist)
3. 中断处理(Interrupt handling)
import java.util.*;
class Queue{
int front;
int rear;
int[] arr;
Queue()
{
front=rear=-1;
arr=new int[10];
}
void enqueue(int a)
{
if(rear==arr.length-1)
System.out.println("overflow");
else
arr[++rear]=a;
if(front==-1)
front++;
}
int dequeue()
{
int x=-1;
if(front==-1)
System.out.println("underflow");
else
x=arr[front++];
if(rear==0)
rear--;
return x;
}
void display()
{
for(int i=front;i<=rear;i++)
System.out.print(arr[i]+" ");
System.out.println();
}
}
public class QueueDemo{
public static void main(String[] args)
{
Queue ob=new Queue();
ob.enqueue(1);
ob.enqueue(2);
ob.enqueue(3);
ob.enqueue(4);
ob.enqueue(5);
ob.display();
ob.dequeue();
ob.display();
}
}
Output:
1 2 3 4 5
2 3 4 5
class LNode{
int data;
LNode next;
LNode(int d)
{
data=d;
}
}
class Queue{
LNode enqueue(LNode head,int a)
{
LNode tmp=new LNode(a);
if(head==null)
head=tmp;
else
{
LNode tmp1=head;
while(tmp1.next!=null)
tmp1=tmp1.next;
tmp1.next=tmp;
}
return head;
}
LNode dequeue(LNode head)
{
if(head==null)
System.out.println("underflow");
else
head=head.next;
return head;
}
void display(LNode head)
{
System.out.println("\n list is : ");
if(head==null){
System.out.println("no LNodes");
return;
}
LNode tmp=head;
while(tmp!=null){
System.out.print(tmp.data+" ");
tmp=tmp.next;
}
}
}
public class QueueDemoLL{
public static void main(String[] args)
{
Queue ob=new Queue();
LNode head=null;
head=ob.enqueue(head,1);
head=ob.enqueue(head,2);
head=ob.enqueue(head,3);
head=ob.enqueue(head,4);
head=ob.enqueue(head,5);
ob.display(head);
head=ob.dequeue(head);
ob.display(head);
}
}
Output
list is :
1 2 3 4 5
list is :
2 3 4 5
二叉树是每个节点最多只有两个分支(即不存在分支度大于2的节点)的树结构。通常分支被称作“左子树”或“右子树”。二叉树的分支具有左右次序,不能随意颠倒。
1. 层次化的数据结构
2. 最上面的元素被称为树根(root)
3. 每个节点在二叉树中最多可以有两个孩子(child)
4. 可以使用索引随机地访问元素。例如:文件系统的层次结构
5. 常见的遍历方法。
前序遍历preorder(从树根root开始) : 打印-左孩子-右孩子
后序遍历postorder(root) : 左孩子-右孩子-打印
中序遍历inorder(root):左孩子-打印-右孩子
1. 可以表示具有某种关系的数据
2. 插入和搜索的效率更高
1. 排序很困难
2. 不太灵活
1. 文件系统的层次结构
2. 二叉树的多种变化具有广泛的应用。
class TLNode
{
int data;
TLNode left,right;
TLNode(int d)
{
data=d;
}
}
public class BinaryTree
{
static void preorder(TLNode r)
{
if(r==null)
return;
System.out.print(r.data+" ");
preorder(r.left);
preorder(r.right);
}
static void inorder(TLNode r)
{
if(r==null)
return;
inorder(r.left);
System.out.print(r.data+" ");
inorder(r.right);
}
static void postorder(TLNode r)
{
if(r==null)
return;
postorder(r.left);
postorder(r.right);
System.out.print(r.data+" ");
}
public static void main(String[] args)
{
TLNode root=new TLNode(1);
root.left=new TLNode(2);
root.right=new TLNode(3);
root.left.left=new TLNode(4);
root.left.right=new TLNode(5);
root.right.left=new TLNode(6);
root.right.right=new TLNode(7);
preorder(root);
System.out.println();
inorder(root);
System.out.println();
postorder(root);
System.out.println();
}
}
Output
1 2 4 5 3 6 7
4 2 5 1 6 3 7
4 5 2 6 7 3 1
二叉查找树是指一棵空树或者具有下列性质的二叉树:
1. 若任意节点的左子树不空,则左子树上所有节点的值均小于它的根节点的值;
2. 若任意节点的右子树不空,则右子树上所有节点的值均大于它的根节点的值;
3. 任意节点的左、右子树也分别为二叉查找树;
1. 具有额外限制的二进制树
2. 限制条件:
左边的子节点必须总是小于根节点
右边的子节点必须总是大于根节点
3. 插入、删除、搜索比二进制树的效率要高得多
1. 保持元素的顺序
2. 可以轻松找到树中的最小和最大节点
3. 按顺序排列,遍历可以得到排序的元素
1. 不可能有随机访问
2. 排序增加了复杂性
class TLNode{
int data;
TLNode left,right;
TLNode(int d)
{
data=d;
}
}
public class BST{
TLNode root;
TLNode insert(int d,TLNode root)
{
if(root==null)
root=new TLNode(d);
else if(d<=root.data)
root.left=insert(d,root.left);
else
root.right=insert(d,root.right);
return root;
}
TLNode search(int d,TLNode root)
{
if(root.data==d)
return root;
else if(d root.data)
root.right = delete(root.right, data);
else
{
if (root.left == null)
return root.right;
else if (root.right == null)
return root.left;
root.data = minValue(root.right);
root.right = delete(root.right, root.data);
}
return root;
}
int minValue(TLNode root)
{
int minv = root.data;
while (root.left != null)
{
minv = root.left.data;
root = root.left;
}
return minv;
}
public static void main(String[] args)
{
BST ob=new BST();
ob.root=ob.insert(50,ob.root);
ob.root=ob.insert(30,ob.root);
ob.root=ob.insert(20,ob.root);
ob.root=ob.insert(20,ob.root);
ob.root=ob.insert(70,ob.root);
ob.root=ob.insert(60,ob.root);
ob.root=ob.insert(80,ob.root);
ob.root=ob.delete(ob.root,50);
System.out.println("******" +ob.root.data);
ob.inorder(ob.root);
TLNode find=ob.search(30,ob.root);
if(find==null)
System.out.println("not found");
else
System.out.println("found : "+find.data);
}
}
Output:
******60
20
20
30
60
70
80
found : 30
堆是一种特别的完全二叉树。若是满足以下特性,即可称为堆:“给定堆中任意节点P和C,若P是C的母节点,那么P的值会小于等于(或大于等于)C的值”。若母节点的值恒小于等于子节点的值,此堆称为最小堆(min heap);反之,若母节点的值恒大于等于子节点的值,此堆称为最大堆(max heap)。在堆中最顶端的那一个节点,称作根节点(root node),根节点本身没有母节点(parent node)。
1. Arr[0]元素将被视为根。
2. length(A) - 数组的大小
3. heapSize(A) - 堆的大小
4. 一般在处理最小和最大元素时使用
5. 对于第i个节点 :
(i-1)/2 | 父节点 |
(2*i)+1 | 左孩子 |
(2*i)+2 | 右孩子 |
1. 可以有2种类型:最小堆和最大堆
2. 最小堆保留最小的元素,顶部和最大堆保留最大的元素
3. 处理最小或最大元素时为O(1)。
1. 不可能有随机访问
2. 只有最小或最大元素可供访问
1. 适用于处理优先权的应用(Suitable for applications dealing with priority)
2. 调度算法(Scheduling algorithm)
3. 缓存(caching)
import java.util.*;
class Heap{
int heapSize;
void build_max_heap(int[] a)
{
heapSize=a.length;
for(int i=(heapSize/2);i>=0;i--)
max_heapify(a,i);
}
void max_heapify(int[] a,int i)
{
int l=2*i+1;
int r=2*i+2;
int largest=i;
if(la[largest])
largest=l;
if(ra[largest])
largest=r;
if(largest!=i)
{
int t=a[i];
a[i]=a[largest];
a[largest]=t;
max_heapify(a,largest);
}
}
//to delete the max element
int extract_max(int[] a)
{
if(heapSize<0)
System.out.println("underflow");
int max=a[0];
a[0]=a[heapSize-1];
heapSize--;
max_heapify(a,0);
return max;
}
void increase_key(int[] a,int i,int key)
{
if(key=0 && a[(i-1)/2]
哈希表是根据键(Key)而直接访问在内存储存位置的数据结构。也就是说,它通过计算出一个键值的函数,将所需查询的数据映射到表中一个位置来让人访问,这加快了查找速度。这个映射函数称做散列函数,存放记录的数组称做散列表。
1. 使用特殊的哈希函数
2. 散列函数将一个元素映射到一个地址进行存储
3. 这提供了恒定的时间访问
4. 任何哈希函数都无法彻底避免碰撞 : 衡量一个哈希函数的好坏的重要指标就是发生碰撞的概率以及发生碰撞的解决方案。以下是几个Hash函数常见的方法 :
直接定址法、数字分析法、除留余数法、分段叠加法、平方取中法、伪随机数法
5. 常见的解决碰撞的方法 :
开放定址法、链地址法、再哈希法、建立公共溢出区
Hash的详解 : HashMap中hash方法的原理-阿里云开发者社区
1. 散列函数有助于在恒定时间内获取元素
2. 是存储元素的有效方法
1. 冲突性质的解决办法增加了复杂性
HashMap(数组+链表+链地址)、HashSet
1. 适用于需要恒定时间获取的应用
import java.util.*;
class HashSetDemo1{
static boolean isUnique(String s)
{
HashSet set =new HashSet();
for(int i=0;i
import java.util.*;
class HashMapDemo
{
static void check(String s)
{
HashMap map=new HashMap();
for(int i=0;i itr = map.keySet().iterator();
while (itr.hasNext()) {
Object x=itr.next();
System.out.println("count of "+x+" : "+map.get(x));
}
}
public static void main(String[] args)
{
String s="hello";
check(s);
}
}
Output
count of e : 1
count of h : 1
count of l : 2
count of o : 1
Java部分代码(查找字符串中特定的字符)
import java.util.*;
class hashTabledemo {
public static void main(String[] arg)
{
// creating a hash table
Hashtable h =
new Hashtable();
Hashtable h1 =
new Hashtable();
h.put(3, "Geeks");
h.put(2, "forGeeks");
h.put(1, "isBest");
// create a clone or shallow copy of hash table h
h1 = (Hashtable)h.clone();
// checking clone h1
System.out.println("values in clone: " + h1);
// clear hash table h
h.clear();
// checking hash table h
System.out.println("after clearing: " + h);
System.out.println("values in clone: " + h1);
}
}
Output
values in clone: {3=Geeks, 2=forGeeks, 1=isBest}
after clearing: {}
values in clone: {3=Geeks, 2=forGeeks, 1=isBest}
图是一种抽象数据类型,用于实现数学中图论的无向图和有向图的概念。
图的数据结构包含一个有限(可能是可变的)的集合作为节点集合,以及一个无序对(对应无向图)或有序对(对应有向图)的集合作为边(有向图中也称作弧)的集合。节点可以是图结构的一部分,也可以是用整数下标或引用表示的外部实体。
图的数据结构还可能包含和每条边相关联的数值(edge value),例如一个标号或一个数值(即权重,weight;表示花费、容量、长度等)。
1. 基本上,它是一组边(group of edges)和节点(vertices)。
2. 图形表示法 : G(V, E); 其中V(G)代表一组顶点,E(G)代表一组边。
3. 图可以是有向或无向的
4. 图可以是连接的,也可以是不连接的
1. 寻找连通性
2. 最短路径
3. 从一个点到另一个点的最小成本
4. 最小生成树
1. 存储图(邻接列表和邻接矩阵)可能会使问题更复杂。
1. 适用于电路网络
2. 适用于像Facebook、LinkedIn等应用
3. 医学科学
import java.util.*;
class Graph
{
int v;
LinkedList adj[];
Graph(int v)
{
this.v=v;
adj=new LinkedList[v];
for(int i=0;i();
}
void addEdge(int u,int v)
{
adj[u].add(v);
}
void BFS(int s)
{
boolean[] visited=new boolean[v];
LinkedList q=new LinkedList();
q.add(s);
visited[s]=true;
while(!q.isEmpty())
{
int x=q.poll();
System.out.print(x+" ");
Iterator itr=adj[x].listIterator();
while(itr.hasNext())
{
int p=itr.next();
if(visited[p]==false)
{
visited[p]=true;
q.add(p);
}
}
}
}
void DFSUtil(int s,boolean[] visited)
{
visited[s]=true;
System.out.println(s);
Iterator itr=adj[s].listIterator();
while(itr.hasNext())
{
int x=itr.next();
if(visited[x]==false)
{
//visited[x]=true;
DFSUtil(x,visited);
}
}
}
void DFS(int s){
boolean visited[]=new boolean[v];
DFSUtil(s,visited);
}
public static void main(String[] args)
{
Graph g=new Graph(4);
g.addEdge(0,1);
g.addEdge(0,2);
g.addEdge(1,2);
g.addEdge(2,0);
g.addEdge(2,3);
g.addEdge(3,3);
g.BFS(2);
g.DFS(2);
}
}
Output:
2 0 3 1 2
0
1
3
参考文章 :
1. Data Structures in Java | Beginners Guide |2023 - Great Learning
2. wikipedia-数据结构