1.链表(Linked List)介绍
- 链表是有序列表,但是列表的在内存中的存储方式却不一定是连续的:
在这张图中我们可以看到,列表有一个头指针,这个头指针记录的是地址为150的值,我们找到150地址对应的值后a1后,a1也存放了一个next域,指向了下一个地址110…这样使得数据连接在一起,上一条数据都存放了下一个数据的地址的存储方式
就称为链表
。
如果不好理解的话就来看这张吧:
这张图描述了链表的一个逻辑结构,而第一张图描绘的是在内存中的实际结构,在内存中链表的节点不一定连续,但是它们可以通过自身的指向而连在一起。
小结:
- 1、链表是以节点的方式来存储的;
- 2、每个节点包含data域(每个节点的数据),next域(每个节点记录的下一个节点的地址);
- 3、链表的节点不一定是连续存放的;
- 4、链表也分为带头节点和不带头节点的链表,根据实际的需求来确定;
2.代码实现单向链表
单向链表是链表结构中最简单的一种链表结构,我们先来实现一下
1.首先需要创建一个节点类,因为我们链表的每个节点里面即存放了值,又存放了下一个节点的位置
//定义一个Node,每个Node对象就是一个节点
class Node{
public int num; //节点存放的值
public Node next; //每个节点指向的下一个节点的地址
public Node(int num) {
this.num = num;
}
//为了查看方便,我们重写一下toString()
@Override
public String toString() {
return "Node{" +
"num=" + num +
'}';
}
}
2.定义完节点类后,我们还需要定义一个链表类,这个类用于管理节点
//定义SingleLinkedList管理每个节点
class SingleLinkedList{
//先初始化一个头节点,头节点一般不要动,因为头节点是为了指示链表的最顶端,如果不存在就无法找到整个链表
private Node head = new Node(0); //不存放具体的数据
}
3.定义完这个节点类,我们要提供添加节点的方法
//添加节点到单向链表
/**
* 思路:
* (1)、找到当前链表的最后一个节点
* (2)、将最后一个节点的next域指向新的节点
*/
public void add(Node newNode){
//由于我们的head节点不能动,我们需要新建一个辅助节点进行遍历
Node temp = head;
while (true){
//遍历链表查看每个节点的next域是不是为null,如果为null,就表示该节点为最后一个节点
if (temp.next == null){
break;
}
//如果不为null,就向后遍历
temp = temp.next;
}
//将链表的最后一个节点指向传进来的新节点,则新节点就为最后一个节点了
temp.next = newNode;
}
4.为了方便我们观察,还需要一个查看整个链表的方法
//显示整个链表
public void list(){
//先判断链表是不是为空
if (head.next == null){
System.out.println("链表为空!");
return;
}
//如果不为空,就需要进行遍历,因为head节点不能动,我们使用一个辅助变量进行遍历,由于我们已经确定了链表不为空,所以直接将辅助节点指向下一个节点
Node temp = head.next;
while (true){
//判断是否已经遍历完整个链表
if (temp == null){
//遍历完就直接退出
break;
}
//为遍历完就进行输出,继续向后遍历
System.out.println(temp.toString());
//输出完该节点后,一定要指向下一个节点
temp = temp.next;
}
}
5.编写完毕后,我们进行测试
public static void main(String[] args) {
SingleLinkedList linkedList = new SingleLinkedList();
for (int i = 0; i < 10; i++) {
linkedList.add(new Node(i+1));
}
linkedList.list();
}
这样,我们就完成了一个简单的单向链表的创建和使用。
但是,我们对链表在进行添加数据的时候,若没有进行排序,那么整个链表也会是无序的。我们改进一下测试代码
public static void main(String[] args) {
SingleLinkedList linkedList = new SingleLinkedList();
linkedList.add(new Node(19));
linkedList.add(new Node(21));
linkedList.add(new Node(10));
linkedList.add(new Node(12));
linkedList.list();
}
可以看到,链表并不是有序的链表。所以我们来改进添加节点的方法,根据值的大小插入到指定的节点位置,如果已经有值 ,就不进行插入,如果插入的值如果在两个节点的值之间,那么就将这个节点插入到这两个节点之间,并形成连接。
//改进添加节点的方法,根据值的大小插入到指定的节点位置
public void addByOrder(Node newNode){
//因为头节点不能动,因此我们仍然通过一个辅助变量进行遍历找到添加的位置
//在单链表中,temp在遍历找到对应的位置,这个位置应该是添加位置的前一个节点,否则无法添加进链表
Node temp = head;
while (true){
/*
如果下一个节点为null说明该节点为最后一个节点,则进行节点添加
如果新插入的节点的值小于某个节点,说明位置找到,则进行节点插入
*/
if (temp.next == null || temp.next.num > newNode.num){
newNode.next = temp.next;//先将新插入的节点指向后一个节点
//这里temp.next由于是一个引用类型,当这个辅助节点指向发生改变原节点的指向也会发生改变。
//就比如两个指针指向同一个变量,其中一个指针去修改这个值,另一个指针去获取这个的值也是变化后的值。
temp.next = newNode; //再将前一个节点指向这个新插入的节点
break;
}
else if (temp.next.num == newNode.num){ //如果已经存在一个节点值,就不再进行添加。
System.out.println("该节点已存在");
return;
}
temp = temp.next;//每遍历一个节点,向后移动一位
}
}
由此,我们就完成了一个可以根据值的大小插入到指定的节点位置的一个单向链表。
光有添加节点还不行,我们需要一个删除节点的方法
//删除节点
public void del(int no){
Node temp = head; //辅助节点,方便我们遍历整个链表
while (true){
if (temp.next == null){ //先进行判空,查看链表是否有该节点(防止空指针)
System.out.printf("链表中未找到值为%d的节点",no);
return;
}
else if (temp.next.num == no){ //若链表中有该节点的值,就进行删除操作
temp.next = temp.next.next; //将前节点的指向改变为后一个节点的后一个节点即可,(没有被任何指向的对象会被JVM自动回收)
break;
}
temp = temp.next; //每查找一个节点,向后移动一位
}
}
测试代码:
int delNum = new Random().nextInt(10);
System.out.printf("删除节点为%d的节点\n",delNum);
linkedList.del(delNum);
System.out.println("遍历整个节点");
linkedList.list();
-
找到节点的情况:
-
未找到节点的情况:
这样,基本就完成了一个完整的单项链表的实现。(修改这个例子不好解释,一般是根据num去修改里面其他的属性)。