1.链表是以节点的方式存储的
2.每个节点包含data域,用来存放数据;和next域,用来指向下一个节点
3.链表的各个节点不一定是连续存储
4.链表分为带头节点的和不带头节点的
5.单链表在内存中的布局如下图
1.先创建一个节点类,用于表示节点,里面包含结点的数据域所需的各个变量以及next域
2.创建一个头节点,数据data域和next初始化都为空
3.直接添加链表尾部的增加方法
4.按顺序添加在链表中的方法
5.删除节点的方法
6.修改节点数据域的方法
7.查询方法
方法 | 思路 |
---|---|
直接将节点添加在链表尾部 | 1.给个临时变量用于遍历,初始化指向头节点; 2.开始遍历链表到最后一个节点; 3.让最后一个节点的next域指向新插入的节点即可 |
方法 | 思路 |
---|---|
按顺序添加在链表中 | 1.给个临时变量用于遍历,初始化指向头节点; 2.开始遍历链表找到需要插入的位置的前一个节点A; 3.让新插入节点的next域指向A节点的next域 4.再让A节点的next域指向新插入的节点 (这两句话可能有点懵,如下图解) |
执行3时候,生成②蓝色的线
执行4的手,接触③黑色的线,同时生成①红色的线
方法 | 思路 |
---|---|
删除节点 | 1.给个临时变量用于遍历,初始化指向头节点; 2.开始遍历链表到要删除节点的前一个结点A; 3.让A结点的next域指向它的next域结点的next节点(A.next = A.next.next) |
修改节点数据域 | 1.给个临时变量用于遍历,初始化指向头节点的下一个节点,即指向链表的第一个节点; 2.开始遍历链表到要修改的节点A; 3.对A的数据域要修改的部分进行赋值即可 |
查询方法 | 1.给个临时变量用于遍历,初始化指向头节点的下一个节点,即指向链表的第一个节点; 2.开始遍历链表到最后一个节点逐个进行输出即可; |
首先肯定需要一个表示节点信息的类,其中包含节点的data数据域和next域
/**
* 节点类
* @author centuowang
* @param
* no:数据域中人物的编号
* no:数据域中人物的名称
* nickname:数据域中人物的小名
* next:next域,用于指向下一个节点
*/
class PersonNode{
public int no;
public String name;
public String nickname;
public PersonNode next;
//构造器,注意构造器中不需要传入next域
public PersonNode(int no, String name, String nickname) {
this.no = no;
this.name = name;
this.nickname = nickname;
}
//重写一些toString方法,方便显示
@Override
public String toString() {
return "PersonNode [no=" + no + ", name=" + name + ", nickname=" + nickname + "]";
}
}
接着就可以开始编写我们的链表类了,在里面封装增删改查的相关方法
/**
* 链表的关键部分,封装了增删改查的方法类
* @author centuowang
* @method
* add(PersonNode node):直接将新插入的节点添加在链表尾部
* orderadd(PersonNode node):对插入的节点依据编号进行排序
* delete(int no):依据编号删除节点
* update(PersonNode node):修改节点
* show():遍历显示输入链表
*/
class LinkList{
//先创建一个头节点
private PersonNode head = new PersonNode(0,"","");
//不按顺序增加方法
//思路:遍历找到链表中的最后一个节点,然后讲其的next指向新加入的节点即可
public void add(PersonNode node) {
//因为头节点不能动,所以需要一个临时变量temp
PersonNode temp = head;
//遍历链表,找到链表的最后一个节点
while(true) {
if(temp.next == null){
//如果该临时节点变量的next域为空,说明它已经是最后一个节点了
break;//将死循环停止
}
//否则,就temp后移,继续判断是不是最后一个节点
temp = temp.next;
}
//跳出循环之后,开始加入,将最后一个节点的next域指向新传入的节点
temp.next = node;
}
//按找节点中数据域的no编号排序加入数据
public void orderadd(PersonNode node) {
//同样的需要一个临时变量,初始化指向头节点
PersonNode temp = head;
//还需要一个标志变量,去标志是否已经存在该编号的节点
boolean flag = false;
//也是一样的需要遍历
while(true){
if(temp.next == null) {
//此时说明已经遍历到了最后一个节点
break;
}else if(temp.next.no > node.no) {//这里注意了,遍历到什么时候停止呢?遍历到需要插入位置的前一个节点停止
break;
}else if(temp.next.no == node.no) {
//此时说明,原链表数据里,已经存在该编号的节点,标志位置为true
flag = true;
break;
}
//temp后移
temp = temp.next;
}
if(flag) {
System.out.printf("编号为%d的节点已存在,不能重复加入\n",node.no);
}else {
//否则的话,开始执行插入
node.next = temp.next;
temp.next = node;
}
}
//删除方法,根据节点编号进行删除
//思路:先遍历到要删除节点的前一个节点,然后改变该节点的next域,使其直接指向要删除节点的后一个节点即可
public void delete(int no) {
//同样的需要一个临时变量,初始化指向头节点
PersonNode temp = head;
//选哟一个标志位去标志是否找到了要删除的节点的前一个节点位置
boolean flag = false;
//开始遍历
while(true) {
if(temp.next == null) {
//已经遍历到链表最后,说明没有要删除的节点,直接终止循环
break;
}
if(temp.next.no == no) {
//如果找到了要删除节点的前一个节点,更改标志位,终止循环
flag = true;
break;
}
//temp后移
temp = temp.next;
}
if(flag) {
temp.next = temp.next.next;
}else {
System.out.printf("要删除的第%d节点不存在\n",no);
}
}
//修改方法(根据节点编号来修改)
//思路:先遍历到需要修改的节点编号
public void update(PersonNode node) {
if(head.next == null) {
System.out.println("链表为空");
}
//同样的需要一个临时变量
PersonNode temp = head.next;
//需要一个标志位来判断是否找到
boolean flag = false;
while(true) {
if(temp == null) {
break;
}
if(temp.no == node.no) {
//找到了要删除的节点,标志位置为true,终止循环
flag = true;
break;
}
//temp后移
temp = temp.next;
}
if(flag) {
//如果找到,进行修改
temp.name = node.name;
temp.nickname = node.nickname;
}else {
//否则给出提示
System.out.printf("需要修改的第%d个节点不存在\n",node.no);
}
}
//查询方法
public void show() {
if(head.next == null) {
System.out.println("链表为空");
}else {
//同样的需要一个临时变量,这里我们不打印头节点,所以temp直接指向head.next
PersonNode temp = head.next;
while(true) {
if(temp == null) {
//如果该临时节点变量为空,说明已经遍历完,直接break,终止循环
break;
}
//如果还没遍历完,就将节点内容打印出来
System.out.println(temp);
//然后后移一个继续判断
temp = temp.next;
}
}
}
}
1.在链表的修改方法中,有人提出,为什么不直接将要修改的节点删除,再用新数据的节点插入到该位置上。
这样的话,就需要对一个功能调用两种方法,代码非常冗余,而且时间复杂度上,遍历删除一个O(n),在指定位置插入一个O(n),明显没有直接写个方法就一个O(n)来的快把
2.我们可以发现,在单链表中的删除功能中不能对自己进行删除,只能定位到要删除节点的前一个节点对其操作。而采用双向链表可以对自身进行删除
单链表只能找后继,如果我们需要找一个节点的前驱,就需要用双向链表可以
首先在节点类中,需要加多一个指向前驱的变量pre,下面来分析具体的方法思路
方法 | 代码思路 |
---|---|
在链表尾部添加节点 | 遍历到最后一个节点A A.next = 新加入的节点 新加入的节点.pre = A |
修改节点 | 和单链表一样 |
查询显示节点 | 和单链表一样 |
上面三个方法都没什么好说的,主要分析下面两个方法:
方法 | 代码思路 |
---|---|
按节点编号添加节点 | 1.找到要加入的位置的前一个节点A 2.新加入节点的next指向A.next 3.A.next指向新加入的节点 4.新加入节点的pre指向A 5.新加入节点的.next.pre指向新加入的节点 如下图解 |
这里我们需要将2号节点加入到1和3之间,那么这里的A节点就是1号节点
执行2,产生上图中①号线
执行3,产生②号线,同时③号线解除
执行4,产生④号线
执行5,产生⑤号线,同时⑥号线解除
方法 | 代码思路 |
---|---|
删除节点 | 1.找到要删除的节点A 2.A.pre.next = A.next 3.A.next.pre = A.pre 如下图解 |
我们删除2号节点,那么这里的A也就是 2号节点本身
执行2,产生①号线,同时③号线解除
执行3,产生②号线,同时四号线解除
这里有人会说了,那要删除的节点还有两个线没解除啊,如上图紫色部分。
这两个是这个节点自己本身的next域和pre域,在删除的时候不必理睬,他们会随着这个节点一起被Java的回收机制回收掉
同样的先创建一个节点类
/**
* 节点类
* @author centuowang
* @param
* no:节点数据域的编号
* name、nickname:节点数据域的可变数据部分
* next:节点的next域,指向后继节点
* pre:节点的pre域,指向前驱节点
*
*/
class DoublePersonNode{
public int no;
public String name;
public String nickname;
public DoublePersonNode next;
public DoublePersonNode pre;
//构造器
public DoublePersonNode(int no, String name, String nickname) {
this.no = no;
this.name = name;
this.nickname = nickname;
}
@Override
public String toString() {
return "DoublePersonNode [no=" + no + ", name=" + name + ", nickname=" + nickname + "]";
}
}
接下来开始链表类的编写
/**
* 链表的关键部分,封装了增删改查的方法类
* @author centuowang
* @method
* add(DoublePersonNode node):直接将新插入的节点添加在链表尾部
* orderadd(DoublePersonNode node):对插入的节点依据编号进行排序
* delete(int no):依据编号删除节点
* update(DoublePersonNode node):修改节点
* show():遍历显示输入链表
*
*/
class DoubleLink{
//先创建一个头节点
DoublePersonNode head = new DoublePersonNode(0,"","");
//直接增加在链表尾部
public void add(DoublePersonNode node) {
//需要一个临时变量
DoublePersonNode temp = head;
//遍历到链表的尾部
while(true) {
if(temp.next == null) {
//temp已经指到链表尾部
break;
}
//后移
temp = temp.next;
}
//开始加入节点
temp.next = node;
node.pre = temp;
}
//自动按节点顺序增加
public void orderadd(DoublePersonNode node) {
//需要一个临时变量
DoublePersonNode temp = head;
//给一个标志位,标志是否能插入
boolean flag = true;
//遍历链表,找到需要插入的位置前的一个节点
while(true) {
if(temp.next == null) {
//遍历到了链表的尾部
break;
}if(temp.next.no > node.no) {
//找到位置
break;
}if(temp.next.no == node.no) {
//该编号的节点已存在
flag = false;
break;
}
//后移
temp = temp.next;
}
if(flag) {
//能插入
node.next = temp.next;
temp.next = node;
node.pre = temp;
//如果不为最后一节点
if(node.next != null) {
node.next.pre = node;
}
System.out.printf("%d编号节点成功插入\n",node.no);
}else {
System.out.printf("%d节点已存在,不能重复插入\n",node.no);
}
}
//删除节点
public void delete(int no) {
//需要一个临时变量
DoublePersonNode temp = head.next;
//需要一个变量来判断是否找到删除的位置
boolean flag = false;
while(true) {
if(temp == null) {
//遍历结束,任然没找到
break;
}
if(temp.no == no) {
//找到
flag = true;
break;
}
temp = temp.next;
}
if(flag) {
temp.pre.next = temp.next;
if(temp.next != null) {
temp.next.pre = temp.pre;
}
System.out.printf("已删除第%d节点\n",no);
}else {
System.out.printf("要删除的第%d节点不存在\n",no);
}
}
//修改节点可变数据域部分的数据
public void update(DoublePersonNode node) {
//需要一个临时变量
DoublePersonNode temp = head.next;
//给个标志位判断是否找到
boolean flag = false;
while(true) {
if(temp == null) {
//遍历结束都没找到
break;
}
if(temp.no == node.no) {
//找到
flag = true;
break;
}
//后移
temp = temp.next;
}
if(flag) {
temp.name = node.name;
temp.nickname = node.nickname;
}else {
System.out.printf("要修改的第%d号节点不存在\n",node.no);
}
}
//查询显示节点
public void show() {
if(head.next == null) {
//为空提示
System.out.println("链表为空");
}else {
//需要一个临时变量
DoublePersonNode temp = head.next;
while(true) {
if(temp == null) {
break;
}
//输出
System.out.println(temp);
//后移
temp = temp.next;
}
}
}
}
下一篇:从1开始学Java数据结构与算法——用单向环形链表解决Josephu问题 .