算法

算法

冒泡排序

  1. 冒泡排序定义

冒泡排序就是重复“从序列右边开始比较相邻两个数字的大小,再根据结果交换两个数字的位置”这一操作的算法。在这个过程中,数字会像泡泡一样,慢慢从右往左“浮”到序列的顶端,所以这个算法才被称为“冒泡排序”。

  1. 冒泡过程

比较天平两边数字大小,如果右边的数字比较小就交换两个数字的位置,这样一来小的数字就排在左边。

image-20200802090650057

由于7>6,所以交换位置变成下图:

image-20200802090856350

由于4<6,所以无需交换位置

image-20200802090928881

image-20200802091341566

继续将天平往左移动一个位置并比较数字。重复同样的操作直到天平到达序列最左边为止。

image-20200802091631111

不断对数字进行交换,天平最终到达了最左边。通过这一系列操作,序列中最小的数字就会移动到最左边。

image-20200802091711873

将天平移回最右边,然后重复之前的操作,直到天平到达左边第2个位置为止。

image-20200802091735258

当天平到达左边第2个位置时,序列中第2小的数字也就到达了指定位置。

image-20200802091807781

将天平再次移回最右边,重复同样的操作直到所有数字都归位为止。

  1. 冒泡解说

在冒泡排序中,第1轮需要比较n-1次,第2轮需要比较n-2次……第n-1轮需要比较1次。因此,总的比较次数为(n-1)+(n-2)+…+1≈n2/2。这个比较次数恒定为该数值,和输入数据的排列顺序无关。

不过,交换数字的次数和输入数据的排列顺序有关。假设出现某种极端情况,如输入数据正好以从小到大的顺序排列,那么便不需要任何交换操作;反过来,输入数据要是以从大到小的顺序排列,那么每次比较数字后便都要进行交换。因此,冒泡排序的时间复杂度为O(n2)。

  1. 冒泡排序实现(Java)
public static void bubbleSort(int[] arr) {
        if (arr == null) {
            System.out.println("请输入数组!");
            return;
        }
        if (arr.length <= 1) {//如果传入的数组是空数组或是只有一个元素的数没必要排序
            System.out.println(Arrays.toString(arr));
            return;
        }
        for (int i = 1; i < arr.length; i++) {//外层循环控制第几轮
            for (int j = arr.length - 1; j > i - 1; j--) {//内层循环控制天平左右两个数字的比较
                int temp;
                if (arr[j] < arr[j - 1]) {//如果天平右边的数字比左边的数字小就交换,否则什么都不做
                    temp = arr[j - 1];
                    arr[j - 1] = arr[j];
                    arr[j] = temp;
                }
            }
        }
        System.out.println(Arrays.toString(arr));
}

选择排序

  1. 选择排序定义

选择排序就是重复“从待排序的数据中寻找最小值,将其与序列最左边的数字进行交换”这一操作的算法。

  1. 选择排序过程

对数字1~9进行排序

image-20200802104306694

image-20200802105538215

使用线性查找查找最小值,找到了数字1

image-20200802105615512

将最小值1与最左边的数字6交换,那么最小值就会放在最左边,如果最小值本身就是最左边那么无需交换

image-20200802105719250

在余下的数据中找寻最小值,这一次找到了2,将数字2和余下的数据中最左边的数字6进行交换

image-20200802105825797

image-20200802105838217

重复同样的行为直到所有数字都归位

image-20200802105908708

  1. 选择排序解说

选择排序使用了线性查找来寻找最小值,因此在第1轮中需要比较n-1个数字,第2轮需要比较n-2个数字……到第n-1轮的时候就只需比较1个数字了。因此,总的比较次数与冒泡排序的相同,都是(n-1)+(n-2)+…+1≈n2/2次。

每轮中交换数字的次数最多为1次。如果输入数据就是按从小到大的顺序排列的,便不需要进行任何交换。选择排序的时间复杂度也和冒泡排序的一样,都为O(n2)。

  1. 选择排序实现(Java)
public static void selectSort(int[] arr) {
        if (arr == null) {
            System.out.println("请输入数组!");
            return;
        }
        if (arr.length <= 1) {//如果传入的数组是空数组或是只有一个元素的数没必要排序
            System.out.println(Arrays.toString(arr));
            return;
        }
        for (int i = 1; i < arr.length; i++) {//外层循环控制第几轮
            int minIdex = i-1;
            int temp;
            for(int j=i;j<arr.length-1;j++){
                if(arr[minIdex]>arr[j]){
                    temp=arr[minIdex];
                    arr[minIdex]=arr[j];
                    arr[j]=temp;
                }
            }
        }
        System.out.println(Arrays.toString(arr));
}

