实现一个功能齐全的链表

首先说明什么是链表,直接给张图


image.png

看的懂吧,看懂我们就开始写了。
等下,我们为什么要写链表,也就是为什么要有这种数据结构,它的功能是什么?
很简单啦,因为数组的插入和删除太耗时间了,我们需要个数据结构解决这个问题。什么?你问我为什么数组的插入和删除耗时间?emmm,自己想想吧。。。

先在eclispe中新建一个class(废话),我们命名为MyprototypeLinkedList,
prototype意思是原型,模板,我写的这个链表功能上参照jdk源码中的LinkedList,功能上来说应该是比较齐全的了,就取个这个名字。
开始写代码..

停!闷头写代码是程序员最忌讳的东西,在代码前应该认真分析需求,设计好整个流程,再来写。我们先来看LinkedList中有什么api可以调用。

image.png

这。。好像有点多,不过我们能看到主要的一些api,增删改查嘛,add,set,remove,update,再来几个常用的clear,size,isEmpty,行了,差不多可以了。我们的链表就做这些api的需求。

明确了需求,接下来写就完事了。
首先给类明确几个属性,Node head,Node end,int size;
这是什么?首先我需要确定链表的头尾,所以在属性上就先声明了。size是链表目前存储值得数量。
等一下,Node是什么?
Node顾名思义,就是节点啦,还没写呢,我们用个私有静态内部类来解决
,Node存储得数据我希望不定死,所以使用泛型T,Node中得属性分别为
public T data,(结点中得数据),public Node<T> next;(指向后置结点)
public Node<T> prev;(指向前置结点)
ps:当然用双向链表啦,单向的太难用了,不适合。
现在 我们的代码大概是这个亚子

package 链表;
public class MyprototypeLinkedList<T> {
    private Node<T> head;
    private Node<T> end;
    private int size;
    private static class Node<T>{
        public T data;
        public Node<T> next;
        public Node<T> prev;
        public Node() {
            this(null,null,null);
        }
        public Node(T data, Node<T> next, Node<T> prev) {
            super();
            this.data = data;
            this.next = next;
            this.prev = prev;
        }       
    }
}

如果你现在把我的程序复制上去,eclipse上会报好多警告,(强迫症福利),其实是因为我们的属性以及Node,都设成private的了,eclipse看到我们写了这么多私有,却都没调用,就认为是废代码,提醒我们删除。
行吧,接下来我们就用起来吧。

首先是类构造方法吧,要初始化3个属性。本来准备这样写

public MyprototypeLinkedList() {
    }
    public MyprototypeLinkedList(Node<T> head, Node<T> end, int size) {
        super();
        this.head = head;
        this.end = end;
        this.size = size;
    }

但是啊,初始化类属性完全可以抽出来嘛,因为clear方法也是相当于重新执行一遍以上的构造。那么我们可以这样写:

public MyprototypeLinkedList() {
             doClear();
    }
    public void clear() {
             doClear();
    }

    private void doClear() {
        head=new Node<T>();
        end=new Node<T>(null,null,head);
        head.next=end;
        size++;
    }

是不是看起来好多了。一个私有的doClear方法,解决了初始化属性和clear功能,这就是代码的复用,或者说封装。
经过初始化,现在结点的关系大概是这样的

image.png

ok,接下来要干啥,当然是增删改查了。先写最复杂的增,首先增应该给两种方法,一种默认的add(T x),直接将新结点加在链表的尾部。一种重载的add(int idx,T x),指定在链表的哪个位置增加结点。那么其实所谓的默认add就是add(size,T x),所以我们可以这样写,顺变把size方法也给写了。

public void add(T x) {
        add(size(),x);
    }
public void add(int idx,T x) {
        
    }
public int size() {
        return size;
    }

接下来就是怎么完成这个add方法了,我们知道,要想在链表中添加结点,那么我们需要在要添加的位置将前置结点的next指向新结点,新节点的prev指向前置结点,后置结点的prev指向新结点,新结点的next指向后置结点。

image.png

我们想在idx这个索引位置添加新结点,那么首先就要找到这个现在在idx位置的结点当作aim,然后新结点的prev连接aim的前置结点,新结点的next在连接aim。
明确了目标,那就写吧,首先我要一个方法能找到目前在idx位置的结点。

等等!我们要知道,程序员是很蠢的,他很可能给的idx是一个负数,还有可能给出一个大于size的数。要是这样那我写的链表不就废了?我得搞个异常提醒下他。
还有,既然是双向链表,那么我们可以优化下查询嘛,如果idx在前半部分,我们从head往后查,如果idx在后半部分,我们从end往前查。
终于,我们开始写了。

得到在idx索引的结点

