Java基础19- 数据结构:【物理结构与逻辑结构】

数据结构:物理结构与逻辑结构

1、数据的存储:
(1)变量
简单类型的变量:
int a = 10;
String str = “hello”;
复杂类型的变量:
int[] arr = {1,2,3,4};

(2)数组:只管数据
存储一组数据
这组数据可以是基本数据类型,也可以是对象。
int[] arr = {1,2,3,4};
Student[] arr = new Student[3];
arr[0] = new Student(“张三”,23);

数组的特点:
①长度是固定的,若要改变长度要用代码改变
②需要额外的变量来记录数组的有效元的个数,例如:total
对于程序员来说:
如果要扩容等,或者要维护元素的个数,都需要大量的工作。

**(3)其他容器:**集合
每一种集合都有自己的特点,适用于特定的类型

2、数据存储的物理结构
(1)连续的存储空间:数组
标表示距离首地址几个间隔的位置

元素是相邻的,在内存中需要开辟的连续的存储空间
缺点:内存比较吃紧时,找一整块空间不好找
优点:访问速度比较快,因为有了首地址,然后根据下标,直接就可以找到对应的元素

在这里插入图片描述

(2)非连续的存储空间:链式,既管数据,又管地址
元素不一定是相邻的,在内存中不需要开辟的连续的存储空间
缺点:访问速度相对数组比较慢,要从头,挨个遍历
优点:不要求空间连续,可以插空

链式中:
线性:链表
单向链表
在这里插入图片描述
双向链表:
在这里插入图片描述单向链表和双向链表找节点时都需要从头开始往后找或者从后往前找

非线性:树
二叉树:
在这里插入图片描述

分为线性和非线性:
线性:数组、链表、队列(数组或链表实现)、栈(数组或链表实现)
非线性:树(二叉树等)、图…

数据结构:
数组、单向链表、双向链表、二叉树、队列、栈、图…

数据的存储结构会影响程序的性能。
数据结构也是算法的基础。

单链表节点:
class Node{
Object data;
Node next;//下一个节点
}

双链表节点:
class Node{
Node pre;//前节点
Object data;
Node next;//后节点
}

二叉树节点:
class TreeNode{
TreeNode parent;父节点
Object data;
TreeNode left;//左节点
TreeNode right;//右节点
}

手动实现动态数组

数据结构:
逻辑结构:针对程序员使用角度来说
物理结构:针对程序员如何实现这个逻辑结构的底层的结构

1、动态数组
物理结构:数组
逻辑结构:动态数组
对于使用者来说,可以自动扩容,自动记录有效元素的个数…

例如:
假设没有Arraylist,自己设计一个

MyArrayList我们自己设计的一种数据结构,一种逻辑结构,当别人用我这个MyArrayList的对象时,就是一个容器对象,
可以用来装对象。

package com.atguigu.test02;

import java.util.Arrays;
public class MyArrayList {
	//为什么使用Object,因为只是说这个容器是用来装对象的,但是不知道用来装什么对象。
	private Object[] data;
	private int total;
	
	public MyArrayList(){
		data = new Object[5];
	}
	
	//添加一个元素
	public void add(Object obj){
		//检查是否需要扩容
		checkCapacity();
		data[total++] = obj;
	}

	private void checkCapacity() {
		//如果data满了,就扩容为原来的2倍
		if(total >= data.length){
			data = Arrays.copyOf(data, data.length*2);
		}
	}
	
	//返回实际元素的个数
	public int size(){
		return total;
	}
	
	//返回数组的实际容量
	public int capacity(){
		return data.length;
	}
	
	//获取[index]位置的元素
	public Object get(int index){
		//校验index的合理性范围
		checkIndex(index);//重复使用的代码封装为一个方法
		return data[index];
	}

	private void checkIndex(int index) {
		if(index<0 || index>=total){
			throw new RuntimeException(index+"对应位置的元素不存在");//更偏向于用的角度来说
//			throw new IndexOutOfBoundsException(index+"越界");//更专业一点
		}
	}
	
	//替换[index]位置的元素
	public void set(int index, Object value){
		//校验index的合理性范围
		checkIndex(index);
		
		data[index] = value;
	}
	
