链表

1,链表的实现

  在实际开发之中对象数组是一项非常实用的技术,并且利用其可以描述出“多”方的概念,例如:一个人有多本书,则在人的类里面一定要提供有一个对象数组保存书的信息,但是传统的对象数组依赖于数组的概念,所以数组里面最大的缺点在于:长度是固定的,正是因为如此在实际开发之中,传统的数组应用是非常有限的(数组的接收与循环处理),如果想要实现灵活的数据保存,那么就必须自己来实现结构。

  传统的对象数组的开发依赖于脚标(索引)的控制,如果想要实现内容的动态控制,那么难度太高了,而且复杂度攀升,所以现在就可以,对于一成不变的数据可以使用对象数组来实现,但是对于可能随时变化的数据就必须实现一个可以动态扩充的对象数组。

  所谓的链表实质性的本质是利用引用的逻辑关系来实现类似于数组的数据处理操作,以保存多方数据的数组类似的功能

  通过分析可得,如果想要实现链表处理,那么需要有一个公共的结构,这个结构可以实现数据的保存以及下一个数据的连接的指向,为了描述这样一个逻辑,可以把每个存储理解为一个节点类,所以此时应该准备出一个节点类出来,但是这个节点类里面可以保存各种类型类型的数据。

  虽然已经清楚需要使用Node节点进行数据的保存,但是毕竟这里面需要牵扯到节点的引用处理关系,那么这个引用处理关系是由使用者来控制吗?这样肯定不可能,所以应该由一个专门的类来进行节点的引用关系的配置。

范例:直接操作node很麻烦

 class Node<E>{
private E data;
private Node next;
public Node(E data) {
this.data = data;
}
public E getData() {
return this.data;
}
public void setNext(Node<E> next) {
this.next = next;
}
public Node getNext() {
return this.next;
}
}
public class Main {
public static void main(String[] args) {
Node<String> n1=new Node<String>("火车头");
Node<String> n2=new Node<String>("车厢一");
Node<String> n3=new Node<String>("车厢二");
Node<String> n4=new Node<String>("车厢三");
Node<String> n5=new Node<String>("车厢四"); n1.setNext(n2);
n2.setNext(n3);
n3.setNext(n4);
n4.setNext(n5); print(n1);
// System.out.println("Hello World!");
}
public static void print(Node<?> node){
if (node!=null){//存在节点
System.out.println(node.getData());
print(node.getNext());//递归调用
}
}
}

这样肯定不可能,所以应该由专门的类来进行节点的引用关系的配置。因为真实的使用者实际上关心的只是数据的存取与获取,所以现在应该对Node类进行包装处理。

2,数据增加

通过之前的分析可以发现在进行链表操作的过程之中为了避免转型的异常处理应该使用泛型,同时也应该设计一个链表的执行标准的接口,同时具体实现该接口的时候还应该通过Node类做出节点的关系描述。

范例:基本结构

 interface ILink<E>{
public void add(E e);
}
class LinkImpl<E> implements ILink<E>{
private class Node{//保存节点的数据关系,【内部类形式】
private E data;//保存的数据
public Node(E data){//有数据的情况下才有意义
this.data=data;
}
}
//--------------以下为Link类中定义的结构---------
}

范例:实现数据增加现在在所定义的Node类之中并没有出现setter和getter方法,是因为内部类中的私有属性也方便外部类直接访问。

 interface ILink<E>{
public void add(E e);
}
class LinkImpl<E> implements ILink<E>{
private class Node{//保存节点的数据关系,【内部类形式】
private E data;//保存的数据
private Node next;
public Node(E data){//有数据的情况下才有意义
this.data=data;
}
//第一次调用:this = LinkImpl.root;
//第二次调用:this = LinkImpl.root.next;
//第三次调用:this = LinkImpl.root.next.next;
public void addNode(Node newNode){//保存新的node数据
if(this.next==null){//当前节点的下一个节点为空
this.next=newNode;//保存当前节点
}else{
this.next.addNode(newNode);
}
}
}
//--------------以下为Link类中定义的成员---------
private Node root;//保存根元素
//--------------以下为Link类中定义的方法--------- @Override
public void add(E e) {
if(e==null){//保存的数据为空
return;
}
//数据本身不具有关联特性的,只有Node类有,那么要想实现关联处理就必须将数据封装在Node类中
Node newNode=new Node(e);
if(this.root==null){//现在没有根节点
this.root=newNode;
}else {//根节点存在
this.root.addNode(newNode);//将新节点保存在合适的位置
} }
}
public class Main {
public static void main(String[] args) {
ILink<String> all=new LinkImpl<String>();
all.add("wanyu");
all.add("hello");
all.add("world");
all.add("great");
} }

