第 13 节 链表的定义与使用


1、本次的操作属于引用部分的加强应用,所以在此部分里面有两点需要依赖:

  • 依赖于引用传递问题

  • this表示当前对象

2、链表的实现的基本模式;

3、开发并且使用可用链表。

链表的基本形式

链表是一种最为简单的数据结构,它的主要目的是依靠引用关系来实现多个数据的保存,那么下面假设现在要保存的数据是字符串。

范例: 定义一个Node类

  • 假设本次保存的数据是String型数据,同时拥有下一个引用;

// 每一个链表实际上就是由多个节点所组成的
class Node { // 定义一个节点
    private String data; // 要保存的数据
    private Node next; // 要保存的下一个节点
    // 每一个Node类对象都必须保存有相应的数据
    public Node(String data) { // 必须有数据才有Node
        this.data = data;
    }
    public void setNext(Node next) {
        this.next = next;
    }
    public Node getNext() {
        return this.next;
    }
    public String getData() {
        return this.data;
    }
}

以上只是一个专门负责保存节点关系的类,但是至于说怎么保存的关系,并不是由Node类进行设置。需要有其他类来进行Node的关系匹配。

范例: 使用第一种形式设置和取出数据

public class MainClass {
    public static void main(String[] args) {
        // 第一步:准备出所有数据
        Node root = new Node("火车头");
        Node n1 = new Node("车厢A");
        Node n2 = new Node("车厢B");
        root.setNext(n1);
        n1.setNext(n2);
        // 第二步:取出所有数据 
        Node currentNode = root; // 当前从根节点开始读取
        while (currentNode != null) { // 当前节点存在数据
            System.out.println(currentNode.getData());
            // 将下一个节点设置为当前节点
            currentNode = currentNode.getNext();
        }
    }
}

实际上以上的操作使用循环并不方便,最好的做法还是应该使用递归操作完成。

范例: 使用第二种方式设置和取出数据

public class MainClass {
    public static void main(String[] args) {
        // 第一步:准备出所有数据
        Node root = new Node("火车头");
        Node n1 = new Node("车厢A");
        Node n2 = new Node("车厢B");
        root.setNext(n1);
        n1.setNext(n2);
        print(root);
    }
    public static void print(Node current) {
        if (current == null) { // 递归结束
            return ; // 结束方法
        }
        System.out.println(current.getData());
        print(current.getNext());
    }
}

对于所有的节点操作由于其并不知道具体的循环次数,所以只能够使用while循环,但是在节点操作中递归的操作要比直接使用while循环代码更加的直观。

**疑问?**整个过程实际上就是设置数据和取出数据的过程。为什么需要Node?

由于数据本身不具备先后的关系,所以使用Node类来封装数据,同时利用Node类来指向下一个节点。

链表的基本雏形

通过分析发现:

  • 用户在操作的过程之中完全没有必要去关心Node类是否存在;

  • 所有的节点的引用关系不应该由用户处理,应该有一个专门的工具类来进行处理

下面需要定义一个类,来帮助客户端去隐藏所有的链表中给出的细节操作。

范例: 链表的基本形式

