如果想了解Java实现的顺序表,请戳这里:【数据结构】使用Java实现顺序表类 SeqList
上述文章介绍的顺序表作为数据存储结构有很多优点,最重要的就是顺序表的元素是随机存取的。但是顺序表也有很多缺点,主要的缺点有两个,一是插入和删除的效率低下,因为插入和删除操作需要变化部分元素的位置,时间复杂度是O(n)。二是数据一旦创建就不能改变大小,如果想扩大数组的容量,那么应该创建一个新的更大的新数组,将原数组复制到新数组中。这是十分不方便的。
于是为了解决上述问题,链表就出现了,链表的主要特点是:1、链表的大小是动态的,随着数据的增加或者删除动态改变长度。2、链表的插入、删除十分高效,对于经常需要插入和删除数据的应用,非常适合使用链表作为数据存储结构。
顺序表和链表都属于线性表。
在看代码之前,我们先看看链表需要完成什么任务,也就是链表的API,这样有助于理解代码,也有助于快速查找自己想实现的功能。
可能要注意的点:
1、使用代码的时候记得把package改成自己的文件所在的包名。
2、代码中LinkList继承的父类AbsList在这里没有写,因为写代码时,我在顺序表SeqList中实现了AbsList类,这个AbsList默认是default,也就是包访问权限(default修饰的类是包访问的),然后我把SeqList和LinkList两个类放在一个包中了。所以其实LinkList使用的AbsList其实是SeqList类中的AbsList,所以这里就使用的是上篇文章中的SeqList中的AbsList,所以如果想让这个类可以使用,去上面文章中找AbsList类copy一下就好了。或者点击---->传送门也能到达
/*
* created on April 16 14:40 2019
*
* @author:lhy
*/
package DS;
import java.util.Iterator;
//创建节点类Lnode
class Lnode<T> implements Comparable<Lnode<T>> {
public T data; // 节点值
public Lnode<T> next; // 保存下个节点
// Lnode构造函数
public Lnode(T key) {
data = key;
next = null;
}
public Lnode(T key, Lnode<T> next) {
data = key;
this.next = next;
}
// 判断节点值是否相等
public boolean equals(Object e) {
Lnode<T> node = (Lnode<T>) e;
return data.equals(node.data);
}
// 实现Comparable的compareTo方法,用于比较链表节点比较大小
public int compareTo(Lnode<T> e) {
Comparable<T> x;
if(data instanceof Comparable) {
x=(Comparable<T>)data;
return (int)x.compareTo(e.data);
}
else {
throw new ClassCastException("类型无法比较");
}
}
//将该节点的值变成字符串格式
public String toString() {
return data.toString();
}
}
/*
* LinkList API介绍
*
* LinkList(): 构造函数
* clear(): 删除整个列表
* removeAll(): 删除整个列表,调用clear函数来实现
* getNode(int i): 获得逻辑上i号节点
* get(int i): 获得逻辑上i号节点的值
* set(int i,T x): 修改i号节点的值
* add(int i,T x): 将值为x的节点插入到i号位置
* add(T key): 在链表尾部插入元素key
* addBack(T key): 在链表尾部插入元素key,和add(T key)函数的作用一样
* addFront(T key): 在链表首部插入元素key
* remove(int i): 删除i号节点,并返回i号节点对应的值
* remove(): 重载remove,删除头节点
* removeFront(): 删除链表头节点,与remove()函数同义
* removeBack(): 删除链表尾节点
* addSort(T value): 将值value按从小到大的排序方式插入链表
* sort(): 对链表按照从小到大的顺序进行排序
* indexOf(int begin,int end,T key): 在索引begin和end之间查找值key,返回逻辑编号
* search(T key): 功能同indexOf,遍历整个链表,一般不使用,主要用于实现字典
* contains(T key): 判断链表中是否存在值为key节点
* toString(): 将链表中的值转换成字符串
* toArray(): 将链表转换成Object数组
* toArray(E[] a): 将单链表转化为特定类型的数组,使用了函数泛型
* iterator(): 返回迭代器对象
*/
public class LinkList<T> extends AbsList<T> implements Iterable<T> {
//LinkList继承了SeqList中的AbsList抽象类,继承了Iterable接口
Lnode<T> first=null,last=null; //头指针和尾指针
Iterator<T> iterator=null; //指向当前节点的迭代器
//构造函数
public LinkList() {
first=last=null;
length=0;
this.iterator=new LinkIterator();
}
//比较器,供内部使用
private int compare(Lnode<T> a,Lnode<T> b) {
return a.compareTo(b);
}
//删除整个列表
public void clear() {
first=last=null;
length=0;
}
//删除整个列表,调用clear函数来实现
public void removeAll() {
clear();
}
//获得逻辑上的i号节点
public Lnode<T> getNode(int i){
//判断i号节点是否越界
if(i<0||i>length-1) {
return null;
}
//判断i号是否为头节点(其实可以不用判断)
if(i==0) {
return first;
}
else {
Lnode<T> p=first;
int j=0;
while (p!=null&&j<i) {
p=p.next;
j++;
}
return p;
}
}
//获得i号节点的值,调用getNode完成具体工作
public T get(int i) {
Lnode<T> pLnode=getNode(i);
if(pLnode==null)
return null;
else
return pLnode.data;
}
//修改i号节点的值
public boolean set(int i,T x) {
Lnode<T> p=getNode(i);
if(p==null) {
return false;
}
else {
p.data=x;
return true;
}
}
//将值为x的节点插入到i号位置
public void add(int i,T x) {
Lnode<T> p,s;
int j=i-1;
s=new Lnode<T>(x,null);
//在空链表中插入节点s
if(first==null||length==0) {
first=s;
last=s;
}
//在头节点之前插入节点s,即i=0时
else if(j<0) {
s.next=first;
first=s;
}
//在链表尾部插入节点s
else if(j>=length-1) {
last.next=s;
last=s;
}
//在链表中间插入节点s
else {
p=getNode(j);
s.next=p.next;
p.next=s;
}
length++;
}
//重载add()函数,在链表尾部插入元素key
public void add(T key) {
add(length,key);
}
//在链表尾部插入元素key,和add(T key)函数的作用一样
public void addBack(T key) {
add(length,key);
}
//在链表首部插入元素key
public void addFront(T key) {
add(0,key);
}
//删除i号节点,并返回i号节点对应的值,通过调用removeNode实现
public T remove(int i) {
Lnode<T> p=removeNode(i);
if(p!=null)
return p.data;
else
return null;
}
//核心算法,删除逻辑上的i号节点,内部使用,返回Lnode
protected Lnode<T> removeNode(int i){
Lnode<T> p,q;
//判断链表是否为空
if(first==null) {
return null;
}
//删除头节点
if(i==0) {
p=first;
first=first.next;
length-=1;
return p;
}
//删除中间节点或尾节点
if(i>=1 && i<=length-1) {
//首先获得i-1位置所在的节点
p=getNode(i-1);
//获得i节点位置上的节点
q=p.next;
p.next=q.next;
if(q==last) {
last=p;
}
length--;
return q;
}
return null;
}
//重载remove,删除头节点
public T remove() {
return removeNode(0).data;
}
//删除头节点,与remove()函数同义
public T removeFront() {
return removeNode(0).data;
}
//删除尾节点
public T removeBack() {
return removeNode(length-1).data;
}
//将值value按从小到大的排序方式插入链表,调用insertOrder实现
public void addSort(T value) {
Lnode<T> s=new Lnode(value);
insertOrder(s);
}
//有序插入的核心算法
private void insertOrder(Lnode<T> s) {
Lnode<T> p1,p2;
length++;
//在空链表中插入节点
if(first==null) {
first=s;
last=first;
return ;
}
//小于头节点的值,将节点插入到头节点之前
if(compare(s, first)<0) {
s.next=first;
first=s;
return ;
}
//大于最后一个节点值,将节点在链表尾部插入
if(compare(s, last)>0) {
last.next=s;
last=s;
return ;
}
//被插入的节点p在p1和p2之间,p1在前,p2在后
p2=first;
p1=p2;
while(p2!=null) {
//s节点比p2节点大时,将p1等于p2,p2后移一个位置,直到s小于等于p2时停止循环,这时s节点的值在p1和p2之间
if(compare(s, p2)>0) {
p1=p2;
p2=p2.next;
}
else {
break;
}
}
s.next=p2;
p1.next=s;
return;
}
//对链表排序
public void sort() {
LinkList<T> sl=new LinkList<T>();//创建一个存放有序序列的链表
Lnode<T> p;
p=this.removeNode(0);//取出无序链表的头节点
while(p!=null) {
sl.addSort(p.data);
p=this.removeNode(0);
}
Lnode<T> q=sl.first;
while(q!=null) {
this.add(q.data);
q=q.next;
}
}
//在索引begin和end之间查找值key,返回逻辑编号
public int indexOf(int begin,int end,T key) {
Lnode<T> p=getNode(begin);//获取开始节点
int i=begin;
while(p!=null&&i<end) {
if(p.data.equals(key))
return i;
p=p.next;
i++;
}
return -1;
}
//功能同indexOf,一般不使用,主要用于实现字典
public T search(T key) {
Lnode<T> p=getNode(0);
while(p!=null) {
if(p.data.equals(key)) {
return p.data;
}
p=p.next;
}
return null;
}
//判断链表中是否存在值为key节点
public boolean contains(T key) {
if(indexOf(0,length,key)==-1) return false;
else return true;
}
//将链表中的值转换成字符串
public String toString() {
String string;
Lnode<T> pLnode;
pLnode=first;
string="(";
while(pLnode!=null) {
string+=pLnode.data.toString()+" ";
pLnode=pLnode.next;
}
return string+")";
}
//将链表转换成Object数组
public Object[] toArray() {
Object[] a=new Object[length];
Lnode<T> pLnode=first;
for(int i=0;i<length;i++) {
a[i]=pLnode.data;
pLnode=pLnode.next;
}
return a;
}
//将单链表转化为特定类型的数组,使用了函数泛型
public <E> E[] toArray(E[] a) {
if(a.length<length) {
//创建一个长度为length(和链表长度相等),类型为数组a的元素类型的数组,并强制转换成E[]类型
a=(E[])java.lang.reflect.Array.newInstance(a.getClass().getComponentType(), length);
}
int i=0;
Object[] result=a;
Lnode<T> xLnode=this.first;
for(i=0;i<length;i++) {
result[i]=xLnode.data;
xLnode=xLnode.next;
}
if(a.length>length) {
a[length]=null;
}
return a;
}
//返回迭代器对象
public Iterator<T> iterator(){
return new LinkIterator();
}
//内部类,实现迭代器
private class LinkIterator implements Iterator<T>{
private int index=0;
private Lnode<T> current=first;
public boolean hasNext() {
//在调用next()之后,index自增,确保index不等于person的长度
return (index!=length() && current!=null);
}
public T next() {
T temp=current.data;
current=current.next;
index++;
return temp;
}
public int nextIndex() {
return index++;//先返回index的值,后加1
}
public void remove() {
//未实现本方法
}
}
}
创建一个LinkList链表对象,对LinkList类中各个函数进行实现检验,代码如下:
/*
* created on April 16 15:40 2019
*
* @author:lhy
*/
package DS;
import java.util.Iterator;
/*
* LinkList API介绍
*
* LinkList(): 构造函数
* clear(): 删除整个列表
* removeAll(): 删除整个列表,调用clear函数来实现
* get(int i): 获得逻辑上i号节点的值
* set(int i,T x): 修改i号节点的值
* add(int i,T x): 将值为x的节点插入到i号位置
* add(T key): 在链表尾部插入元素key
* addBack(T key): 在链表尾部插入元素key,和add(T key)函数的作用一样
* addFront(T key): 在链表首部插入元素key
* remove(int i): 删除i号节点,并返回i号节点对应的值
* remove(): 重载remove,删除头节点
* removeFront(): 删除链表头节点,与remove()函数同义
* removeBack(): 删除链表尾节点
* addSort(T value): 将值value按从小到大的排序方式插入链表
* sort(): 对链表按照从小到大的顺序进行排序
* indexOf(int begin,int end,T key): 在索引begin和end之间查找值key,返回逻辑编号
* search(T key): 功能同indexOf,遍历整个链表,一般不使用,主要用于实现字典
* contains(T key): 判断链表中是否存在值为key节点
* toString(): 将链表中的值转换成字符串
* toArray(): 将链表转换成Object数组
* toArray(E[] a): 将单链表转化为特定类型的数组,使用了函数泛型
* iterator(): 返回迭代器对象
*/
public class Test_LinkList {
public static void main(String[] args) {
LinkList<Integer> linkList=new LinkList<Integer>();
//对linkList赋值
for(int i=0;i<10;i++) {
linkList.add((int)(Math.random()*1000));
}
System.out.println("由系统随机数生成的链表为:"+linkList.toString());
System.out.println("获取7号节点的链表元素为:"+linkList.get(7));
//将下标为1的节点值修改为2333
linkList.set(1,2333);
System.out.println("将1号节点值修改为2333,然后输出:"+linkList.toString());
//向3号位置插入值为5555的节点
linkList.add(3,5555);
System.out.println("向3号节点位置插入值为5555的节点,输出链表:"+linkList.toString());
//向链表尾部插入值为9999的节点
linkList.add(9999);
System.out.println("向链表尾部插入值为9999的节点,输出链表:"+linkList.toString());
//向链表首部插入值为12345的节点
linkList.addFront(12345);
System.out.println("向链表首部插入值为12345的节点,输出链表:"+linkList.toString());
System.out.println("删除4号节点,返回值为:"+linkList.remove(4));
System.out.println("删除4号节点后,输出链表"+linkList.toString());
//删除尾节点
linkList.removeBack();
System.out.println("删除尾节点后,输出链表:"+linkList.toString());
//对linkList进行排序
linkList.sort();
System.out.println("排序后的链表为:"+linkList.toString());
//向链表中有序插入节点值为666的节点
linkList.addSort(666);
System.out.println("向链表中有序插入节点值为666的节点后,输出链表:"+linkList.toString());
System.out.println("查找值为666的节点在链表中位置为:"+linkList.indexOf(0, linkList.length,666));
System.out.println("判断链表中是否有值为665的节点:"+linkList.contains(665));
//获得一个链表迭代器对象
Iterator<Integer> iterator=linkList.iterator();
//使用迭代器输出链表元素
System.out.print("使用迭代器输出链表元素:");
while(iterator.hasNext()) {
System.out.print(iterator.next()+" ");
}
//清空链表
linkList.clear();
System.out.println("\n输出清空后的链表:"+linkList.toString());
}
}
输出:
由系统随机数生成的链表为:(834 738 152 568 863 608 495 975 742 475 )
获取7号节点的链表元素为:975
将1号节点值修改为2333,然后输出:(834 2333 152 568 863 608 495 975 742 475 )
向3号节点位置插入值为5555的节点,输出链表:(834 2333 152 5555 568 863 608 495 975 742 475 )
向链表尾部插入值为9999的节点,输出链表:(834 2333 152 5555 568 863 608 495 975 742 475 9999 )
向链表首部插入值为12345的节点,输出链表:(12345 834 2333 152 5555 568 863 608 495 975 742 475 9999 )
删除4号节点,返回值为:5555
删除4号节点后,输出链表(12345 834 2333 152 568 863 608 495 975 742 475 9999 )
删除尾节点后,输出链表:(12345 834 2333 152 568 863 608 495 975 742 475 )
排序后的链表为:(152 475 495 568 608 742 834 863 975 2333 12345 )
向链表中有序插入节点值为666的节点后,输出链表:(152 475 495 568 608 666 742 834 863 975 2333 12345 )
查找值为666的节点在链表中位置为:5
判断链表中是否有值为665的节点:false
使用迭代器输出链表元素:152 475 495 568 608 666 742 834 863 975 2333 12345
输出清空后的链表:()