链表,可分为以下几种:
单链表
双端链表
有序链表
双向链表
一、单链表
在链表中,我们插入的每数据项都可视为包含在链接点(link)中。而每一个链接点是某个类的对象,我们先(下面也是这样)将这个类叫做Link。而这些节点怎样关联在一起呢?这里再引入一个next字段,表示对下一个字段的引用,整个链表的还有一个表头,我们称之为first,数据每次从这里插入,即first指向新插入的数据,新插入的数据通过next指向下一个数据。如图:
所以,我们可以先创一个类Link,包含一些数据和对下一个链接点的引用:
public class Link{ public long dData; public Link next; public Link(long d) { dData =d; } }
接下来是怎样插入数据的问题,我们来看看insertFirst()方法。对于单链表来说,插入数据时是在表头插入的,新插入的数据,我们可以令它等于原来first的值,然后改变first的值使它指向新插入的数据项就完成了一次插入。
public void insert(int id) { // TODO Auto-generated method stub Link newLink = new Link(id); newLink.next = first;//注意这两步不能颠倒!!!这里指的是把first原来指向的值变成新插入的数据项 指向的下一个的值 first = newLink;//如果颠倒了,那么first一开始指向的链接点就被覆盖,就不能形成一条完整的链。(类似两个数据实现交换) }
同理,删除时,则是插入的逆操作,它通过把first重新指向第二个链接点,断开了和第一个链接点的连接,而怎样把first指向第二个链接点呢?这里只需要通过第一个链接点(不是first)的next字段就可以找到第二个链接点了。
public Link delete() { // TODO Auto-generated method stub Link temp = first; first = first.next; return temp; }
接下来展现完整代码:
package dailypratice; public class Link { public long dData; public Link next; public Link(long d) { dData =d; } }
public class Linklist { private Link first; private int linkcount; public Linklist() { // TODO Auto-generated constructor stub first=null; } public boolean isEmpty() { // TODO Auto-generated method stub return (first==null); } public void insert(int id) { // TODO Auto-generated method stub Link newLink = new Link(id); newLink.next = first; first = newLink; linkcount++; } public Link delete() { // TODO Auto-generated method stub Link temp = first; first = first.next; return temp; } public void display() { // TODO Auto-generated method stub Link curr = first;//在打印时创建一个Linkd对象后,通过next字段沿着链表寻找,直到为空时,表示寻找完毕 while(curr!=null){ System.out.print(curr.dData+" "); curr=curr.next; } } }//测试
public class Linkapp {
public static void main(String[] args) {
Linklist theList = new Linklist();
theList.insert(10);
theList.insert(50);
theList.insert(20);
theList.insert(70);
theList.insert(80);
theList.insert(30);
theList.insert(50);
theList.display();
theList.delete();
theList.delete();
theList.delete();
theList.display();
}
}
//删除前 50 30 80 70 20 50 10
//删除后 70 20 50 10
二、双端链表
1.双端链表(注意和后面的双向链表区分开)的表头和单链表一样,区别在于对最后一个链接点的引用,单链表中最后一个链接点是直接指向null的,而双向链表对最后一个链接点的引用多加了一条,从first指向了最后一个链接点,从而可以使我们在尾端也能插入数据,就像从表头插入数据一样。
在单链表的基础上添加了一个inseretLast()方法,顾名思义,从最后插入一个链接点,值得一提的是,双端链表并不能实现删除最后一个链接点的功能,因为没有指向倒数第二个的引用(这个功能在后面的双向链表将得以实现)。
完整代码:
public class Link { public long dData; public Link next; public Link(long d) { dData =d; } }
public class LinkLastFirst { private Link first; private Link last; public LinkLastFirst() { // TODO Auto-generated constructor stub first=null; last=null; } public void insertFirst(long id) { // TODO Auto-generated method stub Link newLink=new Link(id); if(isEmpty())//因为现在加了一个last引用,所以现在在第一次插入时要考虑到last last=newLink; newLink.next=first; first=newLink; } public void insertLast(long id) { // TODO Auto-generated method stub Link newLink=new Link(id); if(isEmpty()) first=newLink;//同理,第一次插入时链表为空要把first引用指向第一个插入的数据 else{ last.next=newLink;//这里注意与insertFirst区分开,如果链表不为空,那么原来last指向的数据项 } //变为老的数据项的下一个指向新的数据项 last=newLink;//最后,last指向newLink } public long deleteFirst() { // TODO Auto-generated method stub long temp=first.dData; if(first.next==null){//如果这个链表只有一个连接点,删除时,last要指向null last=null; } first=first.next; return temp; } public boolean isEmpty() { // TODO Auto-generated method stub return (first==null); } public void display() { // TODO Auto-generated method stub Link curr = first; while(curr!=null){ System.out.print(curr.dData+" "); curr=curr.next; } System.out.println(); } }
//测试
public class LinkFirstLastApp { public static void main(String[] args) { LinkLastFirst theList = new LinkLastFirst(); theList.insertFirst(100); theList.insertFirst(540); theList.insertFirst(278); theList.insertFirst(70); theList.insertFirst(88); theList.insertFirst(35); theList.insertFirst(40); theList.display(); theList.insertLast(25); theList.insertLast(45); theList.insertLast(55); theList.display(); } }
//结果40 35 88 70 278 540 100
40 35 88 70 278 540 100 25 45 55
三、有序链表
有序链表的关键部分是在于插入,在一个链表中是插入的数据有序比如从小到大,那么每次插入数据时都需要将数据沿着链表移动直到找到一个比它大的数据项,停下来。此时的插入跟上面所讲的链表类型一样,不同的是,在有序链表中我们需要引入一个应用previous,用来记录链接点寻到到合适插入的地方 的 前一项 这样才能使其连接,如图:
这里,就只贴出插入部分的代码了,其他跟单链表的一样。
public void insert(int id) { // TODO Auto-generated method stub Link newLink = new Link(id); Link previous=null; Link current=first;//记录当前的点方便等一下让新插入的链接点通过next链接 while(current!=null&¤t.dData<id){ previous=current;//记录走过时的节点,(不是走过的全部节点!!) current=current.next;//因为插入的数据项大于当前节点,继续寻找。 } if(previous==null)//表示链表的开始 first=newLink; else previous.next=newLink; newLink.next=current; }
//测试
public class Linkapp { public static void main(String[] args) { Linklist theList = new Linklist(); theList.insert(10); theList.insert(50); theList.insert(20); theList.insert(70); theList.insert(80); theList.insert(30); theList.insert(50); theList.display(); } } 10 20 30 50 50 70 80
四、双向链表
双向链表比上述的链表要灵活,因为他在每一个节点处增加了一个prev的引用,使链表在反向的方向也连接起来,这样反向也能遍历,插入,删除。
package dailypratice; public class DoubleLink { public long dData; public DoubleLink next; public DoubleLink prev; public DoubleLink(long d) { dData =d; } }
public class DoubleLinkList { private DoubleLink first; private DoubleLink last; public DoubleLinkList() { // TODO Auto-generated constructor stub first=null; last=null; } public boolean isEmpty() { // TODO Auto-generated method stub return first==null; } public void insertFirst(long id) { // TODO Auto-generated method stub DoubleLink newLink=new DoubleLink(id); if(isEmpty())//這裡和前面講的双端链表一样 last=newLink; else//这里不同,链表不为空时每次从表头插入数据都要考虑将prev指向新插入的数据。 first.prev=newLink; newLink.next=first; first=newLink; } public void insertLast(long id) { // TODO Auto-generated method stub DoubleLink newLink=new DoubleLink(id); if(isEmpty()) first=newLink;//同理 else{ last.next=newLink; newLink.prev=last; } last=newLink;//最后,last指向newLink } public DoubleLink deleteFirst() { // TODO Auto-generated method stub DoubleLink temp=first; if(first.next==null){//如果这个链表只有一个连接点,删除时,last要指向null last=null; }else{ first.next.prev=null;//由图片可知,第一个链接点的prev总是指向null } first=first.next; return temp; } public DoubleLink deleteLast() { DoubleLink temp=last; if(last.next==null){//如果这个链表只有一个连接点,删除时,first要指向null first=null; }else{ last.prev=null;//由图片可知,最后一个链接点的prev要指向null,即把最后一个链接点断开 } last=last.prev; return temp; } //两种打印方式 public void displayFirst() { // TODO Auto-generated method stub DoubleLink curr = first; while(curr!=null){ System.out.print(curr.dData+" "); curr=curr.next; } System.out.println(); } public void displayLast() { // TODO Auto-generated method stub DoubleLink curr = last; while(curr!=null){ System.out.print(curr.dData+" "); curr=curr.prev; } System.out.println(); } }
public class DoubleLinkApp { public static void main(String[] args) { DoubleLinkList theList = new DoubleLinkList(); // theList.insertFirst(10); // theList.insertFirst(50); // theList.insertFirst(20); // theList.insertFirst(70); // theList.insertFirst(80); // theList.insertFirst(30); // theList.insertFirst(50); // System.out.println("调用displayFirst()"); // theList.displayFirst(); // System.out.println("调用displayLast()"); // theList.displayLast(); System.out.println("调用insertLast()"); theList.insertLast(10); theList.insertLast(50); theList.insertLast(20); theList.insertLast(70); theList.insertLast(80); theList.insertLast(30); theList.insertLast(50); System.out.println("调用displayFirst()"); theList.displayFirst(); System.out.println("调用displayLast()"); theList.displayLast(); } }调用insertLast()
调用displayFirst()
10 50 20 70 80 30 50
调用displayLast()
50 30 80 70 20 50 10
链表篇章就到这里了~在我们实际运用中可以通过直接调用现有的链表类用迭代器进行遍历(后面写容器时会讲到),但链表的原理还是要掌握的,作为一个新生代程序猴子。
另外,链表也不一定在头或者尾巴出删除,插入数据,也可以用双向链表来实现从中间处插入删除数据~不明白的可以私聊或者留言评论~