数据结构之链表的详细实现


在我们实现过顺序表后,不难发现顺序表在实现增加删除时要进行 整体的前搬或后移,对于任意位置的插入十分不方便,这里我们就需要链表来进行更好的实现。

一、对于链表的基本解释

抽象形式:
在这里插入图片描述

概念:
链表是一种物理存储结构上非连续存储结构,数据元素的逻辑顺序是通过链表中的引用链接次序实现的 。

注:
1.链式结构在逻辑上是连续的,但是在物理上不是连续的。
2.实现中结点一般是在堆上申请出来的。
3.堆上申请出来的结点可能连续,也可能不连续。

链表的结构可以分为3大块:
1.单向或双向。
2.带头或不带头。
3.循环或非循环。
排列组合下来可以有8种不同的组合,但是相对简单且基础的是,无头结点的单向链表。

二、无头结点的单向链表的实现

1.基本框架的构建

在进行各项方法实现之前,我们最先需要了解的是结点的形式,如图:
在这里插入图片描述
不难看出,每一个结点都是一个独立的空间,那么,在实现结点的程序时,就可以将其定义为一个内部类开辟申请空间,代码如下:

static class ListNode{
	//实现数据域
	public int val;
	//实现指针域
	public ListNode next;
	//实现数据域的构造方法
	public ListNode (int val){
		this.val = val;
	}
}
//实现一个头结点用来指向链表的第一个元素
	public ListNode head;

接下来,我们要了解链表需要实现哪些方法:
1.头插法
2.尾插法
3.插入任意位置
4.查找是否包含某些数据在链表中
5.删除指定的第一个出现的元素
6.删除链表中指定出现的全部元素

为了我们更好的实现不同的方法,我们可以最先实现打印方法,便于我们更加直观实现方法。代码如下:

public void display(){
	//定义一个car来作为遍历的指针
	ListNode car = this.head;
	while(car != null){
		System.out.print(car.val+ " ");
		car = car.next;
	}
	System.out.println();
}

2.头插法

逻辑解释:
首先,假设已经有了两个结点,如图:
在这里插入图片描述
头插,顾名思义就是要将元素放到链表的头部,再将head指针指向新的元素。

操作如图:
在这里插入图片描述
步骤1 :首先要将头结点指向的元素地址传递给新的节点
步骤2:再将 head 指针指向新的 node 元素。

代码如下:

//头插法
public void addFirst(int data){
	ListNode node = new ListNode(data);
	//将头结点指向的元素的地址传给新结点的next
	node.next = head;
	//将新结点的地址传给head
	head = node;
}

代码测试:

public class Test {
    public static void main(String[] args) {
        SignalLinkedList signalLinkedList = new SignalLinkedList();
        signalLinkedList.addFirst(1);
        signalLinkedList.addFirst(2);
        signalLinkedList.addFirst(3);
        signalLinkedList.addFirst(4);
        signalLinkedList.display();
    }
}

在这里插入图片描述
不难理解,因为是头插法,数字在打印出来时顺序正好相反。

3.尾插法

逻辑实现:
同理假设有两个结点,如图:
在这里插入图片描述
这里插入的逻辑思考并不难,直接将链表最后一个元素的 next 存储新结点node 的地址即可,但是,这里我们就需要考虑如何找到链表的最后一个元素,我们可以定义一个 car 专门来寻找它。

操作如图:
在这里插入图片描述
步骤1:找到链表最后一个元素
步骤2:将新结点的地址传递给最后一个元素。

代码如下:

public void addLast(int data){
	ListNode node = new ListNode(data);
	ListNode car = this.head;
	//这里要判断是否为第一个元素
	if(car == null){
		this.head = node;
	}
	else{
		while(car.next != null){
			car = car.next;
		}
		car.next = node;
	}
}

代码测试:

public class Test {
    public static void main(String[] args) {
        SignalLinkedList signalLinkedList = new SignalLinkedList();
        signalLinkedList.addLast(1);
        signalLinkedList.addLast(2);
        signalLinkedList.addLast(3);
        signalLinkedList.addLast(4);
        signalLinkedList.display();
     }
}

在这里插入图片描述

4.任意位置插入

在实现这个操作之前,我们需要先实现一个可以确定链表长度的方法,这个在之后会用到。

代码如下:

public int size(){
	int count = 0;
	ListNode car = this,head;
	while(car != null){
		count++;
		car = car.next;
	}
	return count;
}

逻辑解释:

首先,假设已经有了一串链表,如图:
在这里插入图片描述
这里插入就有很多要注意的地方,插入的位置不能小于链表长度的最小值,不能大于链表的长度。至于,在合适位置插入的方法,逻辑不难理解,要知道,先接后,在连前。

操作如图:
在这里插入图片描述
步骤1:定义一个 car 指针找到要插入位置的上一个位置。
步骤2:将 car 指向的后继结点的地址传递给 node。
步骤3:将 node 的地址传递给上一个元素。

代码如下:

这里判断合法性还需要一个方法实现:

//合法性判断方法
public class IndexWrongFulException extends RuntimeException {
    public IndexWrongFulException() {
    }

    public IndexWrongFulException(String message) {
        super(message);
    }
}
public void addIndex(int index,int data){
	if(index < 0 || index > size()){
		System.out.println("index不合法");
		throw new IndexWrongFulException("index不合法");
	}
	//当index为0时等于头插法
	if(index == 0){
		addFirst(data);
		return;
	}
	//当index为链表长度时可以用尾插法
	if(index == size()){
		addLast(data);
		return;
	}
	//这里可以定义一个内部私有方法来确定car的位置
	private ListNode findIndexSub(int index){
		ListNode car = this.head;
		while(index != 0){
			car = car.next;
			index--;
			//随着index-- car的位置在不断向前
		}
		return car;
	}

	//实现正常插入操作
	ListNode node = new ListNode(data);
	ListNode car = findIndexSub(index);
	node.next = car.next;
	car.next = node;
}

