时空间复杂度,数组链表

时间复杂度的含义 :假设每行代码执行时间是一定的,都是unit,那有一个很重要的规律:
所有代码的执行时间 T(n) 与所有行代码的执行次数 n 成正比:T(n) = O(f(n))
注意:这里仅仅从代码行数的角度考虑,所以你是简单的计算,还是从磁盘读取100M的文件,我们粗略认为两者执行时间都是unit;

时间复杂度的全称是渐进时间复杂度,表示算法的执行时间与数据规模之间的增长关系。类比一下,空间复杂度全称就是渐进空间复杂度(asymptotic space complexity),表示算法的存储空间与数据规模之间的增长关系。

T(n) = O(1)  
O(logn): 实例:  i=1; while (i <= n)  {   i = i * 2; }
O(n)  :一次遍历
O(nlogn):一次遍历里面嵌套 O(logn)de实例
O(n2)场景:两次遍历

有项目之前会进行性能测试,再做代码的时间复杂度、空间复杂度分析,是不是多此一举呢?
1)空间h和时间复杂度分析为我们提供了一个很好的理论分析的方向,并且它是宿主平台无关的,能够让我们对我们的程序或算法有一个大致的认识。
2)渐进式时间,空间复杂度分析只是一个理论模型,只能提供给粗略的估计分析,我们不能直接断定就觉得O(logN)的算法一定优于O(n)
总结:性能测试和时间、空间复杂度应该相辅相成。


length为n的数组,有一个值为a,需要找到a对应下标,从下标0开始遍历查找
最大时间复杂度 :场景最坏的时间复杂度,即数组最后是a,查了n次
最小时间复杂度 :场景最优的时间复杂度,数组第一个就是啊,查了一次
平均时间复杂度:综合考虑所有场景及每个场景出现的概率得出的结果,每个位置概率为1/n;查询次数分别为 1,2,3...;平均查找次数1/n*(1+2+3+..)=(n+1)/2,故为O(n)
平摊时间复杂度:大小为n的ArrayList,插入数据,超出容量需扩容;1-n次插入都是直接插入,但是第n+1次插入需要,先扩容,后迁移n次,但是如果将最后一次平摊到所有n次插入,复杂度就是O(1),所以整体O(1)


为什么数组的下标从0开始?
a[k]_address = base_address + (k)*type_size
java中[int]是正常存成的
又:
C 语言设计者用 0 开始计数数组下标,之后的 Java、JavaScript 等高级语言都效仿了C语言;


a[k]_address = base_address + (k)*type_size
那java中String[2]上面的公式怎么确定的呢?
String的话,数组中存的是地址,而不是值
这是因为:编程语言中的”数组“并不完全等同于,我们在讲数据结构和算法的时候,提到的”数组“。编程语言在实现自己的”数组“类型的时候,并不是完全遵循数据结构”数组“的定义,而是针对编程语言自身的特点,做了调整。
可参考:https://mp.weixin.qq.com/s/E-c41h2v_AfffrlAQpkyLg

单向链表:
节点:存储数据 + 记录下一节点的指针
头结点:记录链表的基地址
尾节点:链表最后一个节点,节点指向空地址NULL

循环链表:
循环链表的尾节点指向链表的头结点

双向链表:
节点: 记录上一节点的指针+数据+记录下一节点的指针
双向链表好处:
1)可以方便获取节点的前节点,以节点删除为例:
删除给定指针指向的节点 p
单向链表:需要先从第一个遍历,找到p的前节点node.next=p;
双向链表:直接定位p的前节点:node =p.before ;

有序链表查找,可以根据大小来判断查找的方向;

如何判断两个单向链表是否相交?直接遍历两个链表,尾部相同就相交;

cpu缓存机制中的数组和链表:
数组简单易用,在实现上使用的是连续的内存空间,可以借助 CPU 的缓存机制,预读数组中的数据,所以访问效率更高。而链表在内存中并不是连续存储,所以对 CPU 缓存不友好,没办法有效预读。

用空间换时间的设计思想
          数组 链表
插入删除  n    1
随机访问  1    n

