首先来了解一下什么是链表?
链表是一种线性表,是物理存储上非连续非顺序的存储结构。(可以理解为一串链子上有许多节点,每个节点有属于自己的成员和指向下一节点的指针)
画图简单理解一下
在链表的点里面存放了数据域和地址域,那么如何用Java来写一个链表的类呢?
整个单链表是一个类,链表里的每一个节点也是一个类,这就要用到Java的内部类来写了。
class TestLink{
public Entry head;//这个是整个链表的头结点
public TestLink(){//构造函数
head = new Entry();//初始化头结点,让其data为-1,并指向空
}
class Entry{//链表中的节点
int data;//节点的数据域
Entry next;//地址
public Entry(){//无参的构造函数,用于头节点的初始化
data = -1;
next = null;
}
public Entry(int val){//存放值的节点,传进了一个val参数,赋值给data域
data = val;
next = null;
}
}
这样就写好了一个链表了。
一. 链表的插入
1.头插法
就是在第一个存放值的节点之前插入新的节点(插在头结点之后)
public void insertHead(int val){
Entry cur = new Entry(val);//new了一个新的节点
cur.next = head.next;//注意先保后面的节点~
head.next = cur;
}
2.尾插法
插在了链表的尾端,需要对单链表进行遍历,找到最后一个节点,然后插入。
public void insertTail(int val){
Entry cur = head;//遍历指针
Entry t = new Entry(val);//新的节点
while(cur.next != null){//对单链表进行遍历的循环
cur = cur.next;
}
cur.next = t;//插入操作
}
3.任意位置插入
public boolean insertPos(int val,int pos){
Entry cur = new Entry(val);
Entry t1 = head;//前驱
Entry t2 = head.next;
int len = 0;
if(pos < 0||pos > getLength()){//没有找到指定位置
return false;
}
else{
while(t2!=null){//找pos位置
len++;//计算当前节点的位置
if(len == pos){//相等则插入
cur.next = t2;
t1.next = cur;
break;
}
//否则一直向后遍历找pos位置
t1 = t1.next;
t2 = t2.next;
}
if(t1.next == null){//在最后一个位置插入
t1.next = cur;
}
return true;
}
}
二. 单链表的逆置
public void reverse2(){
Entry newhead = head;
Entry pre = null;
Entry cur = head;
while(cur != null){//循环情况见上图
Entry curNext = cur.next;
if(curNext == null){
newhead = cur;
}
cur.next = pre;
pre = cur;
cur =curNext;
}
cur = newhead;
while(cur.next!=null){//输出逆置后的单链表
System.out.println(cur.data);
cur = cur.next;
}
}
利用尾插法插入四个元素
逆置后的运行结果如图
三.求倒数第k个数据元素
有两种方法
第一种:
倒数第k即正数第n-k+1个元素,遍历单链表找到这个数就好了
public void kBackwards(int k){
int n = getLength();//链表的长度
int t = 1;//t表示第一个节点
Entry cur = head.next;
if(k < 0||k > n){//如果没有倒数第k元素
System.out.println("无此元素");
}
while(cur != null){
if(t == n-k+1){//判断当前节点是否为倒数第k个元素
System.out.println("倒数第"+k+": "+cur.data);//如果是,打印当前节点的值
}
cur = cur.next;
t++;
}
}
第二种:
public int kBackwards2(int k){
if(k < 0||k >getLength()){
return -1;
}
Entry cur1 = head;
Entry cur2 = head;//快的
while(k-1 > 0){//先让cur2走k-1步
cur2 = cur2.next;
--k;
}
while(cur2.next != null){//然后让cur2走完链表
cur1 = cur1.next;
cur2 = cur2.next;
}
return cur1.data;
}
两种方法各有其优点,第一种更容易想到,第二种更能脱颖而出。
四. 判断两个链表是否相交
//创建两个相交的链表
public static void creatCut(TestLink t1,TestLink t2){
TestLink.Entry head1 = t1.getHead();
TestLink.Entry head2 = t2.getHead();
head1.next.next = head2.next.next;//在第二个节点处相交
}
//判断相交
public static boolean isCut(TestLink t1,TestLink t2){
TestLink.Entry head1 = t1.getHead();//得t1链表的头结点
TestLink.Entry head2 = t2.getHead();//得t2链表的头结点
int len1 = t1.getLength();
int len2 = t2.getLength();
int my_len = len1 - len2;
if(my_len < 0){
head1 = t2.getHead();
head2 = t1.getHead();
}
//肯定确定head1指向的单链表较长
for(int i = 0;i < my_len;i++){
head1 = head1.next;
}
//一人一步走
while(head1 !=null && head2 != null && head1 != head2){
head1 = head1.next;
head2 = head2.next;
}
if(head1 == head2 && head1 !=null && head2 != null){
System.out.println("交点:"+head1.data+" "+head2.data);
return true;
}else{
return false;
}
}
这里应该注意两个链表相交应该是地址相交,不是值相等
五. 判断一个链表是否有环
//创建一个环
public void Loop(){
Entry cur = head;
while(cur.next!=null){
cur = cur.next;
}
cur.next=head.next;
}
//4.判断单链表是否有环?
public boolean hasLoop(){
Entry fast = head;//快指针
Entry slow = head;//慢指针
if(head.next ==null){//只有头结点
return false;
}
while(fast != null && fast.next != null){
fast = fast.next.next;//快的走两步
slow = slow.next;//慢的走一步
if(fast == slow)//如果相等,说明快的追上了慢的,二者相遇,必定有环
return true;
}
return false;
}
//环的入口点
public int intoLoop(){
Entry fast = head;
Entry slow = head;
while(fast.next != null && fast != 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;
}
//求环的长度
public int getLooplen(){
Entry fast = head;
Entry slow = head;
int len = 0;
boolean tag = false;//标志位
//判断是否有环
if(!hasLoop()){
return -1;
}
//第一二次相遇之间的长度就是环的长度
while(fast != null&&fast.next != null){
fast =fast.next.next;
slow = slow.next;
if(fast == slow && tag == true){//第二次相遇,跳出
break;
}
if(fast == slow && tag == false){//第一次相遇,将标志位赋值为true
tag = true;
}
if(tag == true){//如果是true,就给长度加一,直到二者再次相遇
len++;
}
}
return len;
}
运行结果: