链表介绍
链表与数组其实都是表的一个,而数组的存储是一个连续的地址空间,如果想要对数组进行插入删除操作,那么加入插入点在最后的位置,时间复杂度为o(1),但是如果插入点在最前面,而由于地址空间连续那么就需要使之后的所有的地址空间向后移动,时间开销较大,但是如果我们对数组进行访问操作,会访问的很快,根据数组的索引即可直接访问,但是链表只能从头节点开始遍历,链表的插入和删除操作比较简单,只需要改变对应位置的next域即可,这是因为链表的存储方式,地址空间不连续,所以进行插入时,其他的地址空间不会放生变化,
链表是有序的列表,内存如下:
链表的各个节点不一定是连续存储的,链表分为有头节点与没有头节点的
单链表
头节点不存放具体的数据,作用是表示单链表的头
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63
| package 数据结构和算法.链表;
public class SingleLinkedList {
class HeroNode{ int no; String name; String nickName; 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 + '\'' + '}'; } }
HeroNode head=new HeroNode(0,"","");
public void addNode(HeroNode heroNode){ HeroNode temp=head; while (temp.next!=null){ temp=temp.next; } temp.next=heroNode; }
public void show(){ HeroNode temp=head.next; while (temp!=null){ System.out.println(temp); temp=temp.next; } }
HeroNode heroNode1=new HeroNode(1,"haha","heihei"); HeroNode heroNode2=new HeroNode(2,"ha","hei"); HeroNode heroNode3=new HeroNode(1,"hdfb","hei873"); public static void main(String[] args) { SingleLinkedList singleLinkedList=new SingleLinkedList(); singleLinkedList.addNode(singleLinkedList.heroNode1); singleLinkedList.addNode(singleLinkedList.heroNode2); singleLinkedList.addNode(singleLinkedList.heroNode3); singleLinkedList.show(); } }
|
排序插入
现在我们需要提出一个要求,要求这个程序必须按照no的顺序进行插入,并且如果有相同的no则第二个添加的失败,那么我们就需要进行更改添加的方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| public void addNode(HeroNode heroNode){ HeroNode temp=head; while (temp.next!=null){ if (temp.next.no==heroNode.no){ System.out.println("序号相同,本次加入节点失败"); return; } if (temp.next.no>heroNode.no){ HeroNode temp1=temp.next; temp.next=heroNode; heroNode.next=temp1; return; } temp=temp.next; } temp.next=heroNode;
}
|
修改节点信息
1 2 3 4 5 6 7 8 9 10
| public void update(int No){ HeroNode temp=head; while (temp.next!=null){ temp=temp.next; if (temp.no==No){ temp.name="张三"; } } }
|
删除节点
1 2 3 4 5 6 7 8 9 10
| public void delete(int no){ HeroNode temp=head; while (temp.next!=null){ if (temp.next.no==no){ temp.next=temp.next.next; } temp=temp.next; } }
|
疑问
在这个过程中,我们的temp仅仅是一个替代品,为什么改变了temp.next就可以改变head的链表,在
操作下,temp其实拿到了head的地址,同时传过去的不仅仅是head,还有head.next都传输过去了,这里定义的temp其实是引用数据数据类型。
链表练习
单链表有效的个数
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| public void getNums(HeroNode head){ int count=0; if (head.next==null){ System.out.println("链表为空"); }else { HeroNode node=head; while (node.next!=null){ count++; node=node.next; } System.out.println("拥有"+count+"个节点"); }
}
|
查找单链表中倒数第k个节点
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| public void getReciprocalNum(int k,HeroNode head){ int size=getNums(head); if (size==0||k>size){ System.out.println("无效"); return; } HeroNode temp=head; for (int i=0;i<=size-k;i++){ temp=temp.next; } System.out.println(temp.toString());
}
|
单链表的反转
我们将第一个节点加入到节点之后,然后将第一个节点删除,依次循环类推
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| public void reverseLinkedList(HeroNode head) { if (head.next == null) { System.out.println("无效链表"); return; } HeroNode newHead = new HeroNode(0, "", "");
while (head.next!=null){ HeroNode heroNode = head.next; HeroNode temp=heroNode.next; heroNode.next=null; HeroNode temp1=newHead.next; newHead.next=heroNode; newHead.next.next=temp1; head.next=temp; } show(newHead); }
|
其他人的解决方法,此方法巧妙的将添加方法更改到cur上,而不是在新节点上面直接添加
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| public void reverseList(HeroNode head){ if (head.next==null){ return; } HeroNode cur=head.next; HeroNode next=null; HeroNode newHead=new HeroNode(0,"",""); while (cur!=null){ next=cur.next; cur.next=newHead.next; newHead.next=cur; cur=next; } show(newHead); }
|
从尾到头打印单链表
先反转,在打印,(⊙﹏⊙),当然可以
这里展示递归调用的方法
1 2 3 4 5 6 7 8
| public void printLinkList(HeroNode head){ if (head.next==null){ return; } HeroNode tempHead=head.next; printLinkList(tempHead); System.out.println(tempHead); }
|
栈调用
我们知道栈是先进后出的
1 2 3 4 5 6 7 8 9 10 11
| public void printLinkList(HeroNode head){ Stack<HeroNode> heroNodes=new Stack<>(); HeroNode temp=head; while (temp.next!=null){ temp=temp.next; heroNodes.add(temp); } while (heroNodes.size()>0){ System.out.println(heroNodes.pop()); } }
|
上述的两种方法其实都是将同string重写之后输出的,并没有输出next域,也可以输出next域,但是如果还是想要达到这个效果,就需要对压栈的过程再次细化,这个写起来方便,就这样写喽
tostring方法
1 2 3 4 5 6 7 8
| @Override public String toString() { return "HeroNode{" + "no=" + no + ", name='" + name + '\'' + ", nickName='" + nickName + '\'' + '}'; }
|
合并链表,合并之后仍然有序
两个链表如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| public static void mergeLinkedList(HeroNode head1, HeroNode head2) { HeroNode temp2=head2; while (temp2.next!=null){ HeroNode cur=temp2.next; HeroNode next=cur.next; cur.next=null; head2.next=next; HeroNode temp1=head1; while (temp1.next!=null){ if (temp1.next.no== cur.no){ System.out.println("序号重复,本次加入失败"); break; } if (temp1.next.no> cur.no){ cur.next=temp1.next; temp1.next=cur; break; } temp1=temp1.next; } temp1.next=cur; } show(head1);
}
|
双向链表
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| class Node{ int date; Node next; Node pre; public Node(int date) { this.date = date; }
@Override public String toString() { return "Node{" + "date=" + date + '}'; } }
|
插入节点
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| static Node head=new Node(0);
public void insertNode(Node node){ Node temp=head; while (temp.next!=null){ temp=temp.next; } temp.next=node; node.pre=temp; }
public void showNode(Node head){ Node temp=head; while (temp.next!=null){ temp=temp.next; System.out.println(temp); } }
|
顺序插入
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| public void insertByOrder(Node node){ Node cur=head; while (cur.next!=null){ cur=cur.next; if (node.date==cur.date){ System.out.println("相同的序号,不允许加入"); } if (node.date<cur.date){ cur.pre.next=node; node.pre=cur.pre; node.next=cur; cur.pre=node; return; }
} cur.next=node; node.pre=cur; }
|
删除节点
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| public void deleteNode(int no){ Node cur=head; while (cur.next!=null){ cur=cur.next; if (cur.date==no){ if (cur.next==null){ cur.pre.next=null; cur.pre=null; }else { cur.pre.next=cur.next; cur.next.pre=cur.pre; cur.pre=null; cur.next=null; } System.out.println("删除成功"); return; } } System.out.println("删除失败,没有找到对应的节点信息"); }
|
环形链表
约瑟夫问题:
15个教徒和15个非教徒在深海上遇险,必须将一半的人投入海中,其余的人才能幸免于难,于是想了一个办法:30个人围成一圆圈,从第一个人开始依次报数,每数到第九个人就将他扔入大海,如此循环进行直到仅余15个人为止。问怎样排法,才能使每次投入大海的都是非教徒。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| class Node{ int data; Node next;
public Node(int data) { this.data = data; }
@Override public String toString() { return "Node{" + "data=" + data + '}'; } }
|
环形链表的增加
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| public void insertByorder(Node node){
Node cur=first.next; if (cur.data== node.data){ System.out.println("这个节点已经加入了"); return; } while (cur.next.data!=first.data){ if (cur.next.data== node.data){ System.out.println("这个节点已经加入了"); return; } if (cur.next.data>node.data){ node.next=cur.next; cur.next=node; return; } cur=cur.next; } cur.next=node; node.next=first; }
|
环形链表的删除
1 2 3 4 5 6 7 8 9 10 11 12 13 14
|
public void delete(int n) { Node cur = first; for (int i=0;i<n-1;i++){ if (i!=0){ cur=cur.next; } } System.out.println("我是节点"+cur.next+"我真可怜,要被杀了"); cur.next=cur.next.next; }
|
约瑟夫问题解决
这时候我们改一下删除的方法,加上一个循环,注意这一次循环判断条件要增加
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
|
public void delete(int n) { Node cur = first; for (int j = 0; j < 15; j++) { for (int i = 0; i < n - 1; i++) { if (i != 0||j!=0) { cur = cur.next; } } System.out.println("我是节点" + cur.next + "我真可怜,要被杀了"); cur.next = cur.next.next; } }
|