// 每一个链表实际上就是由多个节点所组成的
class Node { // 定义一个节点
    private String data; // 要保存的数据
    private Node next; // 要保存的下一个节点
    // 每一个Node类对象都必须保存有相应的数据
    public Node(String data) { // 必须有数据才有Node
        this.data = data;
    }
    public void setNext(Node next) {
        this.next = next;
    }
    public Node getNext() {
        return this.next;
    }
    public String getData() {
        return this.data;
    }
    // 实现节点的添加
    // 第一次调用(Link):this = Link.root
    // 第二次调用(Node):this = Link.root.next
    // 第三次调用(Node):this = Link.root.next.next
    public void addNode(Node newNode) {
        if (this.next == null) { // 当前节点的下一个为空
            this.next = newNode; // 保存新节点
        } else { // 当前节点之后还存在有节点
            // 当前节点的下一个节点继续保存
            this.next.addNode(newNode);
        }
    }
    // 第一次调用(Link):this = Link.root
    // 第二次调用(Node):this = Link.root.next
    // 第三次调用(Node):this = Link.root.next.next
    public void printNode() {
        System.out.println(this.data);
        if (this.next != null) {
            this.next.printNode();
        }
    }
}
// 需要进行Node类对象的关系处理
class Link { // 负责数据的设置和取出
    private Node root; // 根节点
    public void add(String data) { // 增加数据
        // 为了可以设置数据的先后关系,所以讲data包装在一个Node类对象
        Node newNode = new Node(data);
        // 保存当前数据的时候,现在还没有根节点
        if (this.root == null) { // 一个链表只有一个根节点
            this.root = newNode; // 将新的节点设置为根节点
        } else { // 根节点已经存在
            // 随后增加的元素应由节点来确定其存放的位置
            // 从root节点之后找到合适的位置
            this.root.addNode(newNode);
        }
    }
    public void print() { // 输出数据
        if (this.root != null) { // 存在根节点
            this.root.printNode(); // 交给Node类输出
        }
    }
}
public class MainClass {
    public static void main(String[] args) {
        Link link = new Link(); // 由这个类负责所有的数据操作
        link.add("Hello"); // 存放数据
        link.add("World");
        link.add("haha");
        link.add("nihao");
        link.print(); // 展示数据
    }
}

通过以上的代码实际上就可以发现链表的基本操作特点:

  • 客户端代码不用去关注具体的Node以及引用关系的细节,只关注于提供的Link类中支持的方法;

  • Link类的主要功能是控制Node类对象的产生和根节点;

  • Node类主要负责数据的保存以及引用关系的分配。

开发可用链表

指的是可以使用链表实现数据的增加、修改、删除、查询操作。

程序基本结构

在开发具体的可用链表之前,首先必须明确一个道理:Node类负责所有的节点数据的保存以及节点关系的匹配。所以Node类不可能单独的去使用,而以上的实现里面Node是可以单独使用的,外部可以绕过Link类直接操作Node类,这种操作明显是没有任何意义的。所以下面必须修改设计结构,让Node类只能够被Link类使用。

这个时候使用内部类是一个最好的选择。内部类可以使用private定义,这样一个内部类只能够被一个外部类使用,并且,内部类可以方便的与外部类之间进行私有属性的直接访问。

范例: 链表的开发结构

class Link { // 链表类,外部能够看见的只有这一个类
    // 让其为Link类服务
    private class Node { // 定义的节点类
        private String data;
        private Node next;
        public Node(String data) {
            this.data = data;
        }
    }
    // ========================= 以上为内部类 =========================
    private Node root; // 需要根节点
}

随后进行的就是代码的填充,以及功能的完善。

数据增加:public void add(数据类型 变量)

如果要进行新数据的增加,则应该由Link类负责节点对象的产生,并且由Link类维护根节点,所有的节点关系匹配交给Node类处理。

class Link { // 链表类,外部能够看见的只有这一个类
    // 让其为Link类服务
    private class Node { // 定义的节点类
        private String data;
        private Node next;
        public Node(String data) {
            this.data = data;
        }
        public void addNode(Node newNode) {
            if (this.next == null) { // 当前的下一个节点为空
                this.next = newNode;
            } else { // 向后继续保存
                this.next.addNode(newNode);
            }
        }
    }
    // ========================= 以上为内部类 =========================
    private Node root; // 需要根节点
    public void add(String data) { // 假设不允许有null
        if (data == null) {
            return ;
        }
        Node newNode = new Node(data); // 要保存的数据
        if (this.root == null) { // 当前没有根节点
            this.root = newNode; // 保存根节点
        } else { // 根节点存在,其他节点交给Node类处理
            this.root.addNode(newNode);
        }
    }
}
public class MainClass {
    public static void main(String[] args) {
        Link all = new Link();
        all.add("Hello");
        all.add("World");
        all.add(null);
    }
}

此时使用了一个不许为空的判断。但并不是所有的链表都不许为空。

取得保存元素个数:public int size()

既然每一个链表对象都只有一个root根元素,那么每一个链表就有自己的长度,可以直接在Link类里面设置一个count属性,每一次数据添加完成之后,可以进行个数的自增。

