目录
链表是一种根据元素节点逻辑关系排列起来的一种数据结构。利用链表可以保存多个数据, 这一点
类似于数组的概念, 但是数组本身有一个缺点一一数组的长度固定, 不可改变。在长度固定的情况下首
边的肯定是数组, 但是在现实的开发中往往要保存的内容长度是不确定的, 此时就可以利用链表结构来
代替数组的使用。
链表是-种最为简单的数据结构, 它的主要目的是依靠引用关系来实现多个数据的保存;
@图片来源与网络---------------------------------
链表的基本慨念
之所以会引用一个节类. 是因为只依靠保存的数据无法区分出先后顺序, 而引入了Node类可以包装数
据并指向下一个节点, 所以在Node类的设计中主要保存两个属性:数据( data )与下一个节点引用
( next )
范例:定义一个Node类
class Node { //每一个链表实际上就是由多个节点组成的
private String data;//要保存的数据
private Node next;//要保存的下一个节点
/*每一个Node类对象都必须保存有相应的数据
data 要通过节点包装的数据
*/
public Node(String data){ //必须有数据才有Node
this.data=data;
}
/*设置下一个节点关系
next保存下一个Node类引用
*/
public void setNext(Node next){
this.next=next;
}
/*取得当前的下一个节点
return当前节点的下一个节点引用
*/
public Node getNext(){
return this.next;
}
/*设置或修改当前节点包装的数据
*/
public void setData(String data){
this.data=data;
}
//取得包装的数据
public String getData(){
return this.data;
}
}
Node节点类本身不属于一个简单Java类, 而是一个功能性的表示类, 在这个类中, 主要保存了两
种数据: 一种是存储的对象(此处暂时保存的是String型对象), 另一种存储的是当前节点的下一个节
点( next)。
在进行链表操作的时候, 首先需要的是一个根节点(第一个节点即为根节点), 然后每一个节点的引
用都保存在上一节点的next属性中。而在进行输出的时候也应该按照节点的先后顺序, 一个一个取得每
一个节点所包装的数据。
范例:手工配置节点关系,并使用while循环输出全部节点数据。
public class LinkDemo{
public static void main(String args[]){
//第一步:定义要操作的节点并设置好包装的字符串数据
Node root=new Node("火车头");
Node n1=new Node("车厢A");
Node n2=new Node("车厢B");
root.setNext(n1);
n1.setNext(n2);
//第二步:根据节点关系取出所有数据
Node currentNode=root;
while(currentNode!=null){//当前从根节点开始读取
System.out.println(currentNode.getData);
currentNode=currentNode.getNext();//将下一个节点设置为当前节点
}
}
本程序一共分为以下操作步骤进行。
• 第1步:定义各个独立的节点,同时封装要保存的字符串数据;
第2步:配置不同节点彼此间的操作关系;
· 第3步:由于现在不清楚要输出的节点个数, 只知道输出的结束条件(没有节点就不输出
了,curentNode == null ),所以使用while循环,依次取得每一个节点,并输出里面包装的数据。
虽然利用while 循环可以轻松取得节点中包装的数据,但是这种输出操作本身并不是最理想的,所以最合适的操作应该是采用递归输出。
范例:手工配置节点关系, 通过递归输出全部节点数据。
public class LinkDemo{
public static void main(String args[]){
//第一步:定义要操作的节点并设置好包装的字符串数据
Node root=new Node("火车头");//定义节点,同时包装数据
Node n1=new Node("车厢A");//定义节点,同时包装数据
Node n2=new Node("车厢B");//定义节点,同时包装数据
root.setNext(n1);//设置节点关系
n1.setNext(n2);//设置节点关系
//利用递归方式输出所有的节点数据
public static void print(Node currnet){//第二步:根据节点关系取出所偶有数据
if(current==null){//递归结束条件
return ;//结束方法
}
System.out.println(current.getData());//输出节点包含的数据
print(current.getNext());//递归操作
}
}
}
本程序定义了一个print()方法实现节点数据的输出操作, 在print()方法中会依照节点关系递归调用
本方法, 如果当前节点之后没有节点, 将结束调用。
链表的操作方法及使用
public void add(数据类型 变量) 向链表中增加新的数据
public int size() 取得链表中保存的元素个数
public boolean isEmpty() 判断是否是空链表
public boolean contains(数据类型 变量) 判断某一个数据是否存在
public 数据类型 get(int index) 根据索引取得数据
public void set(int index,数据类型 变量) 使用新的内容替换指定索引的旧内容
public void remove(数据类型 变量) 删除指定数据,如果是对象则要进行对象比较
public 数据类型 [] toArray() 将链表以数组的形式返回
public void clear() 清空链表
//此处例举常用的方法
范例:链表的基本形式。
class Node { //定义一个节点
private String data;//要保存的数据
private Node next;//要保存的下一个节点
public Node(String data){//每一个Node类对象都必须保存相应的数据
this.data=data;
}
public void setNext(Node next){
this.next=next;
}
public Node getNext(){
return this.next;
}
public String getData(){
return this.data;
}
/*实现节点的添加(递归调用,目的是将新节点保存到最后一个节点之后)
第一次调用(Link): this = Link.root
第二次调用(Node): this = Link.root.next
第三次调用(Node): this = Link.root.next.next
newNode 新节点,节点对象由Link类创建
*/
public void addNode(Node newNode){
if(this.next==null){//当前节点的下一个为null
this.next=newNode;//保存新节点
}else{//当前节点之后还保存在节点
this.next.addNode(newNode);//当前节点的下一个节点继续保存
}
}
//递归的方式输出每个节点保存的数据
//第一次调用( Link ): this = Link.root
//第二次调用(Node): this = Link.root.next
//第三次调用( Node): this = Link.root.next.next
public void printNode(){
System.out.println(this.data);//输出当前节点
if(this.next!=null){//还有下一个节点
this.next.printNode();//找到下一个节点继续输出
}
}
}
class Link {//负责数据的设置和输出
private Node root;//根节点
/*
向链表中增加新的数据,如果当前链表没有节点则将第一个数据作为节点
如果有节点则使用Node类将新节点保存到最后一个节点之后
data要保存的数据
*/
public void add(String data){//设置数据的先后关系,所以将data包装在一个Node类对象中
Node newNode=new Node(data);//一个链表只有一个根节点
if(this.root==null){//将新的节点设置为根节点
this.root=newNode;//根节点已经存在
}else{
this.root.addNode(newNode); //交由Node类来进行节点保存
}
}
//使用递归方式,输出节点中的全部数据
public void print(){ //输出数据
if(this.root!=null){//存在根节点
this.root.printNode();//交给Node类输出
}
}
}
public class LinkDemo{
public static void main(String args[]){
Link link=new Link();//由这个类负责所有的数据操作
link.add("hello");//存放数据
link.add("hi");
link.print();//展示数据
}
}
本程序将节点的匹配关系交由Node类来进行处理, 而数据的节点包装以及根节点的管理交由Link
类负责管理, 这样用户在使用链表操作时不需要关注Node类的操作, 只需要通过Link类的add()方法
实现数据增加, 通过print()方法实现数据输出。
数据增加; public void add(数据类型变量)
如果要进行新数据的增加, 则应该由Link 类负责节点对象的产生, 并且由
Link类维护根节点,所有节点的关系匹配交给Node类处理。
//链表的定义
//增加数据
//public void add(数据类型 变量)
class Link { //链表类,外部能看见的只有这一个,之所以定义在内部,主要是让其为Link类服务
private class Node {//为要查询的索引
private String data; //保存数据
private Node next; //引用关系
public Node(String data){
this.data=data;
}
}
//----------以上为内部类-------------
private Node root; //需要根节点
public void add(String data){ //假设不允许有null
if (data==null){
return;
}
Node newNode=new Node(data); //保存的数据
if (this.root==null) {//当前没有根节点
this.root=newNode; //保存根节点
}else{ //根节点存在,其他节点交给Node
this.next.addNode(newNode);
}
}
}
public class lianbiao{
public static void main(String args[]){
Link all=new Link();
all.add("hello");
all.add("hello");
all.add(null);
}
}
取得保存元素个数:public int size()
class Link { //链表类,外部能看见的只有这一个,之所以定义在内部,主要是让其为Link类服务
private class Node {//定义的节点类
private String data; //保存数据
private Node next; //引用关系
public Node(String data){
this.data=data;
}
}
//----------以上为内部类-------------
private Node root; //需要根节点
private int count=0; //保存元素的个数
public void add(String data){ //假设不允许有null
if (data==null) {
return;
}
Node newNode=new Node(data); //保存的数据
if (this.root==null) { //当前没有根节点
this.root=newNode; //保存根节点
}else{ //根节点存在,其他节点交给Node
this.next.addNode(newNode);
}
this.count++; //每一次保存完成后数据量加1.
}
public int size(){ //取得保存的数据量
return this.count;
}
}
public class lianbiao1{
public static void main(String args[]){
Link all=new Link();
all.add("hello");
all.add("hello");
all.add(null);
System.out.println(all.size());//空不会被保存
}
}
判断是否是空链表(size()==0)public boolean isEmpty()
取得保存元素个数:public int size()
数据查询,判断某一个数据是否存在
public boolean contains(数据类型 变量)
class Link {//链表类,外部能看见的只有这一个,之所以定义在内部,主要是让其为Link类服务
private class Node //定义的节点类
{
private String data; //保存数据
private Node next; //引用关系
public Node(String data){
this.data=data;
}
public void addNode(Node newNode){
if (this.next==null){//当前的节点
this.next=newNode;
}else{ //向后继续保存
this.next.addNode(newNode);
}
}
//第一次调用(Link):this=Link.root
//第二次调用(Node):this=Link.root.next
public boolean containsNode(String data){
if(data.equals(this.data)){//当前节点没有数据为要查询的数据
return true; //后面不再查询了
}else{ //当前节点数据不满足查询要求
if(this.next!=null){ //有后续节点
return this.next.containsNode(data);
}else{ //没有后续节点
return false; //没得查了
}
}
}
}
}
//----------以上为内部类-------------
private Node root; //需要根节点
private int count=0; //保存元素的个数
public void add(String data){ //假设不允许有null
if (data==null){
return;
}
Node newNode=new Node(data); //保存的数据
if (this.root==null) {//当前没有根节点
this.root=newNode; //保存根节点
}else{ //根节点存在,其他节点交给Node
this.next.addNode(newNode);
}
this.count++; //每一次保存完成后数据量加1.
}
public int size(){ //取得保存的数据量
return this.count;
}
public boolean isEmpty(){
return this.count==0;
}
public boolean contains(String data){
//现在没有要查询的数据,根节点也不保存数据
if(data==null ||this.root==null){
return false; //没有查询结果
}
return this.root.containsNode(data);
}
}
public class lianbiao2 {
public static void main(String args[]){
Link all=new Link();
System.out.println(all.isEmpty());
all.add("hello");
all.add("hello");
all.add(null);
System.out.println(all.isEmpty());
System.out.println(all.contains("Hello"));
System.out.println(all.size());
}
}
根据索引取得数据:public 数据类型 get(int index)
class Link {//链表类,外部能看见的只有这一个,之所以定义在内部,主要是让其为Link类服务
private class Node { //定义的节点类
private String data; //保存数据
private Node next; //引用关系
public Node(String data){
this.data=data;
}
//第一次调用(Link):this=Link.root
//第二次调用(Node):this=Link.root.next
public boolean containsNode(String data){
if(data.equals(this.data)){//当前节点没有数据为要查询的数据
return true; //后面不再查询了
}else{ //当前节点数据不满足查询要求
if(this.next!=null){ //有后续节点
return this.next.containsNode(data);
}else{ //没有后续节点
return false; //没得查了
}
}
}
public String getNode(int index){
//使用当前的foot内容与要查询的索引进行比较
//随后将foot的内容自增,目的是为了下一次查询方便
if (Link.this.foot++==index){//为要查询的索引
return this.data; //返回当前节点数据
}else{ //现在应该继续向后查询
return this.next.getNode(index);
}
}
}
//------------以上为内部类------------------
private Node root; //需要根节点
private int count=0; //保存元素的个数
private int foot=0;
public void add(String data){ //假设不允许有null
Node newNode=new Node(data); //保存的数据
if (this.root==null) {//为要查询的索引
this.root=newNode; //保存根节点
}else{ //根节点存在,其他节点交给Node
this.next.addNode(newNode);
}
this.count++; //每一次保存完成后数据量加1.
}
public int size(){ //取得保存的数据量
return this.count;
}
public boolean isEmpty(){
return this.count==0;
}
public String get(int index){
if (index >this.count) { //超过了查询范围
return null; //没有数据
}
this.foot=0; //表示从前向后查询
return this.root.getNode(index); //查询过程交给Node类
}
public boolean contains(String data){
//现在没有要查询的数据,根节点也不保存数据
if(data==null ||this.root==null){
return false; //没有查询结果
}
return this.root.containsNode(data);
}
}
}
public class lianbiao3 {
public static void main(String args[]){
Link all=new Link();
all.add("hello");
all.add("hello");
all.add(null);
System.out.println(all.get(1));
System.out.println(all.get(10));
}
}
修改索引数据类型 public void set(int index,数据类型 变量)
根据索引取得数据 public 数据类型 get(int index)
class Link {//链表类,外部能看见的只有这一个,之所以定义在内部,主要是让其为Link类服务
private class Node {//定义的节点类
private String data; //保存数据
private Node next; //引用关系
public Node(String data){
this.data=data;
}
//第一次调用(Link):this=Link.root
//第二次调用(Node):this=Link.root.next
public boolean containsNode(String data){
if(data.equals(this.data)){//当前节点没有数据为要查询的数据
return true; //后面不再查询了
}else{ //当前节点数据不满足查询要求
if(this.next!=null){ //有后续节点
return this.next.containsNode(data);
}else{ //没有后续节点
return false; //没得查了
}
}
}
public String getNode(int index){
//使用当前的foot内容与要查询的索引进行比较
//随后将foot的内容自增,目的是为了下一次查询方便
if (Link.this.foot++==index){ //为要查询的索引
return this.data; //返回当前节点数据
}else{ //现在应该继续向后查询
return this.next.getNode(index);
}
}
public void setNode(int index,String data){
if (Link.this.foot++==index){
this.data=data; //进行内容的修改
}else{
this.next.setNode(index,data);
}
}
}
//------------以上为内部类------------------
private Node root; //需要根节点
private int count=0; //保存元素的个数
private int foot=0;
public void add(String data){ //假设不允许有null
Node newNode=new Node(data); //保存的数据
if (this.root==null) {//当前没有根节点
this.root=newNode; //保存根节点
}else{ //根节点存在,其他节点交给Node
this.next.addNode(newNode);
}
this.count++; //每一次保存完成后数据量加1.
}
public int size(){ //取得保存的数据量
return this.count;
}
public boolean isEmpty(){
return this.count==0;
}
public String get(int index){
if (index >this.count) { //超过了查询范围
return null; //没有数据
}
this.foot=0; //表示从前向后查询
return this.root.getNode(index); //查询过程交给Node类
}
public boolean contains(String data){
//现在没有要查询的数据,根节点也不保存数据
if(data==null ||this.root==null){
return false; //没有查询结果
}
return this.root.containsNode(data);
}
public void set(int index,String data){
if (index>this.count) {
return; //结束方法调用
}
this.foot=0; //重新设置foot属性的内容,作为索引出现
this.root.setNode(index,data);//交给Node类设置数据内容
}
}
public class lianbiao4{
public static void main(String args[]){
Link all=new Link();
all.add("hello");
all.add("hello");
all.add(null);
all.set(1,"elm");
System.out.println(all.get(1));
System.out.println(all.get(10));
}
}
数据删除:public void remove(数据类型 变量)
删除指定数据,如果是对象则要进行对象比较
对于删除数据而言,实际上要分为两种情况的
情况一:要删除的数据是根节点,则root应该变为“根节点.next”,Link类才关心根节点,所以此种情况要在Link类中进行处理;
情况二:要删除的不受根节点,而是其他的普通节点,应该在Node类礼处理,所以此处是从第二个节点开始判断的。
删除数据的最终的形式:当前节点上一节点.next=当前节点.next,即,空出了当前节点。
在Node类里面增加一个removeNode()方法,此方法负责处理非根节点的删除。
class Link {//链表类,外部能看见的只有这一个,之所以定义在内部,主要是让其为Link类服务
private class Node {//定义的节点类
private String data; //保存数据
private Node next; //引用关系
public Node(String data){
this.data=data;
}
//第一次调用(Link):this=Link.root
//第二次调用(Node):this=Link.root.next
public boolean containsNode(String data){
if(data.equals(this.data)){//当前节点没有数据为要查询的数据
return true; //后面不再查询了
}else{ //当前节点数据不满足查询要求
if(this.next!=null){ //有后续节点
return this.next.containsNode(data);
}else{ //没有后续节点
return false; //没得查了
}
}
}
public String getNode(int index){
//使用当前的foot内容与要查询的索引进行比较
//随后将foot的内容自增,目的是为了下一次查询方便
if (Link.this.foot++==index) {//为要查询的索引
return this.data; //返回当前节点数据
}else{ //现在应该继续向后查询
return this.next.getNode(index);
}
}
public void setNode(int index,String data){
if (Link.this.foot++==index){
this.data=data; //进行内容的修改
}else{
this.next.setNode(index,data);
}
}
//第一次调用(Link),previous=Link.root、this=Link.root.next
//第二次调用(Node),previous=Link.root.next、this=Link.root.next.next
//要传递上一个节点以及要删除的数据
public void removeNode(Node previous,String data){
if (data.equals(this.data)) {//为要查询的索引
previous.next=this.next; //空出当前节点
}else{ //一个向后继续查询
this.next.removeNode(this.data);
}
}
}
//------------以上为内部类------------------
private Node root; //需要根节点
private int count=0; //保存元素的个数
private int foot=0;
public void add(String data){ //假设不允许有null
Node newNode=new Node(data); //保存的数据
if (this.root==null) {//为要查询的索引
this.root=newNode; //保存根节点
}else{ //根节点存在,其他节点交给Node
this.next.addNode(newNode);
}
this.count++; //每一次保存完成后数据量加1.
}
public int size(){ //取得保存的数据量
return this.count;
}
public boolean isEmpty(){
return this.count==0;
}
public String get(int index){
if (index >this.count) { //超过了查询范围
return null; //没有数据
}
this.foot=0; //表示从前向后查询
return this.root.getNode(index); //查询过程交给Node类
}
public boolean contains(String data){
//现在没有要查询的数据,根节点也不保存数据
if(data==null ||this.root==null){
return false; //没有查询结果
}
return this.root.containsNode(data);
}
public void set(int index,String data){
if (index>this.count) {
return; //结束方法调用
}
this.foot=0; //重新设置foot属性的内容,作为索引出现
this.root.setNode(index,data);//交给Node类设置数据内容
}
public void remove((String data){
if (this.contains(data)) {//判断数据是否存在
//要删除数据是否是根节点数据
//root是Node类对象,此处直接访问了内部类的私有操作
if (data.equals(this.root.data)) {//判断数据是否存在
this.root=this.root.next; //空出当前根节点
}else{ //不是根元素
//此时根元素已经判断过了,从第二个元素开始判断
this.root.next.removeNode(this.root,data);
}
this.count--; //个数要减少
}
}
}
public class lianbiao5 {
public static void main(String args[]){
Link all=new Link();
all.add("hello");
all.add("hello");
all.add(null);
all.set(1,"elm");
all.remove("hello");
System.out.println(all.set());
System.out.println(all.get(1));
System.out.println(all.get(10));
}
}
将链表变为对象数组:public 数据类型[] toArray()
class Link { //链表类,外部能看见的只有这一个,之所以定义在内部,主要是让其为Link类服务
private class Node {//定义的节点类
private String data; //保存数据
private Node next; //引用关系
public Node(String data){
this.data=data;
}
//第一次调用(Link):this=Link.root
//第二次调用(Node):this=Link.root.next
public boolean containsNode(String data){
if(data.equals(this.data)){//当前节点没有数据为要查询的数据
return true; //后面不再查询了
}else{ //当前节点数据不满足查询要求
if(this.next!=null){ //有后续节点
return this.next.containsNode(data);
}else{ //没有后续节点
return false; //没得查了
}
}
}
public String getNode(int index){
//使用当前的foot内容与要查询的索引进行比较
//随后将foot的内容自增,目的是为了下一次查询方便
if (Link.this.foot++==index) {//为要查询的索引
return this.data; //返回当前节点数据
}else{ //现在应该继续向后查询
return this.next.getNode(index);
}
}
public void setNode(int index,String data){
if (Link.this.foot++==index) {
this.data=data; //进行内容的修改
}else{
this.next.setNode(index,data);
}
}
//第一次调用(Link),previous=Link.root、this=Link.root.next
//第二次调用(Node),previous=Link.root.next、this=Link.root.next.next
//要传递上一个节点以及要删除的数据
public void removeNode(Node previous,String data){
if (data.equals(this.data)) {//当前节点为要删除节点
previous.next=this.next; //空出当前节点
}else{ //一个向后继续查询
this.next.removeNode(this.data);
}
}
//第一次调用(Link),this=Link.root;
//第二次调用(Node),this=Link.root.next;
public void toArrayNode(){
Link.this.retArray[Link.this.foot++]=this.data;
if (this.next!=null) {//有后续元素
this.next.toArrayNode();
}
}
}
//------------以上为内部类------------------
private Node root; //需要根节点
private int count=0; //保存元素的个数
private int foot=0;
private String [] retArray; //返回的数组
public void add(String data){ //假设不允许有null
Node newNode=new Node(data); //保存的数据
if (this.root==null) {//当前没有根节点
this.root=newNode; //保存根节点
}else{ //根节点存在,其他节点交给Node
this.next.addNode(newNode);
}
this.count++; //每一次保存完成后数据量加1.
}
public int size(){ //取得保存的数据量
return this.count;
}
public boolean isEmpty(){
return this.count==0;
}
public String get(int index){
if (index >this.count){ //超过了查询范围
return null; //没有数据
}
this.foot=0; //表示从前向后查询
return this.root.getNode(index); //查询过程交给Node类
}
public boolean contains(String data){
//现在没有要查询的数据,根节点也不保存数据
if(data==null ||this.root==null){
return false; //没有查询结果
}
return this.root.containsNode(data);
}
public void set(int index,String data){
if (index>this.count){
return; //结束方法调用
}
this.foot=0; //重新设置foot属性的内容,作为索引出现
this.root.setNode(index,data);//交给Node类设置数据内容
}
public String[] toArray(){
if (this.root==null){
return null;
}
this.foot=0; //需要脚标控制
this.retArray=new String[this.count]; //根据保存内容开辟数组
this.root.toArrayNode(); //交给Node类处理
return this.retArray;
}
public void remove((String data){
if (this.contains(data)) {//判断数据是否存在
//要删除数据是否是根节点数据
//root是Node类对象,此处直接访问了内部类的私有操作
if (data.equals(this.root.data)) {//为要删除节点
this.root=this.root.next; //空出当前根节点
}else{ //不是根元素
//此时根元素已经判断过了,从第二个元素开始判断
this.root.next.removeNode(this.root,data);
}
this.count--; //个数要减少
}
}
}
public class lianbiao6{
public static void main(String args[]){
Link all=new Link();
all.add("hello");
all.add("hello");
all.add(null);
String [] data=all.toArray();
for (int x=0;x<data.length ;x++ ){
System.out.println(data[x]);
}
}
}
链表使用
class Book{
private String title;
private double price;
public Book(String title,double price){
this.title=title;
this.price=price;
}
public String getInfo(){
return "图书名称:"+this.title+",价格:"+this.price;
}
public boolean compare(Book book){
if (this==book) {
return true;
}
if (book==null) {
return false;
}
if (this.title.equals(book.title)&& this.price==book.price) {
return true;
}
return false;
}
}
//将链表变为对象数组:public 数据类型[] toArray()
//链表类,外部能看见的只有这一个
//之所以定义在内部,主要是让其为Link类服务
class Link {
private class Node {//定义的节点类
private Book data; //保存数据
private Node next; //引用关系
public Node(Book data){
this.data=data;
}
//第一次调用(Link):this=Link.root
//第二次调用(Node):this=Link.root.next
public boolean containsNode(Book data){
if(data.compare(this.data)){//当前节点没有数据为要查询的数据
return true; //后面不再查询了
}else{ //当前节点数据不满足查询要求
if(this.next!=null){ //有后续节点
return this.next.containsNode(data);
}else{ //没有后续节点
return false; //没得查了
}
}
}
public Book getNode(int index){
//使用当前的foot内容与要查询的索引进行比较
//随后将foot的内容自增,目的是为了下一次查询方便
if (Link.this.foot++==index) {//为要查询的索引
return this.data; //返回当前节点数据
}else{ //现在应该继续向后查询
return this.next.getNode(index);
}
}
public void setNode(int index,Book data){
if (Link.this.foot++==index){
this.data=data; //进行内容的修改
}else{
this.next.setNode(index,data);
}
}
//第一次调用(Link),previous=Link.root、this=Link.root.next
//第二次调用(Node),previous=Link.root.next、this=Link.root.next.next
//要传递上一个节点以及要删除的数据
public void removeNode(Node previous,Book data){
if (data.compare(this.data)){//当前节点为要删除节点
previous.next=this.next; //空出当前节点
}else{ //一个向后继续查询
this.next.removeNode(this.data);
}
}
//第一次调用(Link),this=Link.root;
//第二次调用(Node),this=Link.root.next;
public void toArrayNode(){
Link.this.retArray[Link.this.foot++]=this.data;
if (this.next!=null) {//有后续元素
this.next.toArrayNode();
}
}
}
//------------以上为内部类------------------
private Node root; //需要根节点
private int count=0; //保存元素的个数
private int foot=0;
private Book [] retArray; //返回的数组
public void add(Book data){ //假设不允许有null
Node newNode=new Node(data); //保存的数据
if (this.root==null) {//当前没有根节点
this.root=newNode; //保存根节点
}else{ //根节点存在,其他节点交给Node
this.next.addNode(newNode);
}
this.count++; //每一次保存完成后数据量加1.
}
public int size(){ //取得保存的数据量
return this.count;
}
public boolean isEmpty(){
return this.count==0;
}
public Book get(int index){
if (index >this.count){ //超过了查询范围
return null; //没有数据
}
this.foot=0; //表示从前向后查询
return this.root.getNode(index); //查询过程交给Node类
}
public boolean contains(Book data){
//现在没有要查询的数据,根节点也不保存数据
if(data==null ||this.root==null){
return false; //没有查询结果
}
return this.root.containsNode(data);
}
public void set(int index,Book data){
if (index>this.count){
return; //结束方法调用
}
this.foot=0; //重新设置foot属性的内容,作为索引出现
this.root.setNode(index,data);//交给Node类设置数据内容
}
public Book[] toArray(){
if (this.root==null){
return null;
}
this.foot=0; //需要脚标控制
this.retArray=new Book[this.count]; //根据保存内容开辟数组
this.root.toArrayNode(); //交给Node类处理
return this.retArray;
}
public void remove((Book data){
if (this.contains(data)) {//判断数据是否存在
//要删除数据是否是根节点数据
//root是Node类对象,此处直接访问了内部类的私有操作
if (data.compare(this.root.data)) {//为要删除节点
this.root=this.root.next; //空出当前根节点
}else{ //不是根元素
//此时根元素已经判断过了,从第二个元素开始判断
this.root.next.removeNode(this.root,data);
}
this.count--; //个数要减少
}
}
public class lianbiao7{
public static void main(String args[]){
link all=new Link();
all.add(new Book("Java开发",79.8));
all.add(new Book("JSP开发",69.8));
all.add(new Book("Oracle开发",89.8));
System.out.println("保存书的个数:"+all.size());
System.out.println(all.contains(new Book("Java开发",79.8)));
all.remove(new Book("Oracle开发",89.8));
Book[] books=all.toArray();
for (int x=0;x<books.length ;x++ ) {
System.out.println(books[x].getInfo());
}
}
}
//链表最好的使用就是横向替换掉对象数组。
链表的一对多映射
//一对多映射
class Province{
private int pid;
private String name;
private Link cities=new Link;
//setter、getter、无参构造略
public Province(int pid,String name){
this.pid=pid;
this.name=name;
}
public boolean compare(Province province){
if (this==province){
return true;
}
if (province==null){
return false;
}
if (this.pid==province.pid&&this.name.equals(province.name)){
return true;
}
return false;
}
private City cities[];
public void setCities(City cities[]){
this.cities=cities;
}
public City[] getCities(){
return this.cities;
}
public String getInfo(){
return "省份编号:"+this.pid+",名称:"+this.name;
}
}
class City{
private int cid;
private String name;
private Province province;
//setter、getter、无参构造略
public City(int cid,String name){
this.cid=cid;
this.name=name;
}
public boolean compare(City city){
if (this==city){
return true;
}
if(city==null){
return false;
}
if (this.cid==this.cid
&&this.name.equals(city.name)
&&this.province.compare(city,province)) {
return true;
}
return false;
}
public void setProvince(Province province){
this.Province=Province;
}
public Province getProvince(){
return this.province;
}
public String getInfo(){
return "城市编号:"+this.cid+",名称:"+this.name;
}
}
//链表类,外部能看见的只有这一个
//之所以定义在内部,主要是让其为Link类服务
class Link {
private class Node { //定义的节点类
private City data; //保存数据
private Node next; //引用关系
public Node(City data){
this.data=data;
}
//第一次调用(Link):this=Link.root
//第二次调用(Node):this=Link.root.next
public boolean containsNode(City data){
if(data.compare(this.data)){//当前节点没有数据为要查询的数据
return true; //后面不再查询了
}else{ //当前节点数据不满足查询要求
if(this.next!=null){ //有后续节点
return this.next.containsNode(data);
}else{ //没有后续节点
return false; //没得查了
}
}
}
public City getNode(int index){
//使用当前的foot内容与要查询的索引进行比较
//随后将foot的内容自增,目的是为了下一次查询方便
if (Link.this.foot++==index) {//为要查询的索引
return this.data; //返回当前节点数据
}else{ //现在应该继续向后查询
return this.next.getNode(index);
}
}
public void setNode(int index,City data){
if (Link.this.foot++==index){
this.data=data; //进行内容的修改
}else{
this.next.setNode(index,data);
}
}
//第一次调用(Link),previous=Link.root、this=Link.root.next
//第二次调用(Node),previous=Link.root.next、this=Link.root.next.next
//要传递上一个节点以及要删除的数据
public void removeNode(Node previous,City data){
if (data.compare(this.data)) {//当前节点为要删除节点
previous.next=this.next; //空出当前节点
}else{ //一个向后继续查询
this.next.removeNode(this.data);
}
}
//第一次调用(Link),this=Link.root;
//第二次调用(Node),this=Link.root.next;
public void toArrayNode(){
Link.this.retArray[Link.this.foot++]=this.data;
if (this.next!=null) {//有后续元素
this.next.toArrayNode();
}
}
}
//------------以上为内部类------------------
private Node root; //需要根节点
private int count=0; //保存元素的个数
private int foot=0;
private City [] retArray; //返回的数组
public void add(City data){ //假设不允许有null
Node newNode=new Node(data); //保存的数据
if (this.root==null) {//当前没有根节点
this.root=newNode; //保存根节点
}else{ //根节点存在,其他节点交给Node
this.next.addNode(newNode);
}
this.count++; //每一次保存完成后数据量加1.
}
public int size(){ //取得保存的数据量
return this.count;
}
public boolean isEmpty(){
return this.count==0;
}
public City get(int index){
if (index >this.count) { //超过了查询范围
return null; //没有数据
}
this.foot=0; //表示从前向后查询
return this.root.getNode(index); //查询过程交给Node类
}
public boolean contains(City data){
//现在没有要查询的数据,根节点也不保存数据
if(data==null ||this.root==null){
return false; //没有查询结果
}
return this.root.containsNode(data);
}
public void set(int index,City data){
if (index>this.count){
return; //结束方法调用
}
this.foot=0; //重新设置foot属性的内容,作为索引出现
this.root.setNode(index,data);//交给Node类设置数据内容
}
public City[] toArray(){
if (this.root==null) {
return null;
}
this.foot=0; //需要脚标控制
this.retArray=new City[this.count]; //根据保存内容开辟数组
this.root.toArrayNode(); //交给Node类处理
return this.retArray;
}
public void remove((City data){
if (this.contains(data)) {//判断数据是否存在
//要删除数据是否是根节点数据
//root是Node类对象,此处直接访问了内部类的私有操作
if (data.compare(this.root.data)) {//判断数据是否存在
this.root=this.root.next; //空出当前根节点
}else{ //不是根元素
//此时根元素已经判断过了,从第二个元素开始判断
this.root.next.removeNode(this.root,data);
}
this.count--; //个数要减少
}
}
public class lianbiao8 {
public static void main(String args[]){
//第一步:设置关系数据
//1.先准备好给自独立的对象
Province pro =new Province(1,"河北省");
City c1=new City(1001,"唐山");
City c2=new City(1002,"秦皇岛");
City c3=new City(1003,"石家庄");
//2.设置关系
c1.setProvince(pro); //一个城市属于一个省份
c2.setProvince(pro);
c3.setProvince(pro);
pro.getCities().add(c1);
pro.getCities().add(c2);
pro.getCities().add(c3);
//第二步:取出关系
System.out.println(pro.getInfo());
System.out.println("拥有的城市数量:"+pro.getCities().size());
pro.getCities().remove(c1);
City c[]=pro.getCities().toArray();
for (int x=0;x<c.length ;x++ ) {
System.out.println(c[x].getInfo());
}
}
}
链表是如何实现LRU缓冲淘汰算法呢?
1. 如果此数据之前已经被缓存在链表中了,我们遍历得到这个数据对应的结点,并将其从原来的位置删除,然后再插入到链表的头部。
2. 如果此数据没有在缓存链表中,又可以分为两种情况:
如果此时缓存未满,则将此结点直接插入到链表的头部;
如果此时缓存已满,则链表尾结点删除,将新的数据结点插入链表的头部。
缓存的大小有限,当缓冲被用满时,哪些数据应该被清理出去,哪些数据应该被保留?
这就需要缓存淘汰策略来决定。常见有三种:
- 先进先出策略FIFO(First In , First Out)
- 最少使用策略LFU(Least Frequently Used)
- 最近最少使用策略LRU(Least Recently Used)
相对于数组,如果我们申请一个数组时,当内存中没有连续的、足够大的存储空间时,即使内存的剩余总可用空间大于申请的空间值,仍然会申请失败。
而链表则相反,它并不需要一块连续的内存空间,它通过“指针”将一组零散的内存块串联起来使用,所以如果我们申请的是 100MB 大小的链表,根本不会有问题。
下面将介绍常见的链表结构:单链表、双向链表、循环链表
单链表:
链表通过指针将一组零散的内存块串联在一起。内存块称为链表的“结点”。为了将所有的结点串起来。每个链表的结点除了存储数据之外,还需要记录链上的下一个结点的地址。我们把记录下一个结点地址的指针叫做后继指针next。
其实链表有两个结点是比较特殊的,分别是第一个结点与最后一个结点。或者称头结点与尾结点。其中,头结点用来记录链表的基地址。有了基地址,就可以遍历得到整条链表。对于尾结点:指针不是指向下一个结点,而是指向一个空地址NULL,表示这是链表上最后一个结点。
与数组一样,链表也支持数据的查找、插入、和删除操作。在进行插入、删除操作时,为了保持内存数据的连续性,需要做大量的数据搬移,所以时间复杂度是O(n)。而在链表中插入或者删除一个数据,我们并不需要为了保持内存的连续性而搬移结点,因为链表的存储空间本身就不是连续的。所以,在链表中插入和删除一个数据是非常快的,时间复杂度为O(1)。
但是,如果要随机访问第几个元素时,需要根据指针一个结点一个结点地依次遍历,直到找到相应的结点。所以时间复杂度为O(n)。
循环链表:
循环链表与单链表的区别在于尾结点。单链表尾结点指向空地址,表示这是最后的结点,而循环链表的尾结点指针是指向链表的头结点。首尾相连。
双向链表:
双向链表需要额外的两个空间来存储后继结点和前驱结点的地址。所以存储同样多的数据时,双向链表要比单链表占用更多的内存空间。双向链表支持O(1)时间复杂度的情况下找到前驱结点,所以双向链表的插入与删除操作要比单链表简单高效。
首先看删除操作,删除操作有两种情况:
1.删除结点中“值等于某个给定值”的结点;
2.删除给定指针指向的结点。
对于第一种情况,不管单链表还是双向链表,为了查找到值等于给定值的结点,都需要从头结点开始依次遍历,直到找到为止。所以时间复杂度为O(n)。
对于第二种情况,我们要找到了要删除的结点,但是删除某个结点q需要知道其前驱结点,而单链表并不支持直接获取前驱结点,所以,为了找到前驱结点,我们还是需要从头开始遍历链表,直到p->next=q,说明p是q的前驱结点。
但是对于双向链表来说,这种情况就比较有优势了。因为双向链表中的结点已经保存了前驱结点的指针,不需要像单链表那样遍历。所以,针对第二种情况,单链表删除操作需要 O(n) 的时间复杂度,而双向链表只需要在 O(1) 的时间复杂度内就搞定了!
同理,插入也一样。
除了插入、删除操作有优势之外,对于一个有序链表,双向链表的按值查询的效率也要比单链表高一些。因为,我们可以记录上次查找的位置 p,每次查询时,根据要查找的值与 p 的大小关系,决定是往前还是往后查找,所以平均只需要查找一半的数据。
如果你深入研究 LinkedHashMap 的实现原理,就会发现其中就用到了双向链表这种数据结构。
实际上,这里有一个更加重要的知识点需要你掌握,那就是用空间换时间的设计思想。当内存空间充足的时候,如果我们更加追求代码的执行速度,我们就可以选择空间复杂度相对较高、但时间复杂度相对很低的算法或者数据结构。相反,如果内存比较紧缺,比如代码跑在手机或者单片机上,这个时候,就要反过来用时间换空间的设计思路。
缓存实际上就是利用了空间换时间的设计思想。如果我们把数据存储在硬盘上,会比较节省内存,但每次查找数据都要询问一次硬盘,会比较慢。但如果我们通过缓存技术,事先将数据加载在内存中,虽然会比较耗费内存空间,但是每次数据查询的速度就大大提高了。