	//在[index]位置插入一个元素value
	public void insert(int index, Object value){
		/*
		 * (1)考虑下标的合理性
		 * (2)总长度是否够
		 * (3)[index]以及后面的元素往后移动,把[index]位置腾出来
		 * (4)data[index]=value  放入新元素
		 * (5)total++  有效元素的个数增加
		 */
		
		//(1)考虑下标的合理性:校验index的合理性范围
		checkIndex(index);
		
		//(2)总长度是否够:检查是否需要扩容
		checkCapacity();
		
		//(3)[index]以及后面的元素往后移动,把[index]位置腾出来
		/*
		 * 假设total = 5, data.length= 10, index= 1
		 * 有效元素的下标[0,4]
		 * 移动:[1]->[2],[2]->[3],[3]->[4],[4]->[5]
		 * 移动元素的个数:total-index
		 */
		System.arraycopy(data, index, data, index+1, total-index);
		
		//(4)data[index]=value  放入新元素
		data[index] = value;
		
		//(5)total++  有效元素的个数增加
		total++;
	}
	
	//返回所有实际存储的元素
	public Object[] getAll(){
		//返回total个
		return Arrays.copyOf(data, total);
	}
	
	//删除[index]位置的元素
	public void remove(int index){
		/*
		 * (1)校验index的合理性范围
		 * (2)移动元素,把[index+1]以及后面的元素往前移动
		 * (3)把data[total-1]=null  让垃圾回收器尽快回收
		 * (4)总元素个数减少 total--
		 */
		
		//(1)考虑下标的合理性:校验index的合理性范围
		checkIndex(index);
		
		//(2)移动元素,把[index+1]以及后面的元素往前移动
		/*
		 * 假设total=8, data.length=10, index = 3
		 * 有效元素的范围[0,7]
		 * 移动:[4]->[3],[5]->[4],[6]->[5],[7]->[6]
		 * 移动了4个:total-index-1
		 */
		System.arraycopy(data, index+1, data, index, total-index-1);
		
		//(3)把data[total-1]=null  让垃圾回收器尽快回收
		data[total-1] = null;
		
//		(4)总元素个数减少 total--
		total--;
	}
	
	//查询某个元素的下标
/*	public int indexOf(Object obj){
		for (int i = 0; i < total; i++) {
		//这两种写法都有风险   list.add(null);代码正确,null也可以作为有效元素加入到list里     //int   index=list.indexof(null)代码也对,但它不是系统默认值的null,而是存的null,虽然本质上都是空,但逻辑意义不一样
			if(obj.equals(data[i])){
				//if(obj.equals(data[i]){
				return i;//找到,返回第一个找到的
				//obj.equals(data[i]和obj.equals(data[i]都会报异常
			}
		}
		return -1;//没找到返回-1
	}*/
	
	//查询某个元素的下标
	public int indexOf(Object obj){
		if(obj == null){
			for (int i = 0; i < total; i++) {
				if(data[i] == null){//或者写为 if(data[i] == obj)
					return i;
				}
			}
		}else{
			for (int i = 0; i < data.length; i++) {
				if(obj.equals(data[i])){
					return i;
				}
			}
		}
		return -1;
	}
	
	//删除数组中的某个元素
	//如果有重复的,只删除第一个
	public void remove(Object obj){
		/*
		 * (1)先查询obj的[index]
		 * (2)如果存在,就调用remove(index)删除就可以
		 */
		
		//(1)先查询obj的[index]
		int index = indexOf(obj);
		
		if(index != -1){
			remove(index);
		}
		//不存在,可以什么也不做
		//不存在,也可以抛异常
		//throw new RuntimeException(obj + "不存在");
	}
	

//替换某个元素
	public void set(Object old, Object value){
		/*
		 * (1)查询old的[index]
		 * (2)如果存在,就调用set(index, value)
		 */
		
//		(1)查询old的[index]
		int index = indexOf(old);
		if(index!=-1){
			set(index, value);
		}
		
		//不存在,可以什么也不做
		//不存在,也可以抛异常
		//throw new RuntimeException(old + "不存在");
	}
}

test文件:

package com.atguigu.test02;

import java.util.Arrays;

import org.junit.Test;

/*
 * 数据结构:
 * 	 逻辑结构:针对程序员使用角度来说
 * 	物理结构:针对程序员如何实现这个逻辑结构的底层的结构
 * 
 * 1、动态数组
 * 物理结构:数组
 * 逻辑结构:动态数组
 * 	 对于使用者来说,可以自动扩容,自动记录有效元素的个数.....
 */