范例: 修改Link类

  • 增加count属性

private int count = 0; // 保存元素的个数
  • 在add()方法里增加数据的统计操作

public void add(String data) { // 假设不允许有null
    if (data == null) {
        return ;
    }
    Node newNode = new Node(data); // 要保存的数据
    if (this.root == null) { // 当前没有根节点
        this.root = newNode; // 保存根节点
    } else { // 根节点存在,其他节点交给Node类处理
        this.root.addNode(newNode);
    }
    this.count ++; // 每一次保存完成后数据量加一
}
  • 随后为Link类增加一个新的方法:size()

public int size() { // 取得保存的数据量
    return this.count;
}

本程序之中null不会被保存。

判断空链表:public boolean isEmpty()

空链表判断实际上可以通过两种方式完成:

  • 第一个:判断root有对象(是否为null);

  • 第二个:判断保存的数据量(count)。

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

本次是链表组成内部的刨铣,所以一些简单的代码一定要清楚实现原理。

数据查询:public boolean contains(数据类型 变量)

在链表中一定会保存有多个数据,那么基本的判断数据是否存在的方式,以:String为例。循环链表中的内容,并且与要查询的数据进行匹配(equals()),如果查到了返回true,否则返回false。

范例: 修改Link类

public boolean contains(String data) {
    // 没有要查询的数据,也不存在根节点
    if (data == null || this.root == null) {
        return false; // 没有查询结果
    }
    return this.root.containsNode(data);
}

从根元素开始查询数据是否存在。

范例: 在Node类增加方法

// 第一次调用(Link):this = Link.root
// 第二次调用(Node):this = Link.root.next
// 第三次调用(Node):this = Link.root.next.next
public boolean containsNode(String data) {
    if (data.equals(this.data)) { // 当前节点数据为要查询的数据
        return true; // 不再向后查询
    } else { // 当前节点数据不满足查询要求
        if (this.next != null ) { // 有后续节点
            return this.next.containsNode(data);
        } else {
            return false;
        }
    }
}

范例: 定义测试程序

public class MainClass {
    public static void main(String[] args) {
        Link all = new Link();
        all.add("Hello");
        all.add("World");
        all.add(null);
        System.out.println(all.contains("Hello"));
        System.out.println(all.contains("heihei"));
    }
}

本次使用的是String型数据,所以判断数据的时候使用的是equals()方法,可是如果说现在要传递的是一个自定义对象呢?则需要定义一个对象比较的方法(暂时将方法名称定义为compare())。

根据索引取得数据:public 数据类型 get(int index)

通过以上的代码测试可以发现,链表里面保存了多个String类的对象,在程序里面只有数组可以保存多个对象,使用的链表与数组相比较的话,优势就是没有长度限制,所以链表严格意义上来讲就是一个动态对象数组,那么既然链表属于动态对象数组,那么也应该具备像数组那么可以根据索引取得元素的功能。

由于是动态对象数组,所以数组中的每一个元素的索引的内容都一定是动态生成的。

范例: 在Link类里面增加一个foot的属性,表示每一个Node元素的编号

private int foot = 0;

范例: 在每一次查询的时候(一个链表可能查询多次),那么foot应该在每一次查询时都从头开始;但是一定要注意查询的前提:root存在,并且要查询的索引小于链表个数。

public String get(int index) {
	if (index >= this.count) { // 操过了查询范围(索引从0开始)
		return null; // 没有数据
	}
	this.foot = 0; // 表示从前向后查询
	return this.root.getNode(index); // 将查询过程交给Node类处理
}

范例: 在Node类里面实现getNode()方法,内部类和外部类之间可以互相进行私有属性的访问。

public String getNode(int index) {
	// 使用当前的foot与要查询的索引进行比较
	// 随后将foot的内容自增,目的是为了进行下一步查询
	if (Link.this.foot ++ == index) { // 当前节点是要查询的索引
		return this.data; // 返回当前节点数据
	} else { // 继续向后查询
		return this.next.getNode(index);
	}
}

修改指定索引内容:public void set(int index, 数据类型 变量)

