• 博客园logo
  • 会员
  • 周边
  • 新闻
  • 博问
  • 闪存
  • 赞助商
  • YouClaw
    • 搜索
      所有博客
    • 搜索
      当前博客
  • 写随笔 我的博客 短消息 简洁模式
    用户头像
    我的博客 我的园子 账号设置 会员中心 简洁模式 ... 退出登录
    注册 登录

无信不立

  • 博客园
  • 联系
  • 订阅
  • 管理

公告

View Post

【数据结构和算法】之链表

一、为什么学习链表数据结构

  • 经典的链表应用场景,那就是 LRU 缓存淘汰算法

二、链表的数据结构

1、单向链表

2、单向循环链表

3、双向链表

4、双向循环链表

 

三、链表的数据特点及和数组对比

1、链表的数据结构的特点

  • 不支持像数组一样随机访问,查找数据的时间复杂度为O(n)
  • 插入和删除操作,不像数组一样需要搬移数据,时间复杂度为O(1)
  • 不需要连续的内存空间

2、数组和链表的对比

数组:需要申请连续的内存空间;当数组空间不足,需要进行扩容;

链表:无需申请,连续的内存空间;不需要考虑扩容问题

 

四、如何写好链表代码技巧

技巧一:理解指针或引用的含义

  • 将某个变量赋值给指针,实际上就是将这个变量的地址赋值给指针,或者反过来说,指针中存储了这个变量的内存地址,指向了这个变量,通过指针就能找到这个变量。

技巧二:警惕指针丢失和内存泄漏

  • 插入结点时,一定要注意操作的顺序;
  • 删除链表结点时,也一定要记得手动释放内存空间
链表:p->b

// 向链表中插入X节点(错误写法)
p->next = x;  // 将p的next指针指向x结点;
x->next = p->next;  // 将x的结点的next指针指向b结点;

// 向链表中插入X节点(正确写法)
x->next = p->next;  // 将x的结点的next指针指向b结点;
p->next = x;  // 将p的next指针指向x结点;
View Code

技巧三:利用哨兵简化实现难度

  • 针对链表的插入、删除操作,需要对插入第一个结点和删除最后一个结点的情况进行特殊处理。

带头链表(哨兵结点是不存储数据的。因为哨兵结点一直存在,所以插入第一个结点和插入其他结点,删除最后一个结点和删除其他结点,都可以统一为相同的代码实现逻辑了。)

技巧四:重点留意边界条件处理

  • 如果链表为空时,代码是否能正常工作?
  • 如果链表只包含一个结点时,代码是否能正常工作?
  • 如果链表只包含两个结点时,代码是否能正常工作?
  • 代码逻辑在处理头结点和尾结点的时候,是否能正常工作?

技巧五:举例画图,辅助思考

技巧六:多写多练,没有捷径

 

 

五、链表的常见操作算法

1、单链表反转

public class LinkedTest {

    public static void main(String[] args) {

        //step1:组装链表
        Node node1=new Node();
        node1.setData("A");

        Node node2=new Node();
        node2.setData("B");

        Node node3=new Node();
        node3.setData("C");

        node1.setNext(node2);
        node2.setNext(node3);

        //step2:执行链表反转
        Node newLinked=resetLinked(node1);

        //step3:测试链表反转结果
        for(Node node=newLinked;node!=null;node=node.getNext()){
            System.out.println(node.getData());
        }


    }

    /**
     * 单链表反转
     *
     * @param node
     * @return
     */
    public static Node resetLinked(Node node) {
        if (node == null || node.getNext() == null) {
            //如果链表为空,则返回null
            return node;
        }
        Node nextNode = null;
        Node currentNode = node;
        while (currentNode != null) {
            Node tmp = currentNode.getNext();
            currentNode.setNext(nextNode);
            nextNode = currentNode;
            currentNode = tmp;
        }
        return nextNode;
    }

}


class Node {

    private String data;
    private Node next;

    public String getData() {
        return data;
    }

    public void setData(String data) {
        this.data = data;
    }

    public Node getNext() {
        return next;
    }

    public void setNext(Node next) {
        this.next = next;
    }
}
View Code

2、链表中环的检测

public class LinkedTest {

    public static void main(String[] args) {

        //step1:组装链表
        Node node1 = new Node();
        node1.setData("A");

        Node node2 = new Node();
        node2.setData("B");

        Node node3 = new Node();
        node3.setData("C");

        node1.setNext(node2);
        node2.setNext(node3);
        node3.setNext(node1);

        System.out.println(isLoopLinked(node1));
        System.out.println(isLoopLinked2(node1));


    }


    /**
     * 方法1:通过链表追击,判断链表是否有环
     *
     * A->B->C->D->E->F
     *
     * 第一次循环:
     * nextNode=B
     * next=C
     *
     * 第二次循环:
     * first=C
     * next=E
     *
     * 第三次循环:
     * first=D
     * next=A
     *
     * 第四次循环
     * first=E
     * next=C
     *
     * 第五次循环
     * first=F
     * next=E
     *
     * 第六次循环
     * first=A
     * next=A
     *
     * @param node
     * @return
     */
    public static boolean isLoopLinked(Node node) {

        if (node == null || node.getNext() == null) {
            return false;
        }
        Node nextNode = getNextNode(node);
        Node nextNextNode = getNextNextNode(node);
        while (nextNode != null && nextNextNode != null) {
            if (nextNode == nextNextNode) {
                return true;
            }
            nextNode = getNextNode(nextNode);
            nextNextNode = getNextNextNode(nextNextNode);
        }
        return false;
    }


