常见的数据结构可分为「线性数据结构」与「非线性数据结构」,具体为:「数组」、「链表」、「栈」、「队列」、「树」、「图」、「散列表」、「堆」。
数组
数组是将相同类型的元素存储于连续内存空间的数据结构,其长度不可变。
如下图所示,构建此数组需要在初始化时给定长度,并对数组每个索引元素赋值,代码如下:
public static void array() {
// 初始化一个长度为5的数组 array
int[] array=new int[5];
//元素赋值
array[0]=2;
array[1]=2;
array[2]=2;
array[3]=2;
array[4]=2;
}
「可变数组」是经常使用的数据结构,其基于数组和扩容机制实现,相比普通数组更加灵活。常用操作有:访问元素、添加元素、删除元素。
public static void arrayLen(){
// 初始化可变数组
List<Integer> array=new ArrayList<>();
//向尾部添加元素
array.add(2);
array.add(2);
array.add(2);
array.add(2);
array.add(2);
}
链表
// 链表
public void linkedList(){
//实例化节点
ListNode n1=new ListNode(4);
ListNode n2=new ListNode(5);
ListNode n3=new ListNode(1);
//构建引用指向
n1.next=n2;
n2.next=n3;
}
class ListNode{
int val; //节点值
ListNode next;//后继节点引用
ListNode(int x){val=x;}
}
栈
栈是一种具有 「先入后出」 特点的抽象数据结构,可使用数组或链表实现。
public void stack(){
Stack<Integer> stack=new Stack<>();
stack.push(1);//元素1 入栈
stack.push(2);//元素2 入栈
stack.pop(); //出栈--->元素2
stack.pop();//出栈---->元素1
//TODO 注意:通常情况下,不推荐使用Java的Vector以及其子类Stack,而一般将LinkedList作为栈来使用。
}
如下图所示,通过常用操作「入栈 push()」,「出栈 pop()」,展示了栈的先入后出特性。
注意:通常情况下,不推荐使用 Java 的 Vector 以及其子类 Stack ,而一般将 LinkedList 作为栈来使用。
public void stackflag(){
LinkedList<Integer> stack=new LinkedList<>();
stack.addLast(1); //元素1 入栈
stack.addLast(2); //元素2 入栈
stack.removeLast(); //出栈 -->元素2
stack.removeLast();// 出栈--->元素1
}
队列
队列是一种具有 「先入先出」 特点的抽象数据结构,可使用链表实现。
public void queue(){
// 队列是一种具有【先入先出】特点的抽象数据结构,可用链表实现
Queue<Integer> queue=new LinkedList<>();
queue.offer(1);//元素1入队
queue.offer(2);//元素2入队
queue.poll();//出队-->元素1
queue.poll();//出队-->元素2
}
树
树是一种非线性数据结构,根据子节点数量可分为 「二叉树」 和 「多叉树」,最顶层的节点称为「根节点 root」。以二叉树为例,每个节点包含三个成员变量:「值 val」、「左子节点 left」、「右子节点 right」 。
class TreeNode{
int val; //节点值
TreeNode left;//左子节点
TreeNode right;//右子节点
TreeNode(int x){val=x;}
}
public void treeNode(){
//初始化节点
TreeNode n1=new TreeNode(3); //根节点root
TreeNode n2=new TreeNode(4);
TreeNode n3=new TreeNode(5);
TreeNode n4=new TreeNode(1);
TreeNode n5=new TreeNode(2);
//构建引用指向
n1.left=n2;
n1.right=n3;
n2.left=n4;
n2.right=n5;
}
如下图所示,建立此二叉树需要实例化每个节点,并构建各节点的引用指向。
图
图是一种非线性数据结构,由「节点(顶点)vertex」和「边 edge」组成,每条边连接一对顶点。根据边的方向有无,图可分为「有向图」和「无向图」。本文 以无向图为例 开展介绍。
如下图所示,此无向图的 顶点 和 边 集合分别为:
顶点集合: vertices = {1, 2, 3, 4, 5}
边集合: edges = {(1, 2), (1, 3), (1, 4), (1, 5), (2, 4), (3, 5), (4, 5)}
表示图的方法通常有两种:
邻接矩阵: 使用数组 verticesvertices 存储顶点,邻接矩阵 edgesedges 存储边; edges[i][j]edges[i][j] 代表节点 i + 1i+1 和 节点 j + 1j+1 之间是否有边。
public void figure(){
//领接矩阵
int[] vertices={1,2,3,4,5};
int[][]edges={{0,1,1,1,1},
{1,0,0,1,0},
{1,0,0,0,1},
{1,1,0,0,1},
{1,0,1,1,0}};
}
邻接表: 使用数组 verticesvertices 存储顶点,邻接表 edgesedges 存储边。 edgesedges 为一个二维容器,第一维 ii 代表顶点索引,第二维 edges[i]edges[i] 存储此顶点对应的边集和;例如 edges[0] = [1, 2, 3, 4]edges[0]=[1,2,3,4] 代表 vertices[0]vertices[0] 的边集合为 [1, 2, 3, 4][1,2,3,4] 。
public void figure2(){
//邻接表
int[] vertices={1,2,3,4,5};
List<List<Integer>>edges=new ArrayList<>();
List<Integer> edge_1=new ArrayList<>(Arrays.asList(1,2,3,4));
List<Integer> edge_2=new ArrayList<>(Arrays.asList(0,3));
List<Integer> edge_3=new ArrayList<>(Arrays.asList(0,4));
List<Integer> edge_4=new ArrayList<>(Arrays.asList(0,1,4));
List<Integer> edge_5=new ArrayList<>(Arrays.asList(0,2,3));
edges.add(edge_1);
edges.add(edge_2);
edges.add(edge_3);
edges.add(edge_4);
edges.add(edge_5);
}
邻接矩阵 VS 邻接表 :
邻接矩阵的大小只与节点数量有关,即 N2
,其中 N 为节点数量。因此,当边数量明显少于节点数量时,使用邻接矩阵存储图会造成较大的内存浪费。
因此,邻接表 适合存储稀疏图(顶点较多、边较少); 邻接矩阵 适合存储稠密图(顶点较少、边较多)。
散列表
散列表是一种非线性数据结构,通过利用 Hash 函数将指定的「键 key」映射至对应的「值 value」,以实现高效的元素查找。
设想一个简单场景:小力、小特、小扣的学号分别为 10001, 10002, 10003 。
现需求从「姓名」查找「学号」
public void simpleHash(){
String[] names={"小力","小特","小扣"};
System.out.println(names[hash(10001)]);
System.out.println(names[hash(10002)]);
System.out.println(names[hash(10003)]);
}
int hash(int id){
int index=(id-1)%10000;
return index;
}
堆:
堆是一种基于「完全二叉树」的数据结构,可使用数组实现。以堆为原理的排序算法称为「堆排序」,基于堆实现的数据结构为「优先队列」。堆分为「大顶堆」和「小顶堆」,大(小)顶堆:任意节点的值不大于(小于)其父节点的值。
完全二叉树定义: 设二叉树深度为 kk ,若二叉树除第 kk 层外的其它各层(第 11 至 k-1k−1 层)的节点达到最大个数,且处于第 kk 层的节点都连续集中在最左边,则称此二叉树为完全二叉树。
如下图所示,为包含 1, 4, 2, 6, 8 元素的小顶堆。将堆(完全二叉树)中的结点按层编号,即可映射到右边的数组存储形式。
public void heap(){
//初始化小顶堆
Queue<Integer> heap=new PriorityQueue<>();
//元素入堆
heap.add(1);
heap.add(4);
heap.add(2);
heap.add(6);
heap.add(8);
//元素出堆(从小到大)
heap.poll();//->1
heap.poll();//->2
heap.poll();//->4
heap.poll();//->6
heap.poll();//->8
}
所有代码
import java.util.*;
/**
* @program: mydemo
* @description: this is a class
* @author: Mr.zeng
* @create: 2021-03-04 10:44
**/
public class DataStructure1 {
public static void array() {
// 初始化一个长度为5的数组 array
int[] array=new int[5];
//元素赋值
array[0]=2;
array[1]=2;
array[2]=2;
array[3]=2;
array[4]=2;
}
public static void arrayLen(){
// 初始化可变数组
List<Integer> array=new ArrayList<>();
//向尾部添加元素
array.add(2);
array.add(2);
array.add(2);
array.add(2);
array.add(2);
}
// 链表
public void linkedList(){
//实例化节点
ListNode n1=new ListNode(4);
ListNode n2=new ListNode(5);
ListNode n3=new ListNode(1);
//构建引用指向
n1.next=n2;
n2.next=n3;
}
class ListNode{
int val; //节点值
ListNode next;//后继节点引用
ListNode(int x){val=x;}
}
public void stack(){
Stack<Integer> stack=new Stack<>();
stack.push(1);//元素1 入栈
stack.push(2);//元素2 入栈
stack.pop(); //出栈--->元素2
stack.pop();//出栈---->元素1
//TODO 注意:通常情况下,不推荐使用Java的Vector以及其子类Stack,而一般将LinkedList作为栈来使用。
}
public void stackflag(){
LinkedList<Integer> stack=new LinkedList<>();
stack.addLast(1); //元素1 入栈
stack.addLast(2); //元素2 入栈
stack.removeLast(); //出栈 -->元素2
stack.removeLast();// 出栈--->元素1
}
public void queue(){
// 队列是一种具有【先入先出】特点的抽象数据结构,可用链表实现
Queue<Integer> queue=new LinkedList<>();
queue.offer(1);//元素1入队
queue.offer(2);//元素2入队
queue.poll();//出队-->元素1
queue.poll();//出队-->元素2
}
class TreeNode{
int val; //节点值
TreeNode left;//左子节点
TreeNode right;//右子节点
TreeNode(int x){val=x;}
}
public void treeNode(){
//初始化节点
TreeNode n1=new TreeNode(3); //根节点root
TreeNode n2=new TreeNode(4);
TreeNode n3=new TreeNode(5);
TreeNode n4=new TreeNode(1);
TreeNode n5=new TreeNode(2);
//构建引用指向
n1.left=n2;
n1.right=n3;
n2.left=n4;
n2.right=n5;
}
// 表示图的方法通常有两种
public void figure(){
//领接矩阵
int[] vertices={1,2,3,4,5};
int[][]edges={{0,1,1,1,1},
{1,0,0,1,0},
{1,0,0,0,1},
{1,1,0,0,1},
{1,0,1,1,0}};
}
public void figure2(){
//邻接表
int[] vertices={1,2,3,4,5};
List<List<Integer>>edges=new ArrayList<>();
List<Integer> edge_1=new ArrayList<>(Arrays.asList(1,2,3,4));
List<Integer> edge_2=new ArrayList<>(Arrays.asList(0,3));
List<Integer> edge_3=new ArrayList<>(Arrays.asList(0,4));
List<Integer> edge_4=new ArrayList<>(Arrays.asList(0,1,4));
List<Integer> edge_5=new ArrayList<>(Arrays.asList(0,2,3));
edges.add(edge_1);
edges.add(edge_2);
edges.add(edge_3);
edges.add(edge_4);
edges.add(edge_5);
}
//邻接矩阵 VS 邻接表:
//邻接矩阵的大小只与节点数量有关,即N^ ,其中N为节点数量。因此,当边数量明显少于节点数量时,使用邻接矩阵存储图会造成较大的内存浪费
public void hashTable(){
//初始化散列表
Map<String,Integer> dic=new HashMap<>();
//添加key->value 键值对
dic.put("小力",10001);
dic.put("小特",10002);
dic.put("小扣",10003);
//从姓名查找学号
dic.get("小力");//->10001
dic.get("小特");//->10002
dic.get("小扣");//->10003
}
public void simpleHash(){
String[] names={"小力","小特","小扣"};
System.out.println(names[hash(10001)]);
System.out.println(names[hash(10002)]);
System.out.println(names[hash(10003)]);
}
int hash(int id){
int index=(id-1)%10000;
return index;
}
public void heap(){
//初始化小顶堆
Queue<Integer> heap=new PriorityQueue<>();
//元素入堆
heap.add(1);
heap.add(4);
heap.add(2);
heap.add(6);
heap.add(8);
//元素出堆(从小到大)
heap.poll();//->1
heap.poll();//->2
heap.poll();//->4
heap.poll();//->6
heap.poll();//->8
}
public static void main(String[] args) {
new DataStructure1().simpleHash();;
}
}