修改数据和查询数据区别不大,查询的时候当满足索引值得时候只是进行了一个数据的返回,那么此处只需要将数据返回变为数据的重新赋值即可。

范例: 在Link类里面增加set()方法

public void set(int index, String data) {
	if (index >= this.count) {
		return ; // 结束方法调用
	}
	this.foot = 0; // 重新设置foot属性的内容,作为索引出现
	this.root.setNode(index, data); // 交给Node类设置数据内容
}

范例: 在Node类里面增加setNode()方法

public void setNode(int index, String data) {
	if (Link.this.foot ++ == index) {
		this.data = data; // 进行内容的修改
	} else {
		this.next.setNode(index, data);
	}
}

数据删除:public void remove(数据类型 变量)

对于删除数据而言,可以分为两种情况:

  • 情况一:要删除的数据是根节点,则root应该变为“根节点.next”,由于root是由Link类进行维护,所以此操作应该在Link类中定义;

  • 情况二:要删除的是普通节点,应该在Node类中处理。删除数据的最终形式:当前节点上一节点.next = 当前节点.next,即:空出了当前节点。

范例: 在Node类里面增加一个removeNode()方法,此方法专门处理非根节点的删除

// 要传递上一个节点以及要删除的数据
public void removeNode(Node previous, String data) {
	if (data.equals(this.data)) { // 当前节点为要删除节点
		previous.next = this.next; // 空出当前节点
	} else { // 应该向后继续查询
		this.next.removeNode(this, data);
	}
}

范例: 在Link类里面增加根节点的判断

public void remove(String data) {
	if (this.contains(data)) { // 判断数据是否存在
		// 判断要删除的数据是否是根节点的数据
		if (data.equals(this.root.data)) {
			this.root = this.root.next; // 空出当前根节点
		} else { // 不是根元素
			this.root.next.removeNode(this.root, data);
		}
		this.count -- ; // 统计个数减少
	}
}

将链表变为对象数组:public 数据类型[] toArray()

任何情况下,不管是什么样的类都不可能在类中使用输出语句,想要输出数据,一定要将数据返回到调用处进行输出,而链表属于动态对象数组,所以此处最好的做法是将链表以对象数组的形式返回。

Link类的toArray()方法一定要返回一个对象数组,并且这个对象数组一定要被Node类操作,那么这个对象数组最好定义在Link类的属性里面。

范例: 修改Link类的定义

  • 增加一个返回的数组属性内容,之所以将其定义为属性,是因为内部类和外部类都可以访问;

private String[] retArray; // 返回的数组
  • 增加toArray()方法

public String[] toArray() {
	if (this.root == null) {
		return null;
	}
	this.foot = 0; // 需要索引控制
	this.retArray = new String[this.count]; // 根据保存内容个开辟数组
	this.root.toArrayNode(); // 交给Node类处理
	return this.retArray;
}

范例: 在Node类里面处理数组数据的保存

// 第一次调用(Link):this = Link.root
// 第二次调用(Node):this = Link.root.next
public void toArrayNode() {
	Link.this.retArray[Link.this.foot ++] = this.data;
	if (this.next != null) { // 有后续元素
		this.next.toArrayNode();
	}
}

链表数据变为对象数组取出是最为重要的功能。

使用链表、

以上给出的链表严格意义上来讲不能够使用,而且意义也不大,因为它所能够操作的数据类型只有String,毕竟String所保存的数据比较少,所以可以采用自定义类来进行链表的操作。

由于链表中要保存的对象需要实现contains()、remove()等功能,所以在类中要提供有对象比较方法的支持。

** 范例:**定义一个保存图书信息的类

class Book {
	private String title;
	private double price;
	public Book(String title, double price) {
		this.title = title;
		this.price = price;
	}
	public boolean compare(Book book) {
		if (this == book)
			return true;
		if (book == null)
			return false;
		if (this.price == book.price && this.title.equals(book.title))
			return true;
		return false;
	}
	public String getInfo() {
		return "title=" + title + ", price=" + price;
	}
}

范例: 修改链表实现

