简介
单向链表是链表的一种,它由多个结点组成,每个结点都由一个数据域和一个指针域组成,数据域用来存储数据, 指针域用来指向其后继结点。链表的头结点的数据域不存储数据,指针域指向第一个真正存储数据的结点。
代码实现:
public class SinglyLinkedList<T> implements Iterable<T>{
//记录头结点
private Node<T> head;
//记录链表的长度
private int N;
//结点类
private static class Node<T> {
//存储数据
T item;
//下一个结点
Node<T> next;
public Node(T item, Node<T> next) {
this.item = item;
this.next = next;
}
}
public SinglyLinkedList() {
//初始化头结点、
this.head = new Node<>(null, null);
//初始化元素个数
this.N=0;
}
//清空链表
public void clear() {
head.next=null;
this.N=0;
}
//获取链表的长度
public int length() {
return N;
}
//判断链表是否为空
public boolean isEmpty() {
return N==0;
}
//获取指定位置i出的元素
public T get(int i) {
//通过循环,从头结点开始往后找,依次找i次,就可以找到对应的元素
Node<T> n = head.next;
for(int index=0;index<i;index++){
n=n.next;
}
return n.item;
}
//向链表中添加元素t
public void add(T t) {
//找到当前最后一个结点
Node<T> n = head;
while(n.next!=null){
n=n.next;
}
//创建新结点,保存元素t
//让当前最后一个结点指向新结点
n.next= new Node<>(t, null);
//元素的个数+1
N++;
}
//向指定位置i出,添加元素t
public void add(int i, T t) {
//找到i位置前一个结点
Node<T> pre = head;
for(int index=0;index<=i-1;index++){
pre=pre.next;
}
//找到i位置的结点
Node<T> curr = pre.next;
//创建新结点,并且新结点需要指向原来i位置的结点
//原来i位置的前一个节点指向新结点即可
pre.next= new Node<>(t, curr);
//元素的个数+1
N++;
}
//删除指定位置i处的元素,并返回被删除的元素
public T remove(int i) {
//找到i位置的前一个节点
Node<T> pre = head;
for(int index=0;index<=i-1;i++){
pre=pre.next;
}
//要找到i位置的结点
Node<T> curr = pre.next;
//找到i位置的下一个结点
Node<T> nextNode = curr.next;
//前一个结点指向下一个结点
pre.next=nextNode;
//元素个数-1
N--;
return curr.item;
}
//查找元素t在链表中第一次出现的位置
public int indexOf(T t) {
//从头结点开始,依次找到每一个结点,取出item,和t比较,如果相同,就找到了
Node<T> n = head;
for(int i=0;n.next!=null;i++){
n=n.next;
if (n.item.equals(t)){
return i;
}
}
return -1;
}
@Override
public Iterator<T> iterator() {
return new LIterator();
}
private class LIterator implements Iterator<T>{
private Node<T> n;
public LIterator(){
this.n=head;
}
@Override
public boolean hasNext() {
return n.next!=null;
}
@Override
public T next() {
n = n.next;
return n.item;
}
}
}
单链表反转
使用递归可以完成反转,递归反转其实就是从原链表的第一个存数据的结点开始,依次递归调用反转每一个结点, 直到把最后一个结点反转完毕,整个链表就反转完毕。
代码实现:
//用来反转整个链表
public void reverse(){
//判断当前链表是否为空链表,如果是空链表,则结束运行,如果不是,则调用重载的reverse方法完成反转
if (isEmpty()){
return;
}
reverse(head.next);
}
private Node<T> reverse(Node<T> curr){
if(curr.next == null){
head.next = curr;
return curr;
}
Node<T> pre = reverse(curr.next);
pre.next = curr;
curr.next = null;
return curr;
}
快慢指针
快慢指针指的是定义两个指针,这两个指针的移动速度一块一慢,以此来制造出自己想要的差值,这个差值可以让我们找到链表上相应的结点。一般情况下,快指针的移动步长为慢指针的两倍。
中间值问题
下面有一个需求:找出链表的中间值
public class FastSlowTest {
public static void main(String[] args) throws Exception {
//创建结点
Node<String> first = new Node<>("aa", null);
Node<String> second = new Node<>("bb", null);
Node<String> third = new Node<>("cc", null);
Node<String> fourth = new Node<>("dd", null);
Node<String> fifth = new Node<>("ee", null);
Node<String> six = new Node<>("ff", null);
Node<String> seven = new Node<>("gg", null);
//完成结点之间的指向
first.next = second;
second.next = third;
third.next = fourth;
fourth.next = fifth;
fifth.next = six;
six.next = seven;
//查找中间值
String mid = getMid(first);
System.out.println("中间值为:"+mid);
}
/**
* @param first 链表的首结点
* @return 链表的中间结点的值
*/
public static String getMid(Node<String> first) {
return null;
}
//结点类
private static class Node<T> {
//存储数据
T item;
//下一个结点
Node<T> next;
public Node(T item, Node<T> next) {
this.item = item;
this.next = next;
}
}
}
使用快慢指针即可解决:最开始,slow与fast指针都指向链表第一个节点,然后slow每次移动一个指针,fast每次移动两个指针。
代码实现如下:
/**
* @param first 链表的首结点
* @return 链表的中间结点的值
*/
public static String getMid(Node<String> first) {
return getMid(first, first);
}
private static String getMid(Node<String> slowNode, Node<String> fastNode) {
if(fastNode.next == null) return slowNode.item;
fastNode = fastNode.next.next;
slowNode = slowNode.next;
return getMid(slowNode, fastNode);
}
单向链表是否有环
下面有一个需求:判断链表中是否有环
使用快慢指针,如果快慢指针最后有快指针节点等于慢指针(相遇),说明有环
public class CircleListCheckTest {
public static void main(String[] args) throws Exception {
//创建结点
Node<String> first = new Node<String>("aa", null);
Node<String> second = new Node<String>("bb", null);
Node<String> third = new Node<String>("cc", null);
Node<String> fourth = new Node<String>("dd", null);
Node<String> fifth = new Node<String>("ee", null);
Node<String> six = new Node<String>("ff", null);
Node<String> seven = new Node<String>("gg", null);
//完成结点之间的指向
first.next = second;
second.next = third;
third.next = fourth;
fourth.next = fifth;
fifth.next = six;
six.next = seven;
//产生环
seven.next = third;
//判断链表是否有环
boolean circle = isCircle(first);
System.out.println("first链表中是否有环:"+circle);
}
/**
* 判断链表中是否有环
* @param first 链表首结点
* @return ture为有环,false为无环
*/
public static boolean isCircle(Node<String> first) {
return false;
}
//结点类
private static class Node<T> {
//存储数据
T item;
//下一个结点
Node<T> next;
public Node(T item, Node<T> next) {
this.item = item;
this.next = next;
}
}
}
代码实现:
/**
* 判断链表中是否有环
* @param first 链表首结点
* @return ture为有环,false为无环
*/
public static boolean isCircle(Node<String> first) {
return isCircle(first, first);
}
private static boolean isCircle(Node<String> slowNode, Node<String> fastNode) {
if(fastNode.next == null) return false;
slowNode = slowNode.next;
fastNode = fastNode.next.next;
if(slowNode.item.equals(fastNode.item)) return true;
return isCircle(slowNode, fastNode);
}
非递归实现:
/**
* 判断链表中是否有环
* @param first 链表首结点
* @return ture为有环,false为无环
*/
public static boolean isCircle(Node<String> first) {
//定义快慢指针
Node<String> fast = first;
Node<String> slow = first;
//遍历链表,如果快慢指针指向了同一个结点,那么证明有环
while(fast!=null && fast.next!=null){
//变换fast和slow
fast = fast.next.next;
slow = slow.next;
if (fast.equals(slow)){
return true;
}
}
return false;
}
单向链表环入口问题
现有一个需求:需要找打单向链表中环的入口
public class CircleListInTest {
public static void main(String[] args) throws Exception {
Node<String> first = new Node<String>("aa", null);
Node<String> second = new Node<String>("bb", null);
Node<String> third = new Node<String>("cc", null);
Node<String> fourth = new Node<String>("dd", null);
Node<String> fifth = new Node<String>("ee", null);
Node<String> six = new Node<String>("ff", null);
Node<String> seven = new Node<String>("gg", null);
//完成结点之间的指向
first.next = second;
second.next = third;
third.next = fourth;
fourth.next = fifth;
fifth.next = six;
six.next = seven;
//产生环
seven.next = third;
//查找环的入口结点
Node<String> entrance = getEntrance(first);
System.out.println("first链表中环的入口结点元素为:"+entrance.item);
}
/**
* 查找有环链表中环的入口结点
* @param first 链表首结点
* @return 环的入口结点
*/
public static Node<String> getEntrance(Node<String> first) {
return null;
}
//结点类
private static class Node<T> {
//存储数据
T item;
//下一个结点
Node<T> next;
public Node(T item, Node<T> next) {
this.item = item;
this.next = next;
}
}
}
思路:
当快慢指针相遇时,我们可以判断到链表中有环,这时重新设定一个新指针指向链表的起点,且步长与慢指针一样 为1,则慢指针与“新”指针相遇的地方就是环的入口。
代码实现如下:
public static Node<String> getEntrance(Node<String> first) {
//定义快慢指针
Node<String> fast = first;
Node<String> slow = first;
Node<String> temp = null;
//遍历链表,如果快慢指针指向了同一个结点,那么证明有环
while(fast!=null && fast.next!=null){
//变换fast和slow
fast = fast.next.next;
slow = slow.next;
if (fast.equals(slow)){
temp = first;
continue;
}
if(temp != null){
temp = temp.next;
if(temp.item.equals(slow.item))
return temp;
}
}
return null;
}
环入口证明:
从链表起始处到环入口长度为:a,从环入口到Faster和Slower相遇点长度为:x,整个环长为:c。
假设从开始到相遇,Slower走过的路程长为s,由于Faster的步速是Slower的2倍,那么Faster在这段时间走的路程长为2s。
而对于Faster来说,他走的路程还等于之前绕整个环跑的n圈的路程nc,加上最后这一次遇见Slower的路程s。
2s = nc + s
对于Slower来说,他走的路程长度s还等于他从链表起始处到相遇点的距离,所以有:
s = a + x
化简:
a + x = nc
a = nc - x
a = (n-1)c + c-x
a = kc + (c-x)
那么可以看出,c-x,就是从相遇点继续走回到环入口的距离,所以从相遇点继续走回到环入口的距离等于起点到环入口距离