插入排序

  1. 插入排序定义

插入排序是一种从序列左端开始依次对数据进行排序的算法。在排序过程中,左侧的数据陆续归位,而右侧留下的就是还未被排序的数据。插入排序的思路就是从右侧的未排序区域内取出一个数据,然后将它插入到已排序区域内合适的位置上。

对数字1~9排序

image-20200802125114261

假设最左比的数字5已经完成排序,所以此时只有5是已归为数字

image-20200802125314700

接下来,从待排数字(未排序区域)中取出最左边的数字3,将它与左边已归位的数字进行比较。若左边的数字更大,就交换这两个数字。重复该操作,直到左边已归位的数字比取出的数字更小,或者取出的数字已经被移到整个序列的最左边为止

image-20200802125421219

image-20200802125445217

对数字3的操作到此结束。此时3和5已归位,还剩下右边7个数字尚未排序

image-20200802125520940

接下来是第3轮。和前面一样,取出未排序区域中最左边的数字4,将它与左边的数字5进行比较

image-20200802125554186

由于5>4,所以交换这两个数字。交换后再把4和左边的3进行比较,发现3<4,因为出现了比自己小的数字,所以操作结束

image-20200802125625330

于是4也归位了。此时3、4、5都已归位,已排序区域也得到了扩大

image-20200802125644786

重复上述操作即可完成排序

  1. 插入排序解说

在插入排序中,需要将取出的数据与其左边的数字进行比较。就跟前面讲的步骤一样,如果左边的数字更小,就不需要继续比较,本轮操作到此结束,自然也不需要交换数字的位置。

然而,如果取出的数字比左边已归位的数字都要小,就必须不停地比较大小,交换数字,直到它到达整个序列的最左边为止。具体来说,就是第k轮需要比较k-1次。因此,在最糟糕的情况下,第2轮需要操作1次,第3轮操作2次……第n轮操作n-1次,所以时间复杂度和冒泡排序的一样,都为O(n2)。

  1. 插入排序实现(Java)
public static void insertSort(int[] arr) {
        if (arr == null) {
            System.out.println("请输入数组!");
            return;
        }
        if (arr.length <= 1) {//如果传入的数组是空数组或是只有一个元素的数没必要排序
            System.out.println(Arrays.toString(arr));
            return;
        }
        for (int i = 1; i < arr.length; i++) {//外层循环控制第几轮
            int temp;
            int target = i;
            for(int j=i-1;j>=0;j--){
                if(arr[target]>=arr[j]){
                    break;
                }else {
                    temp=arr[j];
                    arr[j]=arr[target];
                    arr[target]=temp;
                    target=j;
                }
            }
        }
        System.out.println(Arrays.toString(arr));
}

堆排序

  1. 堆排序的定义

堆排序的特点是利用了数据结构中的堆进行排序。

在堆中存储数据并按照降维构建堆

image-20200802133740971

现在,所有数据都存进堆里了。为了排序,需要再从堆中把数据一个个取出来

image-20200802133941721

从降序排列的堆中取出数据时会从最大的数据开始取,所以将取出的数据反序输出,排序就完成了

image-20200802134135247

image-20200802134149320

image-20200802134203331

重复上述操作直到排序完成

image-20200802134232108

  1. 堆排序解说

堆排序一开始需要将n个数据存进堆里,所需时间为O(nlogn)。排序过程中,堆从空堆的状态开始,逐渐被数据填满。由于堆的高度小于log2n,所以插入1个数据所需要的时间为O(logn)。

每轮取出最大的数据并重构堆所需要的时间为O(logn)。由于总共有n轮,所以重构后排序的时间也是O(nlogn)。因此,整体来看堆排序的时间复杂度为O(nlogn)。

  1. 堆排序实现

归并排序

  1. 归并排序定义

归并排序算法会把序列分成长度相同的两个子序列,当无法继续往下分时(也就是每个子序列中只有一个数据时),就对子序列进行归并。归并指的是把两个排好序的子序列合并成一个有序序列。该操作会一直重复执行,直到所有子序列都归并为一个整体为止。

  1. 归并排序过程

首先将序列对半分割

image-20200802143044159

image-20200802143056172

image-20200802143116473

image-20200802143130216

分割完毕。接下来对分割后的元素进行合并

image-20200802143204954

把6和4合并,合并后的顺序为[4, 6]

image-20200802143229722

接下来把3和7合并,合并后的顺序为[3, 7]

下面,我们来看看怎么合并[4, 6]和[3, 7]。合并这种含有多个数字的子序列时,要先比较首位数字,再移动较小的数字。

image-20200802143324494

由于4>3,所以移动3

image-20200802143348508