class Link { // 链表类,外部能够看见的只有这一个类
	// 让其为Link类服务
	private class Node { // 定义的节点类
		private Book data;
		private Node next;
		public Node(Book data) {
			this.data = data;
		}
		public void addNode(Node newNode) {
			if (this.next == null) { // 当前的下一个节点为空
				this.next = newNode;
			} else { // 向后继续保存
				this.next.addNode(newNode);
			}
		}
		// 第一次调用(Link):this = Link.root
		// 第二次调用(Node):this = Link.root.next
		// 第三次调用(Node):this = Link.root.next.next
		public boolean containsNode(Book data) {
			if (data.compare(this.data)) { // 当前节点数据为要查询的数据
				return true; // 不再向后查询
			} else { // 当前节点数据不满足查询要求
				if (this.next != null ) { // 有后续节点
					return this.next.containsNode(data);
				} else {
					return false;
				}
			}
		}
		public Book getNode(int index) {
			// 使用当前的foot与要查询的索引进行比较
			// 随后将foot的内容自增,目的是为了进行下一步查询
			if (Link.this.foot ++ == index) { // 当前节点是要查询的索引
				return this.data; // 返回当前节点数据
			} else { // 继续向后查询
				return this.next.getNode(index);
			}
		}
		public void setNode(int index, Book data) {
			if (Link.this.foot ++ == index) {
				this.data = data; // 进行内容的修改
			} else {
				this.next.setNode(index, data);
			}
		}
		// 要传递上一个节点以及要删除的数据
		public void removeNode(Node previous, Book data) {
			if (data.compare(this.data)) { // 当前节点为要删除节点
				previous.next = this.next; // 空出当前节点
			} else { // 应该向后继续查询
				this.next.removeNode(this, data);
			}
		}
		// 第一次调用(Link):this = Link.root
		// 第二次调用(Node):this = Link.root.next
		public void toArrayNode() {
			Link.this.retArray[Link.this.foot ++] = this.data;
			if (this.next != null) { // 有后续元素
				this.next.toArrayNode();
			}
		}
	}
	// ========================= 以上为内部类 =========================
	private Node root; // 需要根节点
	private int count = 0; // 保存元素的个数
	private int foot = 0;
	private Book[] retArray; // 返回的数组
	public void add(Book data) { // 假设不允许有null
		if (data == null) {
			return ;
		}
		Node newNode = new Node(data); // 要保存的数据
		if (this.root == null) { // 当前没有根节点
			this.root = newNode; // 保存根节点
		} else { // 根节点存在,其他节点交给Node类处理
			this.root.addNode(newNode);
		}
		this.count ++; // 每一次保存完成后数据量加一
	}
	public int size() { // 取得保存的数据量
		return this.count;
	}
	public boolean isEmpty() {
		return this.count == 0;
	}
	public boolean contains(Book data) {
		// 没有要查询的数据,也不存在根节点
		if (data == null || this.root == null) {
			return false; // 没有查询结果
		}
		return this.root.containsNode(data);
	}
	public Book get(int index) {
		if (index >= this.count) { // 操过了查询范围(索引从0开始)
			return null; // 没有数据
		}
		this.foot = 0; // 表示从前向后查询
		return this.root.getNode(index); // 将查询过程交给Node类处理
	}
	public void set(int index, Book data) {
		if (index >= this.count) {
			return ; // 结束方法调用
		}
		this.foot = 0; // 重新设置foot属性的内容,作为索引出现
		this.root.setNode(index, data); // 交给Node类设置数据内容
	}
	public void remove(Book data) {
		if (this.contains(data)) { // 判断数据是否存在
			// 判断要删除的数据是否是根节点的数据
			if (data.compare(this.root.data)) {
				this.root = this.root.next; // 空出当前根节点
			} else { // 不是根元素
				this.root.next.removeNode(this.root, data);
			}
			this.count -- ; // 统计个数减少
		}
	}
	public Book[] toArray() {
		if (this.root == null) {
			return null;
		}
		this.foot = 0; // 需要索引控制
		this.retArray = new Book[this.count]; // 根据保存内容个开辟数组
		this.root.toArrayNode(); // 交给Node类处理
		return this.retArray;
	}
}

范例: 实现测试

