数据结构之--链表(Java代码)

以数组作为存储结构,在无序的情况当中,查找关键字效率十分底下。而在有序的情况下,插入关键字也变得效率底下。

而且不管在哪种情况下,数组的删除效率也是很低的。当一个数组创建之后,它的长度是固定的。


而链表,可以解决上面的一些问题。


一.链结点(Link)

在链表中,每个数据项都被包含在“链结点”(Link)中。而链表则是将这些结点给“串联”起来。

一个链结点 可能是某个类的对象,例如这个类叫做Link,因为一个链表中有许多类似的结点,所有很有必要用一个不同于链表的类来表达链节点。

每个Link对象中都包含一个对下一个链结点“引用”的字段(通常叫做next),但是链表 本身的对象中有一个字段指向对一个链结点的引用。



二.单链表

创建单链表的思路:

1.先创建一个Link类,这个类里面包含着链结点的所包含的信息(Data),以及指向下一个链结点的引用(next)。

2.再创建一个LinkList类,对Link类进行插入删除等操作。

3.创建单链表有“头插法” 和 “尾插法” 这两种方法。


  2.1 创建Link类

public class Link {

    public int iData;
    public double dData;
    public Link next;


    public Link( int iData , double dData ){
        this.iData = iData ;
        this.dData = dData ;
    }

    //显示当前链表的信息:
    public void displayLink(){
        System.out.print("["+"iData:"+iData + "," + "dData:"+dData+"]" + " ");
    }
}


  2.2 创建LinkList类(头插法和尾插法)

package Link;

/**
 * Created by Hubbert on 2017/11/16.
 */
public class LinkList {
    public Link first;
    Link p = first;
    public LinkList(){
        first = null;
    }

    //1.头插法
    //逆序,先插先排在前面
    public void insertFirst(int i , double d){
        Link newLink = new Link(i,d); //1.先创建一个链结点
        newLink.next = first;        //2.新的链结点指向first所指的下一个结点
        first = newLink ;             //3.然后first再指向newLink这个新的链结点
    }

    //2.尾插法
    //顺序,后插排在后面
    public void insertLast( int i , double d ){
        Link newLink = new Link(i,d);
        Link p = first;
        //如果插入的时候first结点为空,则first结点要=newLink
        if(first == null){
            newLink.next = first;
            first = newLink;
        } else {
            //要使p一直是最后一个结点
            while( p.next != null){
                p = p.next;
            }
            newLink.next = null;
            p.next = newLink;

        }

    }
   //显示链表的每一个结点
    public void displayLink(){
        Link current = first;
        while(current != null){
            current.displayLink();
            current = current.next;
        }
    }
}


  2.2.1测试代码:头插法  

public static void main(String [] args){
        LinkList linkList = new LinkList();
        linkList.insertFirst(11,1.1);
        linkList.insertFirst(22,2.2);
        linkList.insertFirst(33,3.3);
        linkList.insertFirst(55,5.5);
        linkList.insertFirst(66,6.6);
        linkList.displayLink();
    }


 2.2.2结果如下:逆序输出


 


    2.2.2测试代码:尾插法

public static void main(String [] args){
        LinkList linkList = new LinkList();
        linkList.insertLast(11,1.1);
        linkList.insertLast(22,2.2);
        linkList.insertLast(33,3.3);
        linkList.insertLast(55,5.5);
        linkList.insertLast(66,6.6);
        linkList.displayLink();
    }


    2.2.2结果如下:顺序输出


 


   2.3删除结点方法:

 deleteFirst()方法是inserFirst的逆操作。

 它通过把first重新指向了第二个链结点,断开了和第一个链结点的连接。通过查看第一个链结点的next可以找到第二个链结点:

 

public Link deletFirst(){
        Link temp = first;
        first = first.next;
        return first;
    }

在C++和类似的语言中,从链表中取下一个链结点后,需要考虑如何删除掉这个结点。它仍然存在内存中的某个地方,但是现在没有任何东西指向它。将要如何去处理它呢,

在Java语言中,垃圾收集进程将会在未来的某个时刻销毁它,现在这个还不是我们担心的工作。


2.5 删除指定的结点 和 查找指定的结点

 public Link deletFirst(){
        Link temp = first;
        first = first.next;
        return first;
    }
    public Link findKey( int key ){
        Link current = first ;
        while( current.iData != key ){
            if(current.next == null){
                return null;
            }else {
                current = current.next;
            }
        }
        return current;
    }


    public Link deleteKey( int key ){
        Link current = first;
        Link pre = first ;
        while ( current.iData != key ){
             if(current.next == null ){
                 return null;
             }else {
                 pre = current; //保留当前的结点
                 current = current.next; //继续指向下一个结点,如果下一个结点是待删除结点,
                                        // pre则保留了当前这个结点,所以删除掉下一个结点的操作就是下面所示
             }
        }
        if(current == first){ //如果第一个待删除的结点是first结点
            first = first.next;
        }else{
            pre.next = current.next; //删除当前的结点
        }
        return current;
    }



题目:如何判断链表的长度?

