链表的定义与使用
链表的本质是一个动态的对象数组,实现若干对象的存储。
链表实现简介
实际开发中对象数组是一项非常实用的技术,利用其描述“多”方的概念。但传统的对象数组依赖于数组的概念,所以数组里面最大的缺点在于长度是固定的,所以实际开发中,传统的数组应用是有限的(数组的接收和循环处理)。
想要进行灵活的数据保存,就要自己实现结构。传统对象数组的开发操作依赖于索引的控制,想要实现内容的动态维护,难度太高复杂度攀升,所以现在可以发现对于随时变化的数据必须实现一个可以动态扩充的对象数组。
所谓的链表实质性的本质是利用引用的逻辑关系来实现类似于数组数据处理操作,以一种保存“多”方数据的形式实现数据类似的功能。
要想实现链表处理,需要一个公共的结构,这个结构可以实现数据的保存以及下一个链接的指向,可以理解为节点类,该节点可以保存任意数据类型的数据。
虽然已经清楚需要通过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;
}
}
main{
Node<String> n1 = new Node<String>("test");
Node<String> n2 = new Node<String>("test1");
Node<String> n3 = new Node<String>("test2");
n1.setNext(n2);
n2.setNext(n3);
print(n1); //遍历输出所有节点
}
public static void print(Node<?> node){
if(node != null){
//有节点
System.out.println(node.getData());
print(node.getNext()); //递归调用
}
}
上述代码直接操作Node十分麻烦,使用者实际上关心的只是数据的存储和获取,所以应该对Node类进行包装处理。
数据增加(public void add(E e))
在链表操作过程中为了避免转型的异常应该使用泛型,同时也应该设计链表的标准接口,实现该接口时还应该通过Node类作出节点的关系描述。
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){
//当前节点的下一节点为null
this.next = newNode; //保存当前节点
} else{
this.next.addNode(newNode);
}
}
}
//--------以下为Link类中定义的成员---------
private Node root; //保存根元素
//--------以下为Link类中定义的方法---------
public void add(E e){
if(e == null){
//保存的数据为null
return ; //方法调用直接结束
}
//数据本身不具有关联特性只有Node类有,要先实现关联处理就必须将数据包装在Node了中
Node newNode = new Node(e); //创建一个新节点
if(this.root == null){
this.root = newNode;
} else {
this.root.addNode(newNode); //将新节点保存在合适的位置
}
}
}
class main{
main{
ILink<String> all = new LinkImpl<String>();
all.add("hello");
all.add("word");
all.add("test");
}
}
内部类中的私有属性也方便外部类直接访问,所以不必写setter、getter方法。
Link类只负责数据的操作与根节点的处理,而所有后续节点的处理全部是由Node类负责完成的。
获取集合个数(public int size())
链表中保存大量的数据,往往需要进行数据个数的统计,所以应该在LinkImpl中增加数据统计信息。
interface ILink<E>{
//设置泛型避免安全隐患
public void add(E e); //增加数据
public int size(); //获取数据个数
}
class LinkImpl<E> implements ILink<E>{
//省略内部类:Node
//--------以下为Link类中定义的成员---------
private Node root; //保存根元素
private int count; //保存数据个数
//--------以下为Link类中定义的方法---------
public void add(E e){
if(e == null){
//保存的数据为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;
}
}
//省略main
只是对于数据保存中的一个辅助功能。
空集合判断(public boolean isEmpty())
链表可以保存若干数据,但若没有数据表示空集合,则应该提供一个空判断。
interface ILink<E>{
//设置泛型避免安全隐患
public void add(E e); //增加数据
public int size(); //获取数据个数
public boolean isEmpty(); //判断是否为空集合
}
class LinkImpl<E> implements ILink<E>{
//省略内部类:Node
//--------以下为Link类中定义的成员---------
private Node root; //保存根元素
private int count; //保存数据个数
//--------以下为Link类中定义的方法---------
public void add(E e){
if(e == null){
//保存的数据为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 boolean isEmpty(){
return this.root == null;
//或者
return this.count == 0 ;
}
}
//省略main
使用根节点或长度判断都是一样的。
返回集合数据(public Object[] toArray())
链表本身属于一个动态对象数组,既然是一个动态对象数组,就应该可以把所有的数据以数组的形式返回。此时可以定义一个toArray()方法,但是此时该方法只能返回Object型的数组。
interface ILink<E>{
//设置泛型避免安全隐患
public void add(E e); //增加数据
public int size(); //获取数据个数
public boolean isEmpty(); //判断是否为空集合
public Object [] toArray(); //将集合元素以数组形式返回
}
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){
//当前节点的下一节点为null
this.next = newNode; //保存当前节点
} else{
this.next.addNode(newNode);
}
}
//第一次调用:this = LinkImpl.root
//第二次调用:this = LinkImpl.root.next
//第三次调用:this = LinkImpl.root.next.next
//......
public void toArrayNode(){
LinkImpl.this.returnData [LinkImpl.this.foot ++] = this.data
if(this.next != null){
this.next.toArrayNode();
}
}
}
//--------以下为Link类中定义的成员---------
private Node root; //保存根元素
private int count; //保存数据个数
private int foot; //操作数组角标
private Object[] returnData; //返回数据保存
//--------以下为Link类中定义的方法---------
public void add(E e){
if(e == null){
//保存的数据为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 boolean isEmpty(){
return this.root == null;
//或者
return this.count == 0 ;
}
public Object [] toArray(){
if(this.isEmpty()){
//空集合
return null;
}
this.foot = 0; //脚标清0
this.returnData = new Object [this.count]; //根据已有长度开辟数组
this.root.toArrayNode(); //利用Node类进行递归数据获取
return this.returnData;
}
}
//省略main
集合的数据一般要返回肯定要以对象数组(Object [])的形式返回。
根据索引取得数据(public E get(int index))
链表可以像数组一样进行处理,所以也应该可以向数组一样进行索引数据的获取,这样的情况下就可以继续利用递归的形式来完成。
interface ILink<E>{
//设置泛型避免安全隐患
public void add(E e); //增加数据
public int size(); //获取数据个数
public boolean isEmpty(); //判断是否为空集合
public Object [] toArray(); //将集合元素以数组形式返回
public E get(int index); //根据索引获取数据
}
class LinkImpl<E> implements ILink<E>{
private class Node{
//保存节点数据关系(内部类)
private E data; //保存的数据
private Node next;
//省略部分方法
public E getNode(int index){
if(LinkImpl.this.foot == index){
//索引相同
return this.data; //返回当前数据
} else {
return this.next.getNode(index);
}
}
}
//--------以下为Link类中定义的成员---------
private Node root; //保存根元素
private int count; //保存数据个数
private int foot; //操作数组角标
private Object[] returnData; //返回数据保存
//--------以下为Link类中定义的方法---------
//省略部分方法
public E get(int index){
if(index > this.count){
//索引应该在指定范围内
return null;
} //索引数据的获取应该在Node类完成
this.foot = 0; //重置索引下标
return this.root.getNode(index);
}
}
//省略main
这一特点与数组很相似,但是数组获取一个数据的时间复杂度为1,而链表所需的时间复杂度为n。
链表(修正指定索引数据)(public void set(int index,E data))
现在可以根据索引获取数据,则可以进行数据的修改。
interface ILink<E>{
//设置泛型避免安全隐患
public void add(E e); //增加数据
public int size(); //获取数据个数
public boolean isEmpty(); //判断是否为空集合
public Object [] toArray(); //将集合元素以数组形式返回
public E get(int index); //根据索引获取数据
public void set(int index, E data); //修改指定索引数据
}
class LinkImpl<E> implements ILink<E>{
private class Node{
//保存节点数据关系(内部类)
private E data; //保存的数据
private Node next;
//省略部分方法
public void setNode(int index,E data){
if(LinkImpl.this.foot == index){
//索引相同
this.data =data; //修改数据
} else {
this.next.getNode(index, data);
}
}
}
//--------以下为Link类中定义的成员---------
private Node root; //保存根元素
private int count; //保存数据个数
private int foot; //操作数组角标
private Object[] returnData; //返回数据保存
//--------以下为Link类中定义的方法---------
//省略部分方法
public void set(int index, E data){
if(index > this.count){
//索引应该在指定范围内
return; //方法结束
} //索引数据的获取应该在Node类完成
this.foot = 0; //重置索引下标
this.root.setNode(index, data); //修改数据
}
}
//省略main
这种操作的时间复杂度仍旧为n,因为依旧需要进行遍历处理。
链表(判断数据是否存在)(public boolean contains(E data))
一个集合中往往有大量的数据,有些时候需要判断某个数据是否存在,这个时候可以通过对象比较(equlas())的模式进行判断。
interface ILink<E>{
//设置泛型避免安全隐患
public void add(E e); //增加数据
public int size(); //获取数据个数
public boolean isEmpty(); //判断是否为空集合
public Object [] toArray(); //将集合元素以数组形式返回
public E get(int index); //根据索引获取数据
public void set(int index, E data); //修改指定索引数据
public boolean contains(E data); //判断数据是否存在
}
class LinkImpl<E> implements ILink<E>{
private class Node{
//保存节点数据关系(内部类)
private E data; //保存的数据
private Node next;
//省略部分方法
public boolean containsNode(E data){
if (this.data.equlas(data)){
//找到了
return true;
} else {
if(this.next == null){
//没有后续节点
return false;
} else{
return this.next.containsNode(data); //向后继续判断
}
}
}
}
//--------以下为Link类中定义的成员---------
private Node root; //保存根元素
private int count; //保存数据个数
private int foot; //操作数组角标
private Object[] returnData; //返回数据保存
//--------以下为Link类中定义的方法---------
//省略部分方法
public boolean contains(E data){
if(data ==null){
return false; //没有数据
}
return this.root.containsNode(data); //交给Node类判断
}
}
//省略main
由于整个链表没有null数据存在,所以整体的程序在判断的时候直接使用每一个节点的数据发出equals()方法调用即可。
链表(数据删除)(public void remove(E data))
从集合中删除指定的一个数据内容,也就是说此时传递的是数据内容,完成此种对象需要对象比较的支持。
对于对象的删除,需要考虑两种情况:
- 要删除的是根节点数据(LinkImpl与根节点有关,所以这个判断由根节点完成)
- 要删除的不是根节点数据(由Node类负责)
interface ILink<E>{
//设置泛型避免安全隐患
public void add(E e); //增加数据
public int size(); //获取数据个数
public boolean isEmpty(); //判断是否为空集合
public Object [] toArray(); //将集合元素以数组形式返回
public E get(int index); //根据索引获取数据
public void set(int index, E data); //修改指定索引数据
public boolean contains(E data); //判断数据是否存在
public void remove(E e); //删除数据
}
class LinkImpl<E> implements ILink<E>{
private class Node{
//保存节点数据关系(内部类)
private E data; //保存的数据
private Node next;
//省略部分方法
public void removeNode(Node previoues, E data){
if(this.data.equlas(data)){
previoues.next = this.next; //空出当前节点
} else {
if (this.next != null){
//有后续节点
this.next.removeNode(this, data); //向后继续删除
}
}
}
}
//--------以下为Link类中定义的成员---------
private Node root; //保存根元素
private int count; //保存数据个数
private int foot; //操作数组角标
private Object[] returnData; //返回数据保存
//--------以下为Link类中定义的方法---------
//省略部分方法
public void remove(E e){
if (this.contains(data)){
//判断是否存在
if(this.root.data.equlas(data)){
//根节点为要删除节点
this.root = this.root.next; //根的下一个节点
} else {
//交由NOde类负责删除
this.root.next.removeNode(this.root, data);
}
this.count --; //减少一个集合个数
}
}
}
//省略main
删除逻辑依靠的就是引用的改变处理完成的。
链表(清空链表)(public void clean())
有时需要数据的整体清空处理,此时可直接根据根元素来进行控制,只要root设置为null,后续节点都不存在。
interface ILink<E>{
//设置泛型避免安全隐患
public void add(E e); //增加数据
public int size(); //获取数据个数
public boolean isEmpty(); //判断是否为空集合
public Object [] toArray(); //将集合元素以数组形式返回
public E get(int index); //根据索引获取数据
public void set(int index, E data); //修改指定索引数据
public boolean contains(E data); //判断数据是否存在
public void remove(E e); //删除数据
public void clean(); //清空数据
}
class LinkImpl<E> implements ILink<E>{
private class Node{
//保存节点数据关系(内部类)
private E data; //保存的数据
private Node next;
//省略部分方法
}
//--------以下为Link类中定义的成员---------
private Node root; //保存根元素
private int count; //保存数据个数
private int foot; //操作数组角标
private Object[] returnData; //返回数据保存
//--------以下为Link类中定义的方法---------
//省略部分方法
public void clean(){
this.root = null; //后续节点都没了
this.count =0 ; //个数清0
}
}
//省略main
综合实战:宠物商店
interface ILink<E> {
}
class LinkImpl<E> implements ILink<E> {
}
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;
}
public String getName(){
return this.name;
}
public String getColor(){
return this.color;
}
public boolean equlas(Object obj){
//覆写equals方法
if(obj == null){
return false;
}
if(!(obj instanceof Cat)){
return false;
}
if(this == obj){
return true;
}
Cat cat = (Cat) obj;
return this.name.equlas(cat.name) && this.color.equlas(cat.color);
}
public String toString(){
return "宠物信息:名字:" + this.name + ",颜色:" + this.color ;
}
}
//宠物狗略
class MAIN{
main{
PetShop shop = new PetShop();
shop.add(new Cat("英美","金色"));
Object result [] = shop.search("金色").toArray();
for (Object obj : result){
System.out.println(obj);
}
}
}
了解接口作为标准的重要意义,这样在进行后期程序处理的时候就可以非常的灵活,只要符合标准的对象都可以保存。
综合实战:超市购物车
interface ILink<E> {
}
class LinkImpl<E> implements ILink<E> {
}
interface IGoods{
//定义商品标准
public String getName();
public String 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 allPrcie(){
//计算总价
double all = 0.0;
Object result [] = this.shopcar.getAll();
for(Object obj :result){
IGoods goods = (IGoods) obj;
all += goods.getPrice();
}
return all;
}
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;
}
public String getName(){
return this.name;
}
public double getPrice(){
return this.price;
}
public boolean equlas(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.equlas(book.name) && this.price == book.price;
}
public String toString(){
return "图书信息:名称:" +this.name + ",价格:" + this.price;
}
}
//商品-bag略
public class JavaDemo{
public static void main(String agrs[]){
IShopCar car = new ShopCarImpl();
car.add(new Book("test", 79.8));
Cashier cas = new Cashier(car);
System.out.println("总价:"+ cas.allPrcie() + ",数量:"+ cas.allCount());
}
}
整体代码都是基于链表的整体功能实现的。