被终止的算法课~

DAY1

DAY2(认识复杂度和简单排序算法)

时间复杂度:常数时间指的是和数据量无关的操作所用的时间。在O(f(n))中只要高阶项。 评价算法流程的指标。

先看时间复杂度指标,如果相同,进行实际操作进行比较。

选择排序

    public static void selectionSort(int[] arr){
        if(arr==null||arr.length<2){
            return;
        }
        for(int i=0;i<arr.length-1;i++){
            int minIndex=i;
            for(int j=i+1;j<arr.length;j++){
                minIndex=arr[j]<arr[minIndex]?j:minIndex;
            }
            swap(arr,i,minIndex);
        }
    }
    public static void swap(int[] arr,int i,int j){
        int temp=arr[i];
        arr[i]=arr[j];
        arr[j]=temp;
    }

冒泡排序

    public static void BubbleSort(int[] arr){
        if(arr==null||arr.length<2){
            return;
        }
        for(int e=arr.length-1;e>0;e--){
            for(int i=0;i<e;i++){
                if(arr[i]>arr[i+1]){
                    swap(arr,i,i+1);
                    }
                }
            }
    }
位运算交换两数(原理和性质相关)-值可以相等,内存需要是两块东西
//交换arr数组中,i和j的位置
public static void swap(int[] arr,int i,int j){
    arr[i]=arr[i]^arr[j];
    arr[j]=arr[i]^arr[j];
    arr[i]=arr[i]^arr[j];
}
//抖机灵写法,不用开辟新的内存空间

tips:异或^运算可以理解成无进位相加。(二进制中)

0 ^ N = N , N ^ N = 0

异或运算满足交换律和结合律.

提取一个数最右边的1

int rightOne=eor&(~eor+1);

插入排序

//本质是小范围有序,加入一个数,比较后插入对应位置
public static void insertionSort(int[] arr) {
    if (arr == null || arr.length < 2) {
        return;
    }
    for (int i = 1; i < arr.length; i++) {
        for (int j = i - 1; j >= 0 && arr[j] > arr[j + 1]; j--) {
            swap(arr,j,j+1);
        }
    }
}

二分法查找

(不熟练,自己学)

循环不变量和左闭右闭,左开右开的写法。

left right 和mid的大小关系。

由mid下标所在取值来看是否和while中定义的条件冲突,需要去符合条件。

对数器

想测的方法A---->好实现,好想通的方法B

生成一个随机样本产生器

大数据不一样----》缩小数据,人工干预

调整对了以后再用大量数据测试--》成功即正确

DAY3(认识O(NlogN)的排序)

三种不同顺序的遍历:

DLR--前序遍历(根在前,从左往右,一棵树的根永远在左子树前面,左子树又永远在右子树前面 )
​
LDR--中序遍历(根在中,从左往右,一棵树的左子树永远在根前面,根永远在右子树前面)
​
LRD--后序遍历(根在后,从左往右,一棵树的左子树永远在右子树前面,右子树永远在根前面)
//arr[L,R]范围上求最大值【递归加分治,查找最大值二分,L=R是终止条件】
    public static int process1(int[] arr,int L,int R){
        if(L==R){
            return arr[L];
        }
        int mid=L+((R-L)>>1);
        int maxLeft=process1(arr,L,mid);
        int maxRight=process1(arr,mid+1,R);
        return Math.max(maxLeft,maxRight);
    }

master公式:

T(N)=a * T(N/b)+O(N的d方)

母 = 调用次数*子+剩下过程的时间复杂度

归并排序

//归并排序--->先左右各部分排序,再归并MergeSort
//外排序的方法-》新开辟了一块空间
//master公式求时间复杂度
//时间复杂度 O(N*logN)  额外空间复杂度O(N)
    public static void process2(int[] arr,int L,int R){
        if(L==R){
            return;
        }
        int mid=L+((R-L)>>1);
        process2(arr,L,mid);
        process2(arr,mid+1,R);
        merge(arr,L,mid,R);
    }
    public static void merge(int[] arr,int L,int M,int R){
        int[] help=new int[R-L+1];
        int i=0;
        int p1=L;
        int p2=M+1;
        while(p1<=M&&p2<=R){
            help[i++]=arr[p1]<=arr[p2]?arr[p1++]:arr[p2++];
        }
        while(p1<=M){
            help[i++]=arr[p1++];
        }
        while(p2<=R){
            help[i++]=arr[p2++];
        }
        for(i=0;i<help.length;i++){
            arr[L+i]=help[i];
        }
    }