    /**
     * 通过HashMap破解链表是否有环
     *
     * 前提是链表节点没有重写hashcode和 equals
     *
     * @param node
     * @return
     */
    public static boolean isLoopLinked2(Node node) {
        if (node == null || node.getNext() == null) {
            return false;
        }
        Map<Node, Node> registryMap = new HashMap<>();
        for (Node node1 = node; node1.getNext() != null; node1 = node1.getNext()) {
            Node tmp = registryMap.get(node1);
            if (tmp != null) {
                return true;
            }
            registryMap.put(node1,node1);
        }
        return false;
    }

    /**
     * 获取下一个节点
     *
     * @param node
     * @return
     */
    public static Node getNextNode(Node node) {
        return node != null ? node.getNext() : null;
    }

    /**
     * 获取下下一个节点
     *
     * @param node
     * @return
     */
    public static Node getNextNextNode(Node node) {
        Node nextNode = getNextNode(node);
        return nextNode != null ? getNextNode(nextNode) : null;
    }
    

   
}


class Node {

    private String data;
    private Node next;

    public String getData() {
        return data;
    }

    public void setData(String data) {
        this.data = data;
    }

    public Node getNext() {
        return next;
    }

    public void setNext(Node next) {
        this.next = next;
    }
}
View Code

3、两个有序的链表合并

public class LinkedTest {

    public static void main(String[] args) {

        //step1:组装链表
        Node node1 = new Node();
        node1.setData("A");

        Node node2 = new Node();
        node2.setData("C");

        Node node3 = new Node();
        node3.setData("E");

        //左侧链表组装
        node1.setNext(node2);
        node2.setNext(node3);


        Node node4 = new Node();
        node4.setData("B");

        Node node5 = new Node();
        node5.setData("D");

        Node node6 = new Node();
        node6.setData("F");
        //右侧链表组装
        node4.setNext(node5);
        node5.setNext(node6);


        Node node = mergeLinked(node1, node4);
        while (node != null) {
            System.out.println(node.getData());
            node = node.getNext();
        }

    }

    /**
     * 合并两个有序链表
     *
     * @param leftNode
     * @param rightNode
     * @return
     */
    public static Node mergeLinked(Node leftNode, Node rightNode) {
        if (leftNode == null) {
            return rightNode;
        }
        if (rightNode == null) {
            return leftNode;
        }
        //step1:选择出未来的头节点,作为返回节点
        Node result = null;
        Node headNode = null;
        Node nextLeftNode = null;
        Node nextRightNode = null;
        if (compareTo(leftNode, rightNode)) {
            result = headNode = leftNode;
            nextLeftNode = headNode.getNext();
            nextRightNode = rightNode;
        } else {
            result = headNode = rightNode;
            nextLeftNode = leftNode;
            nextRightNode = rightNode.getNext();
        }

        while (nextLeftNode != null || nextRightNode != null) {

            //左侧剩余链表和右侧剩余链表比对
            while (nextLeftNode != null && nextRightNode != null && compareTo(nextLeftNode, nextRightNode)) {
                headNode.setNext(nextLeftNode);
                headNode = nextLeftNode;
                nextLeftNode = nextLeftNode.getNext();
            }

            //右侧剩余链表头节点和左侧剩余链表头节点比对
            while (nextLeftNode != null && nextRightNode != null && compareTo(nextRightNode, nextLeftNode)) {
                headNode.setNext(nextRightNode);
                headNode = nextRightNode;
                nextRightNode = nextRightNode.getNext();
            }

            //如果两个链表都不为空则进行下一轮比对
            if (nextLeftNode != null && nextRightNode != null) {
                continue;
            }

            //如果链表中已经有一个链表完全合并到主链表中了
            while (nextLeftNode != null) {
                headNode.setNext(nextLeftNode);
                headNode = nextLeftNode;
                nextLeftNode = nextLeftNode.getNext();
            }

            while (nextRightNode != null) {
                headNode.setNext(nextRightNode);
                headNode = nextRightNode;
                nextRightNode = nextRightNode.getNext();
            }
        }

        return result;
    }

    /**
     * 左节点比右节点小
     *
     * @param leftNode
     * @param rightNode
     * @return
     */
    public static boolean compareTo(Node leftNode, Node rightNode) {
        if (leftNode.getData().compareTo(rightNode.getData()) <= 0) {
            return true;
        }
        return false;
    }


   
}


class Node {

    private String data;
    private Node next;

    public String getData() {
        return data;
    }

    public void setData(String data) {
        this.data = data;
    }

    public Node getNext() {
        return next;
    }

    public void setNext(Node next) {
        this.next = next;
    }
}
View Code

4、删除链表倒数第 n 个结点

5、求链表的中间结点

posted on 2020-06-30 08:07  无信不立  阅读(196)  评论(0)    收藏  举报

刷新页面返回顶部
 
博客园  ©  2004-2026
浙公网安备 33010602011771号 浙ICP备2021040463号-3