精华内容
下载资源
问答
  • 快速算法——带你从零开始一步一步优化 目录 快速算法——带你从零开始一步一步优化 什么是快速算法 再次思考 快速算法初步入门 压榨性能再优化 终极优化 参考资料 博客文章版权声明 什么是...

                    快速幂算法——带你从零开始一步一步优化


    目录

                    快速幂算法——带你从零开始一步一步优化

    什么是快速幂算法

    再次思考

     

    快速幂算法初步入门

    压榨性能再优化

    终极优化

    参考资料

    博客文章版权声明


    什么是快速幂算法


    首先,我们先来看一道ACM程序设计题,这道题是杭电OJ中序号为2035的题目,没做过这道题目的同学可以跟着一起做一下(点击此处传送),题目如下:

    问题描述:

    这道题目乍一看会觉得并不难啊,题目短短一行而已,而且思路也很容易,求幂这种算法一般在初学程序设计语言的时候应该都有联系过,只要写一个简单的循环就能够搞定。

    /**
     * 普通的求幂函数
     * @param base 底数
     * @param power  指数
     * @return  求幂结果的最后3位数表示的整数
     */
    long long normalPower(long long base,long long power){
        long long result=1;
        for(int i=1;i<=power;i++){
            result=result*base;
        }
        return result%1000;
    }
    
    

    这道题不是分分钟解决吗?接下来,让我们来写一个主函数测试一下:

    int main(){
        long long base,power;
        cin>>base>>power;
    
        cout<<"base="<<base<<" power="<<power<<" "<<normalPower(base,power)<<endl;
    
        return 0;
    
    }

    然后,让我们愉快的来求一下2^100的结果的后三位数表示的整数是什么吧!输出结果如下:

    为什么答案会是0呢?明明没有错误的啊!~ 

    先不急,我们再来考虑一下,这道题其实出的很有意思,题目要求你输出结果的后三位,为什么不让你直接输出结果呢?难道仅仅只是为了增大题目的难度吗?当然不是,我们在初中就学过“指数爆炸”,下面我们在来回顾一下“指数”的概念:

    指数:在乘方a中,其中的a叫做底数,n叫做指数,结果叫幂。

    f(x)=a^x , 随着x单位长度的递增,f(x)会呈“爆炸性”增长。

    一张纸对折一次,厚度变成原来的2倍。再对折第二次,变为原来的2的2次方倍即4倍。以此类推,假设纸的厚度为0.1mm,则对折24次以后,长度超过1千米;对折39次达55000千米,超过地球赤道长度;对折42次达44万千米,超过地球至月球的距离;对折51次达22亿千米,超过地球至太阳的距离;对折82次为51113光年,超过银河系半径的长度。

    因此,如果题目让你求2的100次方,貌似我们程序设计语言中最大的long lnog类型也无法承载这么大的数值,所以题目才不会要求你输出结果,因为结果可能会非常的大,大到没有任何类型可以承载。所以我们会发现上面的结果为什么是0,因为已经发生溢出了。

    那为什么题目要求输出结果的最后三位数表示的整数呢?有的同学可能会问:求一个数的最后三位数表示的整数好办,只要用这个结果进行“取模”运算,让其对1000取模,得到的数就是这个数最后三位数表示的整数。(例如:12345的最后三位数表示的整数是:12345%1000=345)。但是,你这结果都无法求出来,让我怎么进行“取模”运算呢?你这不是瞎闹吗?

    别急,我们首先来了解一下“取模”运算的运算法则:(具体的证明感兴趣的同学可以问度娘)

    1. (a + b) % p = (a % p + b % p) % p (1)

    2. (a - b) % p = (a % p - b % p ) % p (2)

    3. (a * b) % p = (a % p * b % p) % p (3)

    其中我们只要关注第“3”条法则即可:(a * b) % p = (a % p * b % p) % p ,我们仔细研究一下这个运算法则,会发现多个因子连续的乘积取模的结果等于每个因子取模后的乘积再取模的结果。也就是说,我们如果要求:

    (a*b*c)%d=(a%d*b%d*c%d)%d;

    因此,我们可以借助这个法则,只需要在循环乘积的每一步都提前进行“取模”运算,而不是等到最后直接对结果“取模”,也能达到同样的效果。

    所以,我们的代码可以变成这个样子:

    /**
     * 普通的求幂函数
     * @param base 底数
     * @param power  指数
     * @return  求幂结果的最后3位数表示的整数
     */
    long long normalPower(long long base,long long power){
        long long result=1;
        for(int i=1;i<=power;i++){
            result=result*base;
            result=result%1000;
        }
        return result%1000;
    }

    我们再来测试一下,这样又能不能输出结果呢?我们仍然来求一下2^100的后三位是什么:

    这一次完美的得到了我们想要的结果。2^100的幂结果的后三位整数位376。

    为了打消一些同学对这个运算法则的怀疑,我们再用一个结果比较小的式子来验证一下:我们知道2^10为1024,按理来说,最后输出的结果的后三位数表示的整数应该是24,那么是不是这样呢?我们来试一试:

    最后的结果果然是24,所以这个法则是没有问题的。我们把下面的代码提交给OJ看一下是否能通过:

    #include <iostream>
    #include <cmath>
    
    using namespace std;
    
    /**
     * 普通的求幂函数
     * @param base 底数
     * @param power  指数
     * @return  求幂结果的最后3位数表示的整数
     */
    long long normalPower(long long base, long long power) {
        long long result = 1;
        for (int i = 1; i <= power; i++) {
            result = result * base;
            result = result % 1000;
        }
        return result % 1000;
    }
    
    int main() {
        long long base, power;
        while (true) {
            cin >> base >> power;
            if (base == 0 && power == 0) break;
            cout << normalPower(base, power) << endl;
        }
        return 0;
    
    }

    最后的结果是成功Accept了。

    再次思考


    虽然这个求幂的方法很有用,并且提交给OJ也直接Accept了,但是我们来考虑一下这个算法的时间复杂度,假设我们求2的100次方,那么将会执行100次循环。如果我们分析一下这个算法,就会发现这个算法的时间复杂度为O(N),其中N为指数。求一下小的结果还好,那如果我们要求2的1000000000次方呢?这个程序可能会运行很久很久,具体会多久呢,让我们来测试一下,测试代码如下:

    #include <iostream>
    #include <cmath>
    #include <time.h>
    
    using namespace std;
    
    /**
     * 普通的求幂函数
     * @param base 底数
     * @param power  指数
     * @return  求幂结果的最后3位数表示的整数
     */
    long long normalPower(long long base, long long power) {
        long long result = 1;
        for (int i = 1; i <= power; i++) {
            result = result * base;
            result = result % 1000;
        }
        return result % 1000;
    }
    
    int main() {
        clock_t start, finish;
        //clock_t为CPU时钟计时单元数
        long long base, power;
        cin >> base >> power;
        start = clock();
        //clock()函数返回此时CPU时钟计时单元数
        cout << normalPower(base, power) << endl;
        finish = clock();
        //clock()函数返回此时CPU时钟计时单元数
        cout << "the time cost is" << double(finish - start) / CLOCKS_PER_SEC;
        //finish与start的差值即为程序运行花费的CPU时钟单元数量,再除每秒CPU有多少个时钟单元,即为程序耗时
        return 0;
    
    }

    结果如图所示:

    我们发现,虽然结果是成功求出来了,但是用了将近18秒的时间才求出最后的答案。这效率当然是非常的低下的,更谈不上实际的生产应用了。那么有没有什么好的办法能够对其进行优化呢?接下来就是我们本次的主题了:快速幂算法。

     

    快速幂算法初步入门


    快速幂算法能帮我们算出指数非常大的幂,传统的求幂算法之所以时间复杂度非常高(为O(指数n)),就是因为当指数n非常大的时候,需要执行的循环操作次数也非常大。所以我们快速幂算法的核心思想就是每一步都把指数分成两半,而相应的底数做平方运算。这样不仅能把非常大的指数给不断变小,所需要执行的循环次数也变小,而最后表示的结果却一直不会变。让我们先来看一个简单的例子:

    3^10=3*3*3*3*3*3*3*3*3*3

    //尽量想办法把指数变小来,这里的指数为10

    3^10=(3*3)*(3*3)*(3*3)*(3*3)*(3*3)

    3^10=(3*3)^5

    3^10=9^5

    //此时指数由10缩减一半变成了5,而底数变成了原来的平方,求3^10原本需要执行10次循环操作,求9^5却只需要执行5次循环操作,但是3^10却等于9^5,我们用一次(底数做平方操作)的操作减少了原本一半的循环量,特别是在幂特别大的时候效果非常好,例如2^10000=4^5000,底数只是做了一个小小的平方操作,而指数就从10000变成了5000,减少了5000次的循环操作。

    //现在我们的问题是如何把指数5变成原来的一半,5是一个奇数,5的一半是2.5,但是我们知道,指数不能为小数,因此我们不能这么简单粗暴的直接执行5/2,然而,这里还有另一种方法能表示9^5

    9^5=(9^4)*(9^1)

    //此时我们抽出了一个底数的一次方,这里即为9^1,这个9^1我们先单独移出来,剩下的9^4又能够在执行“缩指数”操作了,把指数缩小一半,底数执行平方操作

    9^5=(81^2)*(9^1)

    //把指数缩小一半,底数执行平方操作

    9^5=(6561^1)*(9^1)

    //此时,我们发现指数又变成了一个奇数1,按照上面对指数为奇数的操作方法,应该抽出了一个底数的一次方,这里即为6561^1,这个6561^1我们先单独移出来,但是此时指数却变成了0,也就意味着我们无法再进行“缩指数”操作了。

    9^5=(6561^0)*(9^1)*(6561^1)=1*(9^1)*(6561^1)=(9^1)*(6561^1)=9*6561=59049

    我们能够发现,最后的结果是9*6561,而9是怎么产生的?是不是当指数为奇数5时,此时底数为9。那6561又是怎么产生的呢?是不是当指数为奇数1时,此时的底数为6561。所以我们能发现一个规律:最后求出的幂结果实际上就是在变化过程中所有当指数为奇数时底数的乘积。

    让我们来看一段简单的动画演示(点击放大):

    接下来,再让我们用代码来演示一下上面的算法:

    long long fastPower(long long base, long long power) {
        long long result = 1;
        while (power > 0) {
            if (power % 2 == 0) {
                //如果指数为偶数
                power = power / 2;//把指数缩小为一半
                base = base * base % 1000;//底数变大成原来的平方
            } else {
                //如果指数为奇数
                power = power - 1;//把指数减去1,使其变成一个偶数
                result = result * base % 1000;//此时记得要把指数为奇数时分离出来的底数的一次方收集好
                power = power / 2;//此时指数为偶数,可以继续执行操作
                base = base * base % 1000;
            }
        }
        return result;
    }

    我们再来测试一下此时的快速幂算法和普通的求幂算法的效率,我们仍然来求2的1000000000次方,看一看用时又会是多少:

    真让人简直不可思议,竟然只花了0.002秒就求出了结果,而且结果也是376,然而普通的算法却用了将近18秒的时间才求出最后的结果。

    压榨性能再优化


    虽然上面的快速幂算法效率已经很高了,但是我们仍然能够再一次的对其进行“压榨级别”的优化。我们上面的代码看起来仍然有些地方可以再进一步地进行简化,例如在if和else代码块中仍然有重复性的代码:

                power = power / 2;
                base = base * base % 1000;

                power = power - 1;//把指数减去1,使其变成一个偶数
                power = power / 2;
    可以压缩成一句:
                power = power / 2;

    因为power是一个整数,例如当power是奇数5时,power-1=4,power/2=2;而如果我们直接用power/2=5/2=2。在整型运算中得到的结果是一样的,因此,我们的代码可以压缩成下面这样:

    long long fastPower(long long base, long long power) {
        long long result = 1;
        while (power > 0) {
            if (power % 2 == 1) {
                result = result * base % 1000;
            }
            power = power / 2;
            base = (base * base) % 1000;
        }
        return result;
    }

    接下来,我们来测试一下优化后的性能如何,仍然是求2的1000000000次方:

    结果仍然是正确的376,但时间上的花费从0.002减少成了0.001。

     

    终极优化


    在C语言中,power%2==1可以用更快的“位运算”来代替,例如:power&1。因为如果power为偶数,则其二进制表示的最后一位一定是0;如果power是奇数,则其二进制表示的最后一位一定是1。将他们分别与1的二进制做“与”运算,得到的就是power二进制最后一位的数字了,是0则为偶数,是1则为奇数。例如5是奇数,则5&1=1;而6是偶数,则6&1=0;因此奇偶数的判断就可以用“位运算”来替换了。

    同样,对于power=power/2来说,也可以用更快的“位运算”进行替代,我们只要把power的二进制表示向右移动1位就能变成原来的一半了。

    最后,我们的代码就能优化成下面这样:
    long long fastPower(long long base, long long power) {
        long long result = 1;
        while (power > 0) {
            if (power & 1) {//此处等价于if(power%2==1)
                result = result * base % 1000;
            }
            power >>= 1;//此处等价于power=power/2
            base = (base * base) % 1000;
        }
        return result;
    }

    我们仍然测试一下求2的1000000000次方,看看终极优化后的代码的性能是怎样的:

    简直可怕,时间花费竟然接近于0秒,我们从最开始的18秒最后压缩到接近0秒,真的是感慨算法的威力!如果同样两家公司,采用不同的算法,给用户带来的体验区别是非常大的,这无不让我们感受到算法的威力。

     

    基础不牢?新手不友好?无人带路?关注《扬俊的小屋》公众号吧!


     

    参考资料


    【1】https://www.rookieslab.com/posts/fast-power-algorithm-exponentiation-by-squaring-cpp-python-implementation#brute-force-python-implementation  作者:Ravi Ojha 翻译:刘扬俊

    【2】百度百科——指数爆炸

    https://baike.baidu.com/item/%E6%8C%87%E6%95%B0%E7%88%86%E7%82%B8/8440078?fr=aladdin 

     

    博客文章版权声明


     

     

     

     

     

     

     

     

    展开全文
  • 上个厕所的功夫,就学会了“快速排序”算法

    万次阅读 多人点赞 2020-05-17 20:25:09
    快速排序由于排序效率在同为O(N*logN)的几种排序方法中效率较高,因此经常被采用,再加上快速排序思想----分治也确实实用,因此很多软件公司的笔试面试,...本文带你五分钟快速了解快排原理和源码。相信能帮助到你。

    快速排序由于排序效率在同为O(N*logN)的几种排序方法中效率较高,因此经常被采用,再加上快速排序思想----分治法也确实实用,因此很多软件公司的笔试面试,包括像BAT、字节、美团等知名IT公司都喜欢考查快速排序原理和手写源码。

    目录

     

    一、概念

    二、基本思想

    空间复杂度

    时间复杂度

    三、算法步骤

    四、具体示例

    五、快排代码

    @java代码

    @python代码

    六、总结


    一、概念

    快速排序,顾名思义就是一种以效率快为特色的排序算法,快速排序(Quicksort)是对冒泡排序的一种改进。由英国计算机专家:托尼·霍尔(Tony Hoare)在1960年提出。

    二、基本思想

    从排序数组中找出一个数,可以随机取,也可以取固定位置,一般是取第一个或最后一个,称为基准数。然后将比基准小的排在左边,比基准大的放到右边;

    如何放置呢,就是和基准数进行交换,交换完左边都是比基准小的,右边都是比较基准大的,这样就将一个数组分成了两个子数组,然后再按照同样的方法把子数组再分成更小的子数组,直到不能分解(子数组只有一个值)为止。以此达到整个数据变成有序序列。


    快速排序采用了一种分治的策略,通常称其为分治法(Divide-and-ConquerMethod),现在各种语言中自带的排序库很多使用的都是快速排序。

    空间复杂度

    快速排序是一种原地排序,只需要一个很小的栈作为辅助空间,空间复杂度为O(log2n),所以适合在数据集比较大的时候使用。

    时间复杂度

    时间复杂度比较复杂,最好的情况是O(n),最差的情况是O(n2),所以平时说的O(nlogn),为其平均时间复杂度。

    • O(n):理想的情况,每次划分所选择的中间数恰好将当前序列几乎等分,经过log2n趟划分,便可得到长度为1的子表。这样,整个算法的时间复杂度为O(nlog2n)。
    • O(n2):最坏的情况,每次所选的中间数是当前序列中的最大或最小元素,这使得每次划分所得的子表中一个为空表,另一子表的长度为原表的长度-1。这样,长度为n的数据表的快速排序需要经过n趟划分,使得整个排序算法的时间复杂度为O(n2)。

    三、算法步骤

    1.选定一个基准数(一般取第一位数字)作为中心点(Pivot);
    2.将大于Pivot的数字放到Pivot的左边;
    3.将小于Pivot的数字放到Pivot的右边;
    4.第一次排序结束后,分别对左右子序列继续递归重复前三步操作。

    四、具体示例

    实例数组:arr[] = {19,97,9,17,1,8};

    1.取出基准数Pivot,以该值为中心轴。

    快速排序中的规则:右边有坑,就从左边Arr[L + n]取值来填,反之左边有坑,则从右边Arr[R - n]取值来填;

    2.从左边取的基准值,左边的Arr[L]就空出来了,则先从右侧取值来填,从最右侧下标开始,在Arr[R] 取到第一个值“8”;

    3.将取到的Arr[R]与基准值比较,发现小于基准值,则插入到Arr[R],占到了基准值Pivot的位置上。

    4.然后从Arr[L+1]的位置取出值,继续向右匹配并排序,将匹配到的值(匹配规则如下)插入到右侧Arr[R]的空位置上;

    匹配规则:大于基准值的插入到Arr[R],如果小于,则直接忽略并跳过,继续向右取值,直到坐标L和坐标R重合。

    5.发现取出的值大于Pivot(基准值),则将其插入到Arr[R]。

    6.左边有坑,从右边Arr[R-1]继续匹配,Arr[R-1] = 1,小于基准值,则插入到Arr[L]的坑中;

    7.右边有坑了,继续从左边取值继续匹配,则取到Arr[L+1] = 9,小于基准值,则忽略并跳过,继续找Arr[L + 1]继续匹配。

    8.继续从左边坐标 + 1 取值继续匹配,则取到Arr[L] = 17,又小于基准值,则忽略并跳过,继续找Arr[L + 1]继续匹配。

    9.最后L坐标和R坐标重合了,将Pivot基准值填入

    10.至此,快速排序第一轮完整流程结束,分出了左右子序列,左边都是小于Pivot基准值的,右边都是大于Pivot基准值的。

    11.继续对左、右子序列递归进行处理,一直缩小到左、右都是一个值,则快速排序结束,最终得出顺序数组{1,8,9,17,19,97};中间递归流程这里不再赘述。

    五、快排代码

    @java代码

    package com.softsec.demo;
    
    /**
     * Created with IDEA
     *
     * @Author Chensj
     * @Date 2020/5/17 19:04
     * @Description
     * @Version 1.0
     */
    public class quickSortDemo {
    
        public static void main(String[] args) {
            // 创建测试数组
            int[] arr = new int[]{19,97,9,17,1,8};
            System.out.println("排序前:");
            showArray(arr); // 打印数组
            // 调用快排接口
            quickSort(arr);
            System.out.println("\n" + "排序后:");
            showArray(arr);// 打印数组
        }
    
        /**
         * 快速排序
         * @param array
         */
        public static void quickSort(int[] array) {
            int len;
            if(array == null
                    || (len = array.length) == 0
                    || len == 1) {
                return ;
            }
            sort(array, 0, len - 1);
        }
    
        /**
         * 快排核心算法,递归实现
         * @param array
         * @param left
         * @param right
         */
        public static void sort(int[] array, int left, int right) {
            if(left > right) {
                return;
            }
            // base中存放基准数
            int base = array[left];
            int i = left, j = right;
            while(i != j) {
                // 顺序很重要,先从右边开始往左找,直到找到比base值小的数
                while(array[j] >= base && i < j) {
                    j--;
                }
    
                // 再从左往右边找,直到找到比base值大的数
                while(array[i] <= base && i < j) {
                    i++;
                }
    
                // 上面的循环结束表示找到了位置或者(i>=j)了,交换两个数在数组中的位置
                if(i < j) {
                    int tmp = array[i];
                    array[i] = array[j];
                    array[j] = tmp;
                }
            }
    
            // 将基准数放到中间的位置(基准数归位)
            array[left] = array[i];
            array[i] = base;
    
            // 递归,继续向基准的左右两边执行和上面同样的操作
            // i的索引处为上面已确定好的基准值的位置,无需再处理
            sort(array, left, i - 1);
            sort(array, i + 1, right);
        }
    
        /**
         * 数组打印
         * @param num
         */
        private static void showArray(int[] num) {
            for (int i = 0; i < num.length; i++) {
                System.out.print(num[i] + " ");
            }
        }
    }
    

    返回结果:

    排序前:
    19 97 9 17 1 8 
    排序后:
    1 8 9 17 19 97 
    Process finished with exit code 0

    @python代码

    #快速排序 传入列表、开始位置和结束位置
    def quick_sort( li , start , end ):
        # 如果start和end碰头了,说明要我排的这个子数列就剩下一个数了,就不用排序了
        if not start < end :
            return
    
        mid = li[start] #拿出第一个数当作基准数mid
        low = start   #low来标记左侧从基准数始找比mid大的数的位置
        high = end  #high来标记右侧end向左找比mid小的数的位置
    
        # 我们要进行循环,只要low和high没有碰头就一直进行,当low和high相等说明碰头了
        while low < high :
            #从high开始向左,找到第一个比mid小或者等于mid的数,标记位置,(如果high的数比mid大,我们就左移high)
            # 并且我们要确定找到之前,如果low和high碰头了,也不找了
            while low < high and li[high] > mid :
                high -= 1
            #跳出while后,high所在的下标就是找到的右侧比mid小的数
            #把找到的数放到左侧的空位 low 标记了这个空位
            li[low] = li[high]
            # 从low开始向右,找到第一个比mid大的数,标记位置,(如果low的数小于等于mid,我们就右移low)
            # 并且我们要确定找到之前,如果low和high碰头了,也不找了
            while low < high and li[low] <= mid :
                low += 1
            #跳出while循环后low所在的下标就是左侧比mid大的数所在位置
            # 我们把找到的数放在右侧空位上,high标记了这个空位
            li[high] = li[low]
            #以上我们完成了一次 从右侧找到一个小数移到左侧,从左侧找到一个大数移动到右侧
        #当这个while跳出来之后相当于low和high碰头了,我们把mid所在位置放在这个空位
        li[low] = mid
        #这个时候mid左侧看的数都比mid小,mid右侧的数都比mid大
    
        #然后我们对mid左侧所有数进行上述的排序
        quick_sort( li , start, low-1 )
        #我们mid右侧所有数进行上述排序
        quick_sort( li , low +1 , end )
     
    
    #入口函数
    if __name__ == '__main__':
        li = [19,97,9,17,1,8]
        quick_sort(li , 0 , len(li) -1 )
        print(li)

    六、总结

    快速排序是当前最为流行的排序算法之一,各大公司面试中频频出现,希望通过这篇文章,让你对快速排序知识点有一定的了解,在日后面试或各种考试中对你有所帮助!

    展开全文
  • 首先要知道数组从大到小排列的充分必要条件:对于数组中任个数,比它小的数在它左侧,比它大的数在右侧。那么我们只需将数组进行如下操作:1.分解 以a[p]为基准,将比它小的移在a[p]后面 ,比它大的都放在a[p]的...

        首先要知道数组从大到小排列的充分必要条件:对于数组中任一个数,比它小的数在它左侧,比它大的数在右侧。那么我们只需将数组进行如下操作:

    1.分解

        以a[p]为基准,将比它小的移在a[p]后面  ,比它大的都放在a[p]的后面的后面。

    如下图:


        然后将比a[p]小的最后一个数与a[p]交换位置。


    2.递归

        将比a[p]小的数用以上步骤排序。

        将比a[p]大的数用以上步骤排序。

    用一个数组举例:

         

    以上步骤可以分解为:

        1.以3为基准,将比3小的移在3后面  ,比3大的都放在3的后面的后面。

         2.将左半段按以上步骤排序。(递归。以2为基准后又以1为基准.......)

         2.将右半段按以上步骤排序。(递归。以6为基准后又以5或4,5或4为基准.......)


    源代码:

    3,7,2,6,5,4,8,1 为例:

    #include<iostream>
    #include<algorithm>
    using namespace std;
    int Partition(int a[],int p,int r){
    	int i = p,j = r+1;
    	int x = a[p];
    	while(true){
    		while(a[++i]<x&&i<r);//a[i]比3大 
    		while(a[--j]>x);//a[j]比3小 
    		if(i<j){
    			swap(a[i],a[j]);
    		}
    		else{
    			break;
    		}
    	}
    	a[p] = a[j];
    	a[j] = x;
    	
    	return j;
    }
    void quicksort(int a[],int p,int r){
    	if(p<r){
    	int q = Partition(a,p,r);
    	quicksort(a,p,q-1);
    	quicksort(a,q+1,r);
    	}
    }
    int main(){
    	int a[8] ={3,7,2,6,5,4,8,1};
    	quicksort(a,0,7);
    	cout<<a[0]<<a[1]<<a[2]<<a[3]<<a[4]<<a[5]<<a[6]<<a[7]<<endl;
    	return 0;
    }


    输出结果:


    展开全文
  • 本文目录、JavaScript的简介二、JavaScript的环境搭建三、JavaScript的基础(变量、常量、运算符、真假值)四、本章小结五、写在后面 、JavaScript的简介 JavaScript是一门非常强大的编程语言。它也是最流行的...

    一、JavaScript的简介

    JavaScript是一门非常强大的编程语言。它也是最流行的编程语言之一,也是互联网上最卓越的语言之一。

    在GitHub(世界上最大的代码托管站点)上,托管了 30w+ 的JavaScript代码仓库(用JavaScript开发的项目数量也是最多的,参看http://githut.info)。使用JavaScript的项目数量还在逐年增长。
    数据统计来源
    JavaScript不仅可用于前端开发,也适用于后端开发,而Node.js就是其背后的技术。Node包的数量也呈指数级增长。JavaScript同样可以用于移动开发领域,并且是Apache Cordova中最流行的语言之一。

    Apache Cordova是一个能让开发者使用HTML、CSS和JavaScript等语言的混合式框架,你可以通过它来搭建应用,并且生成供Android系统使用的APK文件和供iOS(苹果系统)使用的IPA文件。当然,也别忘了桌面端应用开发。我们可以使用一个名为Electron的JavaScript 框架来编写同时兼容Linux、Mac OS 和 Windows的桌面端应用。

    JavaScript 还可以用于嵌人式设备以及物联网(loT)设备。正如你所看到的,到处都有JavaScript的身影!要成为一名Web开发工程师,掌握JavaScript必不可少。

    本章,你会学到JavaScript的语法和一些必要的基础,这样就可以开始开发自己的数据结构和算法了。

    本章内容如下:

    • 环境搭建和JavaScript基础
    • 控制结构和函数
    • JavaScript面向对象编程
    • 调试工具

    二、JavaScript的环境搭建

    我们使用纯JavaScript进行演示,所以选择的是Nodejs平台。

    教程链接(菜鸟教程网):https://www.runoob.com/nodejs/nodejs-install-setup.html

    需要注意的是Nodejs的版本号区别,我们一般在生产环境会选择使用 v.xxx.xx LTS 的版本,而且选择双数的版本。例如:v12.x.x LTS 或者 v14.x.x LTS这样的。

    https://nodejs.org/zh-cn/
    可能有小伙伴会问为什么?

    这就是你们要的为什么:更新日志链接 https://nodejs.org/zh-cn/about/releases/ ,因为生产环境需要稳定,而不是一味地去追寻最新。
    这就是为什么

    三、JavaScript的基础(变量、常量、运算符、真假值)

    本文为系列文章,所以在学习各种数据结构与算法之前,先大概了解一下JavaScript,本章节先熟悉一下有关的基础知识,有利于后面的学习。

    • 变量

    变量保存的数据可以在需要时设置、更新或提取。赋给变量的值都有对应的类型。JavaScript的类型有数字(Number)、字符串(String)、布尔值(Boolean)、函数(Function)和对象(Object),还有 undefined和null,以及数组(Array)、日期(Date)和正则表达式(RegExp)。

    尽管JavaScript的数据类型看起来很多,但不同于Java或者C++等强类型语言,它是弱类型语言,所以只需要在定义的时候使用 var / let 等关键字进行定义即可,无需指定具体的类型。

    
    var temp = [];
    
    temp = new Date();
    
    temp = 1;
    
    // 任意变是优点也是缺点。它双刃剑。。。。至于为什么,熟悉了就知道了。。
    
    • 变量作用域

    作用域是指,在编写的算法函数中,我们能访问变量(在使用函数作用域时,也可以是一个函数)的地方。有局部变量和全局变量两种。

    让我们看一个例子。

    var temp1 = 'global';
    
    function func(){
    	var = temp2 = 'function';
    }
    
    console.log(temp1); // global
    console.log(temp2); // ??? 这是啥
    

    你可能听其他人提过在JavaScript里应该尽量少用全局变量,这是对的。通常,代码质量可以用全局变量和函数的数量来考量(数量越多越糟)。因此,尽可能避免使用全局变量。

    • 运算符

    在编程语言里执行任何运算都需要运算符。在JavaScript里有算术运算符、赋值运算符、比较运算符、逻辑运算符、位运算符、一元运算符和其他运算符。这里需要仔细一点了,也是数据结构中最重要的内容之一。下面的几个表中都一一列举了。

    算术运算符描述
    +加法
    -减法
    *乘法
    /除法
    %取余(除法的余数)
    ++递增(自加)
    递减(自减)
    赋值运算符描述
    =赋值
    +=加赋值(x+=y) == (x=x+y)
    -=减赋值(x-=y) ==(x=x-y)
    *=乘赋值(x*=y) == (x=x*y)
    /=除赋值(x/=y) == (x=x/y)
    %=取余赋值(x %= y) == (x = x % y)
    比较运算符描述
    ==相等
    ===全等
    !=不等
    >大于
    >=大于等于
    <小于
    <=小于等于
    逻辑运算符 (非常重要)描述
    &&与(所有条件必须满足)
    l l或(所有条件满足其一即可)
    !非(直接取反,颠倒黑白)
    位运算符描述
    &
    l
    ~
    ^异或
    <<左移(这个有点难)
    >>右移(这个有点难)

    上面表中的运算符先记住,先不做举例,但是在后面的算法学习中会用到。

    • 真值和假值

    在JavaScript中,true和false有些复杂。在大多数编程语言中,布尔值true和false仅表示true 和 false结果。在JavaScript 中,如 ‘test’ 这样的字符串值,也可以看作 true。

    下表能帮助我们更好地理解 true和false在JavaScript中是如何转换的。

    数据类型转换成布尔值(Boolean)
    undefinedfalse
    nullfalse
    布尔值(true,false)true是true, false是false
    数字(Number)+0、-0和NaN都是false,其它都是true
    字符串(String)如果字符串是空的(长度是0)就是false,其他都是true(长度大于等于1)字符串对象true
    对象(Object)false

    ES6中还有个Symbol 数据类型,表示独一无二的值,本章先忽略,后面再细说。

    • 相等运算符 (== 和 ===)

    这两个相等运算符的使用可能会引起一些困惑。使用 == 时,不同类型的值也可以被看作相等。这样的结果可能会使那些资深的JavaScript开发者都感到困惑。我们用下面的表格给大家分析一下不同类型的值用相等运算符比较后的结果。

    类型(x)类型(y)结果
    nullundefinedtrue
    undefinednulltrue
    字符串x == toNumber(y)
    字符串toNumber(x) == y
    布尔值任何类型x == toNumber(y)
    字符串或数对象x == toPrimitive(y)
    对象字符串或数toPrimitive(y) == y

    如果x和y的类型相同,JavaScript会用equals方法比较这两个值或对象。没有列在这个表格中的其他情况都会返回false。toNumber和toPrimitive方法是内部的,并根据以下表格对其进行估值。

    toNumber方法对不同类型返回的结果如下。

    值类型结果
    undefinedNaN
    null+0
    布尔值如果是true,返回1;如果是false,返回0
    数对应的值

    toPrimitive方法对不同类型返回的结果如下。

    值类型结果
    对象如果对象的valueof方法的结果是原始值,返回原始值;
    对象如果对象的tostring方法返回原始值,就返回这个值;
    对象其它情况都返回一个错误。

    对象用例子来验证一下表格中的结果。首先,我们知道下面的代码输出true(字符串长度大于1)。

    console.log('packt'? true:false);
    

    那么下面这行代码的结果呢?

    console.log('packt' == true);
    // 输出是false,为什么会这样呢?
    

    首先,布尔值会被toNumber方法转成数,因此得到packt==1。

    其次,用toNumber转换字符串值。因为字符串包含字母,所以会被转成 NaN,表达式就变成了 NaN ==1,结果就是false。

    那么下面这行代码的结果呢?

    console.log('packt' == false);
    // 也是输出false,为什么会这样呢?
    

    首先,布尔值会被toNumber方法转成数,因此得到packt==0。

    其次,用toNumber转换字符串值。因为字符串包含字母,所以会被转成NaN,表达式就变成了NaN ==0,结果就是false。

    那么===运算符呢?简单多了。如果比较的两个值类型不同,比较的结果就是false。如果比较的两个值类型相同,结果会根据下表判断。

    类型(x)结果
    x和y的值相同(但不是NaN)true
    字符串x和y是相同的字符true
    布尔值x和y都是true或false布尔值true
    对象x和y引用同一个对象true

    如果x和y类型不同,结果就是false。我们来看一些例子。

    
    console.log('packt'=== true); // false
    console.log('packt'==='packt');// true
    var person1 = { name:'John' };
    var person2 = { name:'John' };
    console.log(person1=== person2)//false ,不同的对象
    
    
    • 控制结构

    JavaScript的控制结构与C和Java里的类似。条件语句支持if.….else 和 svitch。循环支持while、do…while和for。

    • 条件语句

    首先看一下如何构造if…else条件语句。有几种方式。

    如果想让一个脚本在仅当条件(表达式)是true时执行,可以使用if语句,如下所示:

    var num = 1;
    if (num === 1) {
    	console.log('num 等于 1')
    }

    如果想在条件为true的时候执行脚本A,在条件为false(else)的时候执行脚本B,可以使用if…else语句,如下所示。

    var num = 0;
    
    if (num === 1) {
    	console.log('num 等于1')} else {
    	console.log('num 不等于1,num的值是' + num)
    }
    

    if…else语句也可以用三元运算符替换,例如下面的if…else语句。

    if(num === 1){
    	num++;
    } else {
    	num--;
    }
    

    可以用三元运算符替换为:

    (num === 1) ? num++ : num--;
    

    如果我们有多个脚本,可以多次使用if…else,根据不同的条件执行不同的语句。

    var month = 5;
    if(month === 1){
    	console.log('一月');
    } else if (month === 2){
    	console.log('二月');
    } else if (month === 3){
    	console.log('三月');
    } else {
    	console.log('月份不是一月、二月或三月');
    }
    
    

    最后,还有switch语句。如果要判断的条件和上面的一样(但要和不同的值进行比较),可以使用 swtich 语句。

    
    var month = 5;
    switch (month){
    	case 1:
    		console.log('January');break;
    	case 2:
    		console.log('February');break;
    	case 3:
    		console.log('March');break;
    	default:
    		console.log('Month ls not January,February or March');break;
    }
    
    

    对于switch 语句来说,case和break 关键字的用法很重要。case判断当前switch的值是否和case 分支语句的值相等。break 会中止 switch 语句的执行。没有 break 会导致执行完当前的case后,继续执行下一个case,直到遇到break或switch执行结束。最后,还有default 关键字,在表达式不匹配前面任何一种情形的时候,就执行default中的代码(如果有对应的,就不会执行)。

    • 循环

    在处理数组元素时会经常用到循环(数组是后面的内容)。在算法中也会经常用到for循环。

    JavaScript中的for循环与C和Java中的一样。循环的计数值通常是一个数,然后和另一个值比较(如果条件成立就会执行for循环中的代码),之后这个数值会递增或递减。

    在下面的代码里,我们用了一个for循环。当i小于10时,会在控制台中输出其值。i的初始值是0,因此这段代码会输出0到9。

    for (var i = 0; 1 < 10;i++){
    	console.log(i);
    }
    

    我们要关注的下一种循环是while循环。当while的条件判断成立时,会执行循环内的代码。下面的代码里,有一个初始值为0的变量i,我们希望在i小于10(即小于等于9)时输出它的值。输出会是0到9。

    var i = 0;
    
    while (i < 10){
    	console.log(i);
    	i++;
    }
    

    do…while循环和while循环很相似。区别是:在while循环里,先进行条件判断再执行循环体中的代码,而在do…while循环里,是先执行循环体中的代码再判断循环条件。生++:下面的代码同样会输出0到9。

    var i = 0;
    //do...while循环至少会让循环体中的代码执行一次。
    
    do{
     console.log(i);
     i++;
    } while(i < 10)
    
    
    • 调试工具

    具体的不再写了,可以看看下面的这个教程

    手把手教你vscode调试node ,点击一下就过去了。

    https://blog.csdn.net/youngeffort/article/details/86306340

    四、本章小结

    本系列文章主要讲述JavaScript数据结构与算法,但是本文作为开山篇,主要是是先讲述基础,重点需要熟练掌握运算符的使用,在后面的数据结构文章中才能一帆风顺。

    工欲善其事,必先利其器。

    五、写在后面

    这作为一个JavaScript数据结构与算法的开山篇文章,主要目的是让学习JavaScript数据结构与算法的朋友可以快速掌握其基础,本系列也会持续进行更新的。

    有问题请留言或者@博主,谢谢支持o( ̄︶ ̄)o~

    感谢您的阅读,如果此文章或项目对您有帮助,若可以的话请给个一键三连吧!

    GitHub有开源项目,需要的小伙伴可以顺手star一下!

    GitHub: https://github.com/langyuxiansheng

    展开全文
  • 一分钟带你快速认识S参数

    千次阅读 2019-12-02 10:02:50
    S 参数是SI与RF领域工程师必备的基础知识,大家很容易从网络或...是微波传输中的个重要参数。S12为反向传输系数,也就是隔离。S21为正向传输系数,也就是增益。S11为输入反射系数,也就是输入回波损耗,S22为输出...
  • 作者 | 爱笑的眼睛 来源 | 恋习Python(ID:sldata2017)最近在某网站上看到个视频,是关于排序算法的可视化的,看着挺有意思的,也特别喜感。▼6分钟...
  • 快速排序模板,一分钟内写出

    千次阅读 2016-09-22 20:29:03
    作为最常用的算法之一,最好能在一分钟内写出来,生活、工作、面试必备良品。 时间复杂度:O(n*logn)。 思路:其实选择一个数字枢轴作为枢纽pivot,左右指针向中间靠拢,将大于pivot的都置换到其右面,小于pivot的都...
  • 每日小练——快速Fibonacci数算法

    千次阅读 多人点赞 2014-05-21 11:27:41
    上得厅堂,下得厨房,写得代码,翻得围墙,欢迎来到睿不可挡的每日小练!
  • Python实现快速幂取余算法

    千次阅读 2019-09-13 16:27:39
    引子 今天无意中,看到了慧科教育科技集团有限公司-后厂理工学院 的AI...第一眼,看到题目,因为之前没有接触过快速算法,我心想,这也太简单了~~,于是分分钟做出了第版的程序: import math def count_fact...
  • PyTorch 深度学习:60分钟快速入门

    万次阅读 多人点赞 2017-11-29 19:38:18
    )是相互联系的,并形成个非循环图来构建个完整的计算过程.每个变量有个 .grad_fn 属性,它指向创建该变量的个 Function ,用户自己创建的变量除外,它的 grad_fn 属性为None. 如果你想计算导数,可以在个...
  • 分钟让你深入了解分布式系统中常用的一致性哈希算法
  • 算法理解】—— 快速排序(三向切分)

    千次阅读 多人点赞 2016-07-04 09:49:23
    针对于“快速排序”算法个介绍,并对快速排序的优化版——“快速排序(三向切分)”做个介绍。
  • SEO快速排名算法独家揭秘

    千次阅读 2016-08-02 14:05:18
    在2015年11月份的时候在卢松松博客投递了份关于SEO快速排名独家揭秘的算法流程,时过境迁已经过去了大半年,随之而来的也是搜索引擎的点击算法也发生了一些变化,这次我将完善以前的算法资料。 下面我将...
  • 5分钟快速了解MySQL索引的各种类型

    千次阅读 多人点赞 2020-08-17 08:47:19
    存储引擎对每行数据的所有索引字段计算个哈希码,哈希码是个比较小的值,并且不同的数据计算出来的哈希码一般情况下也不一样。哈希索引中存放了这个哈希码和指向这个数据行的指针。 在MySQL中,只有Memory...
  • 很多初学者在刷题的时候,思路飞来飞去,有时候以为是 动态规划 的知识点,结果写了半天代码越写越乱,最后看 discuss 原来可以用 栈 来解决。其实,学算法,刷题蛮干是不行的,需要遵循科学的方法。以下的经验...
  • 一分钟AI: 商汤科技计划赴美IPO,并将在美设立研发中心 美媒称中国导弹阵地遭美国AI技术"快速识别" 美图发布全球首款人工智能绘画机器人Andy 自拍秒变插画 谷歌云平台落地香港,明年将建...
  • 快速的寻路算法 Jump Point Search

    千次阅读 2020-11-13 18:00:00
    作者:runzhiwang,腾讯 TEG 后台开发工程师本文介绍种跳点搜索算法 JPS 以及其四个优化算法,其寻路速度最快可是 A*算法的 273 倍。文中的 JPS-Bit 和 JP...
  • 快速排序算法C++实现[评注版]

    千次阅读 2018-12-11 14:16:59
    而在他们发布的代码通常是差不多的版本,估计也是网上copy一下,自己改改,跑过了就算了,但是通常这样玩根本没有太大作用,如果到一家公司,给你台不能上网的笔记本,20分钟,你是根本写不出来快速排序的算法的,...
  • 快速非局部去噪算法

    千次阅读 2018-05-17 10:39:24
    转载地址:https://blog.csdn.net/qq_26260209/article/details/45219167图像去噪是个经典的课题。然而,对于真实数码照片,要想达到良好... 我们可以在任何本关于数字图像处理的教材上找到多种图像去噪的方法...
  • 快速最优通道布线算法(详细)

    万次阅读 2015-06-25 23:30:38
    众所周知,Moore-Lee的迷宫算法和Hightower的线探索都是单根连线的布线算法,要解决整个电路大量线网(nets)的布线,就要把每个线网分拆成点-点间连线,然后应用这些算法对连线根进行布线。这种串行布线...
  • A.pro读算法の6:快速搞定dfs算法

    千次阅读 多人点赞 2018-08-08 21:58:49
    A.pro不喜欢说那么长的概念和定义,因为那实在是太烧脑了,像我这种蒟蒻看个5分钟就该滚回去睡觉了。 那么,我们需要在最短的时间内搞定这个女装山脉(雾)dfs。 dfs算法,即深度优先算法(Depth First Search)。...
  • 排序算法千千万,只有快排的性能和优化后的泛化性质最好,在介绍快速排序前,先看看这张排序天梯图: 什么是快速排序? 快速排序是冒泡排序的改进版,也是最好的种内排序,在很多面试题中都会出现,也是作为...
  • 快速排序算法在生活中的应用

    千次阅读 2012-11-24 17:31:23
    今天公司组织活动,游戏规则是随机一组30张图片,飞机,月饼,奥运会等等,然后每组10个人,要求在 一分钟内把图片按照从小到大顺序排列好由组长交给裁判,时间最少为优胜. 每组有三次机会,由于不了解规则,第一次机会...
  • 经典算法(19)教你两分钟学会 选择排序

    万次阅读 多人点赞 2020-02-13 13:53:20
    使用图文并茂的方式讲解选择【排序算法】,并有完整的算法逻辑以及代码实现。
  • TCP进入快速恢复时的窗口下降算法

    万次阅读 2016-05-14 10:59:55
    以Linux为例,降窗发生在进入快速恢复的当时(暂时不考虑RTO以及本地拥塞),在降窗之前是个Disorder的状态,指的是系统发现了异常,比如收到了重复ACK或者说收到个推进的ACK携带了SACK信息,然而还不至于到重传的...
  • 一分钟详解「手眼标定」基本原理

    万次阅读 多人点赞 2019-03-06 10:16:14
    前言 机器人的视觉系统分为固定场景视觉系统和运动的「手-眼」视觉系统。摄像机与机器人的手部末端,构成手眼视觉系统。根据摄像机与机器人相互位置的不同,手眼视觉系统分为Eye-in-Hand系统和Eye-to-Hand系统。...
  • 分钟学会归并排序和快速排序

    千次阅读 多人点赞 2020-12-20 12:20:01
    快速排序 最后 前言 先来看看这两个排序在常见的排序中的速度比较(50个随机数): 再来看看数据放大四倍后(200个随机数)的速度: 可见这两种排序在大部分的排序算法中脱颖而出,数据量较少的时候,快速排序比归并...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 102,507
精华内容 41,002
关键字:

一分钟快速算法