public class TestMyArrayList {
	@Test
	public void test1(){
		//1、创建了一个容器对象,比喻,准备了一个箱子
		MyArrayList list = new MyArrayList();
		
		//2、往容器中装对象
		//伪代码
		list.add(new Apple());
		list.add("hello");
		for (int i = 1; i <= 5; i++) {
			list.add(i);
		}
		System.out.println("元素的个数:" + list.size());
		System.out.println("实际的容量:" + list.capacity());
	
		//获取[5]的元素
		Object obj = list.get(5);
		System.out.println(obj);
		
		//替换[5]位置的元素
		list.set(5, "张三");
		obj = list.get(5);
		System.out.println(obj);
		
		
		list.insert(0, "aa");//第0个位置插入aa,容量不够会扩容
		list.insert(0, "bb");
		list.insert(0, "cc");
		list.insert(0, "dd");
		System.out.println("元素的个数:" + list.size());
		System.out.println("实际的容量:" + list.capacity());
		
		Object[] all = list.getAll();
		System.out.println(Arrays.toString(all));
		
		System.out.println("删除后:");
		list.remove(2);
		all = list.getAll();
		System.out.println(Arrays.toString(all));
	
		list.add(null);//null也可以作为有效元素
		int index = list.indexOf(null);
		System.out.println(null+"在我们的容器的位置:" + index);
	}
}
class Apple{
	
}

手动实现单向链表

单向链表实现arraylist

package com.atguigu.test03;

public class SingleLinkedList {
	//这里不需要数组,不需要其他的复杂的结构,我只要记录单向链表的“头”结点
	private Node first;//first中记录的是第一个结点的地址
	private int total;//这里我记录total是为了后面处理的方便,例如:当用户获取链表有效元素的个数时,不用现数,而是直接返回total等
	
	/*设计内部类原因:
	 * 内部类,因为这种Node结点的类型,在别的地方没有用,只在单向链表中,用于存储和表示它的结点关系。
	 * 因为我这里涉及为内部类型。
	 */
	private class Node{
		Object data;//因为数据可以是任意类型的对象,所以设计为Object
		Node next;//因为next中记录的下一个结点的地址,因此类型是结点类型
		//这里data,next没有私有化,是希望在外部类中可以不需要get/set,而是直接“结点对象.data","结点对象.next"使用
		Node(Object data, Node next){
			this.data = data;
			this.next = next;
		}
	}
	
	public void add(Object obj){
		/*
		 * (1)把obj的数据,包装成一个Node类型结点对象
		 * (2)把新结点“链接”当前链表的最后
		 * ①当前新结点是第一个结点
		 * 如何判断是否是第一个   if(first==null)说明暂时还没有第一个
		 * ②先找到目前的最后一个,把新结点链接到它的next中
		 * 如何判断是否是最后一个   if(某个结点.next == null)说明这个结点是最后一个
		 */
//		(1)把obj的数据,包装成一个Node类型结点对象
		//这里新结点的next赋值为null,表示新结点是最后一个结点
		Node newNode = new Node(obj, null);
		
		//①当前新结点是第一个结点
		if(first == null){
			//说明newNode是第一个
			first = newNode;
		}else{
			//②先找到目前的最后一个,把新结点链接到它的next中
			Node node = first;
			while(node.next != null){
				node = node.next;
			}
			//退出循环时node指向最后一个结点
			
			//把新结点链接到它的next中
			node.next = newNode;
		}
		
		total++;
	}
	
	public int size(){
		return total;
	}
	
	public Object[] getAll(){
		//(1)创建一个数组,长度为total
		Object[] all = new Object[total];
		
		//(2)把单向链表的每一个结点中的data,拿过来放到all数组中
		Node node = first;
		for (int i = 0; i < total; i++) {
//			all[i] = 结点.data;
			all[i] = node.data;
			//然后node指向下一个
			node = node.next;
		}
		
		//(3)返回数组
		return all;
	}
	
