一 复杂度
1 时间复杂度
最坏情况
2空间复杂度
一个原始数据类型的数组一般需要24字节的头信息(16个自己的对象开销,4字节用于保存长度以及4个填充字节)再加上保存值所需的内存
二 排序
简单排序
(1) 冒泡排序
比较相邻的元素,如果前一个元素比后一个元素的值大,就交换两个元素的位置。
package SortAlgorithm;
public class BubbleSort {
/*
* 对数组a中的元素进行排序
* */
public static void sort(Comparable[] a) {
for (int i = a.length-1; i > 0; i--) {//定义冒泡的元素个数
for (int j = 0; j < i; j++) {
//比较索引j和索引j+1的值
if (greater(a[j], a[j + 1])) {
exchange(a, j, j + 1);
}
}
}
}
/*
* 比较v元素是否大于w元素
* */
private static boolean greater(Comparable v, Comparable w) {
return v.compareTo(w) > 0;
}
/*
* 数组元素i和j交换位置
* */
private static void exchange(Comparable[] a, int i, int j) {
Comparable temp;
temp = a[i];
a[i] = a[j];
a[j] = temp;
}
}
(2)选择排序
1 每一次遍历的过程中,都假定第一个索引处的元素是最小值,和其他索引处的值依次进行比较,如果当前索引处的值大于其他某个索引处的值,则假定其他某个索引处的值为最小值,最后可以找到最小值所在索引
2 交换第一个索引处和最小值所在的索引处的值
package SortAlgorithm;
public class SelectionSort {
/*
* 对数组a中的元素进行排序
* */
public static void sort(Comparable[] a) {
for (int i=0;i<=a.length-2;i++){//定义选择排序的次数
//定义一个变量,记录最小元素所在索引,默认为参与排序的第一个元素所在的位置索引
int minIndex = i;
for (int j=i+1;j< a.length;j++){
//比较最小索引minIndex处的值和j索引处的值
if (greater(a[minIndex],a[j])){
minIndex = j;
}
}
//交换最小索引minIndex处的值和j索引处的值
exchange(a,i,minIndex);
}
}
/*
* 比较v元素是否大于w元素
* */
private static boolean greater(Comparable v, Comparable w) {
return v.compareTo(w) > 0;
}
/*
* 数组元素i和j交换位置
* */
private static void exchange(Comparable[] a, int i, int j) {
Comparable temp;
temp = a[i];
a[i] = a[j];
a[j] = temp;
}
}
(3)插入排序
将所有的元素分为两组,已经排序的和未排序的;
找到未排序的组中的第一个元素,向已经排序的组中进行插入;
倒叙遍历已经排序的元素,依次和待插入的元素进行比较,直到找到一个元素小于等于待插入元素,那么就把待插入元素放在这个位置,其他的元素向后移动一位。
package SortAlgorithm;
public class InsertionSort {
/*
* 对数组a中的元素进行排序
* */
public static void sort(Comparable[] a) {
for (int i = 1; i < a.length; i++) {//定义选择排序的元素个数
for (int j = i; j > 0; j--) {
//比较索引j和索引j-1的值
if (greater(a[j - 1], a[j])) {
exchange(a, j - 1, j);
} else {
break;
}
}
}
}
/*
* 比较v元素是否大于w元素
* */
private static boolean greater(Comparable v, Comparable w) {
return v.compareTo(w) > 0;
}
/*
* 数组元素i和j交换位置
* */
private static void exchange(Comparable[] a, int i, int j) {
Comparable temp;
temp = a[i];
a[i] = a[j];
a[j] = temp;
}
}
高级排序
(4)希尔排序
/
* 增长量h的确定
int h = 1;
while(h<数组的长度/2){
h = 2h+1;
}
//循环结束后我们就可以确定h的最大值
h的减小规则为
h=h/2
package SortAlgorithm;
import java.util.Currency;
public class ShellSort {
public static void sort(Comparable[] a) {
//根据数组a的长度,确定增长量h的最大值
int h = 1;
while (h < a.length / 2) {
h = 2 * h + 1;
}
//当增长量h小于1,排序结束
//希尔排序
while (h >= 1) {
//找到待插入的元素
for (int i = h; i < a.length; i++) {//定义待插入的元素个数
//把待插入的元素插入到有序数列中
for (int j = i; j >=h ; j-=h) {
//比较索引j和索引j-h的值
if (greater(a[j-h], a[j])) {
//交换元素
exchange(a, j-h, j);
} else {
//待插入元素已经找到了合适的位置,结束循环
break;
}
}
h=h/2;
}
}
}
/*
* 比较v元素是否大于w元素
* */
private static boolean greater(Comparable v, Comparable w) {
return v.compareTo(w) > 0;
}
/*
* 数组元素i和j交换位置
* */
private static void exchange(Comparable[] a, int i, int j) {
Comparable temp;
temp = a[i];
a[i] = a[j];
a[j] = temp;
}
}
(5)归并排序
时间复杂度:O(nlog(n))
空间复杂度;因为有辅助数组,需要额外的内存开销
排序原理
package SortAlgorithm;
public class MergeSort {
//归并所需要的辅助数组
private static Comparable[] assist;
/*
* 比较v元素是否小于w元素
* */
private static boolean less(Comparable v, Comparable w) {
return v.compareTo(w) < 0;
}
/*
* 数组元素i和j交换位置
* */
private static void exchange(Comparable[] a, int i, int j) {
Comparable temp = a[i];
a[i] = a[j];
a[j] = temp;
}
/*
* 对数组a中的元素进行排序
* */
public static void sort(Comparable[] a) {
//1 初始化辅助数组assist
assist = new Comparable[a.length];
//2 定义一个lo变量和hi变量,分别记录数组中最小的索引和最大的索引
int lo = 0;
int hi = a.length - 1;
//3 调用sort重载方法完成数组a中,从索引lo到索引hi的元素的排序
sort(a, lo, hi);
}
/*
* 对数组a中从lo到hi的元素进行排序
* */
private static void sort(Comparable[] a, int lo, int hi) {
//安全性校验
if (hi <= lo) {
return;
}
//对lo到hi之间的数据分成两组
int mid = lo + (hi - lo) / 2;
//分别对每一个数组进行排序
sort(a, lo, mid);
sort(a, mid + 1, hi);
//再把两个组中的数据进行归并
merge(a, lo, mid, hi);
}
/*
*对数组中,从0到mid为一组,从mid+1到hi为一组,对这两组数据进行归并
* */
private static void merge(Comparable[] a, int lo, int mid, int hi) {
//定义三个指针
int i = lo;
int p1 = lo;
int p2 = mid + 1;
//遍历:移动p1指针和p2指针,比较对应索引处的值,找出小的那个,放在辅助数组对应的索引处
while (p1 <= mid && p2 <= hi) {
//比较对应索引处的值
if (less(a[p1], a[p2])) {
assist[i++] = a[p1++];
} else {
assist[i++] = a[p2++];
}
}
//遍历:如果p1的指针没有走完,那么顺序移动p1指针,把对应的元素放在辅助数组的对应索引处
while (p1 <= mid) {
assist[i++] = a[p1++];
}
//遍历:如果p2的指针没有走完,那么顺序移动p2指针,把对应的元素放在辅助数组的对应索引处
while (p2 <= hi) {
assist[i++] = a[p2++];
}
//把辅助数组中的元素拷贝到原数据中
for (int index = lo; index <= hi; index++) {
a[index] = assist[index];
}
}
}
(6)快速排序
快速排序是对冒泡排序的一种改进。
时间复杂度:平均是O(nlog(n)),最坏情况是O(n²)
package SortAlgorithm;
import java.util.concurrent.CopyOnWriteArrayList;
public class QuickSort {
/*
* 比较v元素是否小于w元素
* */
private static boolean less(Comparable v, Comparable w) {
return v.compareTo(w) < 0;
}
/*
* 数组元素i和j交换位置
* */
private static void exchange(Comparable[] a, int i, int j) {
Comparable temp = a[i];
a[i] = a[j];
a[j] = temp;
}
/*
* 对数组a中的元素进行排序
* */
public static void sort(Comparable[] a) {
//1 定义一个low变量和high变量,分别记录数组中最小的索引和最大的索引
int low = 0;
int high = a.length - 1;
//2 调用sort重载方法完成数组a中,从索引lo到索引hi的元素的排序
sort(a, low, high);
}
/*
* 对数组a中从low到high的元素进行排序
* */
private static void sort(Comparable[] a, int low, int high) {
//安全性校验
if (high <= low) {
return;
}
//对lo到hi之间的数据分成两组,(左子组和右子组)
int partition = partition(a, low, high);//返回的是分组的分界值,位置变化后的索引
//分别对每一个数组进行排序
sort(a, low, partition - 1);
sort(a, partition + 1, high);
}
/*
*对数组a中,从索引low到索引high之间的元素进行分组,并返回分组界对应的索引
* */
public static int partition(Comparable[] a, int low, int high) {
//确定分界值
Comparable key = a[low];
//定义两个指针,分别指向切分元素的最小索引值和最大索引值的下一个位置
int left = low;
int right = high + 1;
//初分
while (true) {
//先从右往左扫描,移动right指针,找到一个比分界值小的元素,停止
while (less(key, a[--right])) {//--表示右指针向左移
if (right == low) {
break;
}
}
//再从左往右扫描,移动left指针,找到一个比分界值大的元素,停止
while (less(a[++left], key)) {//++表示左指针向右移动
if (left == high) {
break;
}
}
//判断left>=right,如果是,则证明元素扫描完毕,结束循环,如果不是,则交换元素
if (left >= right) {
break;
} else {
exchange(a, left, right);
}
}
exchange(a, low, right);
return right;
}
}
(7)堆排序
import java.util.Arrays;
//大顶堆:每个节点的值都大于或等于其子节点的值,在堆排序算法中用于升序排列;
//小顶堆:每个节点的值都小于或等于其子节点的值,在堆排序算法中用于降序排列;
public class MaxHeapSortAlgorithm {
public static void main(String[] args) {
int[] arr = {9, 5, 3, 7, 4, 8, 55,2};
int[] res = maxHeapSort(arr);
System.out.println(Arrays.toString(res));
}
public static int[] maxHeapSort(int[] arr) {
int len = arr.length;
buildMaxHeap(arr, len);
for (int i=len-1;i>=0;i--){
swap(arr,i,0);
heapify(arr,i,0);
}
return arr;
}
public static void buildMaxHeap(int[] arr, int len) {
int lastNode = len - 1;
int lastParent = (lastNode - 1) / 2;
for (int i = lastParent; i >= 0; i--) {
heapify(arr, len, i);
}
}
//对第i个节点进行堆调整
public static void heapify(int[] arr, int len, int i) {
if (i >= len) {
return;
}
int leftC = 2 * i + 1;
int rightC = 2 * i + 2;
int maxNode = i;
if (leftC < len && arr[leftC] > arr[maxNode]) {
maxNode = leftC;
}
if (rightC < len && arr[rightC] > arr[maxNode]) {
maxNode = rightC;
}
if (maxNode != i) {
swap(arr, maxNode, i);
heapify(arr, len, maxNode);
}
}
public static void swap(int[] arr, int i, int j) {
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
排序算法的稳定性
数组a中有若干个元素,其中A元素,和B 元素相等,并且A元素在B元素前面,如果使用某种排序算法排序后,能够保证A元素依然在B元素的前面,可以说这个算法是稳定的。
冒泡排序:稳定
选择排序:不稳定
插入排序:稳定
希尔排序:不稳定
归并排序:稳定
快速排序:不稳定
三 线性表
1 顺序表
package Linear;
//顺序表的API设计
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Iterator;
public class SequenceList<T> implements Iterable<T> {
private T[] elements;
private int N;
//构造方法 //加public 是对外开放,可以继承,不加的话默认时protected ,代表只能对同包开放
public SequenceList(int capacity) {
//初始化数组
this.elements = (T[]) new Object[capacity];
//初始化长度
this.N = 0;
}
//清空线性表
public void clear() {
this.N = 0;
}
//判断线性表是否为空,是返回true,否返回false
public boolean isEmpty() {
return N == 0;
}
// 获取线性表的长度
public int length() {
return N;
}
//读取并返回线性表中的第i个元素的值
public T get(int i) {
return elements[i];
}
//向线性表中添加一个元素t<br/>
public void insert(T t) {
if(N==elements.length){
resize(2*elements.length);
}
elements[N++] = t;
}
//在线性表中的第i个元素之前插入一个值为t的数据元素
public void insert(int i, T t) {
//先把i索引处的元素及其后面的元素依次向后移动一位
for (int index = N ; index > i; index--) {
elements[index] = elements[index - 1];
}
//再把t元素放在i索引处即可
N++;
elements[i] = t;
}
//删除并返回线性表中第i个数据元素
public T remove(int i) {
//记录索引处的值
T current = elements[i];
//索引后面的元素依次向前移动一位
for (int index = i; index < N - 1; index++) {
elements[index] = elements[index + 1];
}
//元素个数减1
N--;
if (N<elements.length/4){
resize(elements.length/2);
}
return current;
}
//返回线性表中首次出现的指定的数据元素的位序号,若不存在,则返回-1
public int indexOf(T t) {
for (int i = 0; i < N; i++) {
if (elements[i].equals(t)) {
return i;
}
}
return -1;
}
//根据参数newSize,重置elesment的大小
public void resize(int newSize){
//定义一个临时数组,指向原数组
T[] temp = elements;
//创建新数组
elements = (T[])new Object[newSize];
//把原数组的数据拷贝到新数组即可
for (int i = 0; i < N; i++) {
elements[i]=temp[i];
}
}
@Override
public Iterator<T> iterator() {
return new SIterator();
}
private class SIterator implements Iterator {
//初始化一个指针
private int cusor;
public SIterator() {
this.cusor = 0;
}
@Override
public boolean hasNext() {
return cusor < N;
}
@Override
public Object next() {
return elements[cusor++];
}
}
}
2链表
单向链表
一个数据域,一个指针域。
链表的头结点的数据域不储存数据
package Linear;
import java.util.Iterator;
//链表的API设计
public class LinkList<T> implements Iterable{
//记录头结点
private Node head;
//记录链表的长度
private int N;
//结点类
public 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;
}
//判断链表是否为空,是返回true,否返回false
public boolean isEmpty() {
return N == 0;
}
// 获链表的长度
public int length() {
return N;
}
//读取并返回链表中的第i个元素的值
public T get(int i) {
//通过循环,从头结点开始往后找,依次找i次,就可以找到对应的元素
Node n = head.next;
for (int index = 0; index < i; index++) {
n = n.next;
}
return n.item;
}
//向链表中添加一个元素t<br/>
public void insert(T t) {
//找到当前最后一个结点
Node n = head;
while (n.next != null) {
n = n.next;
}
//创建新结点,保存元素t
Node newNode = new Node(t, null);
//让当前最后一个结点指向新结点
n.next = newNode;
//元素长度+1
N++;
}
//在链表中的第i个元素之前插入一个值为t的数据元素
public void insert(int i, T t) {
//找到i位置前的一个结点
Node pre = head;
for (int index=0;index<i;index++){
pre = pre.next;
}
//找到i位置的结点
Node curr = pre.next;
//创建新结点,并且新结点需要指向原来i位置的结点
Node newNode = new Node(t,curr);
//原来i位置的前一个结点指向新结点即可
pre.next = newNode;
//元素的个数+1
N++;
}
//删除并返回链表中第i个数据元素
public T remove(int i) {
//找到i位置前的一个结点
Node pre = head;
for (int index=0;index<=i;index++){
pre = pre.next;
}
//找到i位置的结点
Node curr = pre.next;
//i位置的后一个结点
Node nextNode = curr.next;
//前一个结点指向下一个结点
pre.next=nextNode;
N--;
return curr.item;
}
//返回链表中首次出现的指定的数据元素的位序号,若不存在,则返回-1
public int indexOf(T t) {
Node 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 LinkIterator();
}
private class LinkIterator implements Iterator{
//定义一个结点
Node n;
public LinkIterator(){
this.n=head;
}
@Override
public boolean hasNext() {//判断是否遍历结束,返回true遍历结束,返回false遍历未结束
return n.next!=null;
}
@Override
public Object next() {//指向下一个结点
n=n.next;
return n.item;
}
}
}
双向链表
一个数据域两个指针域。
链表的头结点的数据域不储存数据,指向前驱结点的指针域为null,指向后继结点的指针域指向第一个真正储存数据的结点
package Linear;
import java.util.Iterator;
public class TwoWayLinkList<T> implements Iterable {
//记录首结点
private Node head;
//记录最后一个结点
private Node last;
//记录链表的长度
private int N;
//结点类
public 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 TwoWayLinkList() {
//初始化头结点
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;
}
//判断链表是否为空,是返回true,否返回false
public boolean isEmpty() {
return this.head.next ==null;
}
// 获链表的长度
public int length() {
return N;
}
//获取链表中的第一个元素
public T getFirst(){
if (isEmpty()){
return null;
}
return head.next.item;
}
//获取链表中的最后一个元素
public T getLast(){
if (isEmpty()){
return null;
}
return last.item;
}
//读取并返回链表中的第i个元素的值
public T get(int i) {
//通过循环,从头结点开始往后找,依次找i次,就可以找到对应的元素
Node n = head.next;
for (int index = 0; index < i; index++) {
n = n.next;
}
return n.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 < i; index++) {
pre = pre.next;
}
//找到i位置的结点
Node curr = pre.next;
//创建新结点,
Node newNode = new Node(t,pre, curr);
//让i位置的前一个结点的下一个结点变为新结点
pre.next = newNode;
//原来i位置的前一个结点指向新结点即可
curr.pre = newNode;
//元素的个数+1
N++;
}
//删除并返回链表中第i个数据元素
public T remove(int i) {
//找到i位置前的一个结点
Node pre = head;
for (int index = 0; index <= i; index++) {
pre = pre.next;
}
//找到i位置的结点
Node curr = pre.next;
//i位置的后一个结点
Node nextNode = curr.next;
//让i位置的前一个结点的下一个结点变为i位置后的下一个结点
pre.next = nextNode;
//让i位置的下一个结点上一个结点变为i位置前的前一个结点
nextNode.pre=pre;
N--;
return curr.item;
}
//返回链表中首次出现的指定的数据元素的位序号,若不存在,则返回-1
public int indexOf(T t) {
Node 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 TWLinkIterator();
}
private class TWLinkIterator implements Iterator {
//定义一个结点
private Node n;
public TWLinkIterator() {
this.n = head;
}
@Override
public boolean hasNext() {//判断是否遍历结束,返回true遍历结束,返回false遍历未结束
return n.next != null;
}
@Override
public Object next() {//返回下一个结点的item是多少
n = n.next;
return n.item;
}
}
//反转整个链表
public void reserve() {
if (isEmpty()) {
return ;
}
reserve(head.next);
}
//反转指定结点,并返回反转后的结点
public Node reserve(Node curr) {
if (curr.next == null) {
head.next = curr;
return curr;
}
//递归的去反转当前结点的下一个结点,返回值就是链表反转后的当前结点的下一个结点
Node pre = reserve(curr.next);
//让返回的结点的下一个结点变为当前结点curr
pre.next =curr;
//把当前结点的下一个结点变为null
curr.next = null;
return curr;
}
}
LinkedList
底层是双向链表
链表的增(insert)删(remove)查(get)方法的时间复杂度是O(n),和线性表虽然是一样的,但是因为链表的物理地址不连续,它不需要预先指定储存空间大小,或者在存储过程中涉及到扩容等操作,同时它没有涉及元素的交换
单链表的反转
1->2->3->4
反转:4->3->2->1
//反转整个链表
public void reserve() {
if (isEmpty()) {
return ;
}
reserve(head.next);
}
//反转指定结点,并返回反转后的结点
public Node reserve(Node curr) {
if (curr.next == null) {
head.next = curr;
return curr;
}
//递归的去反转当前结点的下一个结点,返回值就是链表反转后的当前结点的下一个结点
// Node pre = reserve(curr.next).next=curr;
//反转当前结点的下一个结点的返回值的下一个结点就是当前结点
reserve(curr.next).next=curr;
//让返回的结点的下一个结点变为当前结点curr
// pre.next =curr;
//把当前结点的下一个结点变为null
curr.next = null;
return curr;
}
快慢指针
package test;
import Linear.LinkList;
import org.w3c.dom.Node;
public class FastSlowTest {
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> sixth= new Node<String>("ff",null);
Node<String> seventh= new Node<String>("gg",null);
//完成结点之间的指向
first.next = second;
second.next=third;
third.next = fourth;
fourth.next = fifth;
fifth.next=sixth;
sixth.next=seventh;
//查找中间值
String mid = getMid(first);
System.out.println("中间值:"+mid);
}
//结点类
public static class Node<T> {
//储存数据
T item;
//下一个结点
Node next;
public Node(T item, Node next) {
this.item = item;
this.next = next;
}
}
/*
* @param first 链表的首结点
* @return 链表的中间结点的值
* */
public static String getMid(Node<String> first){
//定义两个指针
Node<String> fast =first;
Node<String> slow = first;
//使用两个指针遍历链表
while (fast!=null && fast.next!=null){
fast=fast.next.next;
slow=slow.next;
}
return slow.item;
}
}
有环链表入口问题
当快慢指针相遇时,我们可以判断到链表中有环,这时重新设定一个新指针指向链表的起点,且步长与慢指针一样为1,则慢指针与新指针相遇的地方就是环的入口。
package test;
import Linear.LinkList;
import org.w3c.dom.Node;
public class FastSlowTest {
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> sixth = new Node<String>("ff", null);
Node<String> seventh = new Node<String>("gg", null);
//完成结点之间的指向
first.next = second;
second.next = third;
third.next = fourth;
fourth.next = fifth;
fifth.next = sixth;
sixth.next = seventh;
//产生环
seventh.next = third;
//判断链表是否有环
boolean circle = isCircle(first);
System.out.println("first链表是否有环:" + circle);
/* //查找中间值
String mid = getMid(first);
System.out.println("中间值:" + mid);*/
//查找链表入口
Node entrance = getEntrance(first);
System.out.println("有环链表入口:" + entrance.item);
}
/*
* @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 = fast.next.next;
slow = slow.next;
if (fast.equals(slow)) {
return true;
}
}
return false;
}
//结点类
public static class Node<T> {
//储存数据
T item;
//下一个结点
Node next;
public Node(T item, Node next) {
this.item = item;
this.next = next;
}
}
/*
* @param first 链表的首结点
* @return 链表的中间结点的值
* */
public static String getMid(Node<String> first) {
//定义两个指针
Node<String> fast = first;
Node<String> slow = first;
//使用两个指针遍历链表
while (fast != null && fast.next != null) {
fast = fast.next.next;
slow = slow.next;
}
return slow.item;
}
/*
* @param first 链表的首结点
* @return 环的入口结点
* */
public static Node getEntrance(Node<String> first) {
//定义快慢指针和临时指针
Node<String> fast = first;
Node<String> slow = first;
Node<String> 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;
}
}
循环链表
约瑟夫问题
package test;
public class Joseph {
public static void main(String[] args) {
//解决约瑟夫问题
//1 构建循环链表,包含41个结点
//用来记录首结点
Node<Integer> first = null;
//用来记录前一个结点
Node<Integer> pre = null;
//遍历
for (int i = 1; i <= 41; i++) {
//如果是第一个结点
if (i == 1) {
first = new Node<>(i, null);
pre = first;
continue;
}
//如果不是第一个结点
Node<Integer> newNode = new Node<>(i, null);
pre.next = newNode;
pre = newNode;
//如果是最后一个结点
if (i == 41) {
pre.next = first;
}
}
//2 需要count计数器,模拟报数
int count = 0;
//3 遍历循环链表
//记录每次遍历的当前结点,默认时首结点
Node<Integer> n = first;
//记录当前结点的上一个结点
Node<Integer> before = null;
while (n != n.next) {
//模拟报数
count++;
//判断当前报数是不是为3
if (count == 3) {
//如果是3,删除并打印当前结点,重置count=0,让当前结点n后移
before.next = n.next;
System.out.print(n.item+",");
count = 0;
n=n.next;
} else {
//如果不是3,让before变为当前结点,让当前结点后移
before = n;
n = n.next;
}
}
//打印最后一个元素
System.out.println(n.item);
}
//结点类
public static class Node<T> {
//储存数据
T item;
//创建下一个结点
Node next;
//构造方法
public Node(T item, Node next) {
this.item = item;
this.next = next;
}
}
}