同样地,再次比较序列中剩下的首位数字

image-20200802143411247

由于4<7,所以移动4

image-20200802143429012

由于6<7,所以移动6

image-20200802143450098

最后移动剩下的7

image-20200802143518254

递归执行上面的操作,直到所有的数字都合为一个整体为止

image-20200802143544912

循环上述操作直到排序完成

  1. 归并排序解说

归并排序中,分割序列所花费的时间不算在运行时间内(可以当作序列本来就是分割好的)。在合并两个已排好序的子序列时,只需重复比较首位数据的大小,然后移动较小的数据,因此只需花费和两个子序列的长度相应的运行时间。也就是说,完成一行归并所需的运行时间取决于这一行的数据量。

看一下上面的图便能得知,无论哪一行都是n个数据,所以每行的运行时间都为O(n)。而将长度为n的序列对半分割直到只有一个数据为止时,可以分成log2n行,因此,总共有log2n行。也就是说,总的运行时间为O(nlogn)。

  1. 归并排序实现(Java)

快速排序

  1. 快速排序的定义

快速排序算法首先会在序列中随机选择一个基准值(pivot),然后将除了基准值以外的数分为“比基准值小的数”和“比基准值大的数”这两个类别,再将其排列成以下形式。

[比基准值小的数] 基准值 [比基准值大的数]

接着,对两个“[ ]”中的数据进行排序之后,整体的排序便完成了。对“[ ]”里面的数据进行排序时同样也会使用快速排序。

  1. 快速排序过程

image-20200802144017243

在序列中随机选择一个基准值。这里选择了4

image-20200802144041195

将其他数字和基准值进行比较。小于基准值的往左移,大于基准值的往右移

image-20200802144108566

首先,比较3和基准值4

image-20200802144142950

因为3<4,所以将3往左移

image-20200802144201164

接下来,比较5和基准值4

image-20200802144229872

因为5>4,所以将5往右移

image-20200802144301790

对其他数字也进行同样的操作,最终结果如上图所示

image-20200802144322056

把基准值4插入序列。这样,4左边就是比它小的数字,右边就是比它大的数字

image-20200802144345820

分别对左边和右边的数据进行排序后,就能完成整体的排序

image-20200802144413278

  1. 快速排序解说

分割子序列时需要选择基准值,如果每次选择的基准值都能使得两个子序列的长度为原本的一半,那么快速排序的运行时间和归并排序的一样,都为O(nlogn)。和归并排序类似,将序列对半分割log2n次之后,子序列里便只剩下一个数据,这时子序列的排序也就完成了。因此,如果像下图这样一行行地展现根据基准值分割序列的过程,那么总共会有log2n行。

image-20200802144846957

每行中每个数字都需要和基准值比较大小,因此每行所需的运行时间为O(n)。由此可知,整体的时间复杂度为O(nlogn)。

如果运气不好,每次都选择最小值作为基准值,那么每次都需要把其他数据移到基准值的右边,递归执行n行,运行时间也就成了O(n2)。这就相当于每次都选出最小值并把它移到了最左边,这个操作也就和选择排序一样了。此外,如果数据中的每个数字被选为基准值的概率都相等,那么需要的平均运行时间为O(nlogn)。

  1. 快速排序实现(Java)

二分查找

  1. 二分查找定义

二分查找也是一种在数组中查找数据的算法,它只能查找已经排好序的数据。二分查找通过比较数组中间的数据与目标数据的大小,可以得知目标数据是在数组的左边还是右边。因此,比较一次就可以把查找范围缩小一半。重复执行该操作就可以找到目标数据,或得出目标数据不存在的结论。

  1. 二分查找过程

要查找数字6

image-20200802145600857

首先找到数组中间的数字,此处为5

image-20200802145705705

将5和要查找的数字6进行比较

image-20200802145736820

把不需要的数字移出查找范围

image-20200802145800251

在剩下的数组中找到中间的数字,此处为7

image-20200802145822315

比较7和6

image-20200802145841156

把不需要的数字移出查找范围

image-20200802145911306

成功找到目标数字

image-20200802145934656

  1. 二分查找解说

二分查找利用已排好序的数组,每一次查找都可以将查找范围减半。查找范围内只剩一个数据时查找结束。

数据量为n的数组,将其长度减半log2n次后,其中便只剩一个数据了。也就是说,在二分查找中重复执行“将目标数据和数组中间的数据进行比较后将查找范围减半”的操作log2n次后,就能找到目标数据(若没找到则可以得出数据不存在的结论),因此它的时间复杂度为O(logn)。

  1. 二分查找实现(Java)

posted @ 2020-08-03 10:07  乌利乌利  阅读(28)  评论(0)    收藏  举报