链表
线性表分为顺序表和链表;
1、顺序存储结构和链式存储结构的区别
顺序存储结构是在内存中开辟一个连续的空间用来存储数据,因此对于内存的需求和苛求,必须是连续的空间,在数据查找(特别是不按照规律排序的数据),时间复杂度较少,效率高。
链式存储结构是采取链表指针来指示数据的存储位置,这就可以是在内存中随意的存储,没有必要连续存储空间的要求,对于内存的要求相对比较容易。但是,要是从小到大顺序排列的数据时,链式存储结构的时间复杂度较小,效率高。但是要是不规则排列的数据一般时间复杂度较高,效率较低。
2、单向链表代码:
package com.zxc.LinklistLearning; import org.junit.Test; /** * Created by Administrator on 2018/2/17 0017. * 单向链表 */ public class LinkList { public class Node{ public String data; private Node next; public Node(String data,Node next){ this.data=data; this.next=next; } } private Node header; private Node tail; private int size; public LinkList(){ header=null; tail=null; } public void addTail(String data){//尾插法 if(header==null){ header=new Node(data,null); tail=header; }else { Node newNode=new Node(data,null); tail.next=newNode; tail=newNode; } size++; } public void addHeader(String data){//头插法 this.header=new Node(data,header);//创建新节点,让新节点的next指向原来的header,并以新创建的节点作为新header //如果插入前是空链表 if(tail==null){ tail=header; } size++; } /** * * @param index:要即将插入节点的索引号 * @return:返回索引指定的现在的节点 */ public Node getNodeByIndex(int index){ Node currentNode=header;//代表从头找 for(int i=0;i<size;i++){ if(i==index){ return currentNode; } currentNode=currentNode.next; } return null; } /** * * @param data:要插入的数据 * @param index:索引处插入 */ public void insert(String data,int index) { if (index < 0 || index > size) { System.out.println("线性表索引越界"); } else { if (header == null) { this.addTail(data); } else { if (index == 0) { addHeader(data); } else { Node preNode = getNodeByIndex(index-1); preNode.next=new Node(data,preNode.next);//先实例化右面,再赋值左侧 } } } size++; } public String delete(int index){ Node del=null; if (index < 0 || index > size) { System.out.println("线性表索引越界"); } if(index==0){ del=header; header=header.next; }else{//多于一个节点 Node prev=this.getNodeByIndex(index-1); del=prev.next; prev.next=del.next; } del.next=null; return del.data; } @Override public String toString() { StringBuffer sb=new StringBuffer(); for(Node current=header;current!=null;current=current.next ){ sb.append(current.data); } return sb.toString(); } @Test public void ok(){ this.addHeader("A"); this.addHeader("B"); this.addTail("C"); System.out.println(this); } }
三、双向链表
每个节点保留两个引用prev,next,让prev指当前节点的上一个节点,让next指向当前节点的下一个节点,此时链表既可以向前也可以向后访问每个节点,这种形式的链表被称为双向链表。
由于是双向的,我们查找元素的时候既可以从header处查找,也可以从tail处查找,从哪里查找取决于离哪个节点更近,一般地,可以通过被搜索的index值来判断它更靠近header,还是更靠近tail,如果index<size/2,可以判断位置更靠近header,应从header处搜索,反之应从tail处搜索。
package com.zxc.LinklistLearning; import com.sun.scenario.effect.impl.sw.sse.SSEBlend_SRC_OUTPeer; import org.junit.Test; /** * Created by Administrator on 2018/2/18 0018. * 双向链表 * */ public class DeLinkList { private class Node{ private String data; private Node prev; private Node next; public Node(String data,Node prev,Node next){ this.data=data; this.prev=prev; this.next=next; } } private Node header; private Node tail; private int size; public DeLinkList(){ header=null; tail=null; } public void addHeader(String data){//头插法 this.header=new Node(data,null,header);//创建新节点,让新节点的next指向原来的header,并以新创建的节点作为新header //如果插入前是空链表 if(tail==null){ tail=header; } size++; } public void addTail(String data){//尾插法 if(header==null){ header=new Node(data,null,null); tail=header; }else { Node newNode=new Node(data,tail,null); tail.next=newNode; tail=newNode; } size++; } /** * * @param index:要即将插入节点的索引号 * @return:返回索引指定的现在的节点 */ public Node getNodeByIndex(int index){ Node currentNode=header;//代表从头找 for(int i=0;i<size;i++){ if(i==index){ return currentNode; } currentNode=currentNode.next; } return null; } /** * * @param data:要插入的数据 * @param index:索引处插入 */ public void insert(String data,int index) { if (index < 0 || index > size) { System.out.println("线性表索引越界"); } else { if (header == null) { this.addHeader(data); } else { if (index == 0) { addHeader(data); } else { Node preNode = getNodeByIndex(index-1); Node current=new Node(data,preNode,preNode.next); current.next.prev=current; preNode.next=current; } } } size++; } public String delete(int index){ Node del=null; if (index < 0 || index > size) { System.out.println("线性表索引越界"); } if(index==0){ del=header; header=header.next; }else{//多于一个节点 Node prev=this.getNodeByIndex(index-1); del=prev.next; prev.next=del.next; del.next.prev=prev; } del.next=null; return del.data; } @Override public String toString() { StringBuffer sb=new StringBuffer(); for(Node current=header;current!=null;current=current.next){ sb.append(current.data); } return sb.toString(); } @Test public void ok(){ addHeader("A"); addTail("B"); addHeader("C"); insert("D",1); this.delete(2); System.out.println(this); } }
四、链表高级使用技巧:打印两个有序链表的公共部分
题目:给定两个有序链表的头指针head1,head2,打印两个链表的公共部分
分析过程:1、如果head1的值小于head2,则head1往下移动。
2、如果head2的值小于head1,则head2往下移动。
3、如果head1的值与head2相等,则打印这个值,然后head1和head2都往下移动。
4、head1或和head2有任何一个移动到null,这个过程停止。
public class PrintCommonPart { public static class Node { public int value; public Node next; public Node(int data) { this.value = data; } } public static void printCommonPart(Node head1, Node head2) { System.out.print("Common Part: "); while (head1 != null && head2 != null) { if (head1.value < head2.value) { head1 = head1.next; } else if (head1.value > head2.value) { head2 = head2.next; } else { System.out.print(head1.value + " "); head1 = head1.next; head2 = head2.next; } } System.out.println(); } public static void printLinkedList(Node node) { System.out.print("Linked List: "); while (node != null) { System.out.print(node.value + " "); node = node.next; } System.out.println(); } public static void main(String[] args) { Node node1 = new Node(2); node1.next = new Node(3); node1.next.next = new Node(5); node1.next.next.next = new Node(6); Node node2 = new Node(1); node2.next = new Node(2); node2.next.next = new Node(5); node2.next.next.next = new Node(7); node2.next.next.next.next = new Node(8); printLinkedList(node1); printLinkedList(node2); printCommonPart(node1, node2); } }
五、约瑟夫环
package com.zxc.LinklistLearning; public class Jhonsef { //定义结点, class Node{ int data; Node next; Node(int data){ this.data = data; } } public static void main(String[] args){ new Jhonsef().yuekill(); } public void yuekill(){ int n = 3;//定义总人数n int m = 3;//和出圈数字m //初始化循环列表,头结点first和尾结点last Node first = new Node(0); first.next = first; //一个节点的时候,头就是尾,尾就是头 Node last = first; for(int i=1; i<n; i++){//形成单链表 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 Node temp = new Node(i); last.next = temp; last = last.next; } last.next = first;//0和16连接,最终让其首尾相连,形成循环链表 ,尾接头形成循环链表(last为尾结点) //以上就是约瑟夫环 //执行出圈操作 System.out.println("出圈顺序为:"); while(last != last.next){ //下面for循环后,last是第m个结点的前一个结点 for(int i=1; i<m; i++){ //遍历要删除节点 前一个出圈数字 last = last.next; } //出循环之后,就是要删除的节点,删除第m个结点 System.out.print(last.next.data+" "); //出圈的数字 last.next = last.next.next; //指向要删除的节点的下一节点 } System.out.print("\n幸运者是:"+last.data);//原来是10号 } }
六、链表和数组的区别
数组:定义时长度固定(在内存中也是连续的),有下标,不能任意删除某个元素
链表:长度不固定(可以根据需要申请内存),没有下标,可以根据需要删除。C语言中的链表是通过指针把相邻的节点联系起来,也就是前一个节点通过指针来保存下一个节点的地址,所以如果能得到头结点就能得到下一个节点,一直到最后一个节点。而Java中的链表,由于没有指针,所以它不能使用指针来保存下一个节点的地址,但是我们可以保存下一个节点对象,这种方法叫做引用。