• 数据结构与算法(二):单链表


    一、什么是链表

    链表是一种数据结构,跟数组不同,链表不需要连续的内存空间,而是通过指针将零散的内存块连接起来。

    因此,链表的查找需要通过节点按顺序遍历,而增加与删除通过只需要操作指针指向,这也造成了相比数组,链表的查找性能消耗大增加和删除消耗小的特点。

    链表由节点组成,一般每个节点最少包含用于储存数据的data区和用于指向下一个节点的指针next,有的链表可能会有头结点或尾结点用于存储链表长度等信息。

    二、链表的简单实现

    先来实现一个简单的节点类:

    /**
     * @Author:huang
     * @Date:2020-06-20 10:19
     * @Description:节点类
     */
    public class Node {
    
        //节点序号
        int num;
    
        //数据
        Object data;
    
        //下一个节点
        Node next;
    
        public Node(int num, Object data) {
            this.num = num;
            this.data = data;
        }
        
        public int getNum() {
            return num;
        }
    
        public Object getData() {
            return data;
        }
    
        public void setNum(int num) {
            this.num = num;
        }
    
        public void setData(Object data) {
            this.data = data;
        }
        
        @Override
        public String toString() {
            return "Node{" +
                "num=" + num +
                ", data=" + data +
                '}';
        }
    }
    

    1.插入节点

    1.1不按照排序的插入

    /**
     * @Author:huang
     * @Date:2020-06-20 10:19
     * @Description:单链表
     */
    public class SingleLinkList {
    	//默认初始化一个头节点
        private Node head = new Node(0, "我是头结点");
    
        /**
         * 添加节点到链表
         * @param node
         */
        public void add(Node node) {
            Node temp = head;
            //遍历链表
            while (true) {
                if (temp.next == null) {
                    break;
                }
                //不是尾节点就继续遍历下一个节点
                temp = temp.next;
            }
            //将尾节点指向即将插入的新节点
            temp.next = node;
        }
    }
    

    1.2按照排序插入

    要想按顺序插入节点,需要确定节点的位置,也就是确定新节点的大小,以图为例:

    遍历链表,我们把遍历到的节点叫A,A之前的节点叫A-1,A之后的节点叫A+1,要插入的节点叫B,有以下几种情况:

    1. 当A的序号大于B时,就把B插到A前,也就是原本的A和A-1之间
    2. 当A的序号等于B时,抛出异常禁止插入
    3. 当A的序号小于B时,就跳过这个节点,继续遍历,然后对比A+1和B的大小
    4. 当A就是链表最后一个节点时,A还是小于B,那就直接让B插到A后,变成链表的尾节点

    按这个思路,我们在原来的类里新增一个方法:

    /**
     * 按顺序添加节点到链表
     * @param node 要插入的节点
     */
    public void addByOrder(Node node) {
        Node temp = head;
        //遍历链表
        while (true) {
            //如果链表到底了就直接插入
            if (temp.next == null) {
                temp.next = node;
                return;
            }else if (temp.next.num > node.num){
                //如果当前节点比当要插入的节点大,就把新节点插到当前节点之前
                node.next = temp.next;
                temp.next = node;
                return;
            }else if (temp.next.num == node.num){
                //如果有节点序号和要插入的节点重复就抛异常
                throw new RuntimeException("插入节点与已有节点序号重复!");
            }
            //如果当前节点比当要插入的节点小,就继续遍历
            temp = temp.next;
        }
    }
    

    2.查找节点

    /**
     * 展示链表全部节点
     */
    public void show() {
        //判断链表是否为空
        if (head.next == null) {
            throw new RuntimeException("链表为空!");
        }
        Node temp = head.next;
        //遍历链表
        while (true) {
            if (temp == null) {
                break;
            }
            System.out.println(temp.toString());
            temp = temp.next;
        }
    }
    
    /**
     * 根据序号获取节点
     * @param num 要获取的节点序号
     * @return
     */
    public Node get(int num){
        //判断链表是否为空
        if (head.next == null) {
            throw new RuntimeException("链表为空!");
        }
        Node temp = head.next;
        //遍历链表
        while (true) {
            if (temp == null) {
                throw new RuntimeException("编号为" + num + "的节点不存在!");
            }
            //如果是要获取的节点
            if (temp.num == num) {
                return temp;
            }
            temp = temp.next;
        }
    }
    

    3.修改节点

    修改节点与按顺序插入逻辑相似,需要先遍历并找到要修改的节点,然后用新节点去替换旧节点,或者干脆直接修改节点内的信息

    /**
     * 修改节点
     * @param node 要更新的节点
     */
    public void update(Node node) {
        Node temp = head;
        //判断链表是否为空
        if (temp.next == null) {
            throw new RuntimeException("链表为空!");
        }
        //获取要更新的节点序号
        int nodeNum = node.num;
        //遍历链表
        while (true) {
            //如果已经遍历完链表
            if (temp == null) {
                throw new RuntimeException("编号为" + temp.num + "的节点不存在!");
            }
            //如果找到了该节点
            if (temp.num == nodeNum) {
                temp.data = node.data;
                return;
            }
            //继续遍历下一节点
            temp = temp.next;
        }
    }
    

    4.删除节点

    因为在单向链表中,节点无法知道自己前一个节点的情况,以图为例:

    如果我们想要删除节点A,那么就要找到A的前一个节点A-1,根据是否存在A后的节点A+1有以下几种情况:

    1. A节点就是链表尾节点,此时只需让A-1节点的next指向null即可

      nodeA-1.next = null

    2. A节点后有A+1,此时让A-1节点指向A+1节点

      nodeA-1.next = nodeA-1.next.next

    按这个思路,在类里新加一个方法:

    /**
     * 删除节点
     * @param num 要删除的节点编号
     */
    public void delete(int num) {
        Node temp = head;
        //判断链表是否为空
        if (temp.next == null) {
            throw new RuntimeException("链表为空!");
        }
        //遍历链表
        while (true) {
            //如果链表到底了
            if (temp.next == null) {
                throw new RuntimeException("编号为" + num + "的节点不存在!");
            }
            //如果找到了待删除节点的前一个节点
            if (temp.next.num == num) {
                //判断待删除节点是否为尾节点
                if (temp.next.next == null){
                    temp.next = null;
                }else {
                    temp.next = temp.next.next;
                }
                return;
            }
            //继续遍历下一节点
            temp = temp.next;
        }
    }
    

    三、完整实现代码

    /**
     * @Author:huang
     * @Date:2020-06-20 10:19
     * @Description:单链表
     */
    public class SingleLinkList {
    
        private Node head = new Node(0, "我是头结点");
    
        /**
         * 添加节点到链表
         * @param node 要插入的节点
         */
        public void add(Node node) {
            Node temp = head;
            //遍历链表
            while (true) {
                if (temp.next == null) {
                    break;
                }
                //不是尾节点就继续遍历下一个节点
                temp = temp.next;
            }
            //将尾节点指向即将插入的新节点
            temp.next = node;
        }
    
        /**
         * 展示链表
         */
        public void show() {
            //判断链表是否为空
            if (head.next == null) {
                throw new RuntimeException("链表为空!");
            }
            Node temp = head.next;
            //遍历链表
            while (true) {
                if (temp == null) {
                    break;
                }
                System.out.println(temp.toString());
                temp = temp.next;
            }
        }
    
        /**
         * 根据序号获取节点
         * @param num 要获取的节点序号
         * @return
         */
        public Node get(int num){
            //判断链表是否为空
            if (head.next == null) {
                throw new RuntimeException("链表为空!");
            }
            Node temp = head.next;
            //遍历链表
            while (true) {
                if (temp == null) {
                    throw new RuntimeException("编号为" + num + "的节点不存在!");
                }
                if (temp.num == num) {
                    return temp;
                }
                temp = temp.next;
            }
        }
    
        /**
         * 按顺序添加节点到链表
         * @param node 要插入的节点
         */
        public void addByOrder(Node node) {
            Node temp = head;
            //遍历链表
            while (true) {
                //如果链表到底了就直接插入
                if (temp.next == null) {
                    temp.next = node;
                    return;
                }
                else if (temp.next.num > node.num){
                    //如果后一节点比当新节点大,就插入当前节点
                    node.next = temp.next;
                    temp.next = node;
                    return;
                }else if (temp.next.num == node.num){
                    //如果后一节点等于新节点,抛异常
                    throw new RuntimeException("插入节点与已有节点序号重复!");
                }
                //如果后一节点比当前节点小,就继续遍历
                temp = temp.next;
            }
        }
    
        /**
         * 修改节点
         * @param node 要更新的节点
         */
        public void update(Node node) {
            Node temp = head;
            //判断链表是否为空
            if (temp.next == null) {
                throw new RuntimeException("链表为空!");
            }
            //获取要更新的节点序号
            int nodeNum = node.num;
            //遍历链表
            while (true) {
                //如果已经遍历完链表
                if (temp == null) {
                    throw new RuntimeException("编号为" + temp.num + "的节点不存在!");
                }
                //如果找到了该节点
                if (temp.num == nodeNum) {
                    temp.data = node.data;
                    return;
                }
                //继续遍历下一节点
                temp = temp.next;
            }
        }
    
        /**
         * 删除节点
         * @param num 要删除的节点编号
         */
        public void delete(int num) {
            Node temp = head;
            //判断链表是否为空
            if (temp.next == null) {
                throw new RuntimeException("链表为空!");
            }
            //遍历链表
            while (true) {
                //如果链表到底了
                if (temp.next == null) {
                    throw new RuntimeException("编号为" + num + "的节点不存在!");
                }
                //如果找到了待删除节点的前一个节点
                if (temp.next.num == num) {
                    //判断待删除节点是否为尾节点
                    if (temp.next.next == null){
                        temp.next = null;
                    }else {
                        temp.next = temp.next.next;
                    }
                    return;
                }
                //继续遍历下一节点
                temp = temp.next;
            }
        }
    }
    

    四、环形链表

    循环链表是一种特殊单链表,他跟单链表的区别在于尾节点的尾指针指向了链表的头结点,也就是相当于将链表连接成了一个环形。

    环形链表可以用于处理能描述为环形的数据,典型的比如约瑟夫问题。

  • 相关阅读:
    Seata-一站式分布式事务解决方案
    nginx相关
    module in JavaScript
    es6this箭头函数
    Python3爬虫:(一)爬取拉勾网公司列表
    markdoen语法
    吴裕雄--天生自然TensorFlow2教程:维度变换
    吴裕雄--天生自然TensorFlow2教程:numpy [ ] 索引
    吴裕雄--天生自然TensorFlow2教程:创建Tensor
    吴裕雄--天生自然TensorFlow2教程:Tensor数据类型
  • 原文地址:https://www.cnblogs.com/Createsequence/p/13168613.html
Copyright © 2020-2023  润新知