0
点赞
收藏
分享

微信扫一扫

算法基础篇

40dba2f2a596 2022-02-27 阅读 24

算法基础

一、什么是算法与大O表示法

算法是一组完成任务的指令。任何代码片段都可视为算法。
算法是一种通过有限过程解决问题的解决方案。

大O 表示法:
大O表示法是一种特殊的表示法,指出了算法的速度有多快;
大O表示法让你能够比较操作数,它指出了算法运行时间的增速;
大O 表示法指出了最糟情况下的运行时间。

举例,假设列表包含n个元素:
简单查找需要检查每个元素,因此需要执行n次操作。使用大O表示法,这个运行时间为 O(n)。
二分查找需要执行log n次操作。使用大O表示法,这个运行时间为 O(log n)。

算法相关数据结构

1、数组

数组中的元素在内存中都是相连的(紧靠在一起的)。当增加元素时,要请求计算机重新分配一块可容纳所有元素的内存,再将所有元素都移到那里。
需要随机地读取元素时,数组的效率很高,因为可迅速找到数组的任何元素。数组随机读取某个元素的时间复杂度为O(1)。
在同一个数组中,所有元素的类型都必须相同(都为int、double等)。

// 数组中的元素类型必须相同
int[] num={1,2,3,4,5};
Stirng[] strs={"a","b","c","d"};

数组:

插入:[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fELiKyzE-1645899170072)(images/1.gif)]

删除:[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5dqemf7D-1645899170072)(images/2.gif)]

2、链表

链表中的元素可存储在内存的任何地方;链表的每个元素都存储了下一个元素的地址,从而使一系列随机的内存地址串在一起。
链表的问题:无法直接读取链表的最后一个元素,因为你不知道它所处的地址,必须先访问元素#1,依次下去;跳跃取值时,效率很低。
链表的优势在插入元素和删除元素方面。

使用链表时,插入元素很简单,只需修改它前面的那个元素指向的地址。
使用数组时,则必须将后面的元素都向后移;如果没有足够的空间,可能还得将整个数组复制到其他地方!
因此,当需要在中间插入元素时,链表是更好的选择。

要删除一个元素,链表也是更好的选择,因为只需修改前一个元素指向的地址即可。而使用数组时,删除元素后,必须将后面的元素都向前移

链表

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Vp142EhL-1645899170073)(images/2.png)]

插入:[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EXXyarwu-1645899170073)(images/3.gif)]

删除:[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jBb1IAnU-1645899170073)(images/4.gif)]

3、栈,队列,双端队列

栈 stack :一个有序的项的集合。添加项和移除项发生在同一端,即后进先出,形式如“弹夹”“叠盘子”。

队列 queue:一系列有序的元素的集合。先进先出,形式如“排队”。

双端队列 deque:一系列有序的元素的集合。允许从两端插入和从两端删除。

####

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0gYDyc4n-1645899170073)(images/3.png)]

入栈:[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-J5Vbfxrk-1645899170074)(images/5.gif)]

出栈:[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FbAD9iW2-1645899170074)(images/6.gif)]

队列

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PIiIV6O0-1645899170074)(images/4.png)]

进队:[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2pKxtFY8-1645899170074)(images/7.gif)]

出队:[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gOT56CcG-1645899170074)(images/8.gif)]

位运算

我们常用的 35 等数字是十进制表示,而位运算的基础是二进制。即人类采用十进制,机器采用的是二进制,要深入了解位运算,就需要了解十进制和二进制的转换方法和对应关系。

二进制

十进制转二进制时,采用 “除 2 取余,逆序排列” 法:

  1. 用 2 整除十进制数,得到商和余数;
  2. 再用 2 整除商,得到新的商和余数;
  3. 重复第 1 和第 2 步,直到商为 0;
  4. 将先得到的余数作为二进制数的高位,后得到的余数作为二进制数的低位,依次排序;

排序结果就是该十进制数的二进制表示。例如十进制数 101 转换为二进制数的计算过程如下:

101 % 2 = 50 余 1
50 % 2 = 25 余 0
25 % 2 = 12 余 1
12 % 2 = 6 余 0
6 % 2 = 3 余 0
3 % 2 = 1 余 1
1 % 2 = 0 余 1

逆序排列即二进制中的从高位到低位排序,得到 7 位二进制数为 1100101,如果要转换为 8 位二进制数,就需要在最高位补 0。即十进制数的 8 位二进制数为 01100101

位运算概述

从现代计算机中所有的数据二进制的形式存储在设备中。即 0、1 两种状态,计算机对二进制数据进行的运算(+、-、*、/)都是叫位运算,即将符号位共同参与运算的运算。 程序中的所有数在计算机内存中都是以二进制的形式储存的。位运算说穿了,就是直接对整数在内存中的二进制位进行操作。

符号描述运算规则
&两个位都为1时,结果才为1
|两个位都为0时,结果才为0
^异或两个位相同为0,相异为1
~取反0变1,1变0
<<左移各二进位全部左移若干位,高位丢弃,低位补0
>>右移各二进位全部右移若干位,对无符号数,高位补0,有符号数,各编译器处理方法不一样,有的补符号位(算术右移),有的补0(逻辑右移)

