数据结构与算法_链表2_双向链表

双向链表结点和单向链表结点的区别在于,多了一个指向前驱的指针。当这个节点是第一个节点时,前驱指针是一个空指针。

单向链表的结构如下图1所示:


图1 单向链表结构图


双向链表的结构如下图2所示:


图2 双向链表结构图

显然通过以上2个图的对比,可以看出链表两种形式的特点。双向链表显然我们在创建的时候需要永久维护的是first和last信息,要是链表不为空,first指向的是初始的链结点,last指向的是表尾的链结点(当只有一个链结点的时候,first和last指向同一个);初始链结点的prev指向null,链尾的链结点next指向null。

对双向链表的基本操作有插入、删除和非空,其中插入有insertFirst()即在首位插入新的链结点、insertLast()即在末尾插入新的链结点、insertAfter()即在某一元素值后插入新的链结点;同理删除有deleteFirst()、deleteLast()和deleteKey()。这几个插入和删除方法,单纯的靠想象是很难的,在敲写代码的时候我们应该结合图,看着图示来写逻辑,要不然绕来绕去一会儿就写懵了。

首先链结点Link应有的属性如下代码所示:

/**
 * 链结点
 */

public class Link {
    public int data;
    public Link next;
    public Link previous;

    public Link(int data) {
        this.data = data;
    }

}

对DoubleLinkList初始时有:

/**
 * 双向列表
 */

public class DoubleLinkedList {

    public Link first = null;
    public Link last = null;

