链表其实也就是 线性表的链式存储结构,与以前讲到的顺序存储结构不一样。ide
咱们知道顺序存储结构中的元素地址都是连续的,那么这就有一个最大的缺点:当作插入跟删除操做的时候,大量的元素须要移动。
如图所示,元素在内存中的位置是挨着的,当中有元素被删除,就产生空隙,因而乎后面的元素须要向前挪动去弥补。
正是由于顺序存储有这这个缺点,因此链式存储结构就变得很是的有意义。测试
首先,链表是有序的列表,可是在内存中它是这样存储的:
this
上图所示中,各个结点不必定是连续存放的,最终会有N个节点连接成一个链表,因此就成了链式存储结构。
另外,由于此链表的每一个结点中只包含一个next域,因此叫单链表。指针
上面提到了头指针,它是链表的必要元素。
由于链表既然也是线性表,因此仍是要有头有尾,头指针就是链表中第一个结点的存储位置。
而最后一个结点,指针指向空,一般用NULL表示或者'^'来表示。
code
与头指针不一样,头结点是不必定要有的,得更具实际需求来定。
有时候为了更加方便的操做链表,会在单链表的第一个结点前设一个结点,称为头结点。对象
加了头结点后,对于第一结点来讲,在其以前插入结点或者删除第一结点,操做方式就与其它的结点相同了,不须要进行额外的判断处理。blog
头结点跟其余结点不一样,它的数据域能够不存储任何信息,有必要的话,能够存储一些其余的附加信息,好比线性表的长度等。
内存
如今咱们已经知道了单向链表的储存形式以及其构成有哪些,那么如今能够用更直观的图来展现单向链表中数据元素之间的关系了。
it
好比,如今要用单链表来存储LOL里英雄的信息。若是不带英雄排名顺序的话,那么能够直接依次在链表的末尾增长新的结点便可。class
package linkedlist; public class SingleLinkedListDemo { public static void main(String[] args) { // 测试 HeroNode hero1 = new HeroNode(1, "易大师","无极剑圣"); HeroNode hero2 = new HeroNode(2, "李青","盲僧"); HeroNode hero3 = new HeroNode(3, "艾希","寒冰射手"); HeroNode hero4 = new HeroNode(4, "菲奥娜","无双剑姬"); // 建立链表 SingleLinkedList singleLinkedList = new SingleLinkedList(); // 加入对象结点 singleLinkedList.addHero(hero1); singleLinkedList.addHero(hero2); singleLinkedList.addHero(hero3); singleLinkedList.addHero(hero4); // 显示链表内容 singleLinkedList.linkList(); } } // 定义SingleLinkedList 管理英雄 class SingleLinkedList { // 初始化一个头结点,不要动这个结点。 private HeroNode headNode = new HeroNode(0, "",""); // 添加结点 到 单向链表 // 当不考虑英雄顺序时,找到当前链表的最后一个结点,再讲此结点的next指向新的结点便可 public void addHero(HeroNode heroNode) { // 由于head结点不能动,因此新建一个临时变量,帮助遍历 HeroNode temp = headNode; // 开始遍历链表,到最后,找最后的结点 while (true) { // 等于null时就是最后了 if (temp.next == null) { break; } // 不然就不是最后,将temp继续向后移动 temp = temp.next; } // 直到退出循环,此时temp就指向了链表的最后 // 将最后的结点指向这个新的结点 temp.next = heroNode; } // 显示链表内容的方法 public void linkList() { // 判断链表是否为空,空的话就不用继续了 if (headNode.next == null) { System.out.println("链表为空"); return; } HeroNode temp = headNode.next; while (true) { // 判断是否已经到了链表最后 if (temp == null) { break; } // 输出结点信息 System.out.println(temp); // 而后后移temp继续输出下一个结点 temp = temp.next; } } } // 定义HeroNode,每一个HeroNode对象就是一个结点 class HeroNode { public int no; public String name; public String nickname; public HeroNode next; // 指向下一个结点 // 构造器 public HeroNode(int heroNo, String heroName, String heroNickname) { this.no = heroNo; this.name = heroName; this.nickname = heroNickname; } // 为了方便显示,重写toString方法 @Override public String toString() { return "HeroNode{" + "no=" + no + ", name='" + name + '\'' + ", nickname='" + nickname + '\'' + '}'; } }
运行一下
HeroNode{no=1, name='易大师', nickname='无极剑圣'} HeroNode{no=2, name='李青', nickname='盲僧'} HeroNode{no=3, name='艾希', nickname='寒冰射手'} HeroNode{no=4, name='菲奥娜', nickname='无双剑姬'} Process finished with exit code 0
能够看到,链表中的结点是按照添加的顺序依次储存的。
上面每一个英雄有本身的排名,那么若是我想不关心添加的顺序,在链表中最终均可以按照英雄的排名进行存储,如何实现呢?
这里的话就没有上面直接在末尾添加那么直接了,可是也不算难理解,看个示意图。
如图所示,如今有一个结点2要添加进来,那么来梳理一下实现的思路:
是否是很简单,不过为了实现第2点,咱们仍是须要借助一个辅助变量temp,能够把它看做一个指针。
temp会从头开始遍历链表,来找到结点2应该添加到的位置,此时会停在结点1,那么:
这样咱们的目的就达成了,代码也就知道怎么去改了。
决定在SingleLinkedList类中,增长一个新方法,能够跟据英雄的排名进行添加。
package linkedlist; public class SingleLinkedListDemo { public static void main(String[] args) { // 测试 HeroNode hero1 = new HeroNode(1, "易大师","无极剑圣"); HeroNode hero2 = new HeroNode(2, "李青","盲僧"); HeroNode hero3 = new HeroNode(3, "艾希","寒冰射手"); HeroNode hero4 = new HeroNode(4, "菲奥娜","无双剑姬"); // 建立链表 SingleLinkedList singleLinkedList = new SingleLinkedList(); // 加入对象结点 singleLinkedList.addByNo(hero1); singleLinkedList.addByNo(hero4); singleLinkedList.addByNo(hero2); singleLinkedList.addByNo(hero3); // 显示链表内容 singleLinkedList.linkList(); } } // 定义SingleLinkedList 管理英雄 class SingleLinkedList { // 初始化一个头结点,不要动这个结点。 private HeroNode headNode = new HeroNode(0, "",""); // 添加结点 到 单向链表 // 当不考虑英雄顺序时,找到当前链表的最后一个结点,再讲此结点的next指向新的结点便可 public void addHero(HeroNode heroNode) { // 由于head结点不能动,因此新建一个临时变量,帮助遍历 HeroNode temp = headNode; // 开始遍历链表,到最后,找最后的结点 while (true) { // 等于null时就是最后了 if (temp.next == null) { break; } // 不然就不是最后,将temp继续向后移动 temp = temp.next; } // 直到退出循环,此时temp就指向了链表的最后 // 将最后的结点指向这个新的结点 temp.next = heroNode; } // 添加方法2:根据排名将英雄按照排名顺序依次放到对应位置 public void addByNo(HeroNode heroNode) { // 借助temp遍历链表,找到添加位置的前一个结点 HeroNode temp = headNode; // 考虑一种状况:当添加的位置已经存在对应排名的英雄,则不能添加 boolean flag = false; while (true) { if (temp.next == null) { break; } if (temp.next.no > heroNode.no) { // 位置找到,在temp的后面添加 break; } else if (temp.next.no == heroNode.no) { // 目标添加位置,已经存在对应编号,不能添加 flag = true; break; } temp = temp.next; // 继续后移 } // 跳出循环,进行添加操做 if (flag) { System.out.printf("准备插入的英雄编号%d已存在,不可加入\n", heroNode.no); } else { // 能够正常插入到链表 heroNode.next = temp.next; temp.next = heroNode; } } // 显示链表内容的方法 public void linkList() { // 判断链表是否为空,空的话就不用继续了 if (headNode.next == null) { System.out.println("链表为空"); return; } HeroNode temp = headNode.next; while (true) { // 判断是否已经到了链表最后 if (temp == null) { break; } // 输出结点信息 System.out.println(temp); // 而后后移temp继续输出下一个结点 temp = temp.next; } } } // 定义HeroNode,每一个HeroNode对象就是一个结点 class HeroNode { public int no; public String name; public String nickname; public HeroNode next; // 指向下一个结点 // 构造器 public HeroNode(int heroNo, String heroName, String heroNickname) { this.no = heroNo; this.name = heroName; this.nickname = heroNickname; } // 为了方便显示,重写toString方法 @Override public String toString() { return "HeroNode{" + "no=" + no + ", name='" + name + '\'' + ", nickname='" + nickname + '\'' + '}'; } }
在main方法中,咱们打乱结点添加的顺序,运行一下,看看最终链表里是否是按照影响的排名顺序存储的
HeroNode{no=1, name='易大师', nickname='无极剑圣'} HeroNode{no=2, name='李青', nickname='盲僧'} HeroNode{no=3, name='艾希', nickname='寒冰射手'} HeroNode{no=4, name='菲奥娜', nickname='无双剑姬'} Process finished with exit code 0
结果正确,符合预期,无论先添加谁,最终在链表里都是按照英雄的排名来存放。
继续测试,我重复添加结点3,看下会如何。
// 加入对象结点 singleLinkedList.addByNo(hero1); singleLinkedList.addByNo(hero4); singleLinkedList.addByNo(hero2); singleLinkedList.addByNo(hero3); singleLinkedList.addByNo(hero3);
运行一下:
准备插入的英雄编号3已存在,不可加入 HeroNode{no=1, name='易大师', nickname='无极剑圣'} HeroNode{no=2, name='李青', nickname='盲僧'} HeroNode{no=3, name='艾希', nickname='寒冰射手'} HeroNode{no=4, name='菲奥娜', nickname='无双剑姬'} Process finished with exit code 0
提示了已经存在了,不可加入。
下面会继续单链表的修改和删除等。