一、思路
上一篇文章,在添加元素时暴力的将其添加到head的后面,如果要根据key的顺序有序的添加怎么办?
我们在添加的时候循环判断key的大小即可,就是和循环到的节点的key比较,但是要比较的话,就得提供比较规则,这个时候就可以使用Comparable,让key extends Comparable,这样key就可以使用compareTo方法了。
二、Java代码实现
直接看put方法即可,其他不变
/**
* 特别注意创建符号类时key extends Comparable,这样key就是可比较的
* 就可以使用compareTo方法,
* 另外需要注意,还要考虑到key已经存在的情况
*/
package mypackage;
import java.util.Iterator;
//<key,value>泛型表示类型
class SymbolTable<key extends Comparable,value> implements Iterable<value>{
//记录头节点
private Node head;
//记录符号表的长度,即节点的个数
private int N;
//定义节点类,这一步至关重要
//注意构造方法的参数有一个是next,这个类型是Node,正是我们定义的这个类Node
//这样的话,我们每个节点就可以传入一个参数,next,然后用当前结点调用.next属性就可以获得下一个节点
//这就是单项符号表链接的核心
private class Node{
//键、值和下一个节点项
key key;
value value;
Node next;
//构造方法
public Node(key key, value value,Node next){
this.key=key;
this.value=value;
this.next=next;
}
}
//构造方法
public SymbolTable(){
//初始化头节点,初步的头节点的键值和下一个节点项都是null,节点个数为0
//头节点的数据项本身就定义为null,他的作用只是指向符号表的第一个由数据的节点而已
//尾节点的下一个节点项本身就定义为null,表示符号表的结束
this.head=new Node(null,null,null);
this.N=0;
}
//清空符号表,使得头节点不指向任何节点,且节点个数为0
public void clear(){
head.next=null;
this.N=0;
}
//获取节点个数
public int length(){
return N;
}
//判断符号表是否为空
public boolean isEmpty(){
return N==0;
}
// 有序符号表只需要确保插入时有序即可
// 插入前循环遍历符号表,如果要插入的key较大,就继续向后循环,
// 直到待插入的key<=当前key,就找打了插入的位置
// 如果待插入的key==当前key,说明之前就存在key,直接替换
// 如果待插入的key<当前key,插入,让当前节点的前一个节点指向新节点,新节点指向当前节点
// 最后N++
public void put(key key ,value value){
Node prenode=head;
Node curnode=head.next;
// key.compareTo(curnode.key)>0,表示判断key大于curnode.key,成立就继续循环
while (curnode!=null&&key.compareTo(curnode.key)>0){
prenode=curnode;
curnode=curnode.next;
}
// 跳出循环表示找到了key<=当前key
// 如果key==当前key,直接替换
if (curnode!=null&&key.compareTo(curnode.key)==0){
curnode.value=value;
return;
}
// 如果key<当前key
else {
// 新节点指向curnode,pre指向newnode,且N++
Node newnode=new Node(key,value,curnode);
prenode.next=newnode;
N++;
}
}
// 根据删除键值对
public void delete(key key){
Node node=head;
while(node.next!=null){
node=node.next;
if (node.key==key){
// 如果找到key。让head指向当前node的下一个节点
head.next=node.next;
// 删除后,N--
N--;
return;
}
}
}
// 查找值,循环遍历,如果找到key返回value
public value get(key key){
Node node=head;
while(node.next!=null){
node=node.next;
if (node.key==key){
// 如果找到key。返回值
return node.value;
}
}
// 循环完了还没找到,返回null
return null;
}
// 遍历
@Override
public Iterator iterator() {
return new SIterator();
}
// 创建一个内部类实现Iterator接口
public class SIterator implements Iterator {
// 定义一个遍历的节点
private Node n;
public SIterator(){
// 初始化为0索引位置
this.n=head;
}
//重写两个方法
@Override
public boolean hasNext() {
// 这个方法判断是否超出最大索引,如果超出会停止遍历
return n.next!=null;
}
@Override
public Object next() {
// 这个方法会遍历得每个节点
n=n.next;
// 迭代处的格式为键:值
return n.key+":"+n.value;
}
}
}
//测试
public class MyJava {
public static void main(String[] args) {
// 创建一个符号表
SymbolTable<Integer,String> symbolTable = new SymbolTable<>();
// 添加节点节点,注意这里打乱循序的存,看最后是不是有序的
symbolTable.put(3,"张飞");
symbolTable.put(4,"赵云");
symbolTable.put(1,"刘备");
symbolTable.put(2,"关羽");
symbolTable.put(5,"黄忠");
symbolTable.put(6,"马超");
System.out.print("添加元素后符号表的值为(和key的顺序是一样的):");
for (String s:symbolTable){
System.out.print(s+";");
}
System.out.println();
System.out.println("元素个数:"+symbolTable.length());
// 替换了1
symbolTable.put(1,"诸葛亮");
System.out.println("替换后1处的元素是"+symbolTable.get(1));
// 删除
symbolTable.delete(1);
System.out.println("删除后元素个数为:"+symbolTable.length());
}
}
结果:
三、key extends Comparable的理解
Comparable是一个接口,在我们的印象中,类继承extends类,类implements接口,接口extends接口,那么key只是一个泛型的占位符,后面extends Comparable,所以key是一个接口?其实这里的概念很模糊,以下截图自一篇文章:
因此,无论T是接口还是类,但是有一点必须要注意,那么就是T一定要实现了Comparable接口,才能使用比较方法,比如本文的Integar内部其实是实现了compareTo方法的。
PS:以下截图来自Java核心技术: