我的伟大航路(4)

今天是双链表

1、链表节点

先上代码

public class ListNode {
    Object data;//数据域
    ListNode next;//后继指针域
    ListNode pre;//前驱指针域

    public ListNode(Object data) {
        this.data = data;
    }//构造器
}

节点比起单链表来说加了前驱的指针域


2、双链表实现的类

双链表,我对于它的理解我认为它应该叫做双向链表,当然也有可能是为了简洁而叫双链表,从字面上很容易让人以为是两条链表,之所以我认为叫它双向链表,是因为,这就是单链表的反向链接,即在双链表中间的节点除了保存自己的数据域下一个节点的指针域,还保存了前一个节点的指针域,我对于双链表出现的理解是,为了补足单链表无法获得前驱节点的问题,有了双指针域的节点,可以从任何一个节点起步,到达任何另一个节点。

/*
哑元:我所理解的哑元是指可以提供给其他方法使用的“空壳”,也就是没有数据的节点
 */
public class DoubleLinkList implements MyList {
    ListNode first = new ListNode(null);//虽然是创建的节点实体,但是可以理解为头指针
    ListNode last = new ListNode(null);//同上可以理解为尾指针
    int size;//链表长度

    public DoubleLinkList() {//该构造器是为了将链表首尾链接,初始中的链表中没有元素,但有基本的头尾节点,故有头的后继指向尾,尾的前驱指向头
        first.next = last;
        last.pre = first;
    }

    /*
    添加节点需要维护4条指向
    设有一个双向链表是   ac   需要添加b到中间
    1、修改a的后继为b
    2、b的后继指向c
    3、b的前驱指向a
    4、c的前驱指向b
     */
    @Override
    public void add(Object element) {//添加节点
        ListNode newNode = new ListNode(element);//创建新的节点
        last.pre.next = newNode;//将尾节点(last)的前驱(last.pre)的后继指针(last.pre.next)改为指向新节点   ps:(原本尾节点的前驱指针是指向尾节点的)
        newNode.next = last;//新节点的后继指针指向尾节点
        newNode.pre = last.pre;//新节点的前驱指针(newNode.pre)指向尾节点之前指向的前驱(last.pre)  ps:(现在尾节点的前驱还没有改变)
        last.pre = newNode;//最后将尾节点的前驱指向新节点
        size++;//长度加一
    }

    /*原理:假设链表为   a b c d e  我们想要删除p节点,我们先创建了一个指向头节点的指针p,并一直往下指,当
     *p指向了c的时候,现在p就相当于c,对于p的操作就相当于对于c的操作
     *为了方便理解这里用c继续说
     * 将c的前驱(b)的后继指针(b.next)转而指向c的后继(d),这里从头来的链表链接成功,即b.next=d.
     * 将c的后继(d)的前驱指针(d.pre)转而指向c的前驱(b),这里从尾来的链表链接成功,即d.pre=b.
     * 这样链表成为了  a b d e
     */
    @Override
    public void deleteByElement(Object element) {//根据元素删除节点
        ListNode p = first.next;//创建新节点p使其指向头节点的后继,即第一个有数据的节点
        while (p != last) {//当p不是尾节点
            if (p.data.equals(element)) {//当p的数据与给定参数相等时表面找到了要删除的节点
                p.pre.next = p.next;//p的前驱(p.pre)的后继指针(p.pre.next)指向了p的后继指针(p.next)
                p.next.pre = p.pre;//p的后继(p.next)的前驱指针(p.pre)指向了p的前驱
                p.next = null;//架空删除的p节点,使其可以快速回收
                p.pre = null;
                size--;
                break;
            }
            p = p.next;
        }
    }

    @Override
    public void deleteByIndex(int index) {
        if (index < 0 || index >= size) {//限制index的边界,提高效率
            return;
        }
        int i = 0;//设置计数器
        ListNode p = first.next;//创建新节点p使其指向头节点的后继,即第一个有数据的节点
        while (p != last) {
            if (i == index) {//当计数器与给定的index相当时表面找到了要删除的节点
                p.pre.next = p.next;
                p.next.pre = p.pre;
                p.pre = null;
                p.next = null;
                size--;
                break;
            }
            p = p.next;
            i++;
        }
    }