代码测试:

1.测试报错代码

public class Test {
    public static void main(String[] args) {
        SignalLinkedList signalLinkedList = new SignalLinkedList();
        signalLinkedList.addLast(1);
        signalLinkedList.addLast(2);
        signalLinkedList.addLast(3);
        signalLinkedList.addLast(4);
        signalLinkedList.addIndex(-1,99);
        signalLinkedList.display();
    }
}

在这里插入图片描述
2.测试正常代码

public class Test {
    public static void main(String[] args) {
        SignalLinkedList signalLinkedList = new SignalLinkedList();
        signalLinkedList.addLast(1);
        signalLinkedList.addLast(2);
        signalLinkedList.addLast(3);
        signalLinkedList.addLast(4);
        signalLinkedList.addIndex(2,99);
        signalLinkedList.display();
    }
}

在这里插入图片描述

5.查找 是否 包含某些数据在链表中

逻辑解释:

这里的是否表明这个方法是只要证明是否有该数字,所以为 boolern 类型,只需要定义一个 car 指针在链表中进行扫描即可。

代码实现:

public boolern contains(int key){
	ListLnode car = this.head;
	while(car != null){
		if(car.val == key){
			return true;
		}
		car = car.next;
	}
	return false;
}

6.删除 指定的第一个 出现的元素

逻辑解释:

同样,首先假设有一串链表。
在这里插入图片描述
假设,这里要删除的元素为 2 ,呢么根据要求只要可以删去第一个 2 就可以了,同样,这里还要考虑链表长度为0,链表中没有要删除的元素的情况,除过这些操作,正常删除的逻辑也不难理解,如下图解释:

在这里插入图片描述
逻辑1:定义一个car指针找到要删除的值
逻辑2:将要删除的值的 next 存放的下一个元素的地址传递给 car.next

代码实现:

public void remove(int key){
	//判断如果为空
	if(this.head == null){
		return 0;
	}
	//如果第一个元素就是要删除的数值
	if(this.head == key){
		this.head = this.head.next;
		return;
	}
	//这里定义一个内部方法用来实现对要删除元素位置的查找
	private LinkNode findRomve(int key){
		LinkNode car = this.head;
		while(car != null){
			if(car.next.val == key){
				return car;
			}
			car = car.next;
		}
		return null;
	}
	LinkNode car = findRomve(key);
	if(car == null){
		System.out.println("没有找到要删除的值");
		return;
	}
	car.next = car.next.next;
}

代码测试:

1.没有找到要删除的值

public class Test {
    public static void main(String[] args) {
        SignalLinkedList signalLinkedList = new SignalLinkedList();
        signalLinkedList.addLast(1);
        signalLinkedList.addLast(2);
        signalLinkedList.addLast(2);
        signalLinkedList.addLast(4);
        signalLinkedList.remove(99);
    }
}

在这里插入图片描述
2.正常删除

public class Test {
    public static void main(String[] args) {
        SignalLinkedList signalLinkedList = new SignalLinkedList();
        signalLinkedList.addLast(1);
        signalLinkedList.addLast(2);
        signalLinkedList.addLast(2);
        signalLinkedList.addLast(4);
        signalLinkedList.remove(2);
        signalLinkedList.display();
    }
}

在这里插入图片描述

7.删除链表中 指定出现的 全部元素

逻辑解释:

同样,我们首先有一个链表,如图:
在这里插入图片描述

同样假设这里要删除的值为 2 ,这里可以定义两个指针,一前一后,前面的指针进行搜索,后面的指针进行跨越。如图:

在这里插入图片描述
逻辑1:定义两个指针确定位置
逻辑2:car 发现是要删除的结点,将下一个结点的地址传递给 prev 指向的 next
逻辑3:car 向后移动
逻辑4:与逻辑 2 相同
逻辑5:与逻辑 3 相同
逻辑6:car 指向的元素不是要删除的元素,将 prev 移动过去。
逻辑7:在判断头结点的值也为要删除的元素后,将 head 指向 prev。

代码实现:

public void removeAllKey(int key){
	//首先判断链表是否存在
	if(this.head == null){
		return;
	}
	ListNode car = this.head.next;
	ListNode prev = this.head;
	while(car != null){
		if(car.val == key){
			//直接跨过要删除的数据
			prev.next = car.next;
			car = car.next;
		}
		else{
			//出现不需要删除的数据时
			prev = car;
			car = car.next;
		}
	}
	//除头结点查找完后,检查头结点是否为要删除元素
	if(this.head.val == key){
		this.head = prev;
	}
}

代码测试:

public class Test {
    public static void main(String[] args) {
        SignalLinkedList signalLinkedList = new SignalLinkedList();
        signalLinkedList.addLast(2);
        signalLinkedList.addLast(2);
        signalLinkedList.addLast(2);
        signalLinkedList.addLast(4);
        signalLinkedList.removeAllKey(2);
        signalLinkedList.display();
    }
}

在这里插入图片描述

8.总结

到这里链表的实现已经基本完成,并不是很复杂,但是逻辑分析十分重要,最后,在这里实现一个清空链表就圆满完成了!

    public void clear(){
        this.head = null;
    }

感谢您的阅读,如有问题,欢迎指正。

  • 1
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值