private Node<T> getNode(int lower,int idx,int upper){
        Node<T> p;
        if(idx<lower||idx>upper) {
            throw new IndexOutOfBoundsException();
        }
        if(idx<size()/2) {
            p=head.next;
            for(int i=0;i<idx;i++) {
                p=p.next;
            }
        }
        else {
            p=end.prev;
            for(int i=size();i>idx;i--) {
                p=p.prev;
            }
        }
        return p;
    }

大功告成,我们在来写一个addBefore方法,来实现在idx索引下结点前的结点插入。它需要的参数很显然了,就是我们的aim结点,和T x

private void addBefore(Node<T> node, T x) {
        Node<T> newNode=new Node<T>(x,node,node.prev);
        node.prev.next=newNode;
        node.prev=newNode;
                size++;
    }

最后我们让我们的add方法调用下addBefore即可。

public void add(int idx,T x) {
        addBefore(getNode(0,idx,size()),x);
    }

完成!!

四分之一。我们还要写删改查啊。接着干。
接下来写查。我们知道,查,只要找到那个结点,直接Node.data不就完事了嘛,我们还知道,我们刚刚就写过一个根据索引找到结点的方法!
问题是不是很简单了

public T get(int idx) {
        return getNode(idx).data;
    }   
    private Node<T> getNode(int idx){
        return getNode(0, idx, size());
    }

过瘾吧,接下来写删。我们知道,删的话,同样要定位到目标结点,是不是我们又写过了!
等下,除了定位,我们还要做的是,把它的前置结点和后置结点连起来即可,至于要删的,谁管它,java垃圾回收比c++好用。当然为了告诉程序员真的删掉了,我们将删除结点的data域取出来,给调用的程序员留个纪念。

image.png
public T remove(int idx) {
        return remove(getNode(idx));
    }
    private T remove(Node<T> node) {
        node.prev.next=node.next;
        node.next.prev=node.prev;
        size--;
        return node.data;
    }

最后写个改,update嘛,我们能找到索引的aim结点(不想强调了,所以说封装很重要啊),改下data域即可。当然为了告诉笨笨的程序员真的改掉了,我们将被改掉的data送给他。
直接上代码吧。

public T update(int idx,T x) {
        return update(getNode(idx),x);
    }
    private T update(Node<T> node,T x) {
        T old=node.data;
        node.data=x;
        return old;
    }

写完了,好像少了一个isEmpty,给出来吧

public boolean isEmpty() {
        return size()==0;
    }

最后给整体代码吧

package 链表;
public class MyprototypeLinkedList<T> {
    private Node<T> head;
    private Node<T> end;
    private int size;
    private static class Node<T>{
        public T data;
        public Node<T> next;
        public Node<T> prev;
        public Node() {
            this(null,null,null);
        }
        public Node(T data, Node<T> next, Node<T> prev) {
            super();
            this.data = data;
            this.next = next;
            this.prev = prev;
        }       
    }
    
    public MyprototypeLinkedList() {
             doClear();
    }
    public void clear() {
             doClear();
    }

    private void doClear() {
        head=new Node<T>();
        end=new Node<T>(null,null,head);
        head.next=end;
        size++;
    }
    public int size() {
        return size;
    }
    public boolean isEmpty() {
        return size()==0;
    }
    public void add(T x) {
        add(size(),x);
    }
    public void add(int idx,T x) {
        addBefore(getNode(0,idx,size()),x);
    }
    public T get(int idx) {
        return getNode(idx).data;
    }
    public T remove(int idx) {
        return remove(getNode(idx));
    }
    public T update(int idx,T x) {
        return update(getNode(idx),x);
    }
    private T update(Node<T> node,T x) {
        T old=node.data;
        node.data=x;
        return old;
    }
    private T remove(Node<T> node) {
        node.prev.next=node.next;
        node.next.prev=node.prev;
        size--;
        return node.data;
    }
    
    private Node<T> getNode(int idx){
        return getNode(0, idx, size());
    }
    private Node<T> getNode(int lower,int idx,int upper){
        Node<T> p;
        if(idx<lower||idx>upper) {
            throw new IndexOutOfBoundsException();
        }
        if(idx<size()/2) {
            p=head.next;
            for(int i=0;i<idx;i++) {
                p=p.next;
            }
        }
        else {
            p=end.prev;
            for(int i=size();i>idx;i--) {
                p=p.prev;
            }
        }
        return p;
    }
    private void addBefore(Node<T> node, T x) {
        Node<T> newNode=new Node<T>(x,node,node.prev);
        node.prev.next=newNode;
        node.prev=newNode;
        size++;
    }
}
image.png
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,142评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,298评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,068评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,081评论 1 291
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,099评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,071评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,990评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,832评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,274评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,488评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,649评论 1 347
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,378评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,979评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,625评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,796评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,643评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,545评论 2 352

推荐阅读更多精彩内容