单链表是一种链式存取的数据结构,用一组地址任意的存储单元存放线性表中的数据元素。链表中的数据是以结点来表示的,每个结点的构成:元素(数据元素的映象) + 指针(指示后继元素存储位置),元素就是存储数据的存储单元,指针就是连接每个结点的地址数据。(逻辑地址相连,物理地址不相连)
我们来用java的内部类来实现单链表的创建
class Link{//一个链表类 public Entry head;//定义链表的头结点 public Link(){//初始化链表时创建一个头结点 head = new Entry(); } class Entry{//Entry 节点类 int data;//数据域 Entry next;//地址域 public Entry(){//将头结点初始化为 data=-1 next=null data = -1; next = null; } public Entry(int val){//其他结点所需的构造函数 data = val; next = null; } } }
链表的简单操作
1,头插法
public void insertHead(int val){ //有这么一个节点 Entry cur = new Entry(val); /* * 第一种 */ cur.next = head.next; head.next = cur; /* * 第二种错误的写法 * 注意::一定要保证能能找到后面的那个结点!!!!所以不能先cur给head.next 这样就找不到原来的head.next了 */ /*head.next = cur; cur.next = head.next;*/ }
2,尾插法
public void insertTail(int val){ Entry tmp; Entry cur = new Entry(val); tmp = head; /* * 遍历链表到最后一个结点 */ while(tmp.next != null){ tmp = tmp.next; } tmp.next = cur; cur.next = null; }
3,将数据插入指定位置
public boolean insertPos(int pos,int val){ if(pos < 0 || pos > getLength()){ return false; }else{ Entry cur = head; //找到插入的地方 for(int i = 0;i <= pos-1;i++){ cur = cur.next; } Entry entry = new Entry(val); //进行插入 entry.next = cur.next; cur.next = entry; return true; } }
4,得到链表长度
//得到单链表的长度 public int getLength(){ int len = 0;//长度标志量 Entry tmp; tmp = head.next; while(tmp != null){//遍历整个链表得到链表长度 len++; tmp = tmp.next; } return len; }
5,打印链表数据
public void show(){ Entry tmp; tmp = head; //遍历打印链表 while(tmp.next != null){ System.out.println("data :"+tmp.next.data); tmp = tmp.next; } }
6,链表的逆置
每次将entry的next指向prev
再prev = entry entry = entry1 entry1 = entry.next;
将它们三个都向后移一个
public Entry reserver(){ Entry newhead = null;//逆置后的新头结点 Entry prev = head;//前驱结点 Entry entry = prev.next; prev.next = null; while(entry.next != null){ Entry entry1 = entry.next; entry.next = prev; prev = entry; entry = entry1; } entry.next = prev; //这时entry就是原来链表的最后一个结点,也就是新链表的头结点 newhead = entry; return newhead; }7,求倒数第k个结点
两种方法
1> 直接遍历链表到链表长度len -k 这时就是倒数第k个结点
public void lastK(int len,int k){ Entry tmp; tmp = head; if(k < 0 ||k > len){ System.out.println("超出链表长度"); }else{ for( int i = 0;i <= len-k;i++ ){ //len就是链表长度,len-k也就是倒数第k个节点 tmp = tmp.next; } } System.out.println(tmp.data); }
2>定义两个引用,将第一个先走k-1步,接下来两个同时向后遍历。直到第一个指向最后一个,另一个就指向倒数第k个
public int newLast(int k){ if(k < 0 || k > getLength()){ return -1; } Entry cur1,cur2; cur1 = head; cur2 = head; while(k-1 > 0){ if(cur2.next != null){ cur2 = cur2.next; k--; }else{ return -1; } } while(cur2.next != null){ cur1 = cur1.next; cur2 = cur2.next; } return cur1.data; }
8,判断一个单链表是否有环,环的入口,环的长度
1>首先创建一个带环的链表
public void createLoop(){ Entry cur = head; while(cur.next != null){ cur = cur.next; } cur.next = head.next.next; }
2>判断是否有环
//判断单链表是否有环 ( 数学归纳法证明为什么是一个走两步一个走一步) 快引用 慢引用 public boolean isLoop(){ Entry fast = head;//定义一个快引用 Entry slow = head;//定义一个慢引用 while(fast != null && fast.next != null){//两个条件是为了防止fast.next.next空指针异常 fast = fast.next.next;//快的一次走两步 slow = slow.next;//慢的一次走两步 if(fast == slow){//如果相遇代表该链表有环 return true; } } return false; }
3>判断环的入口
所以当第一次相遇后一个引用留在相遇点,另一个引用回到原点。同时出发保持相同速度下次遇见的地方就是环的入口点
public int getEntryLoop(){ Entry fast = head; Entry slow = head; if(!isLoop()){//判断是否有环 return -1; } while(fast != null && fast.next != null){ fast = fast.next.next; slow = slow.next; if(fast == slow){ break;//找到第一个相遇点 } } slow = head;//这个引用会到开始 while(fast != slow){ fast = fast.next; slow = slow.next; }//同时出发直到相遇 return slow.data; }
4>判断环的长度
第一种:由上图所示,环的长度即是x+y 也就是第一次相遇之后慢引用所走的路程
public int loopLength1(){ int len = 0; Entry fast = head; Entry slow = head; if(!isLoop()){ return -1; } while(fast != null && fast.next != null){ fast = fast.next.next; slow = slow.next; len++; if(fast == slow){ break; } } return len; }
第二种: 第一次相遇和第二次相遇之间的路程为环的长度
public int loopLength(){ int len = 0; boolean tag = false;//标志是否为第一次相遇 if(!isLoop()){ return -1; } Entry fast = head; Entry slow = head; while(fast != null && fast.next != null){ fast = fast.next.next; slow = slow.next; if(slow == fast && tag == false){ tag = true; } if(tag = true){ len++;//长度 } if(slow == fast && tag == true){ break; } } return len; }
9,判断两个链表是否相交
1>创建两个相交的链表
//创建一个相交的链表 public void createCut(TestLink t1,TestLink t2){ TestLink.Entry head1 = t1.head; TestLink.Entry head2 = t2.head; head1.next.next = head2.next.next; }
2> 判断链表是否相交
将长的链表的引用先走两个链表之差个距离
保证开始走的时候两个引用知道结束走的距离一样
public boolean isCut(TestLink t1,TestLink t2){ TestLink.Entry head1 = t1.head; TestLink.Entry head2 = t2.head; int len1 = t1.getLength(); int len2 = t2.getLength(); int my_len = len1-len2; if(my_len < 0){//确定head1指向的单链表是最长的 head1 = t2.head; head2 = t1.head; } for(int i = 0;i < my_len;i++){ head1 = head1.next; } while(head1.next != null && head2.next != null && head1 != head2){ head1 = head1.next; head2 = head2.next; } if(head1 == head2 && head1 !=null && head2 != null){ return true; }else{ return false; } }