链表与 FIFO LFU LRU
LFU least frequently used:最少使用策略
LRU least recently used:最近使用策略
LRU怎么实现?
链表 :新加入放入头部
数据已经存在,原节点移除,新节点放到头部
数据未存在:
如果已经达到最大值,先删除尾部,后插入头结点;
未达到最大值,直接


如何判断一个字符串是否是回文字符串的问题,我想你应该听过,我们今天的题目就是基于这个问题的改造版本。如果字符串是通过单链表来存储的,那该如何来判断是一个回文串呢?你有什么好的解决思路呢?相应的时间空间复杂度又是多少呢?
单链表存储呢?      --快慢指针遍历+前半部分链表反转
数组存储呢?         直接 i和n-1-i比较
栈存储呢?           遍历压栈 然后出战与原链表比较


链表思路好掌握,但代码十分难写,我精选了5个常见的链表操作。你只要把这几个操作都能写熟练,不熟就多写几遍,我保证你之后再也不会害怕写链表代码。
单链表反转
求链表的中间结点
两个有序的链表合并
删除链表倒数第n个结点
链表中环的检测

链表的代码写法,注意考虑边界条件
空 1节点 2节点 操作头结点 操作尾节点

链表反转:
直接反转源链表:
1 暂存node的next
2 改变node的next指向
3 暂存反转好的部分,为下次反转做准备
4 node走到next 

链表合并部分:
一定是先找到起始节点,然后开始合并,否则理清很难


环监测的思考
1 被指向次数即可,存放到head中 时间复杂度O(n),空间复杂度O(n)
2 O(1)解决此问题,
1)node中value存放被指向次数即可
2)双指针法

代码实例:

public class LinkedTest {

    public static void main(String[] args) {
//        // 正序删除节点
//        Node node6 = new Node('f',null);
//        Node node5 = new Node('e',node6);
//        Node node4 = new Node('d',node5);
//        Node node3 = new Node('c',node4);
//        Node node2 = new Node('b',node3);
//        Node node1 = new Node('a',node2);
//        ergodicNode(node1);
//        Node  ret = deleteNode(node1, 1);
//        ergodicNode(ret);
//        ret = deleteNode(ret, 5);
//        ergodicNode(ret);
//        ret = deleteNode(ret, 3);
//        ergodicNode(ret);

        // 倒序删除节点
//        Node node6 = new Node('f',null);
//        Node node5 = new Node('e',node6);
//        Node node4 = new Node('d',node5);
//        Node node3 = new Node('c',node4);
//        Node node2 = new Node('b',node3);
//        Node node1 = new Node('a',node2);
//        ergodicNode(node1);
//        Node  ret = deleteBackNode(node1, 6);
//        ergodicNode(ret);
//        ret = deleteBackNode(ret, 1);
//        ergodicNode(ret);
//        ret = deleteBackNode(ret, 3);
//        ergodicNode(ret);

        // 判断回文:偶数个节点测试
//        Node node6 = new Node('a',null);
//        Node node5 = new Node('b',node6);
//        Node node4 = new Node('c',node5);
//        Node node3 = new Node('c',node4);
//        Node node2 = new Node('b',node3);
//        Node node1 = new Node('a',node2);
//        System.out.println(isPlindrome(node1));
//        System.out.println(isPlindromeNew(node1));
//        ergodicNode(node3);
//        ergodicNode(node4);

        // 判断回文:奇数个节点测试
//        Node node5 = new Node('a',null);
//        Node node4 = new Node('b',node5);
//        Node node3 = new Node('c',node4);
//        Node node2 = new Node('b',node3);
//        Node node1 = new Node('a',node2);
//        System.out.println(isPlindrome(node1));
//        System.out.println(isPlindromeNew(node1));
//        ergodicNode(node3);
//        ergodicNode(node4);

        // 合并测试
//        Node node6 = new Node('e',null);
//        Node node5 = new Node('c',node6);
//        Node node4 = new Node('b',node5);
//        Node node3 = new Node('d',null);
//        Node node2 = new Node('c',node3);
//        Node node1 = new Node('a',node2);
//        ergodicNode(node1);
//        ergodicNode(node4);
//        Node node = mergeLinked(node1, node4);
//        ergodicNode(node);
//        ergodicNode(node1);
//        ergodicNode(node4);

        // 节点插入与删除
//        Node node5 = new Node('e',null);
//        Node node4 = new Node('d',node5);
//        Node node3 = new Node('c',node4);
//        Node node2 = new Node('b',node3);
//        Node node1 = new Node('a',node2);
//        insertSecond(node1);
//        ergodicNode(node1);
//        deleteSecond(node1);
//        ergodicNode(node1);

        // 指针反转调用实例
//        Node node5 = new Node('e',null);
//        Node node4 = new Node('d',node5);
//        Node node3 = new Node('c',node4);
//        Node node2 = new Node('b',node3);
//        Node node1 = new Node('a',node2);
//        ergodicNode(node1);
//        Node newLinked = reverseOriginLinked(node1);
//        // 输出 e,d,c,b,a, 说明返回的指针确实是反转后的
//        ergodicNode(newLinked);
//        // 输出a 说明确实是原链表被反转了
//        ergodicNode(node1);

        // 判断有环
        Node node4 = new Node('d',null);
        Node node3 = new Node('c',node4);
        Node node2 = new Node('b',node3);
        Node node1 = new Node('a',node2);
        node3.setNext(node1);
        System.out.println(getCycleWithHash(node1));
        System.out.println(getCycleWithDouPoint(node1));
    }