按位与运算符 (&)

定义:参加运算的两个数据,按二进制位进行"与"运算。

运算规则:

0&0=0  0&1=0  1&0=0  1&1=1

总结:两位同时为1,结果才为1,否则结果为0。

例如:3&5 即 0000 0011& 0000 0101 = 0000 0001,因此 3&5 的值得1。

注意:负数按补码形式参加按位与运算。

与运算的用途:

1)清零

如果想将一个单元清零,即使其全部二进制位为0,只要与一个各位都为零的数值相与,结果为零。

2)取一个数的指定位

比如取数 X=1010 1110 的低4位,只需要另找一个数Y,令Y的低4位为1,其余位为0,即Y=0000 1111,然后将X与Y进行按位与运算(X&Y=0000 1110)即可得到X的指定位。

3)判断奇偶

只要根据最未位是0还是1来决定,为0就是偶数,为1就是奇数。因此可以用if ((a & 1) == 0)代替if (a % 2 == 0)来判断a是不是偶数。

按位或运算符(|)

定义:参加运算的两个对象,按二进制位进行"或"运算。

运算规则:

0|0=0  0|1=1  1|0=1  1|1=1

总结:参加运算的两个对象只要有一个为1,其值为1。

例如:3|5即 0000 0011| 0000 0101 = 0000 0111,因此,3|5的值得7。

注意:负数按补码形式参加按位或运算。

或运算的用途:

1)常用来对一个数据的某些位设置为1

比如将数 X=1010 1110 的低4位设置为1,只需要另找一个数Y,令Y的低4位为1,其余位为0,即Y=0000 1111,然后将X与Y进行按位或运算(X|Y=1010 1111)即可得到。

异或运算符(^)

定义:参加运算的两个数据,按二进制位进行"异或"运算。

运算规则:

0^0=0  0^1=1  1^0=1  1^1=0

总结:参加运算的两个对象,如果两个相应位相同为0,相异为1。

异或的几条性质:

  • 1、交换律
  • 2、结合律 (ab)c == a(bc)
  • 3、对于任何数x,都有 xx=0,x0=x
  • 4、自反性: abb=a^0=a;

异或运算的用途:

1)翻转指定位

比如将数 X=1010 1110 的低4位进行翻转,只需要另找一个数Y,令Y的低4位为1,其余位为0,即Y=0000 1111,然后将X与Y进行异或运算(X^Y=1010 0001)即可得到。

2)与0相异或值不变

例如:1010 1110 ^ 0000 0000 = 1010 1110

3)交换两个数

void Swap(int &a, int &b){
    if (a != b){
        a ^= b;
        b ^= a;
        a ^= b;
    }
}

取反运算符 (~)

定义:参加运算的一个数据,按二进制进行"取反"运算。

运算规则:

~1=0
~0=1

总结:对一个二进制数按位取反,即将0变1,1变0。

异或运算的用途:

1)使一个数的最低位为零

使a的最低位为0,可以表示为:a & 1。1的值为 1111 1111 1111 1110,再按"与"运算,最低位一定为0。因为" ~"运算符的优先级比算术运算符、关系运算符、逻辑运算符和其他运算符都高。

左移运算符(<<)

定义:将一个运算对象的各二进制位全部左移若干位(左边的二进制位丢弃,右边补0)。

设 a=1010 1110,a = a<< 2 将a的二进制位左移2位、右补0,即得a=1011 1000。

若左移时舍弃的高位不包含1,则每左移一位,相当于该数乘以2。

右移运算符(>>)

定义:将一个数的各二进制位全部右移若干位,正数左补0,负数左补1,右边丢弃。

例如:a=a>>2 将a的二进制位右移2位,左补0 或者 左补1得看被移数是正还是负。

操作数每右移一位,相当于该数除以2。

复合赋值运算符

位运算符与赋值运算符结合,组成新的复合赋值运算符,它们是:

&=        例:a&=b    相当于     a=a&b

|=        例:a|=b    相当于     a=a|b

>>=      例:a>>=b   相当于     a=a>>b

<<=      例:a<<=b     相当于      a=a<<b

^=        例:a^=b    相当于   a=a^b

运算规则:和前面讲的复合赋值运算符的运算规则相似。

不同长度的数据进行位运算:如果两个不同长度的数据进行位运算时,系统会将二者按右端对齐,然后进行位运算。

以"与运算"为例说明如下:我们知道在C语言中long型占4个字节,int型占2个字节,如果一个long型数据与一个int型数据进行"与运算",右端对齐后,左边不足的位依下面三种情况补足,

  • 1)如果整型数据为正数,左边补16个0。

  • 2)如果整型数据为负数,左边补16个1。

  • 3)如果整形数据为无符号数,左边也补16个0。

  • 如:long a=123;int b=1;计算a& b。

  • 如:long a=123;int b=-1;计算a& b。

  • 如:long a=123;unsigned intb=1;计算a & b。

排序算法

选择排序

选择排序是一种简单直观的排序算法,无论什么数据进去都是 O(n²) 的时间复杂度。所以用到它的时候,数据规模越小越好。唯一的好处可能就是不占用额外的内存空间了吧。

