2021-06-15~2021-06-18
工作总结
工作内容
本周的工作内容跟上周类似,所以就不展开说了,周五的时候突然想了解一下Java一些常用的数据结构,所以今天就来浅谈一下Java常用的数据结构。
技术探索
1.浅谈Java数据结构
首先,数据结构是储存数据的一种结构体,在此结构中储存一些数据,而这些数据之间有一定的关系。
各数据元素之间的相互关系,又包括三个组成成分(数据的逻辑结构、数据的存储结构、数据运算结构)。数据结构的设计过程分成抽象层、数据结构层和实现层。
1.1 数组
数组是最常见的一种数据结构,它是相同类型的用一个标识符封装到一起的基本类型数据序列或者对象序列。数组使用一个统一的数组名和不同的下标来唯一确定数组中的元素。实质上,数组是一个简单的线性序列,因此访问速度很快。(数组名代表的是连续空间的首地址,通过首地址可以依次访问数组所有元素,元素在数组中的排序叫做下标从零开始)
优点:
- 存取速度快
缺点:
- 事先必须知道数组的长度
- 插入删除元素很慢
- 空间通常是有限制的
- 需要大块连续的内存块
- 插入删除元素的效率很低
代码/输出结果:
public void Demo(){
//申明一个数组
//1未赋值的
Integer[] arr1=new Integer[10];
//2已赋值
Integer[] arr2={9,8,7,6,5,4,3,2,1,0};
//对为赋值的数据进行赋值
for (int i = 0; i <arr1.length ; i++) {
arr1[i]=i;
}
//输出两个数组
System.out.println("arr1:");
for(Integer info:arr1){
System.out.println(info);
}
System.out.println("arr2:");
for(Integer info:arr2){
System.out.println(info);
}
}
1.2 链表
链表就是将,n个节点离散分配,彼此通过指针 相连,每个节点只有一个前驱节点,每个节点只有一个后续节点,首节点没有前驱节点,尾节点没有后续节点。确定一个链表我们只需要头指针,通过头指针就可以把整个链表都能推出来。一个完整的链条,每个节点包含2部分,数据域,和一个指向下一个节点引用的指针next。例如在删除操作时,链表只需要改变一下前后节点的引用关系即可,就完成了节点的删除。
优点:
- 空间没有限制
- 插入删除元素很快
缺点:
- 含有大量的引用,占用的内存空间大
- 查找元素需要遍历整个链表,耗时,存取速度慢
代码/输出结果:
package datastructure;
public class ListNodeTest<T> {
private int foot; //根节点索引位置
private int count; //代表链表程度
private Node root; //标识根节点
//链接点类,内部方法实现,外部使用
private class Node{
private T data; //数据信息
private Node next; //下一个节点引用
public Node(T data) {
this.data = data;
}
//添加节点
private void add(T data){
if(this.next == null){
this.next = new Node(data); //如果当前节点的next为null,直接创建一个新的节点
}else {
this.next.add(data); //否则进行递归调用,直到最后在某个为空的节点创建一个新节点
}
}
//删除节点1
public void remove(Node previous, int index){
if(ListNodeTest.this.foot++ == index){
previous.next = this.next; //this表示当前要删除的节点
this.next = null;
ListNodeTest.this.count--;
return;
}else{
this.next.remove(this,index); //递归删除
}
}
//删除节点2
public void remove(Node previous, T data){
if(this.data.equals(data)){
previous.next = this.next;
this.next = null;
ListNodeTest.this.count--;
return ;
}else{
if(this.next != null){
this.next.remove(this,data);
}else{
return;
}
}
}
//修改数据 -- 新数据替换旧数据
public void replace(T oldData,T newData){
if(this.data.equals(newData)){
this.data = newData;
}else{
this.next.replace(oldData, newData); //递归修改,寻找当前节点下一个节点,直到某个节点的值匹配入参
}
}
//修改数据 -- 利用索引修改
public void replace(int index,T newData){
if(ListNodeTest.this.foot++ == index){ //找到了某个值的索引和传入的索引相同,直接替换
this.data = newData;
}else{
this.next.replace(index, newData);
}
}
//查询
public T get(int index){
if(ListNodeTest.this.foot++ == index){
return this.data;
}else{
return this.next.get(index);
}
}
//链表是否包含某个节点
public boolean contains(T data){
if(this.data.equals(data)){ //如果当前的这个data正好和传入的data匹配
return true;
}else{
//如果当前的这个不匹配,则需要查找下一个节点
if(this.next == null){
return false;
}else{
return this.next.contains(data);
}
}
}
}
public ListNodeTest() {
}
//检查链表是否为空
public boolean isEmpty(){
if(count == 0 || this.root == null){
return true;
}else{
return false;
}
}
//获取链表的长度
public int size(){
return this.count;
}
//添加
public void add(T data){
if(this.isEmpty()){ //如果链表为空,新建一个节点
this.root = new Node(data);
}else{
this.root.add(data);
}
this.count++;
}
//删除 -- 按照索引删除
public void remove(int index){
if(this.isEmpty()){
return;
}
if(index < 0 || this.count <= index){
return ;
}
if(index == 0){ //想要删除根节点
Node temp = this.root;
this.root = this.root.next;
temp.next = null;
this.count--;
return ;
}else{
this.foot = 0;
this.root.remove(this.root, index);
}
}
//根据传入的数值删除
public void remove(T data){
if(this.isEmpty()){
return;
}
if(this.root.data.equals(data)){ //如果删除的正好是根节点
Node temp = this.root;
this.root = this.root.next;
temp.next = null;
this.count--;
return ;
}else{
this.root.remove(this.root, data);
}
}
//修改 -- 根据索引修改
public void replace(int index,T newData){
if(this.isEmpty()){
return;
}
if(index < 0 || this.count <= index){
return ;
}
this.foot = 0;
this.root.replace(index, newData);
}
//修改 -- 新老数据替换
public void replace(T oldData,T newData){
if(this.isEmpty()){
return;
}
this.root.replace(oldData, newData);
}
//查询 --- 根据索引查找
public T get(int index){
if(this.isEmpty()){
return null;
}
this.foot = 0;
return this.root.get(index);
}
//是否包含
public boolean contains(T data){
if(this.isEmpty()){
return false;
}
return this.root.contains(data);
}
//打印 toArray
public Object[] toArray(){
if(this.isEmpty()){
return null;
}
int count = this.count;
Object[] retVal = new Object[count];
for(int i=0;i<count;i++){
retVal[i] = this.get(i);
}
return retVal;
}
}
public class main {
public static void main(String[] args) {
//链表
ListNodeTest<String> myList = new ListNodeTest<String>();
//第一次追加元素时会新建一个链表的头
myList.add("a");
myList.add("b");
myList.add("c");
myList.add("d");
myList.add("e");
myList.add("f");
//将链表转数组后打印
Object[] objects = myList.toArray();
System.out.println("----原有链表数据----");
for (Object info:objects) {
System.out.print(info+" ");
}
System.out.println();
//通过索引获取元素
System.out.println("链表索引1位置数据:"+myList.get(1));
//删除链表数据
myList.remove(1);
System.out.println("删除后索引1位置数据:"+myList.get(1));
//替换链表数据
myList.replace(1, "y");
System.out.println("替换后索引1位置数据:"+myList.get(1));
System.out.println(myList.get(1));
//将链表转数组后打印
Object[] objects2 = myList.toArray();
System.out.println("----最后链表数据----");
for (Object info:objects2) {
System.out.print(info+" ");
}
}
}
1.3 栈
栈是一个先入后出的有序列表,栈(Stack)是限制线性表中元素的插入和删除只能在同一端进行的一种特殊线性表。允许插入和删除的一端,为变化的一端,称为栈顶(Top),另一端为固定的一端,称为栈底(Bottom)。根据栈的定义可知,最先放入栈中元素在栈底,最后放入的元素在栈顶
而删除元素刚好相反,最后放入的元素最先删除,最先放入的元素最后删除。所以只有最后进入栈的元素才能最先从栈中获取到,故队列又称为先进后出。
结构图(太丑了见谅):
代码/输出结果:
package datastructure;
import java.util.Arrays;
import java.util.EmptyStackException;
public class StackTest {
//用数组实现栈的底层
private Object[] array;
//栈顶
private int top;
//设置栈的大小
private int size=3;
public StackTest(){
array=new Object[size];
top=-1;
}
public String isEmpty(){
return top==-1?"该栈内未存在元素":"该栈不为空";
}
//出栈
public Object pop(){
if(top==-1){
throw new EmptyStackException();
}
return array[top--];
}
//入栈
public Object push(Object element){
if(top==size-1){
array= Arrays.copyOf(array,size*2);
}
array[++top]=element;
return element;
}
//清空栈
public void clear(){
for (int i=0;i<top;i++){
array[i]=null;
}
this.top=-1;
}
}
public class main {
public static void main(String[] args) {
StackTest stackTest = new StackTest();
stackTest.push("a");
stackTest.push("b");
stackTest.push("c");
String pop1 = (String)stackTest.pop();
System.out.println(pop1);
String pop2 = (String)stackTest.pop();
System.out.println(pop2);
stackTest.clear();
System.out.println(stackTest.isEmpty());
}
}
1.4 队列
队列是一种特殊的线性表,特殊之处在于它只允许在表的前端进行删除操作,而在表的后端进行插入操作,和栈一样,队列是一种操作受限制的线性表。进行插入操作的端称为队尾,进行删除操作的端称为队头。队列中没有元素时,称为空队列。队列的数据元素又称为队列元素。在队列中插入一个队列元素称为入队,从队列中删除一个队列元素称为出队。队列只允许在一端插入,在另一端删除,所以只有最早进入队列的元素才能最先从队列中删除,故队列又称为先进先出(FIFO—first in first out)线性表。
结构图(太丑了见谅):
代码/输出结果:
package datastructure;
public class QueueTest<E> {
private Object[] data=null;
private int maxSize; //队列容量
private int front; //队列头,允许删除
private int rear; //队列尾,允许插入
//构造函数
public QueueTest(){
this(5);
}
public QueueTest(int initialSize){
if(initialSize >=0){
this.maxSize = initialSize;
data = new Object[initialSize];
front = rear =0;
}else{
throw new RuntimeException("初始化大小不能小于0:" + initialSize);
}
}
//判空
public boolean empty(){
return rear==front?true:false;
}
//入队
public boolean add(E e){
if(rear== maxSize){
throw new RuntimeException("队列已满,无法插入新的元素!");
}else{
data[rear++]=e;
return true;
}
}
//出队
public E poll(){
if(empty()){
throw new RuntimeException("空队列异常!");
}else{
E value = (E) data[front]; //保留队列的front端的元素的值
data[front++] = null; //释放队列的front端的元素
return value;
}
}
//队列长度
public int length(){
return rear-front;
}
/**
* 遍历队列
* @param QueueTest
*
*/
public static void traverseQueueTest(QueueTest QueueTest) {
// front的位置
System.out.println("----队列----");
int i = QueueTest.front;
while (i != QueueTest.rear) {
System.out.print(QueueTest.data[i]+" ");
//移动front
i = (i + 1) % QueueTest.data.length;
}
System.out.println();
}
}
public class main {
public static void main(String[] args) {
//队列
QueueTest queueTest = new QueueTest();
queueTest.add("a");
queueTest.add("b");
queueTest.add("c");
queueTest.add("d");
QueueTest.traverseQueueTest(queueTest);
String poll = (String)queueTest.poll();
System.out.println("第一个出队列:"+poll);
String poll2 = (String)queueTest.poll();
System.out.println("第二个出队列:"+poll2);
QueueTest.traverseQueueTest(queueTest);
}
}
1.5二叉树
树是一种非线性的数据结构,相对于线性的数据结构(链表、数组)而言,树的平均运行时间更短(往往与树相关的排序时间复杂度都不会高)。
结构图(太丑了见谅):
代码/输出结果:
package datastructure;
public class TreeNode {
/**
* 序号
*/
public int key=0;
/**
* 值
*/
public String data=null;
public boolean isVisted=false;
/**
* 左儿子节点
*/
public TreeNode leftChild=null;
/**
* 右儿子节点
*/
public TreeNode rightChild=null;
/**
* 默认构造方法
*/
public TreeNode(){}
/**
* @param key 层序编码
* @param data 数据域
*/
public TreeNode(int key,String data){
this.key=key;
this.data=data;
this.leftChild=null;
this.rightChild=null;
}
}
package datastructure;
import java.util.Stack;
public class BinaryTreeTest {
private TreeNode root=null;
/**
* 默认构造方法
* 指定根节点
*/
public BinaryTreeTest(){
root=new TreeNode(1,"rootNode(A)");
}
/**
* 创建一棵二叉树
* <pre>
* A
* B C
* D E F
* </pre>
* @param root
* @author WWX
*/
public void createBinTree(TreeNode root){
TreeNode newNodeB = new TreeNode(2,"B");
TreeNode newNodeC = new TreeNode(3,"C");
TreeNode newNodeD = new TreeNode(4,"D");
TreeNode newNodeE = new TreeNode(5,"E");
TreeNode newNodeF = new TreeNode(6,"F");
newNodeC.rightChild = newNodeF;//root.rightChild.rightChild=newNodeF;
newNodeB.leftChild = newNodeD;//root.leftChild.leftChild=newNodeD;
newNodeB.rightChild = newNodeE;//root.leftChild.rightChild=newNodeE;
root.leftChild = newNodeB;
root.rightChild = newNodeC;
}
/**
* 判断跟节点是否为空
* @return 返回根节点是否为空
*/
public boolean isEmpty(){
return root == null;
}
//树的高度
public int height(){
return height(root);
}
//节点个数
public int size(){
return size(root);
}
/**
* 计算二叉树节点的高度
* @param subTree 节点
* @return 节点高度
*/
private int height(TreeNode subTree){
if(subTree == null)
return 0;//递归结束:空树高度为0
else{
int i = height(subTree.leftChild);
int j = height(subTree.rightChild);
return (i < j) ? (j + 1) : (i + 1);
}
}
/**
* 计算节点大小
* @param subTree 节点
* @return 节点大小
*/
private int size(TreeNode subTree) {
if(subTree == null){
return 0;
} else {
return 1 + size(subTree.leftChild) + size(subTree.rightChild);
}
}
//返回双亲结点
public TreeNode parent(TreeNode element){
return (root == null || root == element) ? null : parent(root, element);
}
public TreeNode parent(TreeNode subTree,TreeNode element){
if(subTree == null)
return null;
if(subTree.leftChild == element || subTree.rightChild == element)
//返回父结点地址
return subTree;
TreeNode p;
//现在左子树中找,如果左子树中没有找到,才到右子树去找
if((p = parent(subTree.leftChild, element)) != null)
//递归在左子树中搜索
return p;
else
//递归在右子树中搜索
return parent(subTree.rightChild, element);
}
public TreeNode getLeftChildNode(TreeNode element){
return (element != null) ? element.leftChild : null;
}
public TreeNode getRightChildNode(TreeNode element){
return (element != null) ? element.rightChild : null;
}
public TreeNode getRoot(){
return root;
}
//在释放某个结点时,该结点的左右子树都已经释放,
//所以应该采用后续遍历,当访问某个结点时将该结点的存储空间释放
public void destroy(TreeNode subTree){
//删除根为subTree的子树
if(subTree != null){
//删除左子树
destroy(subTree.leftChild);
//删除右子树
destroy(subTree.rightChild);
//删除根结点
subTree=null;
}
}
public void traverse(TreeNode subTree){
System.out.println("key:" + subTree.key + "--name:" + subTree.data);;
traverse(subTree.leftChild);
traverse(subTree.rightChild);
}
//前序遍历
public void preOrder(TreeNode subTree){
if(subTree != null){
visted(subTree);
preOrder(subTree.leftChild);
preOrder(subTree.rightChild);
}
}
//中序遍历
public void inOrder(TreeNode subTree){
if(subTree != null){
inOrder(subTree.leftChild);
visted(subTree);
inOrder(subTree.rightChild);
}
}
//后续遍历
public void postOrder(TreeNode subTree) {
if (subTree != null) {
postOrder(subTree.leftChild);
postOrder(subTree.rightChild);
visted(subTree);
}
}
//前序遍历的非递归实现
public void nonRecPreOrder(TreeNode p){
Stack<TreeNode> stack=new Stack<TreeNode>();
TreeNode node=p;
while(node!=null||stack.size()>0){
while(node!=null){
visted(node);
stack.push(node);
node=node.leftChild;
}
while (stack.size()>0){
node=stack.pop();
node=node.rightChild;
}
}
}
//中序遍历的非递归实现
public void nonRecInOrder(TreeNode p){
Stack<TreeNode> stack =new Stack<TreeNode>();
TreeNode node =p;
while(node!=null||stack.size()>0){
//存在左子树
while(node!=null){
stack.push(node);
node=node.leftChild;
}
//栈非空
if(stack.size()>0){
node=stack.pop();
visted(node);
node=node.rightChild;
}
}
}
//后序遍历的非递归实现
public void noRecPostOrder(TreeNode p){
Stack<TreeNode> stack=new Stack<TreeNode>();
TreeNode node =p;
while(p!=null){
//左子树入栈
for(;p.leftChild!=null;p=p.leftChild){
stack.push(p);
}
//当前结点无右子树或右子树已经输出
while(p!=null&&(p.rightChild==null||p.rightChild==node)){
visted(p);
//纪录上一个已输出结点
node =p;
if(stack.empty())
return;
p=stack.pop();
}
//处理右子树
stack.push(p);
p=p.rightChild;
}
}
public void visted(TreeNode subTree){
subTree.isVisted=true;
System.out.println("key:"+subTree.key+"--name:"+subTree.data);;
}
}
package datastructure;
public class main {
public static void main(String[] args) {
//二叉树
TreeNode root=new TreeNode(1,"rootNode(A)");
final BinaryTreeTest bt = new BinaryTreeTest();
bt.createBinTree(root);
System.out.println("the size of the tree is " + bt.size());
System.out.println("the height of the tree is " + bt.height());
System.out.println("*******(前序遍历)[ABDECF]遍历*****************");
bt.preOrder(root);
System.out.println("*******(中序遍历)[DBEACF]遍历*****************");
bt.inOrder(root);
System.out.println("*******(后序遍历)[DEBFCA]遍历*****************");
bt.postOrder(root);
System.out.println("***非递归实现****(前序遍历)[ABDECF]遍历*****************");
bt.nonRecPreOrder(root);
System.out.println("***非递归实现****(中序遍历)[DBEACF]遍历*****************");
bt.nonRecInOrder(root);
System.out.println("***非递归实现****(后序遍历)[DEBFCA]遍历*****************");
bt.noRecPostOrder(root);
}
}
1.6堆与栈
堆
堆内存用来存放由new创建的对象和数组。
在堆中分配的内存,由Java虚拟机的自动垃圾回收器来管理。
栈
栈的优势是,存取速度比堆要快,仅次于直接位于CPU中的寄存器。但缺点是,存在栈中的数据大小与生存期必须是确定的,缺乏灵活性。另外,栈数据可以共享。
堆的优势是可以动态地分配内存大小,生存期也不必事先告诉编译器,Java的垃圾收集器会自动收走这些不再使用的数据。但缺点是,由于要在运行时动态分配内存,存取速度较慢。
总结
1、对于不常用的数据结构还是不熟悉,例如二叉树的实现原理以及之后可能会设计的场景目前还没有概念。
2、应多了解底层的数据结构,拓展编程思维,编写更加高效的代码。
脚踏实地,平稳前行--zwx