相较于归并排序,前几种排序浪费了大量的比较行为。

拓展:小和问题(相等情况先拷贝右边),逆序对问题(改写罢了,原理一样)

//小和问题(改写归并排序)
    public static int process3(int[] arr,int l,int r){
        if(l==r){
            return 0;
        }
        int mid=l+((l-r)>>1);
        return process3(arr,1,mid)+process3(arr,mid+1,r)+merge2(arr,l,mid,r);
    }
    public static int merge2(int[] arr,int L,int M,int R){
        int[] help=new int[R-L+1];
        int i=0;
        int p1=L;
        int p2=M+1;
        int res=0;
        while(p1<=M&&p2<=R){
            res+=arr[p1]<arr[p2]?(R-p2+1)*arr[p1]:0;
            help[i++] = arr[p1] < arr[p2]?arr[p1++]:arr[p2++];//相等先拷贝右组
        }
        while(p1<=M){
            help[i++]=arr[p1++];
        }
        while(p2<=R){
            help[i++]=arr[p2++];
        }
        for(i=0;i<help.length;i++){
            arr[L+i]=help[i];
        }
        return res;
    }

快速排序(最坏O(N2))

随机快排3.0版本O(NlgoN)

(荷兰国旗问题)

快排有点像二分

运算规划取决于划分值的取值

//快速排序
    //arr[L,R]排好序
    public static void quickSort(int[] arr,int L,int R){
        if(L<R){
            swap(arr,L+(int)(Math.random()*(R-L+1)),R);//3.0版本,随机选一个数
            int[]p=partition(arr,L,R);
            quickSort(arr,L,p[0]-1);
            quickSort(arr,p[1]+1,R);
        }
    }
    //这是一个处理arr[L,R]的函数,默认以arr[r]做划分,arr[r]->p  <p  ==p  >p
    //返回等于区域(左边界,右边界),所以返回一个长度为2的数组res,res[0],res[1]
    public static int[] partition(int[] arr,int L,int R){
        int less=L-1;//<区的边界,向右动
        int more=R;//>区的边界,向左动
        while(L<more){//L表示当前数的位置,arr[R]->划分值
            if(arr[L]<arr[R]){
                swap(arr,++less,L++);
            }else if(arr[L]>arr[R]){
                swap(arr,--more,L);
            }else{
                L++;
            }
            swap(arr,more,R);
            return new int[] {less+1,more};
        }
    }
    public static void swap(int[] arr,int i,int j){
        arr[i]=arr[i]^arr[j];
        arr[j]=arr[j]^arr[i];
        arr[i]=arr[i]^arr[j];
    }

DAY4(详解桶排序及排序内容总结)

tips:快速排序空间复杂度最大使O(N),开了N层划分.最小是O(logN)

堆排序

完全二叉树/满二叉树

数组从0出发的连续的一段可看作一颗完全二叉树

大根堆:以某个结点为头的整棵树,最大值是这个结点的值

小根堆:同理改成小。

如何构建大根堆->根据i位置和i的父位置i-1/2比较,循环比较交换,终止条件是i位置小于父位置i-1/2的值.(heapinsert过程 )

heapinsert:
//大根堆构建
    public static void heapInsert(int[] arr,int index){
        while(arr[index]>arr[(index-1)/2]){
            swap(arr,index,(index-1)/2);
            index=(index-1)/2;
        }
    }
heapify
//取出大根堆的最大值,并不改变大根堆的特性.heapify堆化
    public static void heapify(int[] arr,int index,int heapSize){
        int left=index*2+1;//左孩子的下标
        while(left<heapSize){//下方还有孩子,因为左孩子下标肯定小于右孩子下摆,所以判断左孩子即判断有没有孩子
            //两个孩子中,谁的值大就把下标给largest
            int largest=left+1<heapSize&&arr[left+1]>arr[left]?left+1:left;
            //父和孩子之间,谁的值大,谁把下标给largest
            largest=arr[largest]>arr[index]?largest:index;
            if(largest==index){
                break;
            }
            swap(arr,largest,index);
            index=largest;
            left=index*2+1;
        }
    }