public static void main(String [] args){
        LinkList linkList = new LinkList();
        //随机生成要插入的链结点的个数[1...10]
        int random = (int)(Math.random() * 10);
        for(int i = 0 ; i < random ; i++ ){
            int random2 = (int)(Math.random() * 100);
            linkList.insertFirst(random2,random2);
        }
        linkList.displayLink();
        int count = 0;
        Link current = linkList.first;
        while( current!= null){
            count++;
            current = current.next;
        }
        System.out.println("");
        System.out.print("链表长度为:"+count);
    }
结果如图所示:



二.双端链表

双端链表跟传统的链表相似,但是它有一个特性:即增加对“最后一个链结点的引用”,就像对第一个链结点引用一样。

1.多了一个在尾部添加链表的方法。跟尾插法的性质一样。

2.在头插法的时候,需要先判断链表是否为空,如果为空,则第一个插入的链结点则被last所标记。

package Link;

/**
 * Created by Hubbert on 2017/11/18.
 */
public class LinkFirstLastList {
    Link first;
    Link last;
    public LinkFirstLastList(){
        first = null;
        last = null;
    }

    public void inserFirst( int i , double d ){
        Link newLink = new Link(i,d);
        if(first == null){
            last = newLink; //当链表为空时,头插法的时候,第一个插入的链结点将作为last
        }
        newLink.next = first;
        first = newLink;
        
    }
    public void inserLast( int i , double d ){
        Link newLink = new Link(i,d);
        if( first == null ){
            newLink.next = first;
            first = newLink;
            last = newLink;
        } else {
            last.next = newLink;
            last = newLink;
        }
    }

    public void dispalyLink(){
        Link current = first;
        while( current != null){
            current.displayLink();
            current = current.next;
        }
    }
}


那么链表的效率如何呢?

链表在插入和删除的时候仅仅改变一两个引用值,只花费O(1)的时间。

虽然在查找,删除指定的结点需要搜索链表一半链结点,需要O(N)次比较。在数组执行这些操作也是如此。

但是链表仍然要快一些,因为当插入和删除的链结点的时候,链表不需要做任何的移动。

当然了,链表比数组优越的一个优点是,不用像数组那样定长,想用多少内存就用多少内存。


三.双向链表

双向链表(不能跟双端链表产生混淆),在单链表中,只能从头结点开始一个一个往下遍历,却不能方向遍历。

用这样的一个语句

current = current.next;

却没有对应的方法回到前一个链结点。

而双向链表提供了这样的一个能力。即允许向前遍历,也允许向后遍历整个链表。

其中的不同是两个指向其他链表结点的引用,而不是一个next,增加多了一个pre(向前的意思)。



所以Link类的声明变成了这样:

package Link;

/**
 * Created by Hubbert on 2017/11/16.
 */
public class Link {

    public int iData;
    public double dData;
    public Link next;
    public Link previous;//新增加一个指向前的结点


    public Link( int iData , double dData ){
        this.iData = iData ;
        this.dData = dData ;
    }

    //显示当前链表的信息:
    public void displayLink(){
        System.out.print("["+"iData:"+iData + "," + "dData:"+dData+"]" + " ");
    }
}


当然双向链表也是有一定的缺点的,在插入和删除的时候,相对于单链表的两个引用来说,它需要改变四个引用。

多了两个引用,存储的空间也会变大。


DoublyLinkList类(向前向后遍历,查找,插入,删除)

package Link;

/**
 * Created by Hubbert on 2017/11/18.
 */
public class DoublyLinkList {
    public Link first;
    public Link last;

    public DoublyLinkList(){
        first = null;
        last = null;
    }

    //头插法
    public void inserDoublyFirst(int i , double d){
        Link newLink = new Link(i,d);
        if(first == null){
            last = newLink;
        }else {
            first.previous = newLink;
        }
        newLink.next = first;
        first = newLink;
    }
    //尾插法
    public void inserDoublyLast(int i , double d){
        Link newLink = new Link(i,d);
        if(first == null){
            first = newLink;
        }else{
            last.next = newLink;
            newLink.previous = last;
        }
        last = newLink;
    }

    //插入一个链结点newLink(i,d),在关键字key之后
    public boolean insertAfter(int i, double d , int key){
        Link newLink = new Link(i,d);
        Link current = first;
        //获取链结点能够与 key值相关
        while(current.iData != key){
            if(current.next == null){
                return false;
            } else {
                current = current.next;
            }
        }
        if(current.next == null){
            newLink.next = null;
            last = newLink;
        } else {
            newLink.next = current.next;
            current.next.previous = newLink;
        }
        //上面if else 中共同的部分可以提取出来.例如下面的;
        newLink.previous = current;
        current.next = newLink;
        return true;
    }

    //删除关键字链表
    public Link deleteKey(int key){
        Link current = first;
        while(current.iData != key){
            current = current.next;
            if(current == null){
                return null;
            }
        }
        if(current == first){
            first = current.next;
        } else if (current == last){
            last = current.previous;
            current.previous.next = null;
        } else {
            current.previous.next = current.next;
            current.next.previous = current.previous;
        }
        return current;
    }

    //从头结点开始向后遍历
    public void displayForward(){
        Link current = first;
        while (current != null){
            current.displayLink();;
            current = current.next;
        }
    }

    //从尾结点开始向前遍历
    public void displayBackward(){
        Link current = last;
        while( current != null ){
            current.displayLink();
            current = current.previous;
        }
    }
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值