	public void remove(Object obj){
		if(obj == null){//数据为空也是有效结点
			//(1)先考虑是否是第一个
			if(first!=null){//链表非空
				
				//要删除的结点正好是第一个结点
				if(first.data == null){
					//让第一个结点指向它的下一个
					first = first.next;
					total--;
					return;
				}
				
				//要删除的不是第一个结点
				Node node = first.next;//第二个结点
				Node last = first;
				while(node.next!=null){//这里不包括最后一个,因为node.next==null,不进入循环,而node.next==null是最后一个
					if(node.data == null){
						last.next = node.next;
						total--;
						return;
					}
					last = node;
					node = node.next;
				}
				
				//单独判断最后一个是否是要删除的结点
				if(node.data == null){
					//要删除的是最后一个结点
					last.next = null;
					total--;
					return;
				}
			}
		}else{//若是obj不为空,判断方法改变,逻辑不变
			//(1)先考虑是否是第一个
			if(first!=null){//链表非空
				
				//要删除的结点正好是第一个结点
				if(obj.equals(first.data)){
					//让第一个结点指向它的下一个
					first = first.next;
					total--;
					return;
				}
				
				//要删除的不是第一个结点
				Node node = first.next;//第二个结点
				Node last = first;
				while(node.next!=null){//这里不包括最后一个,因为node.next==null,不进入循环,而node.next==null是最后一个
					if(obj.equals(node.data)){
						last.next = node.next;
						total--;
						return;
					}
					last = node;
					node = node.next;
				}
				
				//单独判断最后一个是否是要删除的结点
				if(obj.equals(node.data)){
					//要删除的是最后一个结点
					last.next = null;
					total--;
					return;
				}
			}
		}
	}

	public int indexOf(Object obj){
		if(obj == null){
			Node node = first;
			for (int i = 0; i < total; i++) {
				if(node.data == null){
					return i;
				}
				node = node.next;
			}
		}else{
			Node node = first;
			for (int i = 0; i < total; i++) {
				if(obj.equals(node.data)){
					return i;
				}
				node = node.next;
			}
		}
		return -1;
	}
}

在这里插入图片描述
退出循环时nude指向最后一个结点
test文件:

package com.atguigu.test03;

import java.util.Arrays;

public class TestSingleLinkedList {

	public static void main(String[] args) {
		SingleLinkedList list = new SingleLinkedList();
		
		list.add("张三");
		list.add("李四");
		list.add("王五");
		list.add("赵六");
		list.add("钱七");
		
		System.out.println("元素个数:" + list.size());
	
		Object[] all = list.getAll();
		System.out.println(Arrays.toString(all));
		
		/*list.remove("张三");
		System.out.println("元素个数:" + list.size());
		all = list.getAll();
		System.out.println(Arrays.toString(all));
		
		list.remove("王五");
		System.out.println("元素个数:" + list.size());
		all = list.getAll();
		System.out.println(Arrays.toString(all));
		
		list.remove("钱七");
		System.out.println("元素个数:" + list.size());
		all = list.getAll();
		System.out.println(Arrays.toString(all));*/
		
		System.out.println(list.indexOf("张三"));
		System.out.println(list.indexOf("王五"));
		System.out.println(list.indexOf("钱七"));
	}

}

在这里插入图片描述
画图分析代码:
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
线性表是一种最基本的数据结构,常用于存储和管理有序的数据元素。在Java,我们可以使用数组或链表来设计和实现线性表。 首先,我们来看数组实现线性表的方式。数组是一种连续的存储结构,可以按照索引的方式直接访问元素。我们可以通过定义一个固定大小的数组来创建线性表,并使用一个变量来记录线性表的长度。对于插入和删除操作,需要移动元素位置来完成,而查找操作则可以通过直接访问索引来完成。 另一种实现线性表的方式是使用链表。链表是一种非连续的存储结构,通过每个元素内部包含下一个元素的地址来连接。在Java,我们可以定义一个节点类来表示链表的元素,节点类包含一个数据域和一个指向下个节点的引用。我们可以通过定义一个头节点来创建链表,头节点不存储具体的数据,只用作链表的入口。对于插入和删除操作,只需要修改节点的引用指向即可,而查找操作需要遍历整个链表来找到目标元素。 无论是数组还是链表的实现方式,线性表都可以支持常见的操作,如插入、删除、查找、遍历等。我们可以根据具体的需求选择使用哪种方式,例如,如果需要频繁进行插入和删除操作,则链表的实现方式更为灵活,而数组的实现方式则更适合需要频繁进行索引访问的场景。 在Java,我们还可以利用已有的数据结构类库来简化线性表的设计和实现。例如,Java提供了ArrayList和LinkedList两个类,分别基于数组和链表实现了线性表的功能,并提供了丰富的方法来操作线性表。我们可以直接使用这些类来快速实现和使用线性表,避免了重新设计和实现的过程。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值