数据结构与算法-java-单链表
首先搞懂什么是链表
链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的,链表通过一个指向下一个元素地址的引用将链表中的元素串起来。
那么再对比一下数组
数组的内存是连续分配的,并且是静态分配的,即在使用数组之前需要分配固定大小的空间。可以通过索引直接得到数组中的而元素,即获取数组中元素的时间复杂度为O(1)。
链表分类
链表分为单向链表(Singly linked lis)、双向链表(Doubly linked list)、循环链表(Circular Linked list)。
它在内存里面的实际结构如下,而我们大多探究的是链表的逻辑结构
逻辑和实际的区别就是他的next有可能在180而它本身在110这个地址如下所示。而为了方便我们依然把它们挨着画出并连接
单向链表(Singly linked lis)
单向链表是最简单的链表形式。我们将链表中最基本的数据称为节点(node),每一个节点包含了数据块和指向下一个节点的指针
逻辑结构如下(带头结点)
那么我们用一个实例看看单链表
下面来看代码
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.add(hero1); singleLinkedList.add(hero2); singleLinkedList.add(hero3); singleLinkedList.add(hero4); //显示 singleLinkedList.show(); } } class SingleLinkedList{ //先初始化的头结点,不存放数据 private HeroNode head = new HeroNode(0,"",""); //add //添加的第一步是找到这个链表的最后那个节点,将最后节点的next指向新的即可 public void add(HeroNode heroNode) { //因为head头结点不能动,所以需要辅助temp HeroNode temp = head;//复制过去,既不破坏head又能遍历 //遍历链表,找到最后 while (true) { //找到链表最后 if (temp.next == null) { break; } //如果没有找到最后就后移 temp=temp.next; //继续循环while到if去直到找到最后,跳出循环即break所以出来时候的temp一定是最后节点 } //将最后节点的next指向add的 temp.next = heroNode ; } //show public void show(){ //判断是否链表空 if(head.next == null) { System.out.println("链表为空"); return; } else { HeroNode temp = head.next; while (true) { //到最后了,老规矩 if(temp.next ==null) { break; } //没到最后就输出 System.out.println(temp); //因为我们呢重写了tostring方法,直接就可以打印chu //后移,不然一直原地踏步 temp = temp.next; } } } } //先定义一个HeroNode,每一个HeroNode就是一个节点,就是代替c++中的struct class HeroNode{ public int no; public String name; public String nickname; public HeroNode next; //指向下一节点 //构造器 public HeroNode(int no, String name, String nickname) { this.no = no; this.name = name; this.nickname = nickname; } //为了显示方便 @Override public String toString() { return "HeroNode{" + "no=" + no + ", name='" + name + '\'' + ", nickname='" + nickname + '\'' + ", next=" + next + '}'; } }
但是结果还差了一点,很是奇怪。如下
HeroNode{no=1, name='宋江', nickname='及时雨', next=HeroNode{no=2, name='卢俊义', nickname='玉麒麟', next=HeroNode{no=3, name='吴用', nickname='智多星', next=HeroNode{no=4, name='林冲', nickname='豹子头', next=null}}}}
HeroNode{no=2, name='卢俊义', nickname='玉麒麟', next=HeroNode{no=3, name='吴用', nickname='智多星', next=HeroNode{no=4, name='林冲', nickname='豹子头', next=null}}}
HeroNode{no=3, name='吴用', nickname='智多星', next=HeroNode{no=4, name='林冲', nickname='豹子头', next=null}}
发现是我们的next={}出了问题,因为把所有后面的next一起打印出来了 。因为Next也可以说就是指后面的
所以改为如下
@Override
public String toString() {
return "HeroNode{" +
"no=" + no +
", name='" + name + '\'' +
", nickname='" + nickname + '\'' +
'}';
}
再运行即可
上面的是顺序插入,那么我们不顺插而是在中间插入
中间插入就是
中间插入的好处就是他的顺序即no永远都是从小到大的
代码如下
//中间插 public void addByOrder(HeroNode heroNode) { //因为节点不能动,任然设置辅助temp //我们现在要找的就不是最后节点,而是要插入的前一个节点 HeroNode temp = head; //标识添加的英雄是否已经存在 boolean flag = false; while (true) { //说明temp到了链表最后 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=true) { //说明编号存在 System.out.printf("英雄的编号%d已经存在",heroNode.no); } else { //没有存在,插入进入 heroNode.next=temp.next; temp.next=heroNode; }
知道这些以后那么修改也变得简单
//修改,no不变 public void update(HeroNode heroNode) { //判断是否为空 if (head.next == null) { System.out.println("链表为空"); return; } HeroNode temp = head.next; boolean flag = false; //标识是否找到该节点 while (true) { //最后了必须退出,即遍历完也没找到 if (temp.next == null) { break; } if (temp.no == heroNode.no) { //找到了 flag = true; break; } temp = temp.next; } if(flag=true) { temp.name=heroNode.name; temp.nickname=heroNode.nickname; } else { System.out.printf("没有找到编号%d的节点",heroNode.no); } }
那么接下来我们再看一看其他的方法等
有了上面这个getLength后再看看一个结合的案例
那么我们现在再看一个案例
大概思路就是
1重新再来一个链表
2然后遍历原来的链表并取出中间插入新链表的前面位置及head->next,这样就实现了反序
3原来链表的头部信息复制到新链表即可