    /*
     *更新要简单多了,只需要设置计数器找到给定参数index与计数器相等的节点,改变现在p指针指向的节点的数据域即可
     */
    @Override
    public void update(int index, Object newElement) {
        if (index < 0 || index >= size) {//限制index的边界,提高效率
            return;
        }
        int i = 0;
        ListNode p = first.next;
        while (p != last) {
            if (i == index) {
                p.data = newElement;
                break;
            }
            p = p.next;
            i++;
        }
    }

    //找到即返回true,其他情况返回false
    @Override
    public boolean contatins(Object target) {
        ListNode p = first.next;
        while (p != last) {
            if (p.data.equals(target)) {
                return true;
            }
            p = p.next;
        }
        return false;
    }

    /*
    查找的本质就是添加一个计数器,当计数器(i)与给的的查找(index)相同时,返回该元素
     */
    @Override
    public Object at(int index) {
        if (index < 0 || index >= size) {
            return null;
        }
        int i = 0;
        ListNode p = first.next;
        while (p != last) {
            if (i == index) {
                return p.data;
            }
            p = p.next;
            i++;
        }
        return null;
    }

    @Override
    public int indexOf(Object element) {
        ListNode p = first.next;
        int i = 0;
        while (p != last) {
            if (p.data.equals(element)) {
                return i;
            }
            p = p.next;
            i++;
        }
        return -1;
    }

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder("[");//构建一个StringBuilder,先加上“[”
        ListNode p = first.next;//p指针指向了头节点
        while (p != last) {//当p指向的节点不空
            sb.append(p.data);//将p指向的节点的数据域添加到sb(StringBuilder)
            if (p.next != last)//若p指向的节点不空
                sb.append(",");//则添加一个“,”到sb
            p = p.next;//p指向下一个节点
        }
        sb.append("]");//p全部走完链表以后,加上“]”
        return sb.toString();//StringBuilder构建完成,使用toString转换成字符串
    }

    private ListNode now = first;


    /*
     *下面两个方法是因为这个类实现的接口MyList继承了Iterator(迭代器)的接口,而重写的方法
     * 迭代器:我理解的迭代器是一个个的“查看链表”的东西
     */
    @Override
    public boolean hasNext() {
        return now.next != last;
    }//判定某个节点有无下一个节点

    @Override
    public Object next() {//返回节点的数据
        ListNode next = now.next;
        now = now.next;
        return next.data;
    }
}

老样子我在相关的地方加了详细的注释,而且全是我的理解


3、测试类

下面是我所用的测试类,问题不大

public class DoubleLinkListTest {
    DoubleLinkList list = new DoubleLinkList();

    @Test
    public void add() {
        list.add("a");
        list.add("b");
        list.add("c");
        list.add("d");
        list.add("e");//添加元素
//        System.out.println(list);//输出元素
    }

    @Test
    public void deleteByElement() {
        add();
        list.deleteByElement("b");
        System.out.println(list);
        list.deleteByElement("a");
        System.out.println(list);
    }

    @Test
    public void deleteByIndex() {
        add();
        list.deleteByIndex(2);
        System.out.println(list);
        list.deleteByIndex(2);
        System.out.println(list);
        list.deleteByIndex(0);
        System.out.println(list);
    }

    @Test
    public void update() {
        add();
        System.out.println(list);
        list.update(2, "x");
        System.out.println(list);
        list.update(0, "第一个");
        System.out.println(list);
    }

    @Test
    public void contatins() {
        add();
        boolean a = list.contatins("a");
        System.out.println(a);
    }

    @Test
    public void at() {
        add();
        System.out.println(list.at(0));
        System.out.println(list.at(3));
    }

    @Test
    public void indexOf() {
        add();
        System.out.println(list.indexOf("b"));
        System.out.println(list.indexOf("a"));
        System.out.println(list.indexOf(1));
    }
    @Test
    public void iter(){
        add();
        while (list.hasNext()){
            System.out.println(list.next());
        }
    }
}

4、总结

今天了解了双链表,其实我第一次听到双链表,以为是两个链表在某个节点相交,但是仔细一想这样的话就不具有普遍性了,直达我又看见了“桶”


今天背疼,感觉像有东西在戳我背,对于高数有了新的理解,特别是随着算法的逐渐实现,越来越明白数学的博大精深,因为数学太过厉害,以至于不能很好的找到它的用处,但是计算机出现了,计算机是数学表述自己的一个非常重要的工具。
算法是程序的灵魂,这句话一点也没错


花开如火,也如寂寞。
——顾城

发布了13 篇原创文章 · 获赞 8 · 访问量 1119

猜你喜欢

转载自blog.csdn.net/qq_45000228/article/details/103950028