单链表
1. 基本概念
链表是有序的列表,以节点的方式来进行存储,属于链式存储。
每个节点包含data域和next域,next域用来指向下一个节点。
链表的各个节点并不一定是连续存储。
链表分为带头节点的链表和不带头节点的链表。
2. 带头节点链表的示意图
3. 功能详解
1. 添加节点到链表(添加到最后)
- 创建辅助变量temp,用于遍历整个节点
- 找到链表的最后,把节点添加到链表的最后
2. 添加节点到链表(有序)
- 创建辅助变量temp,用于遍历整个节点
- 找到要添加节点的前一个位置
- 新的节点.next = temp.next
- temp.next = 新的节点
3. 修改节点的信息
- 创建辅助变量temp,用于遍历整个节点
- 首先判断链表是否为空,若为空,则修改失败,结束函数
- 对链表进行遍历,找到要修改的节点
- 找到要修改的节点后,进行修改信息
4. 删除某个节点的信息
- 创建辅助变量temp,用于遍历整个节点.
- 对链表进行遍历,找到要删除的节点。
- temp.next = temp.next.next;
- 被删除的节点,将不会有其它引用指向,会被垃圾回收机制回收。
4. 示意代码
package LinkedList;
public class LinkedListDemo1 {
public static void main(String[] args) {
//测试
//创建一个链表
SingleLinkedList linkedList = new SingleLinkedList();
//创建几个学生
StudentNode s1 = new StudentNode( 5,"小明" );
StudentNode s2 = new StudentNode( 2,"小红" );
StudentNode s3 = new StudentNode( 4,"小黄" );
StudentNode s4 = new StudentNode( 3,"小离" );
StudentNode s5 = new StudentNode( 1,"小三" );
//将学生节点添加至链表中
//第一种方式(添加到末尾)
/*linkedList.add( s1 );
linkedList.add( s2 );
linkedList.add( s3 );
linkedList.add( s4 );
linkedList.add( s5 );*/
//第二种方式(按顺序)
linkedList.addByOrder( s1 );
linkedList.addByOrder( s2 );
linkedList.addByOrder( s3 );
linkedList.addByOrder( s4 );
linkedList.addByOrder( s5 );
//测试修改节点信息
linkedList.updateNode( new StudentNode( 3,"刘海军真帅" ) );
//测试删除节点
linkedList.remove( 3 );
//显示链表
linkedList.show();
}
}
/**
*
*/
class StudentNode {
public int num;
public String name;
public StudentNode next;
//创建构造器
StudentNode (int num, String name){
this.num = num;
this.name = name;
}
@Override
public String toString() {
return "StudentNode{" +
"num=" + num +
", name='" + name +
'}';
}
}
/**
* 定义一个链表类,来对我们的学生链表进行管理
*/
class SingleLinkedList {
//初始化头节点
StudentNode head = new StudentNode( 0,"");
//添加节点到单向链表(不考虑顺序)
//步骤:
//1. 找到最后一个节点
//2. 让最后一个节点的next域指向要添加的节点
public void add(StudentNode studentNode){
//头节点不能动,因此需要一个辅助对象temp来进行遍历
StudentNode temp = head;
//遍历链表,找到最后一个节点
while (true) {
//找到链表的最后,结束循环
if(temp.next == null){
break;
}
//没有找到链表的最后,将temp后移
temp = temp.next;
}
temp.next = studentNode;
}
//添加节点到指定位置
public void addByOrder (StudentNode studentNode) {
//头节点不能动,因此需要一个辅助对象temp来进行遍历
StudentNode temp = head;
boolean flag = false; //标志原链表中是否存在要插入的节点
//遍历链表
while(true){
if(temp.next == null) {
//说明temp在链表的最后,退出循环
break;
}
if(temp.next.num > studentNode.num) {
//找到位置,退出循环
break;
}else if(temp.next.num == studentNode.num){
//说明编号存在
flag = true;
break;
}
temp = temp.next; //后移,遍历当前链表
}
//判断flag
if(flag == false){
studentNode.next = temp.next;
temp.next = studentNode;
}else{
//插入失败,编号已经存在
System.out.printf( "准备插入的学生编号%d已经存在", studentNode.num );
}
}
//修改节点的信息
public void updateNode (StudentNode studentNode) {
if(head.next == null){
System.out.println("链表为空!");
return;
}
//1. 遍历,找到要修改的节点
StudentNode temp = head; //辅助节点,便于遍历
boolean flag = false;
while(true) {
if(temp.num == studentNode.num){
//找到了要修改的节点,结束遍历
flag = true;
break;
}
if(temp.next == null) {
//找到链表的最后都找不到要修改的节点,
break;
}
temp = temp.next; //继续遍历
}
if(flag == false) {
System.out.printf( "修改%d编号的节点失败,不能修改\n", studentNode.num );
}
else {
//修改节点
temp.name = studentNode.name;
}
}
//删除节点
public void remove (int num) {
//找到要删除的节点,然后进行操作
StudentNode temp = head;
boolean flag = false;
//遍历
while(true) {
if(temp.next.num == num){
flag = true;
break;
}
if(temp.next == null){
//找到链表的最后,结束循环
break;
}
temp = temp.next; //继续遍历
}
if(flag == true){
//进行删除操作
temp.next = temp.next.next;
}else{
System.out.println("要删除的节点不存在!!!");
}
}
//显示链表(遍历)
public void show() {
//遍历
StudentNode temp = head; //辅助节点,用于遍历
if(temp.next == null){
System.out.println("目前是一个空链表");
return;
}
while(true) {
if(temp.next == null) {
//结束
break;
}
temp = temp.next;
System.out.println(temp);
}
}
}
2. 双向链表
1. 基本概念
单向链表的缺点:
查找时只能是一个方向,而双向链表可以向前或者向后查找。
单向链表不能实现自我删除,而必须依靠一个辅助节点才能完成。双向链表可以实现自我删除。
什么是双向链表?
双向链表也叫双链表,是链表的一种,它的每个数据结点中都有两个指针,分别指向直接后继和直接前驱。所以,从双向链表中的任意一个结点开始,都可以很方便地访问它的前驱结点和后继结点。
2. 示意图
3. 功能详解
1. 遍历:和单链表一样,只是比单链表多了向前和向后遍历两个方向。
2. 添加(添加到最后):先找到双向链表的最后节点,然后temp.next = newStudentNode, newStudentNode.pre = temp;
3. 修改:与单链表的思路一致。
4. 删除:找到要删除的节点,temp.pre.next = temp.next, temp.next.pre = temp.pre;
4. 示意代码
package LinkedList;
public class LinkedListDemo2 {
public static void main(String[] args) {
//测试
//创建一个链表
DoubleLinkedList linkedList = new DoubleLinkedList();
//创建几个学生
StudentNode1 s1 = new StudentNode1( 5,"小明" );
StudentNode1 s2 = new StudentNode1( 2,"小红" );
StudentNode1 s3 = new StudentNode1( 4,"小黄" );
StudentNode1 s4 = new StudentNode1( 3,"小离" );
StudentNode1 s5 = new StudentNode1( 1,"小三" );
linkedList.add( s1 );
linkedList.add( s2 );
linkedList.add( s3 );
linkedList.add( s4 );
linkedList.add( s5 );
linkedList.remove( 2 );
linkedList.updateNode( new StudentNode1( 3,"hapi" ));
linkedList.show();
}
}
/**
*
*/
class StudentNode1 {
public int num;
public String name;
public StudentNode1 next;
public StudentNode1 pre;
//创建构造器
StudentNode1 (int num, String name){
this.num = num;
this.name = name;
}
@Override
public String toString() {
return "StudentNode{" +
"num=" + num +
", name='" + name +
'}';
}
}
/**
* 定义一个链表类,来对我们的学生链表进行管理
*/
class DoubleLinkedList {
//初始化头节点
StudentNode1 head = new StudentNode1( 0,"");
//添加节点到单向链表(不考虑顺序)
//步骤:
//1. 找到最后一个节点
//2. 让最后一个节点的next域指向要添加的节点,让要添加节点的pre域指向最后一个节点。
public void add(StudentNode1 studentNode1){
//头节点不能动,因此需要一个辅助对象temp来进行遍历
StudentNode1 temp = head;
//遍历链表,找到最后一个节点
while (true) {
//找到链表的最后,结束循环
if(temp.next == null){
break;
}
//没有找到链表的最后,将temp后移
temp = temp.next;
}
temp.next = studentNode1;
studentNode1.pre = temp;
}
//修改节点的信息
public void updateNode (StudentNode1 studentNode1) {
if(head.next == null){
System.out.println("链表为空!");
return;
}
//1. 遍历,找到要修改的节点
StudentNode1 temp = head; //辅助节点,便于遍历
boolean flag = false;
while(true) {
if(temp.num == studentNode1.num){
//找到了要修改的节点,结束遍历
flag = true;
break;
}
if(temp.next == null) {
//找到链表的最后都找不到要修改的节点,
break;
}
temp = temp.next; //继续遍历
}
if(flag == false) {
System.out.printf( "修改%d编号的节点失败,不能修改\n", studentNode1.num );
}
else {
//修改节点
temp.name = studentNode1.name;
}
}
//删除节点
public void remove (int num) {
//找到要删除的节点,然后进行操作
StudentNode1 temp = head;
if(temp.next == null){
System.out.println("链表为空,无法删除");
return;
}
boolean flag = false;
//遍历
while(true) {
if(temp.num == num){
flag = true;
break;
}
if(temp.next == null){
//找到链表的最后,结束循环
break;
}
temp = temp.next; //继续遍历
}
if(flag == true){
//进行删除操作
temp.pre.next = temp.next;
if(temp.next != null){
temp.next.pre = temp.pre;
}
}else{
System.out.println("要删除的节点不存在!!!");
}
}
//显示链表(遍历)
public void show() {
//遍历
StudentNode1 temp = head; //辅助节点,用于遍历
if(temp.next == null){
System.out.println("目前是一个空链表");
return;
}
while(true) {
if(temp.next == null) {
//结束
break;
}
temp = temp.next;
System.out.println(temp);
}
}
}
3. 单向环形链表
1. 基本概念
什么是单向环形链表:把单链表的最后一个节点的next指向头节点而不是null,就构成了一个单向环形链表。
**解决的问题:**约瑟夫问题(Josephu)
2. 示意图
3. 功能详解
1. 构建:先创建第一个节点,让first指向该节点,形成环形。
当我们每创建一个新的节点时,把该节点加入到已有的环形链表中即可。
2. 遍历: 先创建一个辅助指针temp,执行frist节点。
然后遍历链表以temp.next == frist 作为结束条件。
4. 示意代码
package LinkedList;
public class CircleLinkedListDemo1 {
public static void main(String[] args) {
CircleSingleLinkedList circleSingleLinkedList = new CircleSingleLinkedList();
circleSingleLinkedList.add( 5 );
circleSingleLinkedList.show();
circleSingleLinkedList.countStudent( 1, 2,5 );
}
}
class Student {
public int num;
public Student next;
public Student (int num){
this.num = num;
}
}
/**
* 创建环形的单向链表
*/
class CircleSingleLinkedList {
//创建一个first节点。
Student first = null;
//添加节点到单向环形链表中
public void add(int nums){
//数据校验
if(nums < 1){
System.out.println("nums的值不正确");
return;
}
//创建一个辅助指针,用来构建环形单链表
Student temp = null;
for (int i = 1; i <= nums; i++) {
//根据i的值,创建学生节点
Student student = new Student( i );
//如果i == 1
if(i == 1){
first = student;
temp = student;
first.next = first; //构成环
}else {
temp.next = student;
student.next = first;
temp = student;
}
}
}
//遍历环形链表
public void show (){
//判断链表是否为空
if(first == null){
System.out.println("链表为空,无需遍历");
return ;
}
//因为first一直不能动,所以创建一个辅助变量temp来完成遍历。
Student temp = first;
while(true){
System.out.printf("小孩的编号为:%d", temp.num);
System.out.println();
if(temp.next == first){
//结束循环
break;
}
temp = temp.next;// temp后移
}
}
/**
* 根据用户的输入,计算出小孩的出圈顺序
* @param startNum 开始数的编号
* @param countNUm 表示数几下
* @param nums 学生的初始值
*/
public void countStudent(int startNum, int countNUm, int nums){
if(first == null || startNum < 1 || startNum > nums){
System.out.println("输入参数有误");
return;
}
//创建辅助指针,帮助学生出圈。
Student temp = first;
//先遍历,到开始数数的地方结束,就是环形链表的最后
while(true) {
if(temp.next == first){
break;
}
temp = temp.next;
}
//先让first和temp移动startNum -1 次
for (int i = 0; i < startNum -1 ; i++) {
first = first.next;
temp = temp.next;
}
//开始报数,让temp和first同时移动countNUm-1次,然后进行出圈操作
while(true){
if(temp == first){ //圈中只有一个节点
break;
}
//让first和temp同时移动countNUm-1次
for (int i = 0; i < countNUm-1; i++) {
first = first.next;
temp = temp.next;
}
//这时first指向的节点,就是要出圈的节点。
System.out.printf( "学生%d出圈\n", first.num );
first = first.next;
temp.next = first;
}
System.out.printf( "最后留在圈中的小孩是%d", first.num );
}
}
总结:*
本文通过Java代码以及介绍的方式来实现对链表的模拟,包含了对链表的增、删、等基本操作的说明以及代码实现。对单向链表,双向链表,环形链表有了更深一步的认知。