堆最基础的两个方法。

堆排序

O(NlogN)

//堆排序的coding
    public static void heapSort(int[] arr){
        if(arr==null&&arr.length<2){
            return;
        }
        for(int i=0;i<arr.length;i++){
            heapInsert(arr,i);
        }
        int heapSize=arr.length;
        swap(arr,0,--heapSize);
        while(heapSize>0){
            heapify(arr,0,heapSize);
            swap(arr,0,--heapSize);
        }
    }

在时间复杂度O(NlogN)的级别上只有堆排序能做到空间复杂的O(1)

时间复杂度和完全二叉树高度有关

拿到数组可以不用从第一个开始插入heapInsert,可以从最后开始heapify(这一步时间复杂度为O(N)),只有第一部变快了一点。

可修改代码为:

//堆排序的coding
    public static void heapSort(int[] arr){
        if(arr==null&&arr.length<2){
            return;
        }
        /*for(int i=0;i<arr.length;i++){
            heapInsert(arr,i);
        }*/
        for(int i=arr.length-1;i>=0;i--){
            heapify(arr,i,arr.length);
        }
        int heapSize=arr.length;
        swap(arr,0,--heapSize);
        while(heapSize>0){
            heapify(arr,0,heapSize);
            swap(arr,0,--heapSize);
        }
    }

小根堆,java中:

        //优先队列,本质就是小根堆
        PriorityQueue<Integer> heap=new PriorityQueue<Integer>();
	    //如果要大根堆,传入比较器
	    PriorityQueue<Integer> heap=new PriorityQueue<Integer>(new AComp());
	   //对应方法自己查

手写堆和用java提供的数据结构堆的区别就是,java给的细粒化程度低,相当于黑盒,手写可以自由操控。

手写堆有的题目必须用,很重要!!

比较器的使用

(不熟悉,要加强练习,比较器几乎没用过)

pubic static class IdAscendingCompartor implements Comparator<Student>{
    //返回负数的时候,第一个参数排在前面
    //返回正数的时候,第二个参数排在前面
    //返回0的时候,谁在前面无所谓
    @Override
    public int compare(Student o1,Student o2){
        //理解不了就从 if(o1>o2)  return 1;开始推
        return o1.id-o2.id;
        //return o2.id-o1.id; 
    }
}
//系统排序(在main方法里)
Arrays.sort(students,new IdAscendingComparator()); 

计数排序

词频统计

基数排序(桶排序,桶就是容器,可以用不同的数据结构)--有点难理解,但讲解过后也还好。

不基于比较的排序,一定要看数据的状况,进行定制

代码:

    //桶排序(基数排序)
    public static void radixSort(int[] arr,int L,int R,int digit){
        final int radix=10;
        int i=0,j=0;
        //有多少数准备多少个辅助空间
        int[] bucket=new int[R-L+1];
        //有多少位就进出几次
        for(int d=1;d<=digit;d++){
            //10个空间
            //count[0]当前位(d位)是0的数字有多少个
            //count[1]当前位(d位)是(0和1)的数字有多少个
            //count[2]当前位(d位)是(0,1,2)的数字有多少个
            //count[i]当前位(d位)是(0~i)的数字有多少个
            int[] count=new int[radix];
            for(i=L;i<=R;i++){
               // j=getDigit(arr[i],d);
                count[j]++;
            }
            for(i=1;i<radix;i++){
                count[i]=count[i]+count[i-1];
            }
            for(i=R;i>=L;i--){
              //  j=getDigit(arr[i],d);
                bucket[count[j]-1]=arr[i];
                count[j]--;
            }
            for(i=L,j=0;i<=R;i++,j++){
                arr[i]=bucket[j];
            }
        }
    }

精妙的算法!可惜我不会......

DAY5

看牙去了,摸鱼一天。。。。。

希尔排序-》多轮插入排序

排序算法多的稳定性:

排序前后相同数的相对次序。

非基础类型排序非常有用。

选择排序不能,冒泡排序可以, 插入排序可以,归并可以(NlogN),主要注意处理相等时候的做法

快排做不到稳定性,堆排序做不到稳定性

排序总结:

工程上改进,例如小样本量用插入排序,大样本量用快排

DAY6(链表)

