单链表
- 单链表结点
//单链表结点类
public class Node<T> //单链表结点类,T指定结点的元素类型
{
public T data; //数据域,保存数据元素
public Node<T> next; //地址域,引用后继结点
public Node(T data, Node<T> next) //构造结点,data指定数据元素,next指定后继结点
{
this.data = data;
this.next = next;
}
public Node()
{
this(null, null);
}
//4、Node类可声明以下方法:
public String toString() //返回结点元素值对应的字符串
{
return this.data.toString();
}
public boolean equals(Object obj) //比较两个结点值是否相等,覆盖Object类的equals(obj)方法
{
return obj==this || obj instanceof Node && this.data.equals(((Node<T>)obj).data);
}
}
/*问题讨论
1、 当一个类没有声明构造方法时,Java提供默认构造方法。例如
public Node() //提供默认构造方法
{
super(); //默认调用语句
}
当一个类声明了构造方法时,Java不再提供默认构造方法。
例如,当Node类声明了Node(T data, Node<T> next)构造方法时,Java不再提供默认构造方法Node()。
如果Node类需要Node()构造方法,必须自己声明。
2、 Java方法参数没有默认值。例如,Node类可以声明以下构造方法
public Node(T data)
{
this(data, null);
}
但不能将Node(T data, Node<T> next)构造方法声明为如下形式:
public Node(T data, Node<T> next=null)
3、Java不提供默认拷贝构造方法。
Node类不需要拷贝构造方法。若拷贝构造方法以下,复制p引用的结点,
public Node(Node<T> p) //拷贝构造方法
{
this(p.data, p.next);
}
相当于
public Node(Node<T> p)
{
this.data = p.data;
this.next = p.next; //将p结点作为当前结结点的后继结点,含义不对了
}
5、不能声明如下,比较结点值大小
public class Node<T> implements Comparable<Node<T>> //单链表结点类
{
public int compareTo(Node<T> p) //比较相等,比较大小
{
return this.data.compareTo(p.data);
}
}
排序单链表应该要求比较T对象大小,不能要求比较结点大小。
*/
2、带头结点的单链表类,实现线性表接口
//public class SinglyLinkedList<T> implements LList<T> //2.3 线性表的链式表示和实现
public class SinglyLinkedList<T> extends AbstractLList<T> implements LList<T>//第10章,10.2 实现迭代器
{
public Node<T> head; //头指针,指向单链表的头结点
public SinglyLinkedList() //默认构造方法,构造空单链表
{
this.head = new Node<T>(); //创建头结点,data和next值均为null
}
//由指定数组中的多个对象构造单链表。采用尾插入构造单链表
//若element==null,Java将抛出空对象异常;若element.length==0,构造空链表
public SinglyLinkedList(T[] element)
{
this(); //创建空单链表,只有头结点
Node<T> rear=this.head; //rear指向单链表最后一个结点
for (int i=0; i<element.length; i++) //若element==null,抛出空对象异常
{ //element.length==0时,构造空链表
rear.next=new Node<T>(element[i],null); //尾插入,创建结点链入rear结点之后
rear = rear.next; //rear指向新的链尾结点
}
}
public boolean isEmpty() //判断单链表是否空,O(1)
{
return this.head.next==null;
}
//以下length()、toString()、get()、set()方法基于单链表遍历算法
public int length() //返回单链表长度,O(n)
{
int i=0;
Node<T> p=this.head.next; //p从单链表第一个结点开始
while (p!=null) //若单链表未结束
{ i++;
p = p.next; //p到达后继结点
}
return i;
}
//返回单链表所有元素的描述字符串,形式为“(,)”,覆盖Object类的toString()方法,O(n)
public String toString()
{
String str="(";
for (Node<T> p = this.head.next; p!=null; p=p.next)
{ str += p.data.toString();
if (p.next!=null)
str += ","; //不是最后一个结点时后加分隔符
}
return str+")"; //空表返回()
}
public T get(int i) //返回第i(≥0)个元素,若i<0或大于表长则返回null,O(n)
{
if (i>=0)
{
Node<T> p=this.head.next;
for (int j=0; p!=null && j<i; j++)
p = p.next;
if (p!=null)
return p.data; //p指向第i个结点
}
return null; //当i<0或大于表长时
}
//设置第i(≥0)个元素值为x。若i<0或大于表长则抛出序号越界异常;若x==null,不操作。O(n)
public void set(int i, T x)
{
if (x==null) return; //不能设置元素为空对象
Node<T> p=this.head.next;
for (int j=0; p!=null && j<i; j++)
p = p.next;
if (i>=0 && p!=null)
p.data = x; //p指向第i个结点
else throw new IndexOutOfBoundsException(i+""); //抛出序号越界异常
}
//以下insertAfter()、insert()、append()算法讨论单链表插入操作
/* //插入x作为p结点的后继结点,若操作成功返回新插入结点;否则返回null,O(1)
public Node<T> insertAfter(Node<T> p, T x)
{
if (x==null || p==null)
return null;
Node<T> q=new Node<T>(x, p.next); //插入x作为p结点的后继结点
p.next = q;
return q;
}*/
//插入第i(≥0)个元素值为x。若x==null,不插入。
//若i<0,插入x作为第0个元素;若i大于表长,插入x作为最后一个元素。O(n)
public void insert(int i, T x)
{
if (x==null) return; //不能插入空对象
Node<T> p=this.head; //p指向头结点
for (int j=0; p.next!=null && j<i; j++) //寻找插入位置
p = p.next; //循环停止时,p指向第i-1结点或最后一个结点
p.next = new Node<T>(x, p.next); //插入x作为p结点的后继结点,包括头插入(i<=0)、中间/尾插入(i>0)
}
public void append(T x) //在单链表最后添加x对象,O(n)
{
insert(Integer.MAX_VALUE, x); //遍历一次
// insert(this.length(), x); //需遍历单链表两次,效率较低
// this.insertAfter(this.getLast(),x)!=null; //遍历一次
}
/*
//将x对象插入在序号为i结点,若操作成功返回新插入结点;否则返回null,O(n)
public Node<T> insert(int i, T x)
{
if (x==null)
return null; //不能插入空对象
Node<T> p=this.head; //p指向头结点
for (int j=0; p.next!=null && j<i; j++) //寻找插入位置
p = p.next; //循环停止时,p指向第i-1结点或最后一个结点
Node<T> q=new Node<T>(x, p.next); //插入x作为p结点的后继结点
p.next = q; //包括头插入(i<=0)、中间/尾插入(i>0)
return q;
}*/
//以下removeAfter()、remove()、removeAll()算法实现单链表删除操作
/* //删除p结点的后继结点,若操作成功返回删除结点;否则返回null,O(1)
public Node<T> removeAfter(Node<T> p)
{
if (p==null || p.next==null)
return null;
Node<T> q=p.next;
p.next = q.next; //删除p结点的后继结点q
return q;
}*/
//删除第i(≥0)个元素,返回被删除对象。若i<0或i大于表长,不删除,返回null。O(n)
public T remove(int i)
{
if (i>=0)
{
Node<T> p=this.head;
for (int j=0; p.next!=null && j<i; j++) //定位到待删除结点(i)的前驱结点(i-1)
p = p.next;
if (p.next!=null)
{
T old = p.next.data; //获得原对象
p.next = p.next.next; //删除p的后继结点
return old;
}
}
return null; //当i<0或大于表长时
// throw new IndexOutOfBoundsException(i+""); //抛出序号越界异常
}
public void removeAll() //删除单链表所有元素
{
this.head.next=null; //Java将自动收回各结点所占用的内存空间
}
/* 7. 提高单链表操作效率的措施
算法可行,但效率低,时间复杂度是O(n*n)。
public String toString()
{
String str="(";
if (this.length()!=0)
{
for(int i=0; i<this.length()-1; i++)
str += this.get(i).toString()+", ";
str += this.get(this.length()-1).toString();
}
return str+")";
}
*/
//8. 单链表的浅拷贝与深拷贝
//深拷贝构造方法,复制单链表list的所有结点构造新的单链表
public SinglyLinkedList(SinglyLinkedList<T> list)
{
this(); //创建空单链表,只有头结点
Node<T> rear = this.head;
for (Node<T> p=list.head.next; p!=null; p=p.next) //若l