public class MainClass {
	public static void main(String[] args) {
		Link all = new Link();
		all.add(new Book("Java", 12.8));
		all.add(new Book("Oracle", 13.8));
		all.add(new Book("Linux", 14.8));
		System.out.println("保存书的个数:" + all.size());
		System.out.println(all.contains(new Book("Java", 12.8)));
		all.remove(new Book("Oracle", 13.8));
		Book[] books = all.toArray();
		for (int i = 0; i < books.length; i ++) {
			System.out.println(books[i].getInfo());
		}
	}
} 

链表的最好使用就是横向代替对象数组。

在关系中使用链表

链表是动态对象数组,那么在之前进行数据表映射的时候(本次只以一对多为主),都会出现对象数组的概念,所以现在就可以利用链表来实现对象数组的保存。

对于任何一个要使用链表的类而言,一定要提供有对象比较方法。

class Province { // 类名称就是表名称
    private int pid;
    private String name;
    private Link cities = new Link();
    public Province(int pid, String name) {
        this.pid = pid;
        this.name = name;
    }
    public boolean compare(Province province) {
    	if (this == province) 
    		return true;
    	if (province == null)
    		return false;
    	if (this.pid == province.pid && this.name.equals(province.name))
    		return true;
    	return false;
    }
    public Link getCities() {
        return this.cities;
    }
    public String getInfo() {
        return "pid=" + pid + ", name=" + name;
    }
}
class City {
    private int cid;
    private String name;
    private Province province;
    public City(int cid, String name) {
        this.cid = cid;
        this.name = name;
    }
    public boolean compare(City city) {
    	if (this == city)
    		return true;
    	if (city == null)
    		return false;
    	if (this.cid == city.cid && this.name.equals(city.name) 
    			&& this.province.compare(city.province)) 
    		return true;
    	return false;
    }
    public void setProvince(Province province) {
        this.province = province;
    }
    public Province getProvince() {
        return this.province;
    }
    public String getInfo() {
        return "cid=" + cid + ", name=" + name;
    }
}
class Link { // 链表类,外部能够看见的只有这一个类
	// 让其为Link类服务
	private class Node { // 定义的节点类
		private City data;
		private Node next;
		public Node(City data) {
			this.data = data;
		}
		public void addNode(Node newNode) {
			if (this.next == null) { // 当前的下一个节点为空
				this.next = newNode;
			} else { // 向后继续保存
				this.next.addNode(newNode);
			}
		}
		// 第一次调用(Link):this = Link.root
		// 第二次调用(Node):this = Link.root.next
		// 第三次调用(Node):this = Link.root.next.next
		public boolean containsNode(City data) {
			if (data.compare(this.data)) { // 当前节点数据为要查询的数据
				return true; // 不再向后查询
			} else { // 当前节点数据不满足查询要求
				if (this.next != null ) { // 有后续节点
					return this.next.containsNode(data);
				} else {
					return false;
				}
			}
		}
		public City getNode(int index) {
			// 使用当前的foot与要查询的索引进行比较
			// 随后将foot的内容自增,目的是为了进行下一步查询
			if (Link.this.foot ++ == index) { // 当前节点是要查询的索引
				return this.data; // 返回当前节点数据
			} else { // 继续向后查询
				return this.next.getNode(index);
			}
		}
		public void setNode(int index, City data) {
			if (Link.this.foot ++ == index) {
				this.data = data; // 进行内容的修改
			} else {
				this.next.setNode(index, data);
			}
		}
		// 要传递上一个节点以及要删除的数据
		public void removeNode(Node previous, City data) {
			if (data.compare(this.data)) { // 当前节点为要删除节点
				previous.next = this.next; // 空出当前节点
			} else { // 应该向后继续查询
				this.next.removeNode(this, data);
			}
		}
		// 第一次调用(Link):this = Link.root
		// 第二次调用(Node):this = Link.root.next
		public void toArrayNode() {
			Link.this.retArray[Link.this.foot ++] = this.data;
			if (this.next != null) { // 有后续元素
				this.next.toArrayNode();
			}
		}
	}
	// ========================= 以上为内部类 =========================
	private Node root; // 需要根节点
	private int count = 0; // 保存元素的个数
	private int foot = 0;
	private City[] retArray; // 返回的数组
	public void add(City data) { // 假设不允许有null
		if (data == null) {
			return ;
		}
		Node newNode = new Node(data); // 要保存的数据
		if (this.root == null) { // 当前没有根节点
			this.root = newNode; // 保存根节点
		} else { // 根节点存在,其他节点交给Node类处理
			this.root.addNode(newNode);
		}
		this.count ++; // 每一次保存完成后数据量加一
	}
	public int size() { // 取得保存的数据量
		return this.count;
	}
	public boolean isEmpty() {
		return this.count == 0;
	}
	public boolean contains(City data) {
		// 没有要查询的数据,也不存在根节点
		if (data == null || this.root == null) {
			return false; // 没有查询结果
		}
		return this.root.containsNode(data);
	}
	public City get(int index) {
		if (index >= this.count) { // 操过了查询范围(索引从0开始)
			return null; // 没有数据
		}
		this.foot = 0; // 表示从前向后查询
		return this.root.getNode(index); // 将查询过程交给Node类处理
	}
	public void set(int index, City data) {
		if (index >= this.count) {
			return ; // 结束方法调用
		}
		this.foot = 0; // 重新设置foot属性的内容,作为索引出现
		this.root.setNode(index, data); // 交给Node类设置数据内容
	}
	public void remove(City data) {
		if (this.contains(data)) { // 判断数据是否存在
			// 判断要删除的数据是否是根节点的数据
			if (data.compare(this.root.data)) {
				this.root = this.root.next; // 空出当前根节点
			} else { // 不是根元素
				this.root.next.removeNode(this.root, data);
			}
			this.count -- ; // 统计个数减少
		}
	}
	public City[] toArray() {
		if (this.root == null) {
			return null;
		}
		this.foot = 0; // 需要索引控制
		this.retArray = new City[this.count]; // 根据保存内容个开辟数组
		this.root.toArrayNode(); // 交给Node类处理
		return this.retArray;
	}
}
public class MainClass {
	public static void main(String[] args) {
		Link all = new Link();
		// 第一步:设置关系数据
		// 1、先准备好各自独立的对象
        Province pro = new Province(1, "Shaan xi");
        City c1 = new City(1001, "Xi'an");
        City c2 = new City(1002, "Baoji");
        City c3 = new City(1003, "Xianyang");
        // 2、设置关系
        c1.setProvince(pro); // 一个城市属于一个省份
        c2.setProvince(pro);
        c3.setProvince(pro);
        pro.getCities().add(c1);
        pro.getCities().add(c2);
        pro.getCities().add(c3);
        // 第二步:取出关系
        System.out.println(pro.getInfo());
        System.out.println("拥有的城市数量:" + pro.getCities().size());
        pro.getCities().remove(c3);
        City[] c = pro.getCities().toArray();
        for (int i = 0; i < c.length; i ++) {
        	System.out.println(c[i].getInfo());
        }
	}
}

本程序不再受到长度的限制,但是会出现一个新的问题:对于每一个新的类都要定义一个新的属于该类的Link类。

方法解决的是代码的重复问题,但是以上问题并不属于代码的重复,而是属于数据类型的不统一,所以之前学习到的内容就不足以解决这个问题。

总结

1、链表本次讲解的知识一个最基础的单项链表;

2、熟记以下方法的名称以及作用;

No.
方法名称
类型
描述

1

public void add(数据类型 变量)

普通

向链表之中增加新的数据

2

public int size()

普通

取得链表中保存的元素的个数

3

public boolean isEmpty()

普通

判断是否是空链表(size() == 0)

4

public boolean contains(数据类型 变量)

普通

判断某一个数据是否存在

5

public 数据类型 get(int index)

普通

根据索引取得数据

6

public void set(int index, 数据类型 变量)

普通

更新指定索引的数据

7

public void remove(数据类型 变量)

普通

删除指定数据,如果是对象则要进行对象比较

8

public 数据类型[] toArray()

普通

将链表以对象数组的形式返回

3、本次讲解的链表是日后进行Java类集学习的前期原理分析。

最后更新于

这有帮助吗?