    /**
     * desc:统计指向次数,头结点被指向或非头结点被指向两次
     */
    public static int getCycleWithHash(Node node){
        HashMap<Node, Integer> nodes = new HashMap<>();
        Node head = node;
        int i =0;
        while(node != null){
            i++ ;
            node = node.next;
            if(node == head){
                return 0;
            }
            if(nodes.containsKey(node)){
                return nodes.get(node);
            }else{
                nodes.put(node, i);
            }
        }
        return -1;
    }

    /**
     * desc:快慢指针法,环内总能相遇
     */
    public static boolean getCycleWithDouPoint(Node node){
        Node head = node;
        Node fast = node;
        Node slow = node;
        while(fast != null && fast.next != null){
            fast = fast.next.next;
            slow = slow.next;
            if(fast == slow){
                return true;
            }
        }
        return false;
    }

    /**
     * desc:删除链表第n个结点
     */
    public static Node deleteBackNode(Node node, int n){
        int length = getNodeLength(node);
        // 倒序转正序,如删除倒数第一个,就是删除正序第n个
        int sequeal  = length + 1 - n;
        return deleteNode(node,sequeal);
    }

    /**
     * desc:删除链表第n个结点
     */
    public static Node deleteNode(Node node, int n){
        int length = getNodeLength(node);
        if(n <=0 || n>length){
            return node;
        }
        Node ret = node;
        // 删除头
        if(n == 1){
            node = node.next;
            return node;
        }
        // 删除尾
        if(n == length){
            while(n > 2){
                node = node.next;
                n--;
            }
            node.next = null;
            return ret;
        }
        // 中间删除
        // 这里很特别,采坑
        while(n>2){
            node = node.next;
            n--;
        }
        node.next = node.next.next;
        return ret;
    }

    /**
     * desc:链表合并
     */
    public static Node mergeLinked(Node node1, Node node2){
        if(node1 == null ){
            return node2;
        }
        if(node2 == null ){
            return node1;
        }
        // 先找到起始点,这个很关键,否则合并部分很难理清
        Node tmp = null;
        Node ret = null;
        if(node1.getValue() > node2.getValue()){
            ret = node2;
            tmp = node2;
            node2 = node2.next;
        }else{
            ret = node1;
            tmp = node1;
            node1 = node1.next;
        }

        while(node1 != null && node2 != null){
            if(node1.getValue() < node2.getValue()){
                // 改变指向
                tmp.next= node1;
                // 向后移动
                tmp = tmp.next;
                node1 = node1.next;
            }else{
                tmp.next= node2;
                tmp = tmp.next;
                node2 = node2.next;
            }
        }

        //尾部处理,初次书写就丢了
        if(node1 == null){
            tmp.next= node2;
        }else{
            tmp.next= node1;
        }
        return ret;
    }

