一、单链表
1.不带头节点的单链表:
主体:
public class LianBiaowithoutHead <E> {
protected Node<E> head; //声明第一个结点(并不是头结点)
class Node<E>{ //创建内部类Node
protected E element;
protected Node<E> next;
public Node(E data){
this.element = data;
}
}
}
方法:
不带头结点的单链表在进行操作时,移动指针语句:tmp = tmp.next需要写在操作语句之后,因为head结点保存着第一个结点的元素,不可以跳过它。
增删改查等代码较简单不放上来了,这里放一个show方法做代表。
public void show(){
Node<E> tmp = head;
while(tmp != null){
System.out.print(tmp.element+" ");
tmp = tmp.next; //注意在操作语句之后移动tmp,否则会跳过第一个结点,而这里的head是放有数据的
}
System.out.println();
}
2.带头结点的单链表:
主体:
public class LianBiaowithHead <E>{
protected Node <E> head;
class Node<E>{
protected E element;
protected Node<E> next;
public Node(E data){
this.element = data;
}
}
public LianBiaowithHead(){ //需要创立头结点
head = new Node<>((E)new Object());
}
}
方法:
带头结点的单链表在进行删除等操作时,移动指针语句:tmp = tmp.next需要写在操作语句之前,因为head结点的数据域没有东西,需要跳过它再进行打印等操作。
依然放一个show方法做代表。
public void show(){
Node<E> tmp = head;
while(tmp.next != null){
tmp = tmp.next; //跳过头结点打印
System.out.print(tmp.element + " ");
}
System.out.println();
}
3.循环链表
主体:
public class CircleList<E> {//循环单链表(带头结点)
protected Node<E> head;
class Node <E>{
protected E element;
protected Node<E> next;
public Node(E data){
this.element = data;
}
}
public CircleList(){
this.head = new Node<>((E)new Object());
head.next = head;
}
方法:
和上面的2,3一样,记得将最后一个结点的指针域设成head,遍历的循环条件变成 tmp.next != head
以add方法作代表:
public void addTail(E data){
Node<E> n = new Node<E>(data);
Node<E> tmp= head;
while(tmp.next != head){
tmp = tmp.next;
}
n.next = tmp.next;
tmp.next = n;
}
4.单链表的一些其他基本方法
(1)【合并两个有序链表,合并后依然有序】:
思想:
先比较list1,list2的第一个结点元素的大小,小的作合并后newList的头结点,以两个链表的表头同时不为空作循环条件,陆续比较元素大小,小的跟在新表指针后,同时该旧表头往后移动,另一个不动。也就是一个if else语句。
代码实现:
public <E extends Comparable<E>> Node<E> mergeList(Node<E> head1,Node<E> head2){
Node<E> tmp = null;
Node<E> curHead;
if(head1.element.compareTo(head2.element) <0){
curHead = head1;
head1 = head1.next; //别忘了这个的表头要往后移一个
}
else {
curHead = head2;
head2 = head2.next;
}
tmp = curHead;
while(head1!= null && head2!= null){
if(head1.element.compareTo(head2.element)<0){
tmp.next =head1;
head1 = head1.next;
}
else{
tmp.next = head2;
head2 = head2.next;
}
tmp = tmp.next;
}
if(head1 == null){
tmp.next = head2;
}
if(head2 == null){
tmp.next = head1;
}
return curHead;
}
tips1:
因为要使用compareTo()方法比较element,所以要在方法声明时在T后extends Comparable,也就是在说:擦除的时候不用擦的那么彻底,擦到还有Comparable,能让我使用它的方法~
tips2:
自己在写代码的时候一遇到泛型就有点乱,不知道怎么写方法声明的顺序了,正确顺序应该是:pubic <T> 返回值 方法名(){}
(2)【逆序输出链表元素】:
思想:
用递归可以简单实现。运行时一层层的进reversePrint方法,每一层都没有走到底,直到最后一层(head指针为空)进入if语句后return,这才返回上一层继续往下走进行打印数据,这一层的方法才算结束,正常return后再往下走打印数据……,直到return到最初进入的一层。最后输出就是逆序的。
代码实现:
public static <E> void reversePrint(LianBiaowithoutHead<E>.Node<E> head){
if(head == null)
return;
reversePrint(head.next);
System.out.print(head.element + " ");
}
(3)【逆置链表】:
思想:
设置三个指针,也就是除了普通临时指针cur之外,另外设置一个在当前指针之前的指针p,一个在当前指针之后的指针next,以当前指针不空为条件进行循环,每一次循环都会将p,cur之间的指向调转方向。
代码实现:
public Node<E> reverseList(Node<E> head){
Node <E> cur = head;
Node <E> p = null;
Node <E> newHead =null;
while(cur!= null){
Node<E> next = cur.next;
if(next == null)
newHead = cur;
cur.next = p;
p = cur;
cur = next;
}
return newHead;
}
(4)【查找单链表中倒数第k个结点】:
思想:
倒数第k个结点和最后一个结点下标差k-1-,所以可以设立两个指针,一个指针p1先走k-1步,再和另一个起点在head的指针p2同时前进,当p1.next为空时,p2也就走到了倒数第k个结点。
看图:
代码实现:
public E findTheK(int index){
if(head == null || index<0)
return null;
Node<E> p1 = head;
Node<E> p2 = head;
int num = 0;
while (num != index-1){
p2 = p2.next;
num++;
}
while(p2.next != null){
p1 = p1.next;
p2 = p2.next;
}
return p1.element;
}
(5)【查找两个链表中的相交结点】:
注意是地址一致的结点,而不只是数据一样
思想:
最容易想到的是两个嵌套的循环,可是时间复杂度会达到O(n^2),。
再想到可以利用栈的先进后出进行pop后比较,但是同样会付出额外空间复杂度的代价:O(n),而且较复杂,代码较长。
放弃算了
其实,可以将较长的链表截断,使两个链表的长度一致再进行遍历,就可以只用一个循环实现。
看图:
代码实现:
public static <E> LianBiaowithHead<E>.Node<E> banana(LianBiaowithHead<E> list1 , LianBiaowithHead<E> list2){
if(list1.head == null || list2.head == null) return null;
int length1 = list1.getLength();
int length2 = list2.getLength();
int lengthDif = Math.abs(length1-length2);
LianBiaowithHead<E>.Node<E> longHead = list1.head;
LianBiaowithHead<E>.Node<E> shortHead = list2.head;
if(length1 < length2){
longHead = list2.head;;
shortHead = list1.head;
}
for(int i=0; i<lengthDif; i++){
longHead = longHead.next;
}
while(longHead != shortHead){
longHead = longHead.next;
shortHead = shortHead.next;
}
return longHead;
}
tips1:
在测试类中设立公有结点时,list1中用p1遍历到公有结点,list2中用p2遍历到尾,p2.next = p1就可以了。
(6)【在指定结点前插入新结点】:
思想:
先在指定结点之后插入,再交换两结点。
代码实现:
public void LinkedListInsertBefore(Node<E> head, Node<E> pos, E data){//不允许遍历链表,在pos之前插入
Node<E> n = new Node<>(data);
Node<E> taxi = head;
while(taxi.next != head){
if(taxi.next == pos)
break;
taxi = taxi.next;
}
taxi.next = n;
n.next = pos;
}
(7)【如果一个链表有环,得到入环结点】:
思想:
试想有2人在操场跑步,一个是肥宅,一个是现充,要是两人跑的是直跑道,现充只会离肥宅越来越远,但要是环形跑道,现充必定会在将来的某一时刻再次超过肥宅。
所以我们可以设置两个速度不同的指针,肥宅指针p1步长为一,现充指针p2步长为2,如果真的有某一个时刻p1,p2指向了同一个结点,那么这个链表中肯定就有环。
也容易知道,两指针相遇的结点肯定是环结点之一,这样我们根据一个计数器,设置tmp指针遍历,计数器++,当tmp绕完了环一圈,再次指向了相遇结点,length 就等于计数器值。
要环长值有什么用?当然是为了解决下一个问题。
将带环链表伸展开,入环结点也就可以看成是倒数第length个结点,现在我们可以依据之前【查找单链表中倒数第k个结点】的思想,设置两个起点相差length的指针,让他们同时移动,保证他们之间始终相差5,这样,当前面的指针走完了一圈时,后面的指针必定才刚开始进环。这时的p1,p2同时指向入环结点,设置这个为循环的终止条件,当前的p1,p2就保存着入环结点地址。
看图:
代码实现:
public static <E> CircleList<E>.Node<E> isRing(CircleList<E>.Node<E> head){//单链表是否有环,环的入口节点是哪个
if(head == null)
return null;
if(head.next==null)
return null;
CircleList<E>.Node<E> taxi1 = head.next;
CircleList<E>.Node<E> taxi2 = head.next.next;
while(taxi2 != null && taxi1!=null){
if(taxi2 == taxi1){
return taxi1;
}
taxi1 = taxi1.next;
taxi2 = taxi2.next;
if(taxi2.next != null)//避免空指针异常
taxi2 = taxi2.next;
}
return null;
}
public static <E> CircleList<E>.Node<E> ringNode(CircleList<E>.Node<E> head){
CircleList<E>.Node<E> meet = isRing(head);
if(meet == null)
return null;
int length = 0;
CircleList<E>.Node<E> tmp = meet;
do{
tmp = tmp.next;
length++;
}while(tmp != meet); //得到环的长度
System.out.println("length of the ring:"+length);
CircleList<E>.Node<E> p1 = head;
CircleList<E>.Node<E> p2 = head;
int num = 0;
while(num != length){
num++;
p2 = p2.next;
}//p2先走length步
while( p1 != p2){
p1 = p1.next;
p2 = p2.next;
}
return p1;
}
二、双向链表
双向链表也就只是在单链表的基础上增加一个前驱属性而已,操作与单链表基本相同,就不再详细描述啦,这里仅放上代码:
1.非循环双向链表:
主体:
public class DoubleLinkedList<E> {
protected Node<E> head;
class Node<E>{
protected E element;
protected Node<E> before;
protected Node<E> after;
public Node(E data){
element = data;
}
}
public DoubleLinkedList(){
this.head = new Node<>((E)new Object());
head.after = head;
head.before = head;
}
}
方法:
public void addTail(E data){
Node<E> newNode = new Node<>(data);
Node<E> tmp = head;
while(tmp.after != head){
tmp = tmp.after;
}
tmp.after = newNode;
newNode.before = tmp;
newNode.after = head;
}
public void addHead( E data){
Node<E> newNode = new Node<>(data);
Node<E> tmp = head;
newNode.after = head.after;
newNode.before = head;
head.after = newNode;
}
public void delete( E data){
Node<E> tmp = head;
while(tmp.after != head){
if(tmp.element.equals(data)){
tmp.before.after = tmp.after;
tmp.after.before = tmp.before;
}else {
tmp = tmp.after;
}
}
}
public int getLength(){
Node<E> tmp = head;
int size = 0;
while(tmp.after != head){
tmp = tmp.after;
size ++;
}
return size;
}
public void show(){
Node<E> tmp = head;
while(tmp.after != head){
tmp = tmp.after;
System.out.print(tmp.element + " ");
}
System.out.println();
}
public E findValue(int index){
Node<E> tmp = head;
int num = 0;
while(tmp.after != head){ //第一个结点(不是头结点)的下标是0
if( num == index)
return tmp.element;
tmp = tmp.after;
num ++;
}
return null;
}
2.循环双向链表:
主体:
public class DoubleCircularLinkedList <E>{
protected Node<E> head;
class Node<E>{
protected E element;
protected Node<E> before;
protected Node<E> after;
public Node(E data){
element = data;
}
}
public DoubleCircularLinkedList(){
this.head = new Node<>((E)new Object());
head.after = head;
head.before = head;
}
}
方法:
public void addTail(E data){
Node<E> newNode = new Node<>(data);
Node<E> tmp = head;
while(tmp.after != head){
tmp = tmp.after;
}
tmp.after = newNode;
newNode.before = tmp;
newNode.after = head;
}
public void addHead( E data){
Node<E> newNode = new Node<>(data);
Node<E> tmp = head;
newNode.after = head.after;
newNode.before = head;
head.after = newNode;
}
public void delete( E data){
Node<E> tmp = head;
while(tmp.after != head){
if(tmp.element.equals(data)){
tmp.before.after = tmp.after;
tmp.after.before = tmp.before;
}else {
tmp = tmp.after;
}
}
}
public int getLength(){
Node<E> tmp = head;
int size = 0;
while(tmp.after != head){
tmp = tmp.after;
size ++;
}
return size;
}
public void show(){
Node<E> tmp = head;
while(tmp.after != head){
tmp = tmp.after;
System.out.print(tmp.element + " ");
}
System.out.println();
}
public E findValue(int index){
Node<E> tmp = head;
int num = 0;
while(tmp.after != head){ //第一个结点(不是头结点)的下标是0
if( num == index)
return tmp.element;
tmp = tmp.after;
num ++;
}
return null;
}