单链表

写在前面:说起单链表大家可能都比较熟悉,有些人可能会说java或者其他的语言都将这些数据结构封装好了,你直接调用不就好啦,干嘛还要费劲的学这些东西,我想告诉大家的是,就算是现在的高级语言都将这些数据结构封装好了,我们还是要学习的,因为如果你不了解这些数据结构的基本含义的话,是无法熟练的应用那些已经封装好了的东西,所以我们如果不想仅仅变成一个只会搬砖的码农那就好好学习这些底层的东西,认真了解其中的原理。

  1. 链表在大家的想象中可能是像链子一样将数据串联到一起的,但其实在实际的内存中却不是这样的,它在实际的内存中其实是下图这个样子的。
  2. Java 数据结构如何设计 java 数据结构 图_java

  3. 由图我们可以清晰的看到,链表中的节点是由两部分来组成的,第一部分是用来存储数据的,第二部分是用来存储next域,这个next域中存储的是下一个结点的地址信息,最后将这些节点连接串联到一起就构成了链表
  4. 链表中的头节点:头节点并不存储数据,它的作用只是用来将其它节点串联起来,就像是你要串一串手链,你总得有一个头吧,这样其它的珠子才能顺着依次穿好。在逻辑中带头节点的链表就是如下图的一个样子存储的
  5. Java 数据结构如何设计 java 数据结构 图_数据结构_02

  6. 好了,上面我们已经仔细的介绍了链表的存储方式和逻辑结构,下面我们就用代码来实现一下吧
  7. 首先,我们需要先将结点创建出来,我们模拟生成一个存储108水浒好汉的链表,那么这个节点中就要有好汉的排名,好汉的姓名还有好汉的外号,并且一个节点中绝对不能少的就是这个结点的next域。这里需要重写toString方法使其能够按照格式化输出,比较美观,调用时也比较方便
class HeroNode{
	int no;
	String name;
	String nickName;
	HeroNode next;
	
	//创建构造器
	public HeroNode(int no, String name, String nickName) {
		// TODO Auto-generated constructor stub
		this.no = no;
		this.name = name;
		this.nickName = nickName;
	}
	