Link类只是负责数据的操作与根节点的处理,而所有后续节点的处理全部都是由Node类负责完成的。

3,获取集合个数 public int size()

在链表中往往需要保存有大量的数据,那么这些数据往往需要进行数据个数的统计操作,所以应该在LinkImpl子类里面追加有数据统计信息,同时增加与删除时都应该对个数进行修改。

①在ILink接口里追加一个获取数据个数的方法:

 interface ILink<E>{//设置泛型避免安全隐患
public void add(E e);//增加数据
public int size();//获取数据的个数
}

②在LinkImpl的子类中追加有一个个数统计的子类

 private int count;

③在add()的方法里面进行数据个数的追加

 @Override
public void add(E e) {
if(e==null){//保存的数据为空
return;
}
//数据本身不具有关联特性的,只有Node类有,那么要想实现关联处理就必须将数据封装在Node类中
Node newNode=new Node(e);
if(this.root==null){//现在没有根节点
this.root=newNode;
}else {//根节点存在
this.root.addNode(newNode);//将新节点保存在合适的位置
}
this.count++;
}

④在LinkImpl的子类中里面来返回数据的个数

 public int size(){
return this.count;
}

只是对于数据保存中的一个辅助功能。

范例:完整程序

 interface ILink<E>{//设置泛型避免安全隐患
public void add(E e);//增加数据
public int size();//获取数据的个数
}
class LinkImpl<E> implements ILink<E>{
private class Node{//保存节点的数据关系,【内部类形式】
private E data;//保存的数据
private Node next;
public Node(E data){//有数据的情况下才有意义
this.data=data;
}
//第一次调用:this = LinkImpl.root;
//第二次调用:this = LinkImpl.root.next;
//第三次调用:this = LinkImpl.root.next.next;
public void addNode(Node newNode){//保存新的node数据
if(this.next==null){//当前节点的下一个节点为空
this.next=newNode;//保存当前节点
}else{
this.next.addNode(newNode);
}
}
}
//--------------以下为Link类中定义的成员---------
private Node root;//保存根元素
private int count;
//--------------以下为Link类中定义的方法---------
@Override
public void add(E e) {
if(e==null){//保存的数据为空
return;
}
//数据本身不具有关联特性的,只有Node类有,那么要想实现关联处理就必须将数据封装在Node类中
Node newNode=new Node(e);
if(this.root==null){//现在没有根节点
this.root=newNode;
}else {//根节点存在
this.root.addNode(newNode);//将新节点保存在合适的位置
}
this.count++;
}
public int size(){
return this.count;
}
}
public class Main {
public static void main(String[] args) {
ILink<String> all=new LinkImpl<String>();
System.out.println("【增加之前:】数据个数:"+all.size());
all.add("wanyu");
all.add("hello");
all.add("world");
all.add("great");
System.out.println("【增加之后:】数据个数:"+all.size());
}
}

4,空集合判断public boolean isEmpty()

链表里面可以保存有若干个数据,如果说现在链表还没有保存数据,则就表示是一个空集合。

①在ILink接口里面追加有判断方法:

 public boolean isEmpty();//判断是否为空集合

使用根节点和个数本质一样。

②在LinkImpl子类里面覆写此方法;

     public boolean isEmpty(){
// return this.root==null;
return this.count==0;
}

5,返回集合数据 public Object[] toArray()

链表本身就属于一个动态对象数组,那么既然属于一个对象数组,就应该可以把所有的数据以数组的形式返回,那么这个时候就可以定义一个toArray()方法,但是这个时候的方法只能够返回Object型的数组。

①在ILink接口里面追加新的处理方法

 public Object[] toArray();//将集合元素以数组的形式返回

②在LinkImpl子类里面追加有两个属性,

 private int foo;//描述操作数组的脚标
private Object[] returnData;//返回的数据保存

③在Node类中根据递归获取数据

 //第一次调用:this=LinkImpl.root
//第二次调用:this=LinkImpl.root.next
//第三次调用:this=LinkImpl.root.next.mext
public void toArrayNode(){
LinkImpl.this.returnData [foo++]=this.data;
if (this.next!=null){//还有下一个数据
this.next.toArrayNode();
}
}

④在进行数据返回的时候一定要首先判断是否为空集合;

 @Override
public Object[] toArray() {
if(this.isEmpty()){//空集合
return null;//现在没有数据
}
this.foo=0;//脚标清零
this.returnData=new Object[this.count]; //根据已有的数据长度开辟数组
this.root.toArrayNode();//利用Node类进行递归获取数据
return this.returnData;
}

集合的数据一般如果要返回肯定要以对象的形式返回。

6,根据索引取得数据public E get(int index)

链表可以向数组一样处理,所以也应该可以向数组一样进行索引数据的获取,再这样的情况下我们可以使用递归的形式来完成。

①在ILink接口中追加有新的方法

 public E get(int index);//根据索引获取数据

②在Node类里面追加有根据索引获取数据的处理

 @Override
public E get(int index) {
if (index>=this.count){//索引应该在指定的范围之内
return null;
}//索引数据的获取应该由Node类完成
this.foot=0;//重置索引的下标
return this.root.getNode(index);
}

这一特点和数组很相似,但是需要注意,数组获取一个数据的时间复杂度为1,而链表获取一个数据的时间复杂度为n。

7,链表(修改指定索引数据)

现在已经可以根据索引来获取指定的数据了,但是既然可以获取数据,那么也可以进行数据的修改。

①在ILink接口中追加新的方法

 public void set(int index,E data);//修改索引数据

②在Node类之中应该提供有数据修改的处理支持

 public void setNode(int index,E data){
if(LinkImpl.this.foot++==index){//索引相同
this.data=data;//返回当前数据
}else{
this.next.setNode(index,data);
}
}

③在LinkImpl中进行方法覆写

 @Override
public void set(int index, E data) {
if (index>=this.count){//索引应该在指定的范围之内
return ;//方法结束
}//索引数据的获取应该由Node类完成
this.foot=0;//重置索引的下标
this.root.setNode(index,data);//修改数据
}

这种操作的时间复杂度也是N,因为也是需要进行数据的遍历处理

8,链表(判断数据是否存在)public boolean contains(E data)

在一个集合里面往往会保存有大量的数据,有些时候需要判断某个数据是否存在,这个时候就可以通过对象比较的模型(equals())进行判断。

①在ILink接口中追加判断方法

 public boolean contains(E data);//判断数据是否存在

②在Node类中进行依次判断

 public boolean containsNode(E data) {
if(this.data.equals(data)){//对象比较
return true;
}else {
if(this.next==null){//没有后续节点了
return false;//找不到数据
}else {
return this.next.containsNode(data);
}
}
}

③在LinkImpl子类里面实现此方法

 @Override
public boolean contains(E data) {
if(data==null){
return false;
}
return this.root.containsNode(data);//交给Node类判断
}

由于整个链表没有null数据的存在,所以整体的程序在判断的时候直接使用每个节点数据发出的equals()方法调用即可。

9,链表(数据删除)public void remove(E data)

数据的删除指的是可以从集合里面删除掉指定的一个数据内容,也就是说此时传递的是数据内容,那么如果要实现这种的删除操作,依然需要对象的比较的支持,但是对于集合数据的删除需要考虑两种情况:

·要删除的是根节点(LinkImpl与根节点有关,所以这个判断由根节点完成):

·要删除的不是根节点(由Node类负责):

①在ILink接口中追加新的方法;

 public void remove(E e);//数据删除

②在LinkImpl子类里面实现根节点的判断;

 @Override
public void remove(E data) {
if(this.contains(data)){//判断数据是否存在
if (this.root.data.equals(data)){//根节点为要删除节点
this.root=this.root.next;//根的下一个节点
}
}
}

③如果现在根节点并不是要删除的节点,那么就需要进行后续节点判断,但是请一定要记住,此时的根节点已经判断完成,在判断应该从下一个节点开始判断,在Node类中追加删除处理。

 public void removeNode(Node previous,E data){
if(this.data.equals(data)){
previous.next=this.next;//空出当前节点
}else {
if(this.next!=null){//有后续节点
this.next.removeNode(this,data);//向后继续删除
}
}
}

④完善LinkImpl子类中的remove方法

 @Override
public void remove(E data) {
if(this.contains(data)){//判断数据是否存在
if (this.root.data.equals(data)){//根节点为要删除节点
this.root=this.root.next;//根的下一个节点
}else {//交由Node类负责删除
this.root.next.removeNode(this.root,data);
}
this.count--;
}
}

删除的逻辑依靠的就是引用的改变处理完成的。

10,链表(清空数据)public void clean()

有些时候需要进行链表的整体清空处理,这个时候就可以直接根据根节点元素来进行控制,只要root设置为null,那么后续节点就都不存在了。

①在ILink接口中里面追加有清空处理方法

 public void clean();//清空集合

②在LinkImpl的子类里面覆写方法

 @Override
public void clean() {
this.root=null;//后续的所有节点都没了
this.count=0;//个数清零
}

这些就是链表的基本功能,当然这只是一个最简单、最基础的单向链表

11,综合实战:宠物商店

现在假设有一个宠物商店里面可以出售各种宠物,要求可以实现宠物的上架处理、下架处理,也可以根据我们的关键字查询出宠物的信息。

①应该定义出宠物的标准

 interface Pet{//定义宠物的标准
public String getName();//获得名字
public String getColor();//获得颜色
}

②宠物商店应该以宠物的标准为主

 class PetShop{//宠物商店
private ILink<Pet> allPets=new LinkImpl<Pet>();//保存多个宠物信息
public void add(Pet pet){//追加宠物,商品上架
this.allPets.add(pet);//集合中保存对象
}
public void delete(Pet pet){
this.allPets.remove(pet);//集合中保存对象
}
public ILink<Pet> search(String keyword){
ILink<Pet> searchResult=new LinkImpl<Pet>();//保存查询结果
Object[] result=this.allPets.toArray();//获取全部数据
if(result!=null){
for(Object obj:result){
Pet pet=(Pet) obj;
if(pet.getName().contains(keyword)||pet.getColor().contains(keyword)){
searchResult.add(pet);//保存查询结果
}
}
}
return searchResult;
}
}

③根据宠物的标准来定义宠物的信息

 class Cat implements Pet{//实现宠物标准
private String name;
private String color; public Cat(String name, String color) {
this.name = name;
this.color = color;
} @Override
public String getName() {
return name;
} @Override
public String getColor() {
return color;
} @Override
public boolean equals(Object obj) {
if(obj==null){
return false;
}
if(!(obj instanceof Cat)){
return false;
}
if (this==obj){
return true;
}
Cat cat=(Cat) obj;
return this.name.equals(cat.name) && this.color.equals(cat.color);
} @Override
public String toString() {
return "【宠物猫】名字:"+this.name+",颜色:"+this.color;
}
}

④实现宠物商店的操作

 public class Main {
public static void main(String[] args) {
PetShop shop=new PetShop();//开店
shop.add((new Dog("黄斑狗","绿色")));
shop.add((new Cat("小强猫","深绿色")));
shop.add((new Cat("黄猫","深色")));
shop.add((new Dog("黄狗","黄色")));
shop.add((new Dog("斑点狗","灰色")));
Object[] result=shop.search("黄").toArray();
if(result!=null){
for(Object temp:result){
System.out.println(temp.toString());
}
}
}
}