哈希表

无序组织

map和key本质一样,map是key value对应的。

Hashmap的增删改查都是常数级别时间复杂度O(1)。

按放入哈希表的数据类型可分为:直接传值(基础类型),传引用(地址8位).

有序表

(TreeMap/TreeSet)-->java中

有序组织!,性能是O(logN)级别。

按放入有序表的数据类型可分为:直接传值,值大小比较排序(基础类型),比较器比较(引用地址传递).

链表

Coding:反转单向链表和反转双向链表自己练(注意返回值)

Coding:打印两个有序链表的公共部分。

tips:额外数据结构记录(哈希表),快慢指针

Coding:回文链表。

Coding:复制带随机指针的链表/第二种方法

+++二叉树今天第二节课

就听了个题目

DAY7

返回两个不知是否有环链表若相交的第一个入环节点:

链表是否有环

有环:

返回环的入结点(借助HashSet实现,不重复特点)

不借助Hashset,快2慢1指针,若有环一定会相遇。-》之后快指针到头结点,开始快慢各一次走1,一定在入环结点相遇。

Coding:

    //找到链表第一个入环节点,如果无环,返回null
    public static Node getLoopNode(Node head){
        if(head==null||head.next==null||head.next.next==null){
            return null;
        }
        //初始条件是不能相等的
        Node n1=head.next;
        Node n2=head.next.next;
        while(n1!=null){
            if(n2.next==null||n2.next.next==null){
                return null;
            }
            n2=n2.next.next;
            n1=n1.next;
        }
        //快指针移动到头部
        n2=head;
        while(n1!=n2){
            n1=n1.next;
            n2=n2.next;
        }
        return n1;
    }

两个链表都无环,即=null:

各自遍历到最后结点,分别统计长度,判断两链表最后一个结点是否相同.

相同则重新回到起点,长的链表先走(长-短步)

Coding:

//如果两个链表都无环,找到第一个相交节点,如果不相交,返回null
    public static Node noLoop(Node head1,Node head2){
        if(head1==null||head2==null){
            return null;
        }
        int n=0;
        Node cur1=head1;
        Node cur2=head2;
        while(cur1!=null){
            n++;
            cur1=cur1.next;
        }
        while(cur2!=null){
            n--;
            cur2=cur2.next;
        }
        if(cur1!=cur2){
            return null;
        }
        cur1=n>0?head1:head2;//cur1变成长链表的头
        cur2=cur1==head1?head2:head1;//cur2变成短链表的头
        n=Math.abs(n);
        while(n!=0){
            n--;
            cur1=cur1.next;
        }
        while(cur1!=cur2){
            cur1=cur1.next;
            cur2=cur2.next;
        }
        return cur1;
    }

两个链表都有环,但是不相交:

共用环,但是入环结点相同;

共用环,入环结点不同:(这类题思路很重要,代码可以复用 )

//两个有环链表,返回第一个相交节点,如果不相交返回null
    public static Node bothLoop(Node head1,Node loop1,Node head2,Node loop2){
        Node cur1=null;
        Node cur2=null;
        if(loop1==loop2){
            cur1=head1;
            cur2=head2;
            int n=0;
            while(cur1!=loop1){
                n++;
                cur1=cur1.next;
            }
            while(cur2!=loop2){
                n--;
                cur2=cur2.next;
            }
            cur1=n>0?head1:head2;
            cur2=cur1==head1?head2:head1;
            n=Math.abs(n);
            while(n!=0){
                n--;
                cur1=cur1.next;
            }
            return cur1;
        }else {
            cur1=loop1.next;
            while(cur1!=loop1){
                if(cur1==loop2){
                    return loop1;
                }
                cur1= cur1.next;
            }
        }
        return null;
    }

DAY8(二叉树继续)

7/26!!MC玩腻了,糖糖复活!!!

递归方法遍历二叉树

递归序中每个结点经过三次。

先序遍历(简化递归序,第一次来到结点打印,头左右的顺序)

中序遍历(简化递归序,第二次来到结点打印,左头右的顺序)

后序遍历(简化递归序,第三次来到结点打印,左右头的顺序)

将递归代码中按递归序分成三个区域:

    public static void preOrderRecur(TreeNode head){
        if(head==null){
            return;
        }
        //第一次经过结点
        System.out.println(head.val);
        preOrderRecur(head.left);
        //第二次经过结点
        preOrderRecur(head.right);
        //第三次经过结点
    }
