带头节点链表(java语法实现)

链表是一种非连续,非顺序的存储结构,由一系列的节点组成,节点的链接顺序实现了数据元素的逻辑顺序

相对于数组而言,在增删改操作上大大提高了效率(因为不会因为元素的位置变化引起一系列的变动),但同时又失去了随机查询的优点(因为必须进行遍历查询)

单向带头结点链表的结构如下图,用头结点指向第一个节点,每个结点都有两个值value(结点的值)和next(用于连接下一个结点),利用next将所有节点连接形成链

20181122125846659.png

接下来,进行链表的实现

创建一个节点的类Node,有value(int),next(Node),capacity(int)三个属性,和一个带参构造

class Node {
    int value;
    Node next;
    int capacity;

    public Node(int value) {
        this.value = value;
    }
}

创建链表---createLink(Node head)

创建链表,首先要保证这是一个空链才能继续进行

不断输入数据,每输入一个数据创建一个新节点,然后遍历已有的链表,将新节点追加到链表的末尾

watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3N6eTIzMzM=,size_16,color_FFFFFF,t_70

    Node newNode = new Node(num);
    //建立一个临时节点,用于遍历
    Node tempNode = head;
    //当该链表没有达到末尾时(即结节点的next值不为空时),继续向后遍历
    while(tempNode.next != null) {
        tempNode = tempNode.next;
    }

    //此时,结点达到最后一个不为空的结点,此时将新节点追加到该链表的末尾,并将容量增加1
    tempNode.next = newNode;

输出链表---prinLink(Node head)

链表不为空则遍历并输出

for(Node node = head.next; node != null; node = node.next) {
            System.out.print(node.value + "\t");
        }

通过节点的位置查找节点的值---findValueByNode(Node head)

从头结点head开始找,记录head所在的位置为0,每遍历一个元素,位置加一,然后看当前位置与要查找的位置是否一致,如果一致则输出该位置的值,否则继续遍历,直至找到

int counting = 0;
        //遍历链表,每遍历一个节点,计数加一,直至遍历到第count个节点
        for(Node tempNode = head.next; tempNode != null; tempNode = tempNode.next) {
            ++counting;
            if(counting == count) {
                System.out.println("the value you find located on " + count + "is: " + tempNode.value);
            }
        }

向链表中添加一个节点,以追加的方式添加---appendNode(Node head)

原理和创建链表是一样的,只不过此时只需要添加一个节点而已,不再赘述

删除元素值为value的节点---(deleteByValue)

关于节点删除的方式,首先需要判断要删除的元素在链表中是否存在,如果存在,我们要记录待删除节点的上一个节点preNode,然后让待删除节点的下一个节点nextNode成为preNode的next节点

watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3N6eTIzMzM=,size_16,color_FFFFFF,t_70

Node tempNode = head;
        //进行遍历
        while (tempNode.next != null) {
            //如果链表里面的某一个节点值与要删除的值相同,则删除该节点,并将链表大小减一
            if(tempNode.next.value == value) {
                tempNode.next = tempNode.next.next;
                --head.capacity;
                return;
            }
            tempNode = tempNode.next;
        }

        //方法执行到这一步,说明链表中没有要删除的元素值
        System.out.println("there exsit no value you want to delete in link");

向某个指定位置插入节点,value是要插入的节点的值---insertNode(Node head, int value)

要插入的节点为Node,先找到要插入的节点的上一个位置的节点preNode,然后将原来的preNode的next节点记录为nextNode,然后将nextNode赋值给Node的next值,再让Node称为preNode的next节点

watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3N6eTIzMzM=,size_16,color_FFFFFF,t_70

//用counting记录当前位置,当没有找到正确位置时,链表的节点向后移动,并且计数加一
        int counting = 0;
        Node tempNode = head;
        for(; counting != locate; tempNode = tempNode.next) {
            ++counting;
        }

        //注意,要将原来的节点的下一个节点链到当前节点后面,而当前节点则成为原节点的下一个节点
        Node tmp = tempNode.next;
        newNode.next = tmp;
        tempNode.next = newNode;

对链表中的元素进行排序---sortLink(Node head)

这里采用的方式为简单冒泡排序,有n个元素,则进行n-1次排序,冒泡排序详情见之前的文章https://blog.csdn.net/szy2333/article/details/83118677

其实对于链表的排序和数组的排序原理上是一样的,都是将相邻两个元素进行比较,然后交换位置

先看数组的排序

20181122195101407.png

然后看链表的排序,将链表中的相邻节点的值进行比较,如果前节点的值比后面节点的值大,那么将两个节点的值交换即可

watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3N6eTIzMzM=,size_16,color_FFFFFF,t_70

for(int i = 1; i < head.capacity; i++) {
            //遍历链表,并相邻两个元素进行比较
            for(Node tempNode = head.next; tempNode.next != null; tempNode = tempNode.next) {
                //如果当前节点的值比下一个节点的值大
                if(tempNode.next.value > tempNode.next.next.value) {
                    //两个节点的值交换
                    tmp = tempNode.value;
                    tempNode.value = tempNode.next.value;
                    tempNode.next.value = tmp;
                }
            } 
        }

前面说的是方式1,还有方式2,但是复杂多了,不多可以用来练练逻辑

方式2的思想是:交换两个节点的位置,但是位置会影响其前后节点位置的变化,所以需要进行分析

要交换两个节点,则需要记录前一个结点preNode,这样能保证有一个不变的量进行变化后的调整

watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3N6eTIzMzM=,size_16,color_FFFFFF,t_70

watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3N6eTIzMzM=,size_16,color_FFFFFF,t_70

for(int i = 1; i < head.capacity; i++) {
            //遍历链表,并相邻两个元素进行比较
            for(Node tempNode = head; tempNode.next != null && tempNode.next.next != null; tempNode = tempNode.next) {
                //如果当前节点的值比下一个节点的值大
                if(tempNode.next.value > tempNode.next.next.value) {
                    //两个节点的位置交换
                    tmp2 = tempNode.next.next;
                    tmp1 = tempNode.next;
                    tmp1.next = tempNode.next.next.next;
                    tmp2.next = tmp1;
                    tempNode.next = tmp2;
                }
            } 
        }

对于链表的基本操作大致就是这些,下面是源代码及执行结果

完整代码

package 剑指offer;

import java.util.Scanner;


/*
* 注意的地方:
* 1.先不要急着关闭Scanner流,比如createLink方法中用到了该流,findValueByNode也用到了该流,如果在前者的方法汇总关闭的了流,
*   那么在后者使用这个流时就会出现NoSuchElementException
*   原因:打开Scanner后关闭,也就间接地将System.in也关闭了,因为System.in是个静态流,
*   所以引用其的Scanner对象内都是相同状态的。当操作后关闭流之后这时再调用nextInt,在System.in已经关闭了情况下,
*   不能读取到任何数据,就会产生异常
 * */
public class SingleLinkWithHead {
    public static void main(String[] args) {
        SingleLinkWithHead singleLinkWithHead = new SingleLinkWithHead();
        //创建一个头结点
        Node head = new Node(0);
        createLink(head);
        printLink(head);
        findValueByNode(head);
        addNode(head);
        printLink(head);
        deleteByValue(head, 3);
        printLink(head);
        insertNode(head, 5);
        printLink(head);
        sortLink(head);
        printLink(head);
    }

    //对链表中的元素进行排序,以从小到大的方式,这里面用最简单的冒泡排序
    public static void sortLink(Node head) {
        //如果链表为空或者没有元素,则不进行排序
        if(head.capacity <= 1) {
            System.out.println("it needn't be sorted because there exsit one element at most");
            return;
        }

        Node tmp1 = null;
        Node tmp2 = null;
        //共有capacity个元素,所以需要比较capacity-1次
        for(int i = 1; i < head.capacity; i++) {
            //遍历链表,并相邻两个元素进行比较
            for(Node tempNode = head; tempNode.next != null && tempNode.next.next != null; tempNode = tempNode.next) {
                //如果当前节点的值比下一个节点的值大
                if(tempNode.next.value > tempNode.next.next.value) {
                    //两个链表的位置交换
                    tmp2 = tempNode.next.next;
                    tmp1 = tempNode.next;
                    tmp1.next = tempNode.next.next.next;
                    tmp2.next = tmp1;
                    tempNode.next = tmp2;
                }
            } 
        }
    }


    //向某个指定位置插入节点,value是要插入的节点的值
    // 注意:是在某个位置之后添加,如位置为0,则添加的位置就是head之后的第一个节点
    //      如果位置为3,那么添加的是3之后的第四个节点
    public static void insertNode(Node head, int value) {
        //创建新节点,该节点就是要插入的节点
        Node newNode = new Node(value);
        //输入要插入的节点的位置,如果输入的位置不符合条件,则重新输入
        System.out.println("please input the location be inserted:");
        int locate = 0;
        Scanner sc = new Scanner(System.in);
        while (true) {
            locate = sc.nextInt();
            if(locate >= 0 && locate <= head.capacity) {
                break;
            }
            System.out.println("the location you input is incorrect, please input again:");
        }

        //用counting记录当前位置,当没有找到正确位置时,链表的节点向后移动,并且计数加一
        int counting = 0;
        Node tempNode = head;
        for(; counting != locate; tempNode = tempNode.next) {
            ++counting;
        }

        //注意,要将原来的节点的下一个节点链到当前节点后面,而当前节点则成为原节点的下一个节点
        Node tmp = tempNode.next;
        newNode.next = tmp;
        tempNode.next = newNode;

        //不要忘记插入节点后将链表的长度加一
        ++head.capacity;
    }

