最近复习面试题一些技术,发现许多都牵扯到这几种数据结构。所以了解它们是必不可少的,这里做一个学习总结的笔记分享一下。
···································································
数组
数组(Array)是一种 线性表 数据结构,它用一组 连续的内存空间 存储一组具有 相同类型 的数据。
线性表
线性表就是数据排列成一条先一样的结构,每个线性表上的数据最多只有前和后两个方向。
连续的内存空间和相同类型的数据类型
因为这两个限制,数组才能可以随机访问。但是同时数据组的删除和插入等操作变的低效,需要保证连续性而做的大量的数据搬移工作。
数组的应用
用作构建其他数据结构的基础,例如数组列表,堆,哈希表,向量和矩阵。
用于不同的排序算法,例如插入排序,快速排序,冒泡排序和合并排序。
详细参考:数据结构之数组
···································································
链表
链表是一种顺序结构,由相互链接的线性顺序项目序列组成。还是物理存储结构上非连续、非顺序的存储结构。
链表主要有:
1、无头单向非循环链表
2、带头循环单链表
3、不带头双向循环链表
链表的特性
- 链表中的元素称为节点。
- 每个节点都包含一个密钥和一个指向其后继节点(称为next)的指针。
- 名为head的属性指向链接列表的第一个元素。
- 链表的最后一个元素称为尾。
链表实现
public class Node {
public int value;
public Node next;
Node(int value) {
this.value = value;
this.next = null;
}
}
private Node head;
MyLinkedList() {
this.head = null;
}
链表的应用
用于编译器设计中的符号表管理。
用于在使用Alt Tab(使用循环链表实现)的程序之间进行切换。
详情参考:数据结构之链表
···································································
栈
栈是一种物理结构,它存储了一组线性元素,这组元素在操作上有后进先出的特性。另外堆栈只能在一端(称为栈顶(top))对数据项进行插入和删除。
ps:新手可能可能困惑堆栈和栈有啥区别?其实没啥区别…
堆是堆(heap),栈是栈(stack),堆栈是栈。“堆栈”这种叫法,容易让新人掉坑里。
栈的两个基本操作:入栈(push) 出栈(pop)
栈的实现
public class myStack{
private Object[] arr;
private int top;
//无参构造方法
public myStack(){
this.arr = new Object[10];
this.top = -1;
}
//有参构造方法
public myStack(int maxSize){
this.arr = new Object[maxSize];
this.top = -1;
}
//入栈
public void push(Object v){
arr[++top] = v;
}
//出栈
public Object pop(){
return arr[top--];
}
//查看栈顶元素
public Object peek(){
return arr[top];
}
//判断栈是否为空
public boolean isEmpty(){
return (top == -1);
}
}
栈的应用
用于表达式评估(例如:用于解析和评估数学表达式的调车场算法)。
用于在递归编程中实现函数调用。
队列
栈介绍完毕之后,肯定与之相对的 队列。像栈一样,队列也是一种线性表。它允许在表的一端插入数据,在另一端删除元素。插入元素的这一端称之为队尾。删除元素的这一端我们称之为队首。
)
图上描述了队列入队和出队的方式;
队列的实现
public class myQueue{
private Object[] arr;//存放队列元素的数组
private int elements;//队列元素数量
private int front;//队头元素在数组中的索引
private int rear;//队尾元素在数组中的索引
//无参构造方法
public myQueue(){
arr = new Object[10];
elements = 0;
front = 0;
rear = -1;
}
//有参构造方法
public myQueue(int maxSize){
arr = new Object[maxSize];
elements = 0;
front = 0;
rear = -1;
}
//判断队列是否满了
public boolean isFull(){
return (elements == arr.length);
}
//判断队列是否为空
public boolean isEmpty(){
return (elements==0);
}
//查看队首元素
public Object peek() {
return arr[front];
}
}
队列的插入我们需要考虑一下几种情况。
1.队列已经填充满了
2.因为队列在数组中是连续的,如果队列的元素在数组中最后,需要将元素从队首到队尾移到数组中第一位,也就是将后面的位置空出来(参考下图)。
//插入数据到队尾
public void enQueue(Object v){
//判断队列是否满了
if(isFull()) {
System.out.printf("队列已满无法插入")
}
//判断队列是否元素在最后位置
if(elements > 1&& rear == arr.length -1){
for(int i =0;i < elements;i++,front++){
arr[i]=arr[front];
}
front=0;
rear = elements-1;
}
//其他情况正常插入
arr[rear++]=v;
elements++;
}
队列的移除我们需要考虑一下几种情况。
1.队列是空队列
2.队列仅剩一个元素
public Object deQueue(){
if(isEmpty()){
return null;
}
Obeject value = arr[front++];
if(elements == 1){
front=0;
rear=-1;
elements=0;
}else{
elements --;
}
return value;
}
队列的特性
在队尾插入元素,在队首删除元素。
FIFO(先进先出),就向排队取票一样。
参考博客:数据结构:队列的原理和实现
哈希表
哈希表就是根据一个函数H,根据这个函数和查找关键字key,可以直接确定查找值所在位置。
函数H就是哈希函数,它根据key计算出应该存储地址的位置,而哈希表是基于哈希函数建立的一种查找表。
一张图让你更加直观的了解哈希表
哈希表的应用
用于实现数据库索引。
用于实现关联数组。
用于实现"设置"数据结构。
要想创建一个哈希表,还需要考虑哈希函数的选取,以及遇到哈希冲突的解决方式。关于哈希函数构造以及哈希冲突的解决大家可以参考下方链接的博客。
数据结构 Hash表(哈希表)
树
树是一种层次结构,其中数据按层次进行组织并链接在一起。此结构与链接列表不同,而在链接列表中,项目以线性顺序链接。
在过去的几十年中,已经开发出各种类型的树木,以适合某些应用并满足某些限制。一些示例是二叉搜索树,B树,红黑树,展开树,AVL树和n元树。
这里我就只大概介绍二叉树
每个节点最多含有两个子树的树称为二叉树。(我们一般在书中试题中见到的树是二叉树,但并不意味着所有的树都是二叉树。)
在二叉树的概念下又衍生出满二叉树和完全二叉树的概念
满二叉树:除最后一层无任何子节点外,每一层上的所有结点都有两个子结点。也可以这样理解,除叶子结点外的所有结点均有两个子结点。节点数达到最大值,所有叶子结点必须在同一层上
完全二叉树:若设二叉树的深度为h,除第 h 层外,其它各层 (1~(h-1)层) 的结点数都达到最大个数,第h层所有的结点都连续集中在最左边,这就是完全二叉树。
二叉树的实现(简易版)
private static class TreeNode{
int val;
TreeNode left;
TreeNode right;
TreeNode(int x){
val = x;
}
}
二叉树的遍历
先序遍历:先根节点->遍历左子树->遍历右子树
中序遍历:遍历左子树->根节点->遍历右子树
后序遍历:遍历左子树->遍历右子树->根节点
树的应用
二叉树:用于实现表达式解析器和表达式求解器。
二进制搜索树:用于许多不断输入和输出数据的搜索应用程序中。
堆:由JVM(Java虚拟机)用来存储Java对象。
Trap:用于无线网络
详细参考博客:数据结构之树
堆
堆是二叉树的一种特殊情况,其中将父节点与其子节点的值进行比较,并对其进行相应排列。
堆分为两种:最大堆和最小堆,两者的差别在于节点的排序方式。
堆的实现(最大堆)
//因为堆中元素需要比较,所以泛型E实现Comparable接口
public class maxHeap <E extends Comparable<E>>{
private Array<E> value;
public maxHeap(){
value = new Array<E>();
}
//size是数组的初始化大小
public maxHeap(int size){
value = new Array<E>(size);
}
//获得堆中的元素个数
public int getSize() {
return data.getSize();
}
//判断堆中是否为空
public boolean isEmpty() {
return data.isEmpty();
}
}
一个数组实现堆,不使用指针的情况下怎么判断父节点和子节点呢?
其实节点在数组中的位置index 和它的父节点以及子节点的索引之间有一个映射关系。
如果i是节点的索引,那么下列公式就能计算出该节点的父节点以及左右子节点。
public int parent(int i){
if(i == 0){
throw new IllegalArgumentException("该节点为根节点");
}
return (i-1)/2;
}
public int leftChild(int i){
return 2*i+1;
}
public int rightChild(int i){
return 2*i+2;
}
还有堆得上浮下沉删除,插入等操作就不一一总结了。
堆的应用
可以实现优先级队列,以为能通过堆属性进行优先级排序
可以在o(log n)时间实现队列功能。
用于查找定数组中k个最小(或最大)的值
用于堆排序算法
详细参考博客:数据结构:堆(Heap)
图
图是由顶点以及连接各个顶点之间的边组成的一种数据结构。
图分为有向图和无向图
图的存储结构:邻接矩阵,邻接表,十字链表
图的实现(邻接矩阵)
public class Graph{
private ArrayList<String> vertexList;//储存顶点集合
private int[][] edges;//储存邻接矩阵
private int numOfEdges;//边的数量
//显示矩阵
public void show(){
for(int[] link:edges){
Syste.out.println(Arrays.toString(link));
}
}
//得到边数
public int getNumOfEdges(){
return this.numOfEdges;
}
//返回节点对应的数据 0 ->"A" 1->"B" 2->"C"
public String getValueByIndex(int i){
return vertexList.get(i);
}
//初始化
public Graph(int n){
edges = new int[n][n];
vertexList = new ArrayList<String>(n);
numOfEdges = 0;
}
//插入顶点
public void insertVertex(String vertex){
vertexList.add(vertex);
}
//添加边
public void insertEdge(int v1.int v2 ,int weight){
edges[v1][v2] = weight;
edges[v2][v1] = weight;
numOfEdges++;
}
}
图的遍历
图的遍历分为深度优先遍历和广度优先遍历。
图的应用
用于表示社交媒体网络。每个用户都是一个顶点,并且在用户连接时会创建一条边。
用于表示搜索引擎的网页和链接。互联网上的网页通过超链接相互链接。每页是一个顶点,两页之间的超链接是一条边。用于Google中的页面排名。
用于表示GPS中的位置和路线。位置是顶点,连接位置的路线是边。用于计算两个位置之间的最短路径。
详细参考:数据结构之图