链表由若干个“节点”组成,每个节点含有数据域和指针域
有一个指针域,就是单向链表;两个指针域,可以组成双向链表
链表有链头和链尾,链头数据域必须为null,链尾指针域必须为null
单链表节点包含一个数据域和一个指针域
链表的头结点的数据域不存储数据,指针域指向第一个真正存储数据的结点
链表插入和删除只需要修改指节点对应的针域指针指向即可!
其中包含实现单链表的递归反转(面试考点):
需求:原链表中数据为: 1->2->3>4
package cn.itcast.algorithm.linear;
import java.util.Iterator;
public class LinkList implements Iterable{
//记录头结点
private Node head;
//记录链表的长度
private int N;
//结点类
private class Node {
//存储数据
T item;
//下一个结点
Node next;
public Node(T item, Node next) {
this.item = item;
this.next = next;
}
}
public LinkList() {
//初始化头结点、
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 n = head.next;
for(int index=0;index iterator() {
return new LIterator();
}
private class LIterator implements Iterator{
private Node n;
public LIterator(){
this.n=head;
}
@Override
public boolean hasNext() {
return n.next!=null;
}
@Override
public Object next() {
n = n.next;
return n.item;
}
}
//用来反转整个链表
public void reverse(){
//判断当前链表是否为空链表,如果是空链表,则结束运行,如果不是,则调用重载的reverse方法完成反转
if (isEmpty()){
return;
}
reverse(head.next);
}
//反转指定的结点curr,并把反转后的结点返回
public Node reverse(Node curr){
if (curr.next==null){
head.next=curr;
return curr;
}
//递归的反转当前结点curr的下一个结点;返回值就是链表反转后,当前结点的上一个结点
Node pre = reverse(curr.next);
//让返回的结点的下一个结点变为当前结点curr;
pre.next=curr;
//把当前结点的下一个结点变为null
curr.next=null;
return curr;
}
}
每个结点都由一个数据域和两个指针域组成
其中一个指针域用来指向其后继结点,另一个指针域用来指向前驱结点
链表的头结点的数据域不存储数据,指向前驱结点的指针域值为null
package cn.itcast.algorithm.linear;
import java.util.Iterator;
public class TowWayLinkList implements Iterable {
//首结点
private Node head;
//最后一个结点
private Node last;
//链表的长度
private int N;
//结点类
private class Node{
public Node(T item, Node pre, Node next) {
this.item = item;
this.pre = pre;
this.next = next;
}
//存储数据
public T item;
//指向上一个结点
public Node pre;
//指向下一个结点
public Node next;
}
public TowWayLinkList() {
//初始化头结点和尾结点
this.head = new Node(null,null,null);
this.last=null;
//初始化元素个数
this.N=0;
}
//清空链表
public void clear(){
this.head.next=null;
this.head.pre=null;
this.head.item=null;
this.last=null;
this.N=0;
}
//获取链表长度
public int length(){
return N;
}
//判断链表是否为空
public boolean isEmpty(){
return N==0;
}
//获取第一个元素
public T getFirst(){
if (isEmpty()){
return null;
}
return head.next.item;
}
//获取最后一个元素
public T getLast(){
if (isEmpty()){
return null;
}
return last.item;
}
//插入元素t
public void insert(T t){
if (isEmpty()){
//如果链表为空:
//创建新的结点
Node newNode = new Node(t,head, null);
//让新结点称为尾结点
last=newNode;
//让头结点指向尾结点
head.next=last;
}else {
//如果链表不为空
Node oldLast = last;
//创建新的结点
Node newNode = new Node(t, oldLast, null);
//让当前的尾结点指向新结点
oldLast.next=newNode;
//让新结点称为尾结点
last = newNode;
}
//元素个数+1
N++;
}
//向指定位置i处插入元素t
public void insert(int i,T t){
//找到i位置的前一个结点
Node pre = head;
for(int index=0;index iterator() {
return new TIterator();
}
private class TIterator implements Iterator{
private Node n;
public TIterator(){
this.n=head;
}
@Override
public boolean hasNext() {
return n.next!=null;
}
@Override
public Object next() {
n=n.next;
return n.item;
}
}
}
get(int i): 每一次查询,都需要从链表的头部开始,依次向后查找,随着数据元素 N 的增多,比较的元素越多,时间复杂度为 O(n)insert(int i,T t): 每一次插入,需要先找到 i 位置的前一个元素,然后完成插入操作,随着数据元素 N 的增多,查找的元素越多,时间复杂度为 O(n);remove(int i): 每一次移除,需要先找到 i 位置的前一个元素,然后完成插入操作,随着数据元素 N 的增多,查找的元素越多,时间复杂度为 O(n)相比较顺序表,链表插入和删除的时间复杂度虽然一样,但仍然有很大的优势,因为 链表的物理地址是不连续的,它不需要预先指定存储空间大小 ,好处就是存储过程中不会涉及到 扩容 等操作,同时它并没有涉及的 元素的交换 。相比较顺序表,链表的查询操作性能会比较低。因此,如果我们的程序中查询操作比较多,建议使用顺序表,增删操作比较多,建议使用链表。
package cn.itcast.algorithm.test;
public class FastSlowTest {
public static void main(String[] args) throws Exception {
//创建结点
Node first = new Node("aa", null);
Node second = new Node("bb", null);
Node third = new Node("cc", null);
Node fourth = new Node("dd", null);
Node fifth = new Node("ee", null);
Node six = new Node("ff", null);
Node 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 first) {
//定义两个指针
Node fast = first;
Node slow = first;
//使用两个指针遍历链表,当快指针指向的结点没有下一个结点了,就可以结束了,结束之后,慢指针指向的结点就是中间值
while(fast!=null &&fast.next!=null){
//变化fast的值和slow的值
fast = fast.next.next;
slow=slow.next;
}
return slow.item;
}
//结点类
private static class Node {
//存储数据
T item;
//下一个结点
Node next;
public Node(T item, Node next) {
this.item = item;
this.next = next;
}
}
}
package cn.itcast.algorithm.test;
public class CircleListCheckTest {
public static void main(String[] args) throws Exception {
//创建结点
Node first = new Node("aa", null);
Node second = new Node("bb", null);
Node third = new Node("cc", null);
Node fourth = new Node("dd", null);
Node fifth = new Node("ee", null);
Node six = new Node("ff", null);
Node seven = new Node("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 first) {
//定义快慢指针
Node fast = first;
Node slow = first;
//遍历链表,如果快慢指针指向了同一个结点,那么证明有环
while(fast!=null && fast.next!=null){
//变换fast和slow
fast = fast.next.next;
slow = slow.next;
if (fast == slow){
return true;
}
}
return false;
}
//结点类
private static class Node {
//存储数据
T item;
//下一个结点
Node next;
public Node(T item, Node next) {
this.item = item;
this.next = next;
}
}
}
package cn.itcast.algorithm.test;
public class CircleListInTest {
public static void main(String[] args) throws Exception {
Node first = new Node("aa", null);
Node second = new Node("bb", null);
Node third = new Node("cc", null);
Node fourth = new Node("dd", null);
Node fifth = new Node("ee", null);
Node six = new Node("ff", null);
Node seven = new Node("gg", null);
//完成结点之间的指向
first.next = second;
second.next = third;
third.next = fourth;
fourth.next = fifth;
fifth.next = six;
six.next = seven;
//产生环
seven.next = third;
//查找环的入口结点
Node entrance = getEntrance(first);
System.out.println("first链表中环的入口结点元素为:"+entrance.item);
}
/**
* 查找有环链表中环的入口结点
* @param first 链表首结点
* @return 环的入口结点
*/
public static Node getEntrance(Node first) {
//定义快慢指针
Node fast = first;
Node slow = first;
Node temp = null;
//遍历链表,先找到环(快慢指针相遇),准备一个临时指针,指向链表的首结点,继续遍历,直到慢指针和临时指针相遇,那么相遇时所指向的结点就是环的入口
while(fast!=null && fast.next!=null){
//变换快慢指针
fast = fast.next.next;
slow = slow.next;
//判断快慢指针是否相遇
if (fast.equals(slow)){
temp = first;
continue;
}
//让临时结点变换
if (temp!=null){
temp = temp.next;
//判断临时指针是否和慢指针相遇
if (temp.equals(slow)){
break;
}
}
}
return temp;
}
//结点类
private static class Node {
//存储数据
T item;
//下一个结点
Node next;
public Node(T item, Node next) {
this.item = item;
this.next = next;
}
}
}
循环链表,顾名思义,链表整体要形成一个圆环状。
public static void main(String[] args) throws Exception {
//构建结点
Node first = new Node(1, null);
Node second = new Node(2, null);
Node third = new Node(3, null);
Node fourth = new Node(4, null);
Node fifth = new Node(5, null);
Node six = new Node(6, null);
Node seven = new Node(7, null);
//构建单链表
first.next = second;
second.next = third;
third.next = fourth;
fourth.next = fifth;
fifth.next = six;
six.next = seven;
//构建循环链表,让最后一个结点指向第一个结点
seven.next = first;
}
问题描述:传说有这样一个故事,在罗马人占领乔塔帕特后, 39 个犹太人与约瑟夫及他的朋友躲到一个洞中, 39 个犹太人决定宁愿死也不要被敌人抓到,于是决定了一个自杀方式, 41 个人排成一个圆圈,第一个人从 1 开始报数,依次往后,如果有人报数到 3 ,那么这个人就必须自杀,然后再由他的下一个人重新从 1 开始报数,直到所有人都自杀身亡为止。然而约瑟夫和他的朋友并不想遵从。于是,约瑟夫要他的朋友先假装遵从,他将朋友与自己安排在第 16 个与第 31 个位置,从而逃过了这场死亡游戏 。问题转换:41 个人坐一圈,第一个人编号为 1 ,第二个人编号为 2 ,第 n 个人编号为 n 。1. 编号为 1 的人开始从 1 报数,依次向后,报数为 3 的那个人退出圈;2. 自退出那个人开始的下一个人再次从 1 开始报数,以此类推;3. 求出最后退出的那个人的编号。图示:解题思路:1. 构建含有 41 个结点的单向循环链表,分别存储 1~41 的值,分别代表这 41 个人;2. 使用计数器 count ,记录当前报数的值;3. 遍历链表,每循环一次, count++ ;4. 判断 count 的值,如果是 3 ,则从链表中删除这个结点并打印结点的值,把 count 重置为 0 ;
public class Main {
public static void main(String[] args) throws Exception {
//1.构建循环链表
Node first = null;
//记录前一个结点
Node pre = null;
for (int i = 1; i <= 41; i++) {
//第一个元素
if (i==1){
first = new Node(i,null);
pre = first;
continue;
}
Node node = new Node<>(i,null);
pre.next = node; pre = node;
if (i==41){
//构建循环链表,让最后一个结点指向第一个结点
pre.next=first;
}
}
//2.使用count,记录当前的报数值
int count=0;
//3.遍历链表,每循环一次,count++
Node n = first;
Node before = null;
while(n!=n.next){
//4.判断count的值,如果是3,则从链表中删除这个结点并打印结点的值,把count重置为0;
count++;
if (count==3){
//删除当前结点
before.next = n.next;
System.out.print(n.item+",");
count=0;
n = n.next;
}
else {
before=n;
n = n.next;
}
}
/*打印剩余的最后那个人*/
System.out.println(n.item);
}
}