以数组作为存储结构,在无序的情况当中,查找关键字效率十分底下。而在有序的情况下,插入关键字也变得效率底下。
而且不管在哪种情况下,数组的删除效率也是很低的。当一个数组创建之后,它的长度是固定的。
而链表,可以解决上面的一些问题。
一.链结点(Link)
在链表中,每个数据项都被包含在“链结点”(Link)中。而链表则是将这些结点给“串联”起来。
一个链结点 可能是某个类的对象,例如这个类叫做Link,因为一个链表中有许多类似的结点,所有很有必要用一个不同于链表的类来表达链节点。
每个Link对象中都包含一个对下一个链结点“引用”的字段(通常叫做next),但是链表 本身的对象中有一个字段指向对一个链结点的引用。
二.单链表
创建单链表的思路:
1.先创建一个Link类,这个类里面包含着链结点的所包含的信息(Data),以及指向下一个链结点的引用(next)。
2.再创建一个LinkList类,对Link类进行插入删除等操作。
3.创建单链表有“头插法” 和 “尾插法” 这两种方法。
2.1 创建Link类
public class Link {
public int iData;
public double dData;
public Link next;
public Link( int iData , double dData ){
this.iData = iData ;
this.dData = dData ;
}
//显示当前链表的信息:
public void displayLink(){
System.out.print("["+"iData:"+iData + "," + "dData:"+dData+"]" + " ");
}
}
2.2 创建LinkList类(头插法和尾插法)
package Link;
/**
* Created by Hubbert on 2017/11/16.
*/
public class LinkList {
public Link first;
Link p = first;
public LinkList(){
first = null;
}
//1.头插法
//逆序,先插先排在前面
public void insertFirst(int i , double d){
Link newLink = new Link(i,d); //1.先创建一个链结点
newLink.next = first; //2.新的链结点指向first所指的下一个结点
first = newLink ; //3.然后first再指向newLink这个新的链结点
}
//2.尾插法
//顺序,后插排在后面
public void insertLast( int i , double d ){
Link newLink = new Link(i,d);
Link p = first;
//如果插入的时候first结点为空,则first结点要=newLink
if(first == null){
newLink.next = first;
first = newLink;
} else {
//要使p一直是最后一个结点
while( p.next != null){
p = p.next;
}
newLink.next = null;
p.next = newLink;
}
}
//显示链表的每一个结点
public void displayLink(){
Link current = first;
while(current != null){
current.displayLink();
current = current.next;
}
}
}
2.2.1测试代码:头插法
public static void main(String [] args){
LinkList linkList = new LinkList();
linkList.insertFirst(11,1.1);
linkList.insertFirst(22,2.2);
linkList.insertFirst(33,3.3);
linkList.insertFirst(55,5.5);
linkList.insertFirst(66,6.6);
linkList.displayLink();
}
2.2.2结果如下:逆序输出
2.2.2测试代码:尾插法
public static void main(String [] args){
LinkList linkList = new LinkList();
linkList.insertLast(11,1.1);
linkList.insertLast(22,2.2);
linkList.insertLast(33,3.3);
linkList.insertLast(55,5.5);
linkList.insertLast(66,6.6);
linkList.displayLink();
}
2.2.2结果如下:顺序输出
2.3删除结点方法:
deleteFirst()方法是inserFirst的逆操作。
它通过把first重新指向了第二个链结点,断开了和第一个链结点的连接。通过查看第一个链结点的next可以找到第二个链结点:
public Link deletFirst(){
Link temp = first;
first = first.next;
return first;
}
在C++和类似的语言中,从链表中取下一个链结点后,需要考虑如何删除掉这个结点。它仍然存在内存中的某个地方,但是现在没有任何东西指向它。将要如何去处理它呢,
在Java语言中,垃圾收集进程将会在未来的某个时刻销毁它,现在这个还不是我们担心的工作。
2.5 删除指定的结点 和 查找指定的结点
public Link deletFirst(){
Link temp = first;
first = first.next;
return first;
}
public Link findKey( int key ){
Link current = first ;
while( current.iData != key ){
if(current.next == null){
return null;
}else {
current = current.next;
}
}
return current;
}
public Link deleteKey( int key ){
Link current = first;
Link pre = first ;
while ( current.iData != key ){
if(current.next == null ){
return null;
}else {
pre = current; //保留当前的结点
current = current.next; //继续指向下一个结点,如果下一个结点是待删除结点,
// pre则保留了当前这个结点,所以删除掉下一个结点的操作就是下面所示
}
}
if(current == first){ //如果第一个待删除的结点是first结点
first = first.next;
}else{
pre.next = current.next; //删除当前的结点
}
return current;
}
题目:如何判断链表的长度?
public static void main(String [] args){
LinkList linkList = new LinkList();
//随机生成要插入的链结点的个数[1...10]
int random = (int)(Math.random() * 10);
for(int i = 0 ; i < random ; i++ ){
int random2 = (int)(Math.random() * 100);
linkList.insertFirst(random2,random2);
}
linkList.displayLink();
int count = 0;
Link current = linkList.first;
while( current!= null){
count++;
current = current.next;
}
System.out.println("");
System.out.print("链表长度为:"+count);
}
结果如图所示:
二.双端链表
双端链表跟传统的链表相似,但是它有一个特性:即增加对“最后一个链结点的引用”,就像对第一个链结点引用一样。
1.多了一个在尾部添加链表的方法。跟尾插法的性质一样。
2.在头插法的时候,需要先判断链表是否为空,如果为空,则第一个插入的链结点则被last所标记。
package Link;
/**
* Created by Hubbert on 2017/11/18.
*/
public class LinkFirstLastList {
Link first;
Link last;
public LinkFirstLastList(){
first = null;
last = null;
}
public void inserFirst( int i , double d ){
Link newLink = new Link(i,d);
if(first == null){
last = newLink; //当链表为空时,头插法的时候,第一个插入的链结点将作为last
}
newLink.next = first;
first = newLink;
}
public void inserLast( int i , double d ){
Link newLink = new Link(i,d);
if( first == null ){
newLink.next = first;
first = newLink;
last = newLink;
} else {
last.next = newLink;
last = newLink;
}
}
public void dispalyLink(){
Link current = first;
while( current != null){
current.displayLink();
current = current.next;
}
}
}
那么链表的效率如何呢?
链表在插入和删除的时候仅仅改变一两个引用值,只花费O(1)的时间。
虽然在查找,删除指定的结点需要搜索链表一半链结点,需要O(N)次比较。在数组执行这些操作也是如此。
但是链表仍然要快一些,因为当插入和删除的链结点的时候,链表不需要做任何的移动。
当然了,链表比数组优越的一个优点是,不用像数组那样定长,想用多少内存就用多少内存。
三.双向链表
双向链表(不能跟双端链表产生混淆),在单链表中,只能从头结点开始一个一个往下遍历,却不能方向遍历。
用这样的一个语句
current = current.next;
却没有对应的方法回到前一个链结点。
而双向链表提供了这样的一个能力。即允许向前遍历,也允许向后遍历整个链表。
其中的不同是两个指向其他链表结点的引用,而不是一个next,增加多了一个pre(向前的意思)。
所以Link类的声明变成了这样:
package Link;
/**
* Created by Hubbert on 2017/11/16.
*/
public class Link {
public int iData;
public double dData;
public Link next;
public Link previous;//新增加一个指向前的结点
public Link( int iData , double dData ){
this.iData = iData ;
this.dData = dData ;
}
//显示当前链表的信息:
public void displayLink(){
System.out.print("["+"iData:"+iData + "," + "dData:"+dData+"]" + " ");
}
}
当然双向链表也是有一定的缺点的,在插入和删除的时候,相对于单链表的两个引用来说,它需要改变四个引用。
多了两个引用,存储的空间也会变大。
DoublyLinkList类(向前向后遍历,查找,插入,删除)
package Link;
/**
* Created by Hubbert on 2017/11/18.
*/
public class DoublyLinkList {
public Link first;
public Link last;
public DoublyLinkList(){
first = null;
last = null;
}
//头插法
public void inserDoublyFirst(int i , double d){
Link newLink = new Link(i,d);
if(first == null){
last = newLink;
}else {
first.previous = newLink;
}
newLink.next = first;
first = newLink;
}
//尾插法
public void inserDoublyLast(int i , double d){
Link newLink = new Link(i,d);
if(first == null){
first = newLink;
}else{
last.next = newLink;
newLink.previous = last;
}
last = newLink;
}
//插入一个链结点newLink(i,d),在关键字key之后
public boolean insertAfter(int i, double d , int key){
Link newLink = new Link(i,d);
Link current = first;
//获取链结点能够与 key值相关
while(current.iData != key){
if(current.next == null){
return false;
} else {
current = current.next;
}
}
if(current.next == null){
newLink.next = null;
last = newLink;
} else {
newLink.next = current.next;
current.next.previous = newLink;
}
//上面if else 中共同的部分可以提取出来.例如下面的;
newLink.previous = current;
current.next = newLink;
return true;
}
//删除关键字链表
public Link deleteKey(int key){
Link current = first;
while(current.iData != key){
current = current.next;
if(current == null){
return null;
}
}
if(current == first){
first = current.next;
} else if (current == last){
last = current.previous;
current.previous.next = null;
} else {
current.previous.next = current.next;
current.next.previous = current.previous;
}
return current;
}
//从头结点开始向后遍历
public void displayForward(){
Link current = first;
while (current != null){
current.displayLink();;
current = current.next;
}
}
//从尾结点开始向前遍历
public void displayBackward(){
Link current = last;
while( current != null ){
current.displayLink();
current = current.previous;
}
}
}