非递归遍历二叉树

借助栈来实现:

先序遍历

//非递归先序遍历
public static void preOrderUnRecur(TreeNode head){
    if(head==null){
        return;
    }
    Stack<TreeNode> stack=new Stack<TreeNode>();
    stack.push(head);
    while(!stack.isEmpty()){
        head=stack.pop();
        System.out.println(head.val);
        if(head.right!=null){
            stack.push(head.right);
        }
        if(head.left!=null){
            stack.push(head.left);
        }
    }
}

后序遍历

借助另一个收集栈,先左后右,把打印步骤换成入栈,最后打印收集栈,即得入栈的逆序,即左右头,后序遍历实现.

    public static void posOrderUnRecur(TreeNode head){
        if(head==null){
            return;
        }
        Stack<TreeNode> stack1=new Stack<>();
        Stack<TreeNode> stack2=new Stack<>();
        stack1.push(head);
        while(stack1!=null){
            head=stack1.pop();
            stack2.push(head);
            if(head.left!=null){
                stack1.push(head.left);
            }
            if(head.right!=null){
                stack1.push(head.right);
            }
            while(stack2!=null){
                System.out.print(stack2.pop()+" ");
            }
        }
    }

中序遍历

注意左边界的概念:

public static void inOrderUnRecur(TreeNode head){
    if(head==null){
        return;
    }
    Stack<TreeNode> stack=new Stack<>();
    while(!stack.isEmpty()||head!=null){
        if(head!=null){
            stack.push(head);
            head=head.left;
        }else {
            head=stack.pop();
            System.out.println(head);
            head=head.right;
        }
    }
}

能这么写的原因是树能够被左边界完全分解。

这几种遍历方法能够自己写出来!!!!

直观打印一颗二叉树

如何完成二叉树的宽度优先遍历(求一颗二叉树的宽度)

宽度优先遍历就是横着遍历:

//借助队列实现
public static void Width(Node head){
    if(head==null){
        return;
    }
    Queue<Node> queue=new LinkedList<>();
    //JAVA中的队列就是LinkedList
    while(!queue.isEmpty()){
        Node cur=queue.poll();
        sout
        if(cur.left!=null){
            queue.add(head.left);
        }
        if(cur.right!=null){
            queue.add(head.right);
        }
    }
}

求最大宽度:(借助哈希表实现,每个结点记录对应的层数,遍历时如果层数相同,则当前宽度+1,若不同则更新|每次让下个入栈的结点入表,记录对应的层数为当前层数+1)

代码:

    //求二叉树的最大宽度
    public static int BinaryWidth(TreeNode head){
        if(head==null){
            return 0;
        }
        Queue<TreeNode> queue=new LinkedList<>();
        queue.add(head);
        HashMap<TreeNode,Integer> levelMap=new HashMap<>();
        levelMap.put(head,1);
        int curLevel=1;
        int curLevelNodes=0;
        int max=Integer.MIN_VALUE;
        while(!queue.isEmpty()){
            head=queue.poll();
            int curNodeLevel=levelMap.get(head);
            if(curNodeLevel==curLevel){
                curLevelNodes++;
            }else{
                max=Math.max(curLevelNodes,max);
                curLevelNodes=1;
            }
            if(head.left!=null){
                levelMap.put(head.left,curLevel+1);
                queue.add(head.left);
            }
            if(head.right!=null){
                levelMap.put(head.right,curLevel+1);
                queue.add(head.right);
            }
        }
        max=Math.max(max,curLevelNodes);
        return max;
    }

DAY9

2023/8/06

如何判断一棵树是搜索二叉树?

搜索二叉树:一颗树里面,每一颗子树,左树都比节点小,右树都比节点大。

如何判断一棵树是完全二叉树?

按照宽度遍历:

1.任意结点,有右无左,返回false。

2.出现一个结点有左无右(不双全),后面所以结点都是叶节点(无左右孩子)

如何判断一棵树是不是满二叉树?

最大深度l,节点数N。

N=2(l次方) -1

如何判断一棵树是否是平衡二叉树?(二叉树题目套路)

停止~重心转移到另外的算法课上

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值