    /**
     * 插入一个链结点
     *
     * @param data
     */
    public void insertFirst(int data) { //伪代码 }

    /**
     *从队尾插入链结点
     *
     * @param data
     */
    public void insertLast(int data) { //伪代码 }

   //等等其他对双向链表的操作方法
}

首先是非空判断,显然当first==null时说明链表中没有链结点,代码:

    /**
     * 链表是否为空
     *
     * @return
     */
    public boolean isEmpty() {

        return first == null;
    }

2.insertFirst()从首位插入新的链结点,先画图,图示如下:


图3 表头插入

根据图示敲代码逻辑,具体如下:

  /**
     * 插入一个链结点
     *
     * @param data
     */
    public void insertFirst(int data) {
        Link newLink = new Link(data); //根据数值创建结点

        if (isEmpty()) {
            last = newLink;
        } else {
            first.previous = newLink;//如果不为空的话 一开始first指向的初始结点的prev字段指向新的newlink 从这里我们可以看出first只是一个引用
            newLink.next = first;
        }

        first = newLink;
    }

3.insertLast()从表尾插入新的链结点,逻辑和表头插入比较相似,代码如下:

    /**
     * 从队尾插入链结点
     *
     * @param data
     */
    public void insertLast(int data) {
        Link newLink = new Link(data);
        if (isEmpty()) {
            first = newLink;
        } else {
            last.next = newLink;
            newLink.previous = last;
        }

        last = newLink;
    }

4.deleteFirst()删除操作,删除操作就是从双向链表中拿出第一个结点,返回值类型为Link:

    /**
     * 删除第一个结点
     *
     * @return
     */
    public Link deleteFirst() {

        if (!isEmpty()) { //首先判断链表是否为空 

            Link current = first;
            if (current.next == null) {
                //如果整个链表中只有一个元素 那就是最后一个 直接让last指向null 就可以了
                last = null;
            } else {
                //当前结点的下一个结点的previous指向是null 表示着下一个结点是第一个链结点
                current.next.previous = null;
            }

            first = current.next;//无论是只有一个结点(只有一个的话first =null) 多个结点当前要删除的结点的下一个都是为第一个
            return current;

        } else {
            System.out.println("the doubleLinkList is null , cannot do delete");
            return null;
        }

    }

5.deleteLast()删除表尾结点,返回值类型也是Link:

    /**
     * 删除最后一个结点
     *
     * @return
     */
    public Link deleteLast() {

        if (!isEmpty()) {

            Link current = last;
            if (current.previous == null) {	//满足条件 说明链表中只有一个结点 直接让first指向null
                first = null;
            } else {
                current.previous.next = null;//末尾结点的前一结点的next指向null
            }

            last = current.previous;
            return current;
        } else {
            System.out.println("the doubleLinkList is null , cannot do delete");
            return null;
        }

    }
以上操作相对简单,依然是敲代码的时候画图理解。

6.insertAfter(int  key,int  data)在某一要素结点后插入新的结点,要处理的东西比较多,图4中有比较好的解释:


图4  某结点current后插入新的结点

首先我们要找到要出入的位置,之后处理前后结点的问题,代码如下:

    /**
     * 在指定结点后面插入新的data元素结点
     *
     * @param key  根据key找到指定结点
     * @param data 要插入的新结点
     */
    public String insertAfter(int key, int data) {

        Link newLink = new Link(data);

        //1.先找到key对应的位置
        Link current = first;
        while (current != null && current.data != key) {
            current = current.next;

            if (current == null) {
                //说明走到了链表的最后一个结点 依然没有对应的结点 就输出没有此节点
                return "cannot find the key,insert failed";
            }
        }

        if (current == last) {
            //当前链表的最后一项
            newLink.next = null;
            last = newLink;
        } else {

            //2.要操作4个节点 最好是自己画图体会整个过程
            newLink.next = current.next;
            current.next.previous = newLink;
        }


        newLink.previous = current;
        current.next = newLink;

        return "insert succeed";
    }
我设定的返回值类型是string,根据链表的情况返回不同的处理信息。


7.deleteKey(int  key)根据key值找到对应的结点,然后处理删除结点之后的前后结点关系:

    /**
     * 根据关键元素值 删除对应的链结点
     *
     * @param key
     * @return
     */
    public Link deleteKey(int key) {

        //1.先找到对应的链结点 从头开始遍历找对应的值 这样的查找是O(N)的  肯定会有更高效的
        Link current = first;
        while (current != null && current.data != key) {
            current = current.next;
            if (current == null) {
                //说明遍历到了链表的尾部 依然找不到对应的链结点
                return null;
            }
        }

        //2.说明找到key对应的链结点 current 删除之
        if (current == first) {
            //删除的是第一个链结点位置
            first = current.next;
            current.next.previous = null;

        } else if (current == last) {
            //删除的是最后一个链结点
            last = current.previous;
            current.previous.next = null;

        } else {
            current.next.previous = current.previous;
            current.previous.next = current.next;
        }

        //todo  以上代码可以优化成以下形式 可能稍微难理解一点 假设要删除的点是首位 current.previous就是null
//        if (current == first) {
//            //要是删除第一个链结点的话
//            first = current.next;
//        } else {
//            current.previous.next = current.next;
//        }
//
//        if (current == last) {
//            //要是删除最后一个链结点的话
//            last = current.previous;
//        } else {
//            current.next.previous = current.previous;
//        }

        return current;
    }


8.就是从头到尾遍历整个链表以及从尾到头遍历整个链表的写法:
 /**
     * 从头往后遍历整个链表
     */
    public void showAllFromFirst() {
        if (!isEmpty()) {
            Link current = first;
            while (current != null) {
                System.out.print(current.data + "\t");
                current = current.next;
            }
        } else {
            System.out.println("当前双向链表是空");
        }

        System.out.println("\n");

    }

    /**
     * 从尾到头遍历整个链表
     */
    public void showAllFromLast() {
        if (!isEmpty()) {
            Link current = last;
            while (current != null) {
                System.out.print(current.data + "\t");
                current = current.previous;
            }
        } else {
            System.out.println("当前双向链表是空");
        }
        System.out.println("\n");
    }


测试类:

/**
 * 双向链表测试
 */

public class DoubleLinkedListTest {
    public static void main(String[] args) {
        DoubleLinkedList doubleLinkedList = new DoubleLinkedList();

        doubleLinkedList.insertFirst(4);
        doubleLinkedList.insertFirst(2);
        doubleLinkedList.insertFirst(5);

        doubleLinkedList.insertLast(7);
        doubleLinkedList.insertLast(1);
        doubleLinkedList.insertLast(6);  //5 2 4 7 1 6

        doubleLinkedList.deleteFirst();       //2 4 7 1 6

        System.out.println("取出链表末尾的元素"+doubleLinkedList.deleteLast().data); //取出6后为2 4 7 1

        String insertAfteResult = doubleLinkedList.insertAfter(6, 9);//"cannot find the key,insert failed"
        System.out.println(insertAfteResult);

        doubleLinkedList.showAllFromFirst(); // 2 4 7 1

        System.out.println("删除后输出结果");
        doubleLinkedList.deleteKey(4);       // 2 7 1
        doubleLinkedList.showAllFromFirst();

        System.out.println("从后往前输出");  // 1 7 2
        doubleLinkedList.showAllFromLast();

    }
}

输出结果:



以上代码,如有问题请大家及时指出。关于单向链表请在此链接中查看数据结构之链表1_单链表及基本概念

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值