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.小节实战案例 - 手写双向非循环链表
非循环双向链表:
-
首尾不相连。
-
每个节点需要存储上个节点和下个节点的地址, 头节点上个节点地址为null, 尾节点下个节点地址为null。
优势:
链表优势在于中间的添加和删除效率高。
劣势:
链表不具备随机访问的能力。所以每次获取元素的值都从头或从尾一个一个找,可以判断要查询元素的索引值,如果要查询索引值超过最大索引的一半,从后往前找。如果没有超过最大索引的一半,从前往后找。
添加节点的方式:
-
头插法: 新添加的节点作为头节点。
-
尾插法: 新添加的节点作为尾节点。
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"
所谓双端队列是指两端都可以进行进队和出队操作的队列,如下图所示,将队列的两端分别称为前端和后端,两端都可以入队和出队。其元素的逻辑结构仍是线性结构
输出受限的双端队列,即一个端点允许插入和删除,另一个端点只允许插入的双端队列。
输入受限的双端队列,即一个端点允许插入和删除,另一个端点只允许删除的双端队列。