    //删除元素值为value的节点,简单起见,之间将要删除的值作为参数传入
    public static void deleteByValue(Node head, int value) {
        //如果链表为空,退出方法
        if(head.next == null) {
            System.out.println("there is no element to delete");
            return;
        }

        //建立临时节点,赋值为head
        Node tempNode = head;
        //进行遍历
        while (tempNode.next != null) {
            //如果链表里面的某一个节点值与要删除的值相同,则删除该节点,并将链表大小减一
            if(tempNode.next.value == value) {
                tempNode.next = tempNode.next.next;
                --head.capacity;
                return;
            }
            tempNode = tempNode.next;
        }

        //方法执行到这一步,说明链表中没有要删除的元素值
        System.out.println("there exsit no value you want to delete in link");
    }

    //向链表中添加一个节点,以追加的方式添加
    public static void addNode(Node head) {
        //先创建一个新节点
        System.out.print("please input the value that will be added:");
        Scanner sc = new Scanner(System.in);
        int value = sc.nextInt();
        Node newNode = new Node(value);

        //建立临时节点,赋值为head
        Node tempNode = head;
        //进行遍历,当表达到末尾时,将新节点追加
        for(; tempNode.next != null; tempNode = tempNode.next) {
        }

        tempNode.next = newNode;
        //添加完节点不要忘记将链表的大小增加1
        ++head.capacity;
    }

    //输入节点的序号,查找该位置的节点的值
    public static void findValueByNode(Node head) {
        //如果链表为空,退出方法
        if(head.next == null) {
            System.out.println("the link is empty or count not in right region");
            return;
        }

        //先给count一个初始值0
        int count = 0;
        System.out.println("please input the count you want to find:");
        Scanner sc = new Scanner(System.in);
        //给count重新输入一个值,如果该值在链表的可查范围内,则结束输入
        while(true) {
            count = sc.nextInt();
            if(count > 0 && count <= head.capacity) {
                break;
            }

            System.out.println("the num you input is not suitable, please input again:");
        }

        //创建一个变量counting进行计数
        int counting = 0;
        //遍历链表,每遍历一个节点,计数加一,直至遍历到第count个节点
        for(Node tempNode = head.next; tempNode != null; tempNode = tempNode.next) {
            ++counting;
            if(counting == count) {
                System.out.println("the value you find located on " + count + "is: " + tempNode.value);
            }
        }
    }

    //打印链表
    public static void printLink(Node head) {
        //链表为空,则无法打印
        if(head.next == null) {
            System.out.println("the link is empty, can't be printed");
            return;
        }

        //不为空,遍历整个链表并打印
        for(Node node = head.next; node != null; node = node.next) {
            System.out.print(node.value + "\t");
        }

        System.out.print("\n");
    }

    //创建链表
    public static void createLink(Node head) {
        //先判断链表是否为空,如果链表不为空,则无需创建,退出该方法
        if(head.next != null) {
            System.out.println("the link is not empty, can't be created");
            return;
        }

        //否则,通过键盘输入要插入的结点的值
        System.out.println("please input the values of link:");
        Scanner sc = new Scanner(System.in);
        while(true) {
            int num = sc.nextInt();
            //当值为0则停止输入
            if(0 == num) {
                break;
            }

            //否则,创建一个新节点,新节点的value就是num
            Node newNode = new Node(num);
            //建立一个临时节点,用于遍历
            Node tempNode = head;
            //当该链表没有达到末尾时(即结节点的next值不为空时),继续向后遍历
            while(tempNode.next != null) {
                tempNode = tempNode.next;
            }

            //此时,结点达到最后一个不为空的结点,此时将新节点追加到该链表的末尾,并将容量增加1
            tempNode.next = newNode;
            ++head.capacity;
        }
        
        //节点录入完毕之后,此时的head就是创建好的链表
    }
}


//结点类,每个结点都有自身的值value,连接的下一个节点next,以及各点连接起来的链的大小capacity
//成员变量会自动赋初值为0或null
class Node {
    int value;
    Node next;
    int capacity;

    public Node(int value) {
        this.value = value;
    }
}

