链表可能是继数组后第二种使用的最广泛的通用存储结构,链表可以取代数组,作为其他存储结构的基础,例如栈,队列.除非需要频繁通过下标随机访问各个数据,否则在很多实用数组的地方都可以实用链表代替.
1.链接点
在链表中,每个数据项都被包含在”链接点(Link)中”.一个链接点是某个类的对象,这个类可以叫做Link.每个Link对象中包含一个对下一个链接点引用的字段(通常叫做next).链表本省对象中有一个字段指向对第一个链接点的引用.
如图,
2.单链表
第一个程序LinkList.java显示了一个单链表.一共有三个操作
>在链表头插入一个数据项
>在链表头删除一个数据项
>遍历链表显示它的内容
代码
package chap5_链表;
/**
* Link对象
* @author yangdandan
*
*/
public class Link {
public int iData;//数据
public double dData;//数据
public Link next;//对下一个Link对象的引用
public Link(int id, double dd) {
iData = id;
dData = dd;
}
public void displayLink(){
System.out.println("{"+iData+", "+dData+"}");
}
}
package chap5_链表;
/**
* LinkList类
* @author yangdandan
*
*/
public class LinkList {
private Link first;
public void LinkList() {//链表为空
first = null;
}
public boolean isEmpty(){//链表是否为空
return (first==null);
}
//表头插入链接点
public void insertFirst(int id,double dd){
Link newLink = new Link(id,dd);
newLink.next = first;//newLink-->old first
first = newLink;//first-->newLink
}
//删除表头的链接点
public Link deleteFirst(){
Link temp = first;
first = first.next;//first-->old next
return temp;
}
//遍历链表
public void displayList(){
System.out.println("list(first-->last): ");
Link current = first;//current首先指向first
while(current != null){//链表尾端为null
current.displayLink();
current = current.next;
}
System.out.println("");
}
}
测试:
package chap5_链表;
/**
* 单向链表测试类
* @author yangdandan
*
*/
public class LinkListApp {
public static void main(String[] args) {
LinkList lList = new LinkList();
lList.insertFirst(22, 2.99);
lList.insertFirst(44, 4.99);
lList.insertFirst(66, 6.99);
lList.insertFirst(88, 8.99);
lList.displayList();
while(!lList.isEmpty()){
Link aLink = lList.deleteFirst();
System.out.print("Deleted ");
aLink.displayLink();
System.out.println("");
}
lList.displayList();
}
}
运行结果:
list(first-->last):
{88, 8.99}
{66, 6.99}
{44, 4.99}
{22, 2.99}
Deleted {88, 8.99}
Deleted {66, 6.99}
Deleted {44, 4.99}
Deleted {22, 2.99}
list(first-->last):
3.查找和删除指定节点
代码:
package chap5_链表_插入删除指定;
/**
* Link对象
* @author yangdandan
*
*/
public class Link {
public int iData;//数据
public double dData;//数据
public Link next;//对下一个Link对象的引用
public Link(int id, double dd) {
iData = id;
dData = dd;
}
public void displayLink(){
System.out.println("{"+iData+", "+dData+"}");
}
}
find()
查找链接点的时候,先让current变量指向first,然后通过不断让自己赋值为current.next,沿着链表向前移动.遍历每一个链接点,看它是否和要寻找的链接点的关键值相等,找到就返回对该链接点的引用.如果find()遍历到链表的尾端,没有找到对应的链接点,则返回null.
delete()
删除指定节点的时候,需要找到对应的节点,这个逻辑和find()方法类似.删除链接点需要获取要被删除的链接点current,然后将前一个链接点previous的next指向当前链接点的下一个,即current.next.
效果图,
package chap5_链表_插入删除指定;
/**
* LinkList类
*
* @author yangdandan
*
*/
public class LinkList {
private Link first;
public void LinkList() {// 链表为空
first = null;
}
public boolean isEmpty() {// 链表是否为空
return (first == null);
}
// 表头插入链接点
public void insertFirst(int id, double dd) {
Link newLink = new Link(id, dd);
newLink.next = first;// newLink-->old first
first = newLink;// first-->newLink
}
// 查找指定节点
public Link find(int key) {
Link current = first;
while (current.iData != key) {// 没有找到链接点,则比较下一个链接点
if (current.next == null) {// 如果到达表尾
return null;
}
current = current.next;
}
return current;
}
// 删除指定链接点
public Link delete(int key) {
Link current = first;
Link previous = first;
while (current.iData != key) {
// 找到对应的链接点
if (current.next == null) {
return null;
} else {
// 获取当前链接点和它的前一个链接点
previous = current;
current = current.next;
}
}
// 如果只有一个链接点,则只需要删除第一个链接点,first指向表尾
if (current == first) {
first = first.next;
} else {
// 前一个链接点指向当前链接点的下一个链接点
previous.next = current.next;
}
return current;
}
// 遍历链表
public void displayList() {
System.out.println("list(first-->last): ");
Link current = first;// current首先指向first
while (current != null) {// 链表尾端为null
current.displayLink();
current = current.next;
}
System.out.println("");
}
}
测试类:
package chap5_链表_插入删除指定;
/**
* 单向链表测试类
* @author yangdandan
*
*/
public class LinkListApp {
public static void main(String[] args) {
LinkList lList = new LinkList();
lList.insertFirst(22, 2.99);
lList.insertFirst(44, 4.99);
lList.insertFirst(66, 6.99);
lList.insertFirst(88, 8.99);
lList.displayList();
Link f = lList.find(44);
if(f !=null){
System.out.println("Found link with key "+f.iData);
}else{
System.out.println("Cannot find link");
}
Link d = lList.delete(66);
if(d!= null){
System.out.println("Deleted link with key "+d.iData);
}else{
System.out.println("Cannot delete link");
}
lList.displayList();
}
}
运行结果:
list(first-->last):
{88, 8.99}
{66, 6.99}
{44, 4.99}
{22, 2.99}
Found link with key 44
Deleted link with key 66
list(first-->last):
{88, 8.99}
{44, 4.99}
{22, 2.99}
5.双端链表
双端链表和传统的链表十分相似,但是它新增了一个对最后一个链接点的引用.传统的链表在表尾插入链接点,需要从表头一直遍历到表尾,效率很低.双端链表有了对最后一个链接点的引用可以很方便的在表尾插入链接点.
效果图,
双端列表中FirstLastList中有first和last两项,分别指向链表中的第一个链接点和最后一个链接点.如果链表中只有一个链接点,那么first和last都指向它.如果链表没有链接点,那么first和last都为null.
这里insertFirst(),如果isEmpty()为真,last指向新节点,insertLast(),first指向新节点.如果从表头删除,链表只有一个节点的时候,last需要赋值为null.
代码:
package chap5_链表_双端列表;
/**
* Link对象
* @author yangdandan
*
*/
public class Link {
public long dData;//数据
public Link next;//对下一个Link对象的引用
public Link(long d) {
dData = d;
}
public void displayLink(){
System.out.println(dData+" ");
}
}
package chap5_链表_双端列表;
/**
* LinkList类
*
* @author yangdandan
*
*/
public class FirstLastList {
private Link first;
private Link last;
public void LinkList() {// 链表为空
first = null;
last = null;
}
public boolean isEmpty() {// 链表是否为空
return (first == null);
}
// 表头插入链接点
public void insertFirst(long dd) {
Link newLink = new Link(dd);
if (isEmpty()) {
last = newLink;// newLink <-- last
}
newLink.next = first;// newLink-->old first
first = newLink;// first-->newLink
}
// 在表尾插入链接点
public void insertLast(long dd) {
Link newLink = new Link(dd);
if (isEmpty()) {
first = newLink;
} else {
last.next = newLink;
}
last = newLink;
}
// 删除表头元素
public long deleteFirst() {
long temp = first.dData;
if (first.next == null) {
last = null;
}
first = first.next;
return temp;
}
// 遍历链表
public void displayList() {
System.out.println("list(first-->last): ");
Link current = first;// current首先指向first
while (current != null) {// 链表尾端为null
current.displayLink();
current = current.next;
}
System.out.println("");
}
}
测试类:
package chap5_链表_双端列表;
/**
* 单向链表测试类
* @author yangdandan
*
*/
public class FirstLastApp {
public static void main(String[] args) {
FirstLastList lList = new FirstLastList();
lList.insertFirst(22);
lList.insertFirst(44);
lList.insertFirst(66);
lList.insertFirst(88);
lList.insertLast(11);
lList.insertLast(33);
lList.insertLast(55);
lList.insertLast(99);
lList.displayList();
lList.deleteFirst();
lList.deleteFirst();
lList.displayList();
}
}
运行结果:
list(first-->last):
88
66
44
22
11
33
55
99
list(first-->last):
44
22
11
33
55
99
6.链表的效率
链表在表头插入和删除速度很快,仅需要该表一两个引用值,所以花费O(1).链表的查找和删除,以及在指定的链接点后面插入需要O(N)次比较.由于链表不需要移动,特别是复制的时间远远大于比较时间的时候.链表可以扩展到所有可用内存,而数组在开始创建的时候大小就固定了.与向量相比,虽然向量是一种可以扩展的数组,但是它的大小只能以固定的大小扩展,所以在内存使用效率上还是比链表低.
7.有序链表
有序链表指的是链表中的数据保持有序,链表按照链接点中的关键值排列有序,链表头是最大或者最小的链接点.
代码:
package chap5_链表_有序链表;
/**
* Link对象
* @author yangdandan
*
*/
public class Link {
public long dData;//数据
public Link next;//对下一个Link对象的引用
public Link(long dd){
dData = dd;
}
public void displayLink(){
System.out.println(dData+" ");
}
}
package chap5_链表_有序链表;
/**
* LinkList类
*
* @author yangdandan
*
*/
public class SortedList {
private Link first;
public void LinkList() {// 链表为空
first = null;
}
public boolean isEmpty() {// 链表是否为空
return (first == null);
}
public void insert(long key) {
Link newLink = new Link(key);
Link previous = null;
Link current = first;
while (current != null && key > current.dData) {
previous = current;
current = current.next;
}
if (previous == null) {
first = newLink;
} else {
previous.next = newLink;
}
newLink.next = current;
}
public Link remove(){
Link temp = first;
first = first.next;
return temp;
}
// 遍历链表
public void displayList() {
System.out.println("list(first-->last): ");
Link current = first;// current首先指向first
while (current != null) {// 链表尾端为null
current.displayLink();
current = current.next;
}
System.out.println("");
}
}
测试类:
package chap5_链表_有序链表;
/**
* 单向链表测试类
* @author yangdandan
*
*/
public class LinkListApp {
public static void main(String[] args) {
SortedList lList = new SortedList();
lList.insert(22);
lList.insert(44);
lList.insert(16);
lList.insert(18);
lList.displayList();
lList.remove();
lList.displayList();
}
}
运行结果:
list(first-->last):
16
18
22
44
list(first-->last):
18
22
44
8.双向链表
双向链表可以向前遍历也可以向后遍历,原因是因为其中的链接点中有两个指向其他链接点的引用.一个引用指向后一个链接点,一个指向前一个链接点.双向链表的缺点是插入和删除比较麻烦,要同时处理四个链接点的引用,两个连接前一个链接点,两个连接后一个链接点,由于多了两个引用,那么占的内存空间也是要大一点的.
代码:
package chap5_链表_双向链表;
/**
* Link对象
*
* @author yangdandan
*
*/
public class Link {
public long dData;// 数据
public Link next;// 对下一个Link对象的引用
public Link previous;// 对前一个Link对象的引用
public Link(long d) {
dData = d;
}
public void displayLink() {
System.out.println(dData+" ");
}
}
package chap5_链表_双向链表;
/**
* LinkList类
*
* @author yangdandan
*
*/
public class DoubleLinkedList {
private Link first;
private Link last;
public void LinkList() {// 链表为空
first = null;
}
public boolean isEmpty() {// 链表是否为空
return (first == null);
}
// 表头插入链接点
public void insertFirst(long dd) {
Link newLink = new Link(dd);
if (isEmpty()) {
last = newLink;
} else {
first.previous = newLink;
}
newLink.next = first;
first = newLink;
}
// 在表尾插入链接点
public void insertLast(long dd) {
Link newLink = new Link(dd);
if (isEmpty()) {
first = newLink;
} else {
last.next = newLink;
newLink.previous = last;
}
last = newLink;
}
// 删除表头的链接点
public Link deleteFirst() {
Link temp = first;
if (first.next == null) {
last = null;
} else {
first.next.previous = null;
}
first = first.next;
return temp;
}
// 删除表尾链接点
public Link deleteLast() {
Link temp = last;
if (first.next == null) {// 如果只有一个链接点
first = null;
} else {
last.previous.next = null;
last = last.previous;
}
return temp;
}
// 插入指定链接点
public boolean insertAfter(long key, long dd) {
Link current = first;
// 找到指定链接点
while (current.dData != key) {
current = current.next;
if (current == null) {
return false;
}
}
Link newLink = new Link(dd);
if (current == last) {
newLink.next = null;
last = newLink;
} else {
newLink.next = current.next;
current.next.previous = newLink;
}
newLink.previous = current;
current.next = newLink;
return true;
}
// 删除指定链接点
public Link deleteKey(long key) {
Link current = first;
while (current.dData != key) {
current = current.next;
if (current == null) {
return null;
}
}
if (current == first) {
first = current.next;
} else {
current.previous.next = current.next;
}
if(current == last){
last = current.previous;
}else{
current.next.previous = current.previous;
}
return current;
}
// 从前向后遍历链表
public void displayForward() {
System.out.println("list(first-->last): ");
Link current = first;// current首先指向first
while (current != null) {// 链表尾端为null
current.displayLink();
current = current.next;
}
System.out.println("");
}
//从后向前遍历链表
public void displayBackward(){
System.out.println("list(last-->first)");
Link current = last;
while(current != null){
current.displayLink();
current = current.previous;
}
System.out.println("");
}
}
测试类:
package chap5_链表_双向链表;
/**
* 双向链表测试类
*
* @author yangdandan
*
*/
public class DoublyLinkedApp {
public static void main(String[] args) {
DoubleLinkedList lList = new DoubleLinkedList();
lList.insertFirst(22);
lList.insertFirst(44);
lList.insertFirst(66);
lList.insertLast(11);
lList.insertLast(33);
lList.insertLast(55);
lList.displayForward();
//lList.displayBackward();
lList.deleteFirst();
lList.deleteLast();
lList.deleteKey(11);
System.out.println("-----------");
lList.displayForward();
lList.insertAfter(22, 77);
lList.insertAfter(33, 88);
lList.displayForward();
}
}
运行结果:
list(first-->last):
66
44
22
11
33
55
-----------
list(first-->last):
44
22
33
list(first-->last):
44
22
77
33
88