所有的程序开发都是以接口为标准进行开发的,这样在进行后期程序处理的时候就可以非常的灵活,只要符合标准的对象都可以保存。

12,综合实战:超市实战

①定义一个商品的标准

 interface IGoods{//定义商品的标准
public String getName();
public double getPrice();
}

②定义购物车处理标准

 interface IShopCar{//购物车的标准
public void add(IGoods goods);//添加商品信息
public void delete(IGoods goods);//添加商品信息
public Object[] getAll();//获得购物车中的全部商品信息
}

③定义购物车的实现类

 class ShopCarImpl implements IShopCar{//购物车
private ILink<IGoods> allGoodses=new LinkImpl<IGoods>();
public void add(IGoods goods){
this.allGoodses.add(goods);
}
public void delete(IGoods goods){
this.allGoodses.remove(goods);
}
public Object[] getAll(){
return this.allGoodses.toArray();
}
}

④定义收银台

 class Cashier{//收银台
private IShopCar shopCar;//驼峰命名法
public Cashier(IShopCar shopCar) {
this.shopCar = shopCar;
}
public double allPrice(){//计算商品总价
double money=0.0;
Object[] result=this.shopCar.getAll();
if(result!=null){
for(Object obj:result){
IGoods goods=(IGoods) obj;
money +=goods.getPrice();
}
}
return money;
}
public int allCount(){//计算商品总个数
return this.shopCar.getAll().length;
}
}

⑤定义商品信息

 class Book implements IGoods{
private String name;
private double price;
public Book(String name, double price) {
this.name = name;
this.price = price;
}
@Override
public String getName() {
return this.name;
}
@Override
public double getPrice() {
return this.price;
}
public boolean equals(Object obj){
if(obj==null){
return false;
}
if(this==obj){
return true;
}
if (!(obj instanceof Book)){
return false;
}
Book book=(Book) obj;
return this.name.equals(book.name) && price==book.price;
}
}

⑥代码测试的编写

 public class Main {
public static void main(String[] args) {
IShopCar car=new ShopCarImpl();
car.add(new Book("Java开发",79.8));
car.add(new Book("MySql开发",40.0));
car.add(new Book("Spring开发",99.8));
car.add(new Bag("黑色电脑包",201.0));
Cashier cashier=new Cashier(car);
System.out.println("总价格:"+cashier.allPrice());
System.out.println("总数量:"+cashier.allCount());
Object[] result=car.getAll();
if(result!=null){
for(Object temp:result){
System.out.println(temp.toString());
}
}
}
}

Java链表设计的更多相关文章

  1. 链表设计与Java实现,手写LinkedList这也太清楚了吧!!!

    链表设计与实现 在谈链表之前,我们先谈谈我们平常编程会遇到的很常见的一个问题.如果在编程的时候,某个变量在后续编程中仍需使用,我们可以用一个局部变量来保存该值,除此之外一个更加常用的方法就是使用容器了 ...

  2. java课程设计团队博客《基于学院的搜索引擎》

    JAVA课程设计 基于学院网站的搜索引擎 对学院网站用爬虫进行抓取.建索(需要中文分词).排序(可选).搜索.数据摘要高亮.分页显示.Web界面. 一.团队介绍 学号 班级 姓名 简介 2016211 ...

  3. 用Java如何设计一个阻塞队列,然后说说ArrayBlockingQueue和LinkedBlockingQueue

    前言 用Java如何设计一个阻塞队列,这个问题是在面滴滴的时候被问到的.当时确实没回答好,只是说了用个List,然后消费者再用个死循环一直去监控list的是否有值,有值的话就处理List里面的内容.回 ...

  4. Java课程设计-算术运算测试(D级) 齐鲁工业大学 计科20-1 王瀚垠 202003010033

    Java课程设计-算术运算测试(D级) 齐鲁工业大学 计科20-1 王瀚垠 202003010033 目录 1.项目简介 2.项目采用技术 3.功能需求分析 4.项目亮点 5.项目功能架构图和UML类 ...

  5. Java界面设计 Swing(1)

    Java界面设计的用途 开发者可以通过Java SE开发丰富并且强大的具有图形界面的桌面应用程序.也可以设计一些提高效率的工具软件,帮助自己处理机械性工作. Java 的图形界面工具包,可以用于工具类 ...

  6. JAVA 链表操作:循环链表

    主要分析示例: 一.循环链表简述 二.单链表循环链表 三.双链表循环链表 一.循环链表简述 循环链表即链表形成了一个循环的结构,尾节点不再指向NULL,而是指向头节点HEAD,此时判定链表的结束是尾节 ...

  7. (转)Java API设计清单

    转自: 伯乐在线 Java API设计清单 英文原文 TheAmiableAPI 在设计Java API的时候总是有很多不同的规范和考量.与任何复杂的事物一样,这项工作往往就是在考验我们思考的缜密程度 ...

  8. java(课程设计之记事本界面部分代码公布)

    代码:涉及记事本的一些界面......!! /* *java课程设计之记事本(coder @Gxjun) * 编写一个记事本程序 * 要求: * 用图形用户界面实现. * 能实现编辑.保存.另存为.查 ...

  9. java课程设计(计算器)

    JAVA课程 设 计 报 告 1206401-18   瞿杰 一.设计时间 2013年6月 24日-----6月28日 二.设计地点 湖南城市学院实验楼计算机506机房 三.设计目的 1.巩固学习VB ...

随机推荐

  1. tornado异步请求响应速度的实例测试

    tornado异步请求响应速度的实例测试

  2. 《高性能mysql》笔记(第一章,mysql的架构与历史)

    mysql的服务器逻辑架构图如下: 目前工作用的5.5版本,5.5版本开始mysql开始将innoDB作为默认的存储引擎,innoDB的表是基于聚簇索引建立的. mysql的存储引擎锁管理非常重要,在 ...

  3. angular自定义组件

    https://cli.angular.io/ 打开终端创建header组件: ng g component components/header import { Component, OnInit ...

  4. Windows10下Anaconda虚拟环境下安装pycocotools

    0 - 步骤 安装visualcppbuildtools_full.exe(链接:https://blog.csdn.net/u012247418/article/details/82314129) ...

  5. bing map for wpf 怎么显示中文地图

    添加一下代码: string suriFormat = "http://r2.tiles.ditu.live.com/tiles/r{quadkey}.png?g=41"; Mic ...

  6. 011-多线程-JUC集合-Queue-PriorityBlockingQueue和DelayQueue

    一.PriorityBlockingQueue简介 PriorityBlockingQueue是一个支持优先级的无界阻塞队列.默认情况下元素采用自然顺序升序排列.也可以自定义类实现compareTo( ...

  7. idea中Lombok的Buider构造器模式,getter/setter正确使用方法

    public class ApiUser implements Serializable { private Long id; /*** * 用户类型:single,org(organization) ...

  8. LODOP获取打印状态码和时间列表

    之前有博文介绍获取打印状态码和打印状态码的含义,相关博文:LODOP获取打印机状态码和状态码含义测试.此外 ,也有获取状态码及其变化的方法,可以获取打印状态码的列表,列表包含每个状态和每个状态的时间. ...

  9. halcon学习_字符识别1

    实例图片 大体步骤:1.读取图片                   2.图像预处理(阈值分割,提取标签部分,缩小处理区域)                  3.将标签区域的最小外接矩形,从原图中剪 ...

  10. 是什么在阻止我们学习unity2019?

    背景 时过境迁,这是一篇老文,写于2019年5月. 在学习最新的unity ecs过程中,以及学习最新的effect 时,在迈出第一步的时候即遭遇一些困难(学习和测试环境搭建不起来,有时候真的很无语) ...