Chapter 3 链表
文章目录
3.1 链表
3.1.1 链表介绍
- 链表以结点方式存储,存储空间不一定连续
- 每个结点包含数据域和指针域
- 每个结点不一定是连续存储
- 链表有带头结点和不带头结点的,根据需求确定使用哪一种
3.1.2 链表CRUD
1、按输入顺序建立一个学生链表
/**
* The type Single linked list.
* @author ybs
*/
public class SingleLinkedListTest {
public static void main(String[] args) {
StudentNode s1 = new StudentNode(1, "jack", "c1");
StudentNode s2 = new StudentNode(2, "york", "c1");
StudentNode s3 = new StudentNode(3, "peter", "c1");
StudentNode s4 = new StudentNode(4, "marry", "c1");
SingleLinkedList singleLinkedList = new SingleLinkedList();
singleLinkedList.addNode(s1);
singleLinkedList.addNode(s2);
singleLinkedList.addNode(s3);
singleLinkedList.addNode(s4);
singleLinkedList.listLinkedList();
}
}
class SingleLinkedList{
/**
* head 头结点
*/
public StudentNode head = new StudentNode(0, "", "");
public void addNode(StudentNode student){
StudentNode temp = head;
//找到最后一个结点
while(true){
if(temp.next==null) {
break;
}
temp = temp.next;
}
temp.next=student;
}
public void listLinkedList(){
if(head.next==null){
System.out.println("空链表");
return;
}
StudentNode temp = head.next;
while(true){
if(temp==null){
break;
}
System.out.println(temp);
temp = temp.next;
}
}
}
class StudentNode{
public int stuNo;
public String stuName;
public String stuClass;
public StudentNode next;
public StudentNode(int stuNo, String stuName, String stuClass) {
this.stuNo = stuNo;
this.stuName = stuName;
this.stuClass = stuClass;
}
@Override
public String toString() {
return "Student{" +
"stuNo=" + stuNo +
", stuName='" + stuName + '\'' +
", stuClass='" + stuClass + '\'' +
'}';
}
}
2、按照编号顺序建立一个学生表
/**
* The type Single linked list.
* @author ybs
*/
public class SingleLinkedListTest {
public static void main(String[] args) {
StudentNode s1 = new StudentNode(1, "jack", "c1");
StudentNode s2 = new StudentNode(2, "york", "c1");
StudentNode s3 = new StudentNode(3, "peter", "c1");
StudentNode s4 = new StudentNode(4, "marry", "c1");
SingleLinkedList singleLinkedList = new SingleLinkedList();
/* singleLinkedList.addNode(s1);
singleLinkedList.addNode(s2);
singleLinkedList.addNode(s3);
singleLinkedList.addNode(s4);*/
singleLinkedList.addNodeByOrder(s3);
singleLinkedList.addNodeByOrder(s1);
singleLinkedList.addNodeByOrder(s2);
singleLinkedList.addNodeByOrder(s4);
singleLinkedList.addNodeByOrder(s4);
singleLinkedList.listLinkedList();
}
}
class SingleLinkedList{
/*其他同1*/
//按照序号加入链表
public void addNodeByOrder(StudentNode student){
StudentNode temp = head;
boolean flag = false;//判断是否有重复的序号
//找到插入位置
while(true){
if(temp.next==null){
break;
}
if(temp.next.stuNo > student.stuNo){
break;
}else if(temp.next.stuNo == student.stuNo){
flag=true;
break;
}
temp = temp.next;
}
if(flag){
System.out.printf("重复序号%d,无法加入\n",student.stuNo);
}else{
student.next = temp.next;
temp.next = student;
}
}
}
class StudentNode{
/*同1*/
}
3、修改链表
/**
* The type Single linked list.
* @author ybs
*/
public class SingleLinkedListTest {
public static void main(String[] args) {
StudentNode s1 = new StudentNode(1, "jack", "c1");
StudentNode s2 = new StudentNode(2, "york", "c1");
StudentNode s3 = new StudentNode(3, "peter", "c1");
StudentNode s4 = new StudentNode(4, "marry", "c1");
SingleLinkedList singleLinkedList = new SingleLinkedList();
/* singleLinkedList.addNode(s1);
singleLinkedList.addNode(s2);
singleLinkedList.addNode(s3);
singleLinkedList.addNode(s4);*/
singleLinkedList.addNodeByOrder(s3);
singleLinkedList.addNodeByOrder(s1);
singleLinkedList.addNodeByOrder(s2);
singleLinkedList.addNodeByOrder(s4);
singleLinkedList.addNodeByOrder(s4);
singleLinkedList.listLinkedList();
singleLinkedList.updateNode(new StudentNode(3, "sam", "c2"));
singleLinkedList.listLinkedList();
}
}
class SingleLinkedList{
/*其他同1*/
//修改结点
//根据要修改的结点newStudent的stuNo编号查找修改
public void updateNode(StudentNode newStudent){
if(head.next==null){
System.out.println("空链表");
return;
}
StudentNode temp = head.next;
boolean flag = false;//判断是否找到序号
while(true){
if(temp==null){
break;
}
if(temp.stuNo== newStudent.stuNo){
flag=true;
break;
}
temp = temp.next;
}
if(flag){
temp.stuName = newStudent.stuName;
temp.stuClass = newStudent.stuClass;
}else{
System.out.printf("未找到序号%d的学生",newStudent.stuNo);
}
}
}
class StudentNode{
/*同1*/
}
3.1.3 单链表题目
1、求单链表的有效结点个数
//获取单链表的长度
public static int getLength(StudentNode head){
if(head.next == null){
return 0;
}
int length = 0;
StudentNode temp = head.next;
while(temp!=null){
length++;
temp=temp.next;
}
return length;
}
2、查找单链表倒数第k个结点
//获取单链表倒数第index个元素
//1.先得到单链表目前的长度size 2.从头遍历(size-index)个
public static StudentNode getLastIndexNode(StudentNode head, int index){
if(head.next==null || index <= 0 || index > getLength(head)){
return null;
}
int size = getLength(head);
StudentNode temp = head.next;
for(int i=0;i<(size-index);++i){
temp = temp.next;
}
return temp;
}
3、单链表反转
//反转单链表
//1.设置一个新的头结点reverseHead 2.遍历链表,每次将结点放到reverseHead的最前端 3.将原来的头结点指向新的头结点的下一个结点
public static void reverseLinkedList(StudentNode head){
//如果为空或者只有一个结点就直接返回
if(head.next==null||head.next.next==null){
return;
}
StudentNode temp = head.next;//循环指针
StudentNode next = null;//指向当前循环下一个的指针
StudentNode reverseHead = new StudentNode(0, "", "");//新的头结点
while(temp!=null){
next = temp.next;
temp.next = reverseHead.next;
reverseHead.next = temp;
temp = next;
}
head.next = reverseHead.next;
}
4、从尾到头打印单链表
//从尾到头打印单链表
public void reverseListLinkedList(){
if(head.next==null){
return;
}
Stack<StudentNode> stack = new Stack<>();
StudentNode temp = head.next;
while(temp!=null){
stack.push(temp);
temp = temp.next;
}
while(stack.size()>0){
System.out.println(stack.pop());
}
}
5、合并两个有序单链表,合并之后依然有序
//合并两个有序单链表,合并依然有序
public static SingleLinkedList mergeLinkedList(SingleLinkedList sglList1, SingleLinkedList sglList2){
SingleLinkedList singleLinkedList = new SingleLinkedList();
StudentNode list1 = sglList1.head;
StudentNode list2 = sglList2.head;
StudentNode newList = new StudentNode(0, "", "");
if(list1.next==null){
newList.next = list2.next;
singleLinkedList.head = newList;
return singleLinkedList;
}else if(list2.next==null){
newList.next = list1.next;
singleLinkedList.head = newList;
return singleLinkedList;
}
StudentNode temp1 = list1.next;
StudentNode temp2 = list2.next;
StudentNode tempNewList = newList;
while (temp1!=null && temp2!=null){
if(temp1.stuNo< temp2.stuNo){
tempNewList.next=temp1;
tempNewList = tempNewList.next;
temp1 = temp1.next;
}else{
tempNewList.next=temp2;
tempNewList = tempNewList.next;
temp2 = temp2.next;
}
}
if (temp1==null){
tempNewList.next = temp2;
}else{
tempNewList.next = temp1;
}
singleLinkedList.head = newList;
return singleLinkedList;
}
3.2 双向链表
3.2.1 双向链表介绍
- 单链表只能往一个方向查找,双向链表可以向前向后查找
- 单链表的结点不能自我删除,双链表的结点可以
3.2.2 双向链表CRUD
public class DoubleLinkedListTest {
public static void main(String[] args) {
StudentNode2 s1 = new StudentNode2(1, "jack", "c1");
StudentNode2 s2 = new StudentNode2(2, "york", "c1");
StudentNode2 s3 = new StudentNode2(3, "peter", "c1");
StudentNode2 s4 = new StudentNode2(4, "marry", "c1");
StudentNode2 s5 = new StudentNode2(5, "sam", "c2");
DoubleLinkedList doubleLinkedList = new DoubleLinkedList();
doubleLinkedList.addNode(s1);
doubleLinkedList.addNode(s2);
doubleLinkedList.addNode(s3);
doubleLinkedList.addNode(s4);
doubleLinkedList.addNode(s5);
//doubleLinkedList.updateNode(new StudentNode2(3, "peter", "c2"));
//doubleLinkedList.deleteNode(3);
doubleLinkedList.listLinkedList();
}
}
class DoubleLinkedList{
/**
* head 头结点
*/
public StudentNode2 head = new StudentNode2(0, "", "");
public void addNode(StudentNode2 student){
StudentNode2 temp = head;
//找到最后一个结点
while(true){
if(temp.next==null) {
break;
}
temp = temp.next;
}
temp.next=student;
student.previou = temp;
}
public void listLinkedList(){
if(head.next==null){
System.out.println("空链表\n");
return;
}
StudentNode2 temp = head.next;
while(true){
if(temp==null){
break;
}
System.out.println(temp);
temp = temp.next;
}
}
//删除结点
//根据编号删除结点
public void deleteNode(int stuNo){
StudentNode2 temp = head.next;
boolean flag = false;//判断是否找到该结点
while(true){
if(temp==null){
break;
}
if(temp.stuNo==stuNo){
flag=true;
break;
}
temp = temp.next;
}
if(flag){
temp.next.previou = temp.previou;
temp.previou.next = temp.next;
}else{
System.out.printf("未找到编号为%d的结点",stuNo);
}
}
//修改结点
//根据要修改的结点newStudent的stuNo编号查找修改
public void updateNode(StudentNode2 newStudent){
if(head.next==null){
System.out.println("空链表");
return;
}
StudentNode2 temp = head.next;
boolean flag = false;//判断是否找到序号
while(true){
if(temp==null){
break;
}
if(temp.stuNo== newStudent.stuNo){
flag=true;
break;
}
temp = temp.next;
}
if(flag){
temp.stuName = newStudent.stuName;
temp.stuClass = newStudent.stuClass;
}else{
System.out.printf("未找到序号%d的学生",newStudent.stuNo);
}
}
}
class StudentNode2 {
public int stuNo;
public String stuName;
public String stuClass;
public StudentNode2 next;
public StudentNode2 previou;
public StudentNode2(int stuNo, String stuName, String stuClass) {
this.stuNo = stuNo;
this.stuName = stuName;
this.stuClass = stuClass;
}
@Override
public String toString() {
return "Student{" +
"stuNo=" + stuNo +
", stuName='" + stuName + '\'' +
", stuClass='" + stuClass + '\'' +
'}';
}
}
3.3 循环链表和约瑟夫问题
3.3.1 单向环形链表的应用场景
Josephu(约瑟夫、约瑟夫环)问题
3.3.2 单向环形链表的构建与遍历
1、构建思路
先创建一个first结点,并形成环形,每增加一个新的结点,就把该结点加入已有的环形链表中
2、遍历思路
先让一个辅助指针cur指向first,然后通过while遍历环形链表,当cur.next=first时结束
3、实现
public class JosephuProblemTest {
public static void main(String[] args) {
CircleSingleLinkedList circleSingleLinkedList = new CircleSingleLinkedList();
circleSingleLinkedList.addGirl(5);
circleSingleLinkedList.listGirls();
}
}
class CircleSingleLinkedList{
public Girl first = null;
public CircleSingleLinkedList(){}
public void addGirl(int num){
if (num<1){
System.out.println("num不符合");
return;
}
Girl curGirl = null;
for(int i=1;i<=num;++i){
Girl girl = new Girl(i);
if (i==1){
first = girl;
curGirl = first;
girl.setNext(first);
}else{
curGirl.setNext(girl);
girl.setNext(first);
curGirl = girl;
}
}
}
public void listGirls(){
if (first==null){
System.out.println("空表");
return;
}
Girl curGirl = first;
while(true){
System.out.println("Girl "+curGirl.getId());
if(curGirl.getNext()==first){
break;
}
curGirl = curGirl.getNext();
}
}
}
class Girl{
private int id;
private Girl next;
public Girl(int id) {
this.id = id;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public Girl getNext() {
return next;
}
public void setNext(Girl next) {
this.next = next;
}
}
3.3.3 约瑟夫问题实现
思路:n 一共有几个人; k 表示从第k个人开始报数; m 表示每次数几个数
- 需要创建一个辅助指针helper,事先应该指向环形链表的最后一个结点
- 报数前,让 first 和 helper 同时移动 k-1 次
- 报数时,让 first 和 helper 同时移动 m-1 次
- 此时 first 指向的结点出圈
/**
* 约瑟夫问题实现
*
* @param startId 表示从第几个开始数数
* @param countNum 表示每次数到几出圈
* @param sumNum 表示最初有多少小孩
*/
public void josephuCountGirl(int startId,int countNum,int sumNum){
if (first==null || startId < 1 || startId > sumNum){
System.out.println("链表或输入异常");
return;
}
Girl helper = first;
//将helper移动到最后一个结点
while(true){
if(helper.getNext()==first){
break;
}
helper = helper.getNext();
}
//将first移动到起始结点,helper移动到first的前一个结点
for(int i=0;i<startId-1;++i){
first = first.getNext();
helper = helper.getNext();
}
//数数,出圈
while(true){
//first==helper时,说明圈内只剩下一个结点,跳出
if (first==helper){
break;
}
//数数
for (int i=0;i<countNum-1;++i){
first = first.getNext();
helper = helper.getNext();
}
System.out.printf("%d出圈\n",first.getId());
first = first.getNext();//first移动到出圈结点的下一个结点
helper.setNext(first);//helper.next指向first,出圈结点被移出链表
}
System.out.printf("最后剩下%d\n",first.getId());
}