1. 算法步骤

首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置。

再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。

重复第二步,直到所有元素均排序完毕。

2. 动图演示

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-T3kzE7XS-1645899170074)(images/9.gif)]

3.代码实现

public class SelectionSort implements IArraySort {

    @Override
    public int[] sort(int[] sourceArray) throws Exception {
        int[] arr = Arrays.copyOf(sourceArray, sourceArray.length);

        // 总共要经过 N-1 轮比较
        for (int i = 0; i < arr.length - 1; i++) {
            int min = i;

            // 每轮需要比较的次数 N-i
            for (int j = i + 1; j < arr.length; j++) {
                if (arr[j] < arr[min]) {
                    // 记录目前能找到的最小值元素的下标
                    min = j;
                }
            }
            // 将找到的最小值和i位置所在的值进行交换
            if (i != min) {
                int tmp = arr[i];
                arr[i] = arr[min];
                arr[min] = tmp;
            }

        }
        return arr;
    }
}

冒泡排序

冒泡排序(Bubble Sort)也是一种简单直观的排序算法。它重复地走访过要排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来。走访数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。这个算法的名字由来是因为越小的元素会经由交换慢慢"浮"到数列的顶端。

作为最简单的排序算法之一,冒泡排序给我的感觉就像 Abandon 在单词书里出现的感觉一样,每次都在第一页第一位,所以最熟悉。冒泡排序还有一种优化算法,就是立一个 flag,当在一趟序列遍历中元素没有发生交换,则证明该序列已经有序。但这种改进对于提升性能来

说并没有什么太大作用。

1. 算法步骤

比较相邻的元素。如果第一个比第二个大,就交换他们两个。

对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。这步做完后,最后的元素会是最大的数。

针对所有的元素重复以上的步骤,除了最后一个。

持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。

2. 动图演示

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gEjnuQZW-1645899170075)(images/10.gif)]

3.什么时候最快

当输入的数据已经是正序时(都已经是正序了,我还要你冒泡排序有何用啊)。

4.什么时候最慢

当输入的数据是反序时(写一个 for 循环反序输出数据不就行了,干嘛要用你冒泡排序呢,我是闲的吗)。

5.代码实现

public class BubbleSort implements IArraySort {

    @Override
    public int[] sort(int[] sourceArray) throws Exception {
        // 对 arr 进行拷贝,不改变参数内容
        int[] arr = Arrays.copyOf(sourceArray, sourceArray.length);

        for (int i = 1; i < arr.length; i++) {
            // 设定一个标记,若为true,则表示此次循环没有进行交换,也就是待排序列已经有序,排序已经完成。
            boolean flag = true;

            for (int j = 0; j < arr.length - i; j++) {
                if (arr[j] > arr[j + 1]) {
                    int tmp = arr[j];
                    arr[j] = arr[j + 1];
                    arr[j + 1] = tmp;

                    flag = false;
                }
            }

            if (flag) {
                break;
            }
        }
        return arr;
    }
}

插入排序

插入排序的代码实现虽然没有冒泡排序和选择排序那么简单粗暴,但它的原理应该是最容易理解的了,因为只要打过扑克牌的人都应该能够秒懂。插入排序是一种最简单直观的排序算法,它的工作原理是通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。

插入排序和冒泡排序一样,也有一种优化算法,叫做拆半插入。

1. 算法步骤

将第一待排序序列第一个元素看做一个有序序列,把第二个元素到最后一个元素当成是未排序序列。

从头到尾依次扫描未排序序列,将扫描到的每个元素插入有序序列的适当位置。(如果待插入的元素与有序序列中的某个元素相等,则将待插入元素插入到相等元素的后面。)

2. 动图演示

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-o53iXJYb-1645899170075)(images/11.gif)]

3.代码实现

public class InsertSort implements IArraySort {

    @Override
    public int[] sort(int[] sourceArray) throws Exception {
        // 对 arr 进行拷贝,不改变参数内容
        int[] arr = Arrays.copyOf(sourceArray, sourceArray.length);

        // 从下标为1的元素开始选择合适的位置插入,因为下标为0的只有一个元素,默认是有序的
        for (int i = 1; i < arr.length; i++) {

            // 记录要插入的数据
            int tmp = arr[i];

            // 从已经排序的序列最右边的开始比较,找到比其小的数
            int j = i;
            while (j > 0 && tmp < arr[j - 1]) {
                arr[j] = arr[j - 1];
                j--;
            }

            // 存在比其小的数,插入
            if (j != i) {
                arr[j] = tmp;
            }

        }
        return arr;
    }
}
        for (int i = 1; i < arr.length; i++) {

            // 记录要插入的数据
            int tmp = arr[i];

            // 从已经排序的序列最右边的开始比较,找到比其小的数
            int j = i;
            while (j > 0 && tmp < arr[j - 1]) {
                arr[j] = arr[j - 1];
                j--;
            }

            // 存在比其小的数,插入
            if (j != i) {
                arr[j] = tmp;
            }

        }
        return arr;
    }
}

持续更新中…

举报

相关推荐

0 条评论