首先搞懂什么是链表
链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的,链表通过一个指向下一个元素地址的引用将链表中的元素串起来。
那么再对比一下数组
数组的内存是连续分配的,并且是静态分配的,即在使用数组之前需要分配固定大小的空间。可以通过索引直接得到数组中的而元素,即获取数组中元素的时间复杂度为O(1)。
链表分类
链表分为单向链表(Singly linked lis)、双向链表(Doubly linked list)、循环链表(Circular Linked list)。
它在内存里面的实际结构如下,而我们大多探究的是链表的逻辑结构
逻辑和实际的区别就是他的next有可能在180而它本身在110这个地址如下所示。而为了方便我们依然把它们挨着画出并连接
单向链表(Singly linked lis)
单向链表是最简单的链表形式。我们将链表中最基本的数据称为节点(node),每一个节点包含了数据块和指向下一个节点的指针
逻辑结构如下(带头结点)
那么我们用一个实例看看单链表
下面来看代码
public classSingleLinkedListDemo {public static voidmain(String[] args) {//测试//先创建节点
HeroNode hero1 = new HeroNode(1, "宋江", "及时雨");
HeroNode hero2= new HeroNode(2, "卢俊义", "玉麒麟");
HeroNode hero3= new HeroNode(3, "吴用", "智多星");
HeroNode hero4= new HeroNode(4, "林冲", "豹子头");//创建要给的链表
SingleLinkedList singleLinkedList = newSingleLinkedList();//加入
singleLinkedList.add(hero1);
singleLinkedList.add(hero2);
singleLinkedList.add(hero3);
singleLinkedList.add(hero4);//显示
singleLinkedList.show();
}
}classSingleLinkedList{//先初始化的头结点,不存放数据
private HeroNode head = new HeroNode(0,"","");//add//添加的第一步是找到这个链表的最后那个节点,将最后节点的next指向新的即可
public voidadd(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 voidshow(){//判断是否链表空
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
classHeroNode{public intno;publicString name;publicString nickname;public HeroNode next; //指向下一节点//构造器
public HeroNode(intno, String name, String nickname) {this.no =no;this.name =name;this.nickname =nickname;
}//为了显示方便
@OverridepublicString 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 voidaddByOrder(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 voidupdate(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原来链表的头部信息复制到新链表即可