执行结果

watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3N6eTIzMzM=,size_16,color_FFFFFF,t_70

 

/* * 基于链表节点实现二叉树节点 */ package dsa; public class BinTreeNode implements BinTreePosition { protected Object element;//该节点中存放的对象 protected BinTreePosition parent;//父亲 protected BinTreePosition lChild;//左孩子 protected BinTreePosition rChild;//右孩子 protected int size;//后代数目 protected int height;//高度 protected int depth;//深度 /**************************** 构造方法 ****************************/ public BinTreeNode() { this(null, null, true, null, null); } public BinTreeNode( Object e,//节点内容 BinTreePosition p,//父节点 boolean asLChild,//是否作为父节点的左孩子 BinTreePosition l,//左孩子 BinTreePosition r)//右孩子 { size = 1; height = depth = 0; parent = lChild = rChild = null;//初始化 element = e;//存放的对象 //建立与父亲的关系 if (null != p) if (asLChild) p.attachL(this); else p.attachR(this); //建立与孩子的关系 if (null != l) attachL(l); if (null != r) attachR(r); } /**************************** Position接口方法 ********************************/ //返回当前节点中存放的对象 public Object getElem() { return element; } //将对象obj存入当前节点,并返回此前的内容 public Object setElem(Object obj) { Object bak = element; element = obj; return bak; } /**************************** BinTreePosition接口方法 *************************/ //判断是否有父亲(为使代码描述简洁) public boolean hasParent() { return null != parent; } //返回当前节点的父节点 public BinTreePosition getParent() { return parent; } //设置当前节点的父节点 public void setParent(BinTreePosition p) { parent = p; } //判断是否为叶子 public boolean isLeaf() { return !hasLChild() && !hasRChild(); } //判断是否为左孩子(为使代码描述简洁) //若当前节点有父亲,而且是左孩子,则返回true;否则,返回false public boolean isLChild() { return (hasParent() && this == getParent().getLChild()) ? true : false; } //判断是否有左孩子(为使代码描述简洁) public boolean hasLChild() { return null != lChild; } //返回当前节点的左孩子 public BinTreePosition getLChild() { return lChild; } //设置当前节点的左孩子(注意:this.lChild和c.parent都不一定为空) public void setLChild(BinTreePosition c) { lChild = c; } //判断是否为右孩子(为使代码描述简洁) //若当前节点有父亲,而且是右孩子,则返回true;否则,返回false public boolean isRChild() { return (hasParent() && this == getParent().getRChild()) ? true : false; } //判断是否有右孩子(为使代码描述简洁) public boolean hasRChild() { return null != rChild; } //返回当前节点的右孩子 public BinTreePosition getRChild() { return rChild; } //设置当前节点的右孩子(注意:this.rChild和c.parent都不一定为空) public void setRChild(BinTreePosition c) { rChild = c; } //返回当前节点后代元素的数目 public int getSize() { return size; } //在孩子发生变化后,更新当前节点及其祖先的规模 public void updateSize() { size = 1;//当前节点 if (hasLChild()) size += getLChild().getSize();//左子树的规模 if (hasRChild()) size += getRChild().getSize();//右子树的规模 if (hasParent()) getParent().updateSize();//递归更新各个真祖先的规模记录 } //返回当前节点的高度 public int getHeight() { return height; } //在孩子发生变化后,更新当前节点及其祖先的高度 public void updateHeight() { height = 0;//先假设没有左、右孩子 if (hasLChild()) height = Math.max(height, 1+getLChild().getHeight());//左孩子 if (hasRChild()) height = Math.max(height, 1+getRChild().getHeight());//右孩子 if (hasParent()) getParent().updateHeight();//递归更新各个真祖先的高度记录 } //返回当前节点的深度 public int getDepth() { return depth; } //在父亲发生变化后,更新当前节点及其后代的深度 public void updateDepth() { depth = hasParent() ? 1+getParent().getDepth() : 0;//当前节点 if (hasLChild()) getLChild().updateDepth();//沿孩子引用逐层向下, if (hasRChild()) getRChild().updateDepth();//递归地更新所有后代的深度记录 } //按照中序遍历的次序,找到当前节点的直接前驱 public BinTreePosition getPrev() { //若左子树非空,则其中的最大者即为当前节点的直接前驱 if (hasLChild()) return findMaxDescendant(getLChild()); //至此,当前节点没有左孩子 if (isRChild()) return getParent();//若当前节点是右孩子,则父亲即为其直接前驱 //至此,当前节点没有左孩子,而且是左孩子 BinTreePosition v = this;//从当前节点出发 while (v.isLChild()) v = v.getParent();//沿左孩子链一直上升 //至此,v或者没有父亲,或者是父亲的右孩子 return v.getParent(); } //按照中序遍历的次序,找到当前节点的直接后继 public BinTreePosition getSucc() { //若右子树非空,则其中的最小者即为当前节点的直接后继 if (hasRChild()) return findMinDescendant(getRChild()); //至此,当前节点没有右孩子 if (isLChild()) return getParent();//若当前节点是左孩子,则父亲即为其直接后继 //至此,当前节点没有右孩子,而且是右孩子 BinTreePosition v = this;//从当前节点出发 while (v.isRChild()) v = v.getParent();//沿右孩子链一直上升 //至此,v或者没有父亲,或者是父亲的左孩子 return v.getParent(); } //断绝当前节点与其父亲的父子关系 //返回当前节点 public BinTreePosition secede() { if (null != parent) { if (isLChild()) parent.setLChild(null);//切断父亲指向当前节点的引用 else parent.setRChild(null); parent.updateSize();//更新当前节点及其祖先的规模 parent.updateHeight();//更新当前节点及其祖先的高度 parent = null;//切断当前节点指向原父亲的引用 updateDepth();//更新节点及其后代节点的深度 } return this;//返回当前节点 } //将节点c作为当前节点的左孩子 public BinTreePosition attachL(BinTreePosition c) { if (hasLChild()) getLChild().secede();//摘除当前节点原先的左孩子 if (null != c) { c.secede();//c脱离原父亲 lChild = c; c.setParent(this);//确立新的父子关系 updateSize();//更新当前节点及其祖先的规模 updateHeight();//更新当前节点及其祖先的高度 c.updateDepth();//更新c及其后代节点的深度 } return this; } //将节点c作为当前节点的右孩子 public BinTreePosition attachR(BinTreePosition c) { if (hasRChild()) getRChild().secede();//摘除当前节点原先的右孩子 if (null != c) { c.secede();//c脱离原父亲 rChild = c; c.setParent(this);//确立新的父子关系 updateSize();//更新当前节点及其祖先的规模 updateHeight();//更新当前节点及其祖先的高度 c.updateDepth();//更新c及其后代节点的深度 } return this; } //前序遍历 public Iterator elementsPreorder() { List list = new List_DLNode(); preorder(list, this); return list.elements(); } //中序遍历 public Iterator elementsInorder() { List list = new List_DLNode(); inorder(list, this); return list.elements(); } //后序遍历 public Iterator elementsPostorder() { List list = new List_DLNode(); postorder(list, this); return list.elements(); } //层次遍历 public Iterator elementsLevelorder() { List list = new List_DLNode(); levelorder(list, this); return list.elements(); } /**************************** 辅助方法 ****************************/ //在v的后代中,找出最小者 protected static BinTreePosition findMinDescendant(BinTreePosition v) { if (null != v) while (v.hasLChild()) v = v.getLChild();//从v出发,沿左孩子链一直下降 //至此,v或者为空,或者没有左孩子 return v; } //在v的后代中,找出最大者 protected static BinTreePosition findMaxDescendant(BinTreePosition v) { if (null != v) while (v.hasRChild()) v = v.getRChild();//从v出发,沿右孩子链一直下降 //至此,v或者为空,或者没有右孩子 return v; } //前序遍历以v为根节的(子)树 protected static void preorder(List list, BinTreePosition v) { if (null == v) return;//递归基:空树 list.insertLast(v);//访问v preorder(list, v.getLChild());//遍历左子树 preorder(list, v.getRChild());//遍历右子树 } //中序遍历以v为根节的(子)树 protected static void inorder(List list, BinTreePosition v) { if (null == v) return;//递归基:空树 inorder(list, v.getLChild());//遍历左子树 list.insertLast(v);//访问v inorder(list, v.getRChild());//遍历右子树 } //后序遍历以v为根节的(子)树 protected static void postorder(List list, BinTreePosition v) { if (null == v) return;//递归基:空树 postorder(list, v.getLChild());//遍历左子树 postorder(list, v.getRChild());//遍历右子树 list.insertLast(v);//访问v } //层次遍历以v为根节的(子)树 protected static void levelorder(List list, BinTreePosition v) { Queue_List Q = new Queue_List();//空队 Q.enqueue(v);//根节点入队 while (!Q.isEmpty()) { BinTreePosition u = (BinTreePosition) Q.dequeue();//出队 list.insertLast(u);//访问v if (u.hasLChild()) Q.enqueue(u.getLChild()); if (u.hasRChild()) Q.enqueue(u.getRChild()); } } }
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值