	//重写toString方法
	@Override
	public String toString() {
		return "HeroNode [no=" + no + ", name=" + name + ", nickName=" + nickName + "]";
	}
  1. 第二步,我们就该创建这个单链表了,首先我们想到的是该如何将节点串联起来呢,那我们先用最简单的方式来实现,就是在最后一个节点的末尾去添加其他节点,那我们就需要遍历整个链表找到位于链表的最后的那个节点,最后一个节点的标志就是它的next域=null
//创建单链表
class SingleLinkedList{
	//创建头结点,不用来存储数据
	private HeroNode head = new HeroNode(0, "", "");
	//创建添加结点的方法,目前只实现从最后添加
	public void addNode(HeroNode heroNode) {
		//因为head结点不能移动,所以需要一个辅助变量temp
		HeroNode temp = head;
		//遍历链表找到最后的结点
		while(true)
		{
			if(temp.next == null)
			{
				break;
			}
			//如果没有找到就将temp后移
			temp = temp.next;
		}
		//当退出while循环的时候。temp就指向了链表的最后
		//将最后这个结点的next指向heroNode
		temp.next = heroNode;
	}
}
  1. 写完添加节点后,我们发现了这种添加方式的弊端就是只能将新的节点添加到整个链表的末尾,当我们新添加的节点编号比前面的节点小也只能排在其后面,所以这里新创建一种方法,当我插入新的节点时,让其判断这个这个新的节点的编号的大小,然后插入到它该插入的地方,让整个链表按照一定的顺序排列(我这里采用的是让整个链表按照编号从小到大的顺序排列)。这里定义的标志变量的意思就是如果找到要添加的节点的编号那么就将flag变为true就证明已经找到了这节点,下面的几个方法中的flag的用法都是一样的
//按顺序插入节点
	public void insertNode(HeroNode node) {
		//因为head结点不能移动,所以需要一个辅助变量temp
		HeroNode temp = head;
		//定义一个布尔变量的flag用于判断编号是否已经存在,初始默认为是false
		boolean flag = false;
		while(true)
		{
			if(temp.next == null)
			{
				break;
			}
			//确定你要插入的数据的位置,就是说你要插入的节点的编号必须是后面的大,前面的小
			if (temp.next.no > node.no) 
			{
				break;
			}
			else if (temp.next.no == node.no) {
				flag = true;
				break;
			}
			temp = temp.next;
		}
		if (flag) {
			System.out.printf("此编号已经存在,请重新添加!");
		}
		else {
			node.next = temp.next;
			temp.next = node;
		}
	}
  1. 修改节点信息,当我们发现我们创建的链表中的节点信息存在问题,我们要修改怎么办呢?我们可以按照节点的编号信息找到这个需要修改的节点,那我们同样采用遍历的方法,当我遍历到的节点的编号和我要修改的节点的编号一致的时候就跳出这个循环,然后将新的英雄的名字和外号赋值给被修改的节点即可。
//修改链表中的信息,根据编号来修改
	public void update(HeroNode newHeroNode) {
		if (head.next == null) {
			return;
		}
		HeroNode tempHeroNode = head;
		boolean flag = false;
		while(true)
		{
			//如果遍历到末尾还没有找到的话就说明没有这个编号
			if (tempHeroNode.next == null) {
				break;
			}
			//如果节点的编号与要修改的编号一致的话,就是找到了这个要被修改的节点
			if (tempHeroNode.no == newHeroNode.no) {
				flag = true;
				break;
			}
			//如果没找到就继续向后移动
			tempHeroNode = tempHeroNode.next;
		}
		if (flag) {
			tempHeroNode.name = newHeroNode.name;
			tempHeroNode.nickName = newHeroNode.nickName;
		}
		else {
			System.out.printf("对不起,没有找到要修改的%d结点信息!\n", newHeroNode.no);
		}
	}
  1. 删除节点,如果要删除一个节点就是要将这个结点的前一个节点的next域指向这个节点的后一个节点
//删除链表中的结点
	public void delNode(int no) {
		HeroNode tempHeroNode = head;
		boolean flag = false;
		while(true)
		{
			if (tempHeroNode.next == null) {
				break;
			}
			//这里找到的是要删除的节点的前一个节点
			if (tempHeroNode.next.no == no) {
				flag = true;
				break;
			}
			tempHeroNode = tempHeroNode.next;
		}
		if (flag) {
		    //将前一个节点的next域等于被删除的节点的next域
		    //因为被删除节点的next域存放的是下一个节点的地址,这样的话就把被删除节点的前一个节点与后一个节点连接了起来
			tempHeroNode.next = tempHeroNode.next.next;
		}
		else {
			System.out.printf("对不起,没有找到要删除的元素%d!\n", no);
		}
		
	}
  1. 显示这个链表,通过遍历可以
//显示链表,通过遍历的方法
	public void showList() {
		//通过辅助变量temp,遍历链表
		if (head.next == null) {
			System.out.println("链表为空!");
			return;
		}
		
		//
		HeroNode temp = head.next;
		while(true)
		{
			if (temp == null) {
				break;
			}
			//当不为空的时候直接输出结点信息,并将temp移动到下一个结点的位置
			System.out.println(temp);
			//将temp后移,输出下一个结点
			temp = temp.next;
		}
	}
  1. 最后我们来测试一下我们所写的代码合不合适就可以了
public class SingleLinkedListDemo {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		//先创建结点
		HeroNode heroNode1 = new HeroNode(1, "宋江", "及时雨");
		HeroNode heroNode2 = new HeroNode(2, "卢俊义", "玉麒麟");
		HeroNode heroNode3 = new HeroNode(3, "吴用", "智多星");
		HeroNode heroNode4 = new HeroNode(4, "林冲", "豹子头");
		SingleLinkedList singleLinkedList = new SingleLinkedList();
//		singleLinkedList.addNode(heroNode1);
//		singleLinkedList.addNode(heroNode4);
//		singleLinkedList.addNode(heroNode2);
//		singleLinkedList.addNode(heroNode3);
		singleLinkedList.insertNode(heroNode1);
		singleLinkedList.insertNode(heroNode4);
		singleLinkedList.insertNode(heroNode2);
		singleLinkedList.insertNode(heroNode3);
		singleLinkedList.showList();
//		System.out.println("修改后的结点信息为:");
//		HeroNode newHeroNode = new HeroNode(3, "无用", "没用");
//		HeroNode newHeroNode2 = new HeroNode(5, "无用", "没用");
//		singleLinkedList.update(newHeroNode);
//		singleLinkedList.update(newHeroNode2);
//		singleLinkedList.showList();
		System.out.println("删除后的链表信息为:");
		singleLinkedList.delNode(3);
		singleLinkedList.delNode(1);
		singleLinkedList.delNode(8);
		singleLinkedList.showList();
	}
}