现在在进行面试的时候,很多公司都会要求你编写数组排序,链表,二叉树等,所以对于数据结构的掌握成为面试的基本功,数据结构主要的核心组成:引用+递归。
链表的基本介绍
如果要想保存多个对象,我们现在能想到的概念就只有对象数组,也就是说,如果该数组可以保存任意对象,那么又可以想到Object型的数组:
Object data[]=new Object[3];
但在实际开发中,我们就要面临一些问题,数组的不足是长度固定的线性结构,虽然以上的代码可以存放多个内容,但是一旦我们的内容不足或者是内容过多就会产生内存的浪费,如果此时要解决此类问题最好的办法就是不定义一个长度固定的数组 有多少数据就保存多少数据,应该采用火车车厢的设计模式,动态的进行车厢的挂载,那么假设每截车厢只保留一个数据,那么现在假设可以不受内存的限制,就可以证明解决了数组长度的问题
1 class Node{ //因为只有Node类才可以保存数据的同时设置数据的先后关系 2 private Object data;//真正保存的数据 3 private Node next;//定义下一个节点 4 public Node(Object data){//车厢里面一定要去保存有数字 5 this.data=data; 6 } 7 public void setData(Object data){ 8 this.data=data; 9 } 10 public Object getData(){ 11 return this.data; 12 } 13 public void setNext(Node next){ 14 this.next=next; 15 } 16 public Node getNext(){ 17 return this.next; 18 } 19 } 20 public class Newbegin{ 21 public static void main(String args[]) throws Exception{ 22 //1.封装几个节点 23 Node root=new Node("火车头");//现在假设存放的数是String 24 Node n1=new Node("车厢A"); 25 Node n2=new Node("车厢B"); 26 Node n3=new Node("车厢C"); 27 //2.需要设置节点关系 28 root.setNext(n1); 29 n1.setNext(n2); 30 n2.setNext(n3); 31 //3.输出节点 32 print(root); 33 } 34 public static void print(Node node){ 35 if(node!=null){//表示当前存在有节点 36 System.out.println(node.getData()); 37 print(node.getNext()); 38 } 39 } 40 }
如果你要定义一个火车车厢你肯定不可能保留一个数据,因为只需要另一个指向,指向下一个节点,
在整个链表的实现过程之中Node类的核心作用:保存数据,连接节点关系,但是以上的代码麻烦,发现客户端(主方法)需要自己来进行节点的创建,所谓的链表就是需要一个单独的类,假设:Link通过这个类实现Node类的数据保存以及关系处理。
链表实现结构说明
如果按照以上的方式来进行链表的实现,那我们对开发者是非常痛苦,因为所有的使用者必须自己来处理数据的关系,所以为了更好的处理应该追加一个程序类来完成,假设这个类叫做Link类
但是如果现在对于程序的初期结构采用如下的方式定义那么也会出现问题。
1 class Link{ //负责链表的操作 2 //将Node定义为内部类,表示Node类只为Link类 3 private class Node{ //负责数据与节点关系的匹配 4 5 } 6 } 7 public class Newbegin{ 8 public static void main(String args[]) throws Exception{ 9 10 } 11 }
使用私有的内部类才可以保证只有link类才有权操作 Node类,而且使用了内部类之后还有一个最明显的特点,内部类和外部类之间可以方便的进行私有属性的访问,也就避免了seter和geter方法调用的尴尬,
1 class Link{ //负责链表的操作 2 //将Node定义为内部类,表示Node类只为Link类 3 private class Node{ //负责数据与节点关系的匹配 4 private Object data;//保存节点的数据 5 private Node next;//保存下一个节点 6 public Node(Object data){ 7 this.data=data; 8 } 9 } 10 //----------------以下为link类------------// 11 12 } 13 public class Newbegin{ 14 public static void main(String args[]) throws Exception{ 15 16 } 17 }
按照以上的模式可以编写代码,但是对于链表的核心功能:增加,修改,删除。判断某一数据是否存在。
增加链表的数据 public void add(Object data)
如果要想在链表之中实现一个数据的增加操作,则应该首先在链表里面提供有一个 追加方法,而这个追加方法上的参数是Object类型,
1.在Link类中追加新方法定义
1 public void add(Object data){ 2 if(data==null){//人为的追加了规定,不允许存放空值 3 return ;//方法结束调用 4 } 5 }
2.那么该如何进行数据的保存呢?应该首先定义一个根节点,才可以添加后面的子节点,这个根节点的类型一定是Node。则应该在Link类里面追加有一个Node类的属性,
private Node root;//属于根节点,没有根节点就无法进行数据的保存
3.后续节点操作:
1 class Link{ //负责链表的操作 2 //将Node定义为内部类,表示Node类只为Link类 3 private class Node{ //负责数据与节点关系的匹配 4 private Object data;//保存节点的数据 5 private Node next;//保存下一个节点 6 public Node(Object data){ 7 this.data=data; 8 } 9 //第1次调用:this=Link.root 10 //第2次调用:this=Link.root.next; 11 //第3次调用:this=Link.root.next; 12 public void addNode(Node newNode){//处理节点关系 13 if(this.next==null){//当前节点下一个为空 14 this.next=newNode; 15 }else{//现在当前节点的下一个不为空 16 this.next.addNode(newNode); 17 } 18 } 19 } 20 //----------------以下为link类------------// 21 private Node root;//属于根节点,没有根节点就无法进行数据的保存 22 public void add(Object data){ 23 if(data==null){//人为的追加了规定,不允许存放空值 24 return ;//方法结束调用 25 } 26 //如果要想进行数据的保存,那么就必须将数据封装在Node节点类里面 27 //如果没有封装,则无法确认好节点的先后顺序 28 Node newNode=new Node(data); 29 if(this.root==null){ //当前并没有根节点 30 this.root=newNode;//第一个节点设置为根节点 31 }else{ //根节点已经存在了 32 //应该把此时的节点顺序的处理交给Node类自己完成 33 this.root.addNode(newNode); 34 } 35 } 36 } 37 public class Newbegin{ 38 public static void main(String args[]) throws Exception{ 39 Link all=new Link(); 40 all.add("hello"); 41 all.add("world"); 42 all.add("gl"); 43 } 44 }
Node的数据保存和节点关系的匹配
4.取得元素个数:public int size()
一个链表之中可以保存多个元素数据,那么为了操作方便,就需要知道其一共保存的数据个数,所有需要有一个计数的统计操作
1.在Link类中追加一个统计个数的属性
private int count=0;//当前的保存个数
2.随后在每次进行数据追加的时候都应该进行一个个数的累加
1 public void add(Object data){ 2 if(data==null){//人为的追加了规定,不允许存放空值 3 return ;//方法结束调用 4 } 5 //如果要想进行数据的保存,那么就必须将数据封装在Node节点类里面 6 //如果没有封装,则无法确认好节点的先后顺序 7 Node newNode=new Node(data); 8 if(this.root==null){ //当前并没有根节点 9 this.root=newNode;//第一个节点设置为根节点 10 }else{ //根节点已经存在了 11 //应该把此时的节点顺序的处理交给Node类自己完成 12 this.root.addNode(newNode); 13 } 14 count++; 15 }
3.可以直接取得元素的个数
5,判断是否为空链表:public boolean isEmpty()
现在如果根元素为null或者保存的元素个数为0,则认为链表是一个空的链表。则isEmpty()返回的就应该是true;
1 public boolean isEmpty(){ 2 return this.root==null&&this.count==0; 3 }
为空判断与个数可以结合在一起完成,不需要过多的复杂的操作逻辑,
6,取得全部数据:public Object [] toArray()
首先链表是一个动态对象数组,那么既然是动态对象数组,返回的内容也一定就应该是一个对象数组,但是如果要想进行数组的返回,首先必须要开辟一个数组(数组长度就是count属性内容),同时这个的数组内容的填充应该依次将节点中的数据取出,才可以正常完成,
1.在Link类中一定会存在有一个toArray()的方法,该方法的返回值一定是Object[]
2.所有在返回的对象数组中的数据都在Node类里面,那么就证明这些数据应该Node类中利用递归来依次取出那么在外部类中Link中定义一个返回类型的属性,
3.在Node类中处理节点数据
4.数据的存储和返回是链表开发中使用最多的功能
1 class Link{ //负责链表的操作 2 //将Node定义为内部类,表示Node类只为Link类 3 private class Node{ //负责数据与节点关系的匹配 4 private Object data;//保存节点的数据 5 private Node next;//保存下一个节点 6 public Node(Object data){ 7 this.data=data; 8 } 9 //第1次调用:this=Link.root 10 //第2次调用:this=Link.root.next; 11 //第3次调用:this=Link.root.next; 12 public void addNode(Node newNode){//处理节点关系 13 if(this.next==null){//当前节点下一个为空 14 this.next=newNode; 15 }else{//现在当前节点的下一个不为空 16 this.next.addNode(newNode); 17 } 18 } 19 //第一次调用:this=Link.root 20 //第一次调用:this=Link.root.next 21 public void toArrayNode(){ 22 Link.this.retData[Link.this.foot++]=this.data; 23 if(this.next!=null){//现在还有下一个节点 24 this.next.toArrayNode(); 25 } 26 } 27 } 28 //----------------以下为link类------------// 29 private Object[] retData;//返回类型 30 private int foot=0;//操作脚标 31 private int count=0;//当前的保存个数 32 private Node root;//属于根节点,没有根节点就无法进行数据的保存 33 public void add(Object data){ 34 if(data==null){//人为的追加了规定,不允许存放空值 35 return ;//方法结束调用 36 } 37 //如果要想进行数据的保存,那么就必须将数据封装在Node节点类里面 38 //如果没有封装,则无法确认好节点的先后顺序 39 Node newNode=new Node(data); 40 if(this.root==null){ //当前并没有根节点 41 this.root=newNode;//第一个节点设置为根节点 42 }else{ //根节点已经存在了 43 //应该把此时的节点顺序的处理交给Node类自己完成 44 this.root.addNode(newNode); 45 } 46 count++; 47 } 48 public int size(){//取得元素个数 49 return this.count; 50 } 51 public boolean isEmpty(){ 52 return this.root==null&&this.count==0; 53 } 54 public Object[] toArray(){ 55 if(this.count==0){ 56 return null; 57 } 58 //现在链表中存在有数据,则开辟指定长度的数组 59 //该数组一定要交给Node类进行处理 60 this.retData=new Object[this.count]; 61 this.foot=0;//进行清零的处理,需要进行脚标操作 62 this.root.toArrayNode();//将数据的取出处理交给Node类完成 63 return this.retData; 64 } 65 } 66 public class Newbegin{ 67 public static void main(String args[]) throws Exception{ 68 Link all=new Link(); 69 all.add("hello") ; 70 all.add("world"); 71 all.add("gl"); 72 Object result[]=all.toArray(); 73 for(int x=0;x<result.length;x++){ 74 System.out.println(result[x]); 75 } 76 } 77 }
7.判断数据是否存在 public boolean contains(Object data)
在链表中存在很多数据,判断数据是否存在链表之中,可以利用contains()方法进行完成,
判断的过程
1.判断链表是否有数据,如果没有数据不需要进行后续迭代
2.当我们链表之中存在有数据之后,进行的数据的依次递归判断,考虑到标准化问题,这个操作需要equals()方法支持。
1.在Node类里面追加有一个containsNode()方法,用于数据查找递归
这个方法一定是由root这个对象首先调用
1 public boolean contains(Object search){ 2 //没有要查询的内容以及链表为空 3 if(search==null||this.root==null){ 4 return false; 5 } 6 return this.root.containsNode(search); 7 }
查找操作需要使用equals()方法 如果是一个自定义的类则一定要覆写equals()方法
取得指定索引数据:public Object get(int index)
链表属于动态数组,数组应该有索引取得内容的形式
1.在Node内里面追加一个新的方法,用于索引查找
1 public Object get(int index){ 2 if(index>=this.count){//超过了保存的个数 3 return null; 4 } 5 this.foot=0; 6 return this.root.getNode(index); 7 }
修改指定索引数据 public void set(int index,Object newData)
现在如果发现某些数据的保存有问题,则可以根据索引进行修改处理操作,
1.Node 类里面追加一个方法:setNode();
1 public void setNode(int index,Object newData){ 2 if(Link.this.foot++==index){//索引相同 3 this.data=newData; 4 return ;//结束 5 }else{ 6 if(this.next!=null){ 7 this.next.setNode(index,newData); 8 } 9 } 10 }
10.数据删除 public void remove(Object data)
如果要想进行数据的删除处理,则需要考虑两种情况,
情况一:要删除的是根节点
如果要删除的是根节点,则意味着Link中的根节点的保存需要发生变化;
情况二:删除的不是根节点
所有的处理的操作应该交给Node来进行处理。
1 class Link{ //负责链表的操作 2 //将Node定义为内部类,表示Node类只为Link类 3 private class Node{ //负责数据与节点关系的匹配 4 private Object data;//保存节点的数据 5 private Node next;//保存下一个节点 6 public Node(Object data){ 7 this.data=data; 8 } 9 //第1次调用:this=Link.root 10 //第2次调用:this=Link.root.next; 11 //第3次调用:this=Link.root.next; 12 public void addNode(Node newNode){//处理节点关系 13 if(this.next==null){//当前节点下一个为空 14 this.next=newNode; 15 }else{//现在当前节点的下一个不为空 16 this.next.addNode(newNode); 17 } 18 } 19 //第一次调用:this=Link.root 20 //第一次调用:this=Link.root.next 21 public void toArrayNode(){ 22 Link.this.retData[Link.this.foot++]=this.data; 23 if(this.next!=null){//现在还有下一个节点 24 this.next.toArrayNode(); 25 } 26 } 27 //第一次调用:this=Link.root 28 //第一次调用:this=Link.root.next 29 public boolean containsNode(Object search){ 30 if(search.equals(this.data)){//找到了 31 return true; 32 }else{ 33 if(this.next!=null){//当前节点之后 34 return this.next.containsNode(search); 35 }else{//没有节点 36 return false; 37 } 38 } 39 } 40 //第一次调用:this=Link.root 41 //第一次调用:this=Link.root.next 42 public Object getNode(int index){ 43 if(Link.this.foot++==index){ 44 return this.data; 45 }else{ 46 this.next.getNode(index); 47 } 48 return null; 49 } 50 public void setNode(int index,Object newData){ 51 if(Link.this.foot++==index){//索引相同 52 this.data=newData; 53 return ;//结束 54 }else{ 55 if(this.next!=null){ 56 this.next.setNode(index,newData); 57 } 58 } 59 } 60 //第一次调用:this=Link.root.next,previous=Link.root; 61 //第二次调用:this=Link.root.next.next,previous=Link.root.next; 62 public void removeNode(Node previous,Object data){ 63 if(this.data.equals(data)){//当前节点为要删除节点 64 previous.next=this.next;//删除当前了 65 }else{ 66 this.next.removeNode(this,data); 67 } 68 } 69 } 70 //----------------以下为link类------------// 71 private Object[] retData;//返回类型 72 private int foot=0;//操作脚标 73 private int count=0;//当前的保存个数 74 private Node root;//属于根节点,没有根节点就无法进行数据的保存 75 public void add(Object data){ 76 if(data==null){//人为的追加了规定,不允许存放空值 77 return ;//方法结束调用 78 } 79 //如果要想进行数据的保存,那么就必须将数据封装在Node节点类里面 80 //如果没有封装,则无法确认好节点的先后顺序 81 Node newNode=new Node(data); 82 if(this.root==null){ //当前并没有根节点 83 this.root=newNode;//第一个节点设置为根节点 84 }else{ //根节点已经存在了 85 //应该把此时的节点顺序的处理交给Node类自己完成 86 this.root.addNode(newNode); 87 } 88 count++; 89 } 90 public int size(){//取得元素个数 91 return this.count; 92 } 93 public boolean isEmpty(){ 94 return this.root==null&&this.count==0; 95 } 96 public boolean contains(Object search){ 97 //没有要查询的内容以及链表为空 98 if(search==null||this.root==null){ 99 return false; 100 } 101 return this.root.containsNode(search); 102 } 103 public Object[] toArray(){ 104 if(this.count==0){ 105 return null; 106 } 107 //现在链表中存在有数据,则开辟指定长度的数组 108 //该数组一定要交给Node类进行处理 109 this.retData=new Object[this.count]; 110 this.foot=0;//进行清零的处理,需要进行脚标操作 111 this.root.toArrayNode();//将数据的取出处理交给Node类完成 112 return this.retData; 113 } 114 public void set(int index,Object newData){ 115 if(index>=this.count){//超过了保存的个数 116 return ;//结束方法调用 117 } 118 this.foot=0; 119 this.root.setNode(index,newData); 120 } 121 public Object get(int index){ 122 if(index>=this.count){//超过了保存的个数 123 return null; 124 } 125 this.foot=0; 126 return this.root.getNode(index); 127 } 128 public void remove(Object data){ 129 if(this.contains(data)){//如果该数据存在则进行删除处理 130 //首先需要判断要删除的是否为根节点数据 131 if(this.root.data.equals(data)){//首先需要判断 132 this.root=this.root.next;//根节点变为下一个节点 133 }else{//不是根节点 134 this.root.next.removeNode(this.root,data); 135 } 136 this.count--; 137 } 138 } 139 } 140 141 public class Newbegin{ 142 public static void main(String args[]) throws Exception{ 143 Link all=new Link(); 144 all.add("hello") ; 145 all.add("world"); 146 all.add("gl"); 147 all.set(1,"你好"); 148 all.remove("hello"); 149 all.remove("gl"); 150 Object result[]=all.toArray(); 151 for(int x=0;x<result.length;x++){ 152 System.out.println(result[x]); 153 } 154 // System.out.println("==========="); 155 // System.out.println(all.contains("hello")); 156 // System.out.println( all.contains("xxx")); 157 // System.out.println("==========="); 158 // System.out.println(all.get(0)); 159 // System.out.println(all.get(3)); 160 } 161 }