    /**
     * desc:插入节点,未考虑空,两次修改指针
     */
    public static  void insertSecond1(Node node ){
        Node s = new Node('s', null);
        Node originSecond = node.next;
        node.next = s;
        s.next = originSecond;
    }

    /**
     * desc:考虑空,一次修改指针
     */
    public static  void insertSecond(Node node ){
        if(node == null){
            node = new Node('s', null);
        }else{
            Node s = new Node('s', node.next);
            node.next = s;
        }
    }

    public static  void deleteSecond(Node node ){
        if(node == null || node.next == null){
            node = null;
        }else{
            node.next = node.next.next;
        }
    }

    /**
     * desc:链表反转,直接反转源链表
     */
    public static  Node reverseOriginLinked(Node node){
        Node newLinked = null;
        Node node1 = null;
        while(node != null){
            // 暂存指针
            node1 = node.next;
            // 反转指针
            node.next = newLinked;
            // 保存反转后的结果
            newLinked = node;
            // 指针前进
            node = node1;
        }
        return newLinked;
    }

    /**
     * desc:创建新的链表,达到反转的效果,本质上原链表未修改
     */
    public static  Node reverseLinked(Node node){
        Node start = new Node(node.getValue(), null);
        while(node.next != null){
            node = node.next;
            start = new Node(node.getValue() ,start);
        }
        return start;
    }

    /**
     * desc:遍历链表,打印值
     */
    public static  void ergodicNode(Node node){
        while(node != null){
            System.out.print(node.getValue()+",");
            node = node.next;
        }
        System.out.println();
    }

    /**
     * desc:判断是否 回文字符串,通过建立新的节点实现,不推荐
     */
    public static boolean isPlindrome(Node node){
        Node fast = new Node(node.getValue(),node.getNext());
        Node slow = new Node(node.getValue(),node.getNext());

        Node slowTemp = new Node(slow.getValue(),null);
        while( fast != null && fast.getNext() != null ){
            fast = fast.getNext().getNext();
            slow = slow.getNext();
            slowTemp = new Node(slow.getValue(),slowTemp);
        }
        // 偶数个函数调整
        int i = 0;
        while( node != null){
            node = node.getNext();
            i++;
        }
        if(i%2 == 0){
            slowTemp = slowTemp.getNext();
        }

        while(slow != null && slowTemp !=null){
            if(slow.getValue() != slowTemp.getValue()){
                return false;
            }
            slow = slow.getNext();
            slowTemp = slowTemp.getNext();
        }
        return true;
    }

    /**
     * desc:假设偶数个节点
     */
    public static boolean isPlindromeNew(Node node){
        if(node == null){
            return false;
        }

        boolean isOdd = isOddSize(node);

        Node fast = node;
        Node slow = node;
        Node reverse = null;
        Node tmp = null;
        while(fast != null && fast.next != null){
            fast = fast.next.next;

            tmp = slow.next;
            slow.next = reverse;
            reverse = slow;
            slow = tmp;
        }
        // 奇数长度过中间节点不比较,直接跳过
        if(isOdd){
            slow = slow.next;
        }

        while(reverse != null){
            if(reverse.getValue() != slow.getValue()){
                return false;
            }
            reverse = reverse.next;
            slow = slow.next;
        }
        return true;
    }

    /**
     * desc:获取节点长度
     */
    public static int getNodeLength(Node node){
        int length = 0;
        while(node != null){
            node = node.next;
            length ++;
        }
        return length;
    }

    /**
     * desc:判断是否奇数个长度
     */
    public static  boolean isOddSize(Node node){
        int length = getNodeLength(node);
        if(length % 2 == 1){
            return true;
        }
        return false;
    }

    /**
     * desc:定义指针节点
     */
    static class Node{
        char value;
        Node next;

        public Node(char value, Node next){
            this.value = value;
            this.next = next;
        }

        public  char getValue(){
            return value;
        }

        public  Node getNext(){
            return next;
        }
        public  void setNext(Node next){
             this.next = next;
        }
    }
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值