算法图解学习笔记

算法图解

第一章 算法简介

1.1二分查找

输入需要是一个有序的元素列表。如果要查找的元素包含在列表中,二分查找返回其位置;否则返回null

一般而言,对于包含n个元素的列表,用二分查找最多需要log(n)步,而简单查找最多需要n步。【注:此处的log指的是以2为底,n为参数的对数运算】

函数binary_search接受一个有序数组和一个元素。如果指定的元素包含在数组中,这个函数将返回其位置。你将跟踪要在其中查找的数组部分——开始时为整个数组。

查询方法

 private static int search(int[] array,int find){
        //定义开始位置
        int begin=0;
        //定义结束的位置
        int end=array.length-1;

        //当开始位置在结束位置左侧时
        while(begin<=end){
            //找到中间位置
            int mid=(begin+end)/2;
            //根据索引找到对应的在数组中的元素
            int res=array[mid];
            if (res==find){
                return mid; //如果需要查询的元素与mid索引照应的元素相同,则返回索引
            }else if(res>find){
                end=mid-1;  //猜的数字大了,数在前半部分
            }else {
                begin=mid+1;//猜的数字小了,数在后半部分
            }
        }

        return -1;
    }

print输出结果方法

private static void print(int[] array, int index, int item) {
        if (index == -1)
            System.out.println("数组 "+ Arrays.toString(array) +" 中没有 " + item + " 这个元素");
        else
            System.out.println("数组 "+ Arrays.toString(array) +" 中存在 " + item + " 这个元素,它的索引为:" + index);
    }

main方法测试

public static void main(String[] args) {
        int[] array = {1,2,3,4,5};
        int item = 4;
        int index = search(array, item);
        print(array, index, item);

        int item1 = 7;
        int index1 = search(array, item1);
        print(array, index1, item1);
    }

运行结果如下图所示

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mr0nae5Y-1644745968463)(C:\Users\Zty\AppData\Roaming\Typora\typora-user-images\image-20220212155803452.png)]

1.2大O表示法

大O表示法是一种特殊的表示法,指出了算法的速度有多快

假设列表包含n个元素。简单查找需要检查每个元素,因此需要执行n次操作。使用大O表示法,这个运行时间为O(n),但是没有单位。大O表示法让你能够比较操作数,它指出了算法运行时间的增速。

注意:大O表示法说的是最糟的情形,也可以理解为最大消耗的时间,而不是说某一种理想情况下的最佳值。

下面按从快到慢的顺序列出了我们经常会遇到的5种大O运行时间。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-czvF6VIl-1644745968465)(C:\Users\Zty\AppData\Roaming\Typora\typora-user-images\image-20220212160352113.png)]

1.3小节

❑ 二分查找的速度比简单查找快得多。

❑ O(log n)比O(n)快。需要搜索的元素越多,前者比后者就快得越多。

❑ 算法运行时间并不以秒为单位。

❑ 算法运行时间是从其增速的角度度量的。

❑ 算法运行时间用大O表示法表示。

第二章 选择排序

2.1数组和链表

数组占用的是一块连续的内存区,而链表在内存中是分散的,链表的每个元素都存储了下一个元素的地址,从而使一系列随机的内存地址串在一起。

数组支持“随机访问”,即访问其中任意一个元素所消耗的时间是相同的。但是对于链表就不是了,链表只能通过头节点指针,从每一个节点,依次往下找,如果要想访问List(10000),不得不通过前面的9999个节点;访问所需的时间复杂度分别是O(1)与O(n),方式一种是“随机访问”,一种是“顺序访问”。

2.2选择排序

public class _02选择排序test {
    private static void SelectSort(int[] array){

        //for循环遍历每一个元素
        for (int i=0;i<array.length-1;i++) {
            //默认最小值为第i个元素
            int smallest=array[i];
            //对应的索引
            int index=i;

            //.从i对应的数之后开始遍历数组
            for (int j=i+1;j<array.length;j++){
               // 判断当前遍历的元素是否比定义最小的元素小,用此方式求出最小值
                if(array[j]<smallest){
                    smallest=array[j];
                    index=j;
                }
            }
            //互换位置
            int temp=array[i];
            array[i]=smallest;
            array[index]=temp;
        }
    }


    public static void main(String[] args) {
        int[] array = {5,3,2,1,4};
        SelectSort(array);
        System.out.println(Arrays.toString(array));
    }
}


2.3小节

❑ 数组的元素都在一起。

❑ 链表的元素是分开的,其中每个元素都存储了下一个元素的地址。

❑ 数组的读取速度很快。

❑ 链表的插入和删除速度很快。

第三章 递归

3.1递归

如果使用循环,程序的性能可能更高;如果使用递归,程序可能更容易理解

递归与for循环对比:

public class _03递归for {
    public static void main(String[] args) {
        for (int i = 9; i > 0; i--) {
            System.out.print(i);
        }
    }
}

输出结果:

987654321

public class _03递归 {
    public static void main(String[] args) {
        recursion(9);
    }

    private static void recursion(int n) {
        // 递归终止条件,也就是递归的出口
        if (n == 0){
            return ;
        } else {
            //递归调用
            recursion(n - 1);
            // 输出当前递归的数
            System.out.println(n);
        }
    }
}

输出结果:

123456789

原理:递归是在中执行的,栈是一种先进后出的数据结构,方法先要入栈,然后再出栈。

3.2基线条件和递归条件

由于递归函数调用自己,因此编写这样的函数时很容易出错,进而导致无限循环。

编写递归函数时,必须告诉它何时停止递归。正因为如此,每个递归函数都有两部分:基线条件(base case)和递归条件(recursive case)。递归条件指的是函数调用自己,而基线条件则指的是函数不再调用自己,从而避免形成无限循环。

基线条件可以理解为结束递归或者跳出递归的条件,递归条件即满足继续执行函数调用自己的条件。

3.3栈

调用另一个函数时,当前函数暂停并处于未完成状态。该函数的所有变量的值都还在内存中。

栈包含未完成的函数调用,每个函数调用都包含还未完成的函数。使用栈很方便,因为我们无需自己跟踪函数堆——栈完成了这项工作。

使用栈虽然很方便,但是也要付出代价:存储详尽的信息可能占用大量的内存。每个函数调用都要占用一定的内存,如果栈很高,就意味着计算机存储了大量函数调用的信息。在这种情况下,你有两种选择:

❑ 重新编写代码,转而使用循环。

❑ 使用尾递归。

假设你编写了一个递归函数,但不小心导致它没完没了地运行。正如你看到的,对于每次函数调用,计算机都将为其在栈中分配内存。递归函数没完没了地运行时,将给栈带来什么影响?

答:调用栈不断加层,会越来越长,直至计算机内存不够。

3.4小节

❑ 递归指的是调用自己的函数。

❑ 每个递归函数都有两个条件:基线条件和递归条件。

❑ 栈有两种操作:压入和弹出。

❑ 所有函数调用都进入调用栈。

❑ 调用栈可能很长,这将占用大量的内存。

第四章 快速排序

4.1分而治之

分而治之(divide and conquer, D&C)——一种著名的递归式问题解决方法。

D&C的工作原理:

(1) 找出简单的基线条件;

(2) 确定如何缩小问题的规模,使其符合基线条件。

编写涉及数组的递归函数时,基线条件通常是数组为空或只包含一个元素。陷入困境时,请检查基线条件是不是这样的。

递归求和

public class _03递归求和 {
    public static void main(String[] args) {
        int[] arr = {2, 4, 6};
        // 用递归求数组之和
        int sum = recursiongetsum(arr, 0);
        System.out.println(Arrays.toString(arr) + "中各数字之和为:" + sum);
    }

    private static int recursiongetsum(int[] arr, int index) {
        if (index == (arr.length - 1)) {
            return arr[index];
        }else {
            return arr[index] + recursiongetsum(arr, index+1);
        }
    }

}

4.2快速排序

如何对包含三个元素的数组进行排序了,步骤如下:

(1) 选择基准值。

(2) 将数组分成两个子数组:小于基准值的元素和大于基准值的元素。

(3) 对这两个子数组进行快速排序

归纳证明----一种推理证明方式

归纳证明是一种证明算法行之有效的方式,它分两步:基线条件和归纳条件。是不是有点似曾相识的感觉?例如,假设我要证明我能爬到梯子的最上面。递归条件是这样的:如果我站在一个横档上,就能将脚放到上一个横档上。换言之,如果我站在第二个横档上,就能爬到第三个横档。这就是归纳条件。而基线条件是这样的,即我已经站在第一个横档上。因此,通过每次爬一个横档,我就能爬到梯子最顶端。

对于快速排序,可使用类似的推理。在基线条件中,我证明这种算法对空数组或包含一个元素的数组管用。在归纳条件中,我证明如果快速排序对包含一个元素的数组管用,对包含两个元素的数组也将管用;如果它对包含两个元素的数组管用,对包含三个元素的数组也将管用,以此类推。因此,我可以说,快速排序对任何长度的数组都管用。它很有趣,并与D&C协同发挥作用。

快速排序(java)参考:(52条消息) 快速排序(java实现)_王玉Student的博客-CSDN博客_java快速排序

public class _04快速排序 {
    public static void quickSort(int[] a,int left,int right){
        //分别创建i哨兵、j哨兵、temp存储基准值的临时变量、t交换的临时变量
        int i,j,temp,t;
        //终止递归,终止排序的条件(此时已经是排好的顺序)
        if (left > right) {
            return;
        }
        //分别用i、j存储两个哨兵
        i = left;
        j = right;
        //temp存储的就是基准值
        temp = a[left];
        //跳出while循环之后,因为循环的条件是i<j,所以,跳出循环时,i和j是相等的
        while (i < j) {
            //哨兵j从右往左找
            while (temp <= a[j] && i < j) {
                j--;
            }
            while (temp >= a[i] && i < j) {
                i++;
            }
            //交换两个数在数组中的位置
            if (i < j) {//当哨兵i和哨兵j没有相遇时
                t = a[j];
                a[j] = a[i];
                a[i] = t;
            }
        }
        //最终基准值归位
        a[left] = a[i];
        a[i] = temp;

        quickSort(a, left, j - 1);//继续处理左边的,这里是递归的过程
        quickSort(a, j + 1, right);//继续处理右边的,这里是递归的过程
    }

    public static void main(String[] args) {
        int[] arr = new int[] {3,1,2,5,4};
        quickSort(arr, 0, arr.length - 1);
        System.out.println(Arrays.toString(arr));
    }
}

4.3再谈大O

选择排序,其运行时间为O(n2),速度非常慢。

还有一种名为合并排序(merge sort)的排序算法,其运行时间为O(n log n),比选择排序快得多!快速排序的情况比较棘手,在最糟情况下,其运行时间为O(n2)【此处为n的平方】。

在平均情况下,快速排序的运行时间为O(n log n)。你可能会

比较合并排序和快速排序

快速排序的性能高度依赖于你选择的基准值***。假设你总是将第一个元素用作基准值,且要处理的数组是有序的。由于快速排序算法不检查输入数组是否有序,因此它依然尝试对其进行排序。此时数组并没有被分成两半,相反,其中一个子数组始终为空,这导致调用栈非常长*。现在假设你总是将中间的元素用作基准值,在这种情况下,调用栈短得多!因为你每次都将数组分成两半,所以不需要那么多递归调用。你很快就到达了基线条件,因此调用栈短得多。第一个示例展示的是最糟情况,而第二个示例展示的是最佳情况。在最糟情况下,栈长为***O(n)***,而在最佳情况下,栈长为***O(logn)***。

最佳情况也是平均情况。只要你每次都随机地选择一个数组元素作为基准值,快速排序的平均运行时间就将为O(n log n)。快速排序是最快的排序算法之一,也是D&C典范。

4.4小节

❑ D&C将问题逐步分解。使用D&C处理列表时,基线条件很可能是空数组或只包含一个元素的数组。

❑ 实现快速排序时,请随机地选择用作基准值的元素。快速排序的平均运行时间为O(n log n)。

❑ 大O表示法中的常量有时候事关重大,这就是快速排序比合并排序快的原因所在。

❑ 比较简单查找和二分查找时,常量几乎无关紧要,因为列表很长时,O(log n)的速度比O(n)快得多。

因此调用栈短得多。第一个示例展示的是最糟情况,而第二个示例展示的是最佳情况。在最糟情况下,栈长为***O(n)***,而在最佳情况下,栈长为***O(logn)***。

最佳情况也是平均情况。只要你每次都随机地选择一个数组元素作为基准值,快速排序的平均运行时间就将为O(n log n)。快速排序是最快的排序算法之一,也是D&C典范。

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
数据结构学习资料分享 内容概览: 本次分享包涵了大学计算机相关专业必学的“数据结构”课程的一系列学习资料。主要包括: 算法代码:我们提供了多种数据结构的实现代码,包括数组、链表、栈、队列、树、图等。这些代码不仅能帮助你理解数据结构的基本概念,而且能让你明白如何在实际情况中应用这些数据结构。 笔记:详细且系统的笔记,涵盖了数据结构的各个方面,从基础概念到复杂的数据结构如堆、B树等。这些笔记有助于你系统地复习和学习数据结构。 相关书籍推荐:为了更深入地理解数据结构,我们推荐了几本经典的教材和参考书籍。这些书籍将帮助你建立完整的数据结构知识体系。 适用人群: 这份学习资料适用于所有大学计算机相关专业的学生,无论你是初学者还是已经有一定的数据结构基础。同时,对于对数据结构感兴趣的非专业人士,这份资料也是一个很好的起点。 使用建议: 结合理论和实践:在学习的过程中,请结合算法代码和理论知识。尝试自己编写代码实现数据结构,并在遇到问题时参考提供的代码。 由浅入深:建议先从基础的数据结构开始学习,如数组和链表,然后再学习更复杂的数据结构如树和图。 多做练习:数据结构是实践性很强的学科。通过多做练习,你可以更好地理解数据结构的基本概念和原理,并提高编程能力。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值