iOS 算法的前世今生:算法原理、常用算法(一)排序算法

----------------------------------何谓算法--------------------------

算法其实就是:把物理现象数学化,并按照规则求结果(把物理现象抽象成数字符号)。

要不怎么说:一个好的数学家一定是一个好的物理学家来着。

因为他懂得了物理现象到数学的真谛 ,就是:符号化、规律化、数学化。

比如一个简单的拼图游戏:(window7 经典小游戏)

我们小时候玩的拼图板

https://blog.csdn.net/dcrmg/article/details/52069043iOS 算法的前世今生:算法原理、常用算法(一)排序算法_第1张图片

里面就用到了很多算法,比如:A=B,B =C,C=D,D=A  替换算法。

还有网上流行的解绳套问题:(其实里面就拓扑学的算法)

-------------------------------------- 常用排序算法----------------------------

废话不多说,进入第一讲: 常用排序算法

参考链接:https://www.jianshu.com/p/77ba54a46ad7

由于链接的帖子中讲的比较详细,我这里仅做补充讲解

常用的排序算法如下:

1. 冒泡排序算法(Bubble Sort)、2. 快速排序算法(quick sort)、3. 选择排序算法(select sort)、4.  插入排序(insert sort)、5. 归并排序(merge sort)、6. 希尔排序(shell sort)、7. 基数排序(radix sort)、8. 计数排序(counting sort)、9. 桶排序(bucket sort)

(关于什么是时间复杂度,可以参考:https://blog.csdn.net/qq_41907991/article/details/97443681

iOS 算法的前世今生:算法原理、常用算法(一)排序算法_第2张图片

 

前言,其实目前主流的算法,核心都是我们小学学的“二分法”,不要因为大公司面试啥的老提算法,大家就觉得很高大上啥的,算法应该是融入我们生活中的,应该是很亲民的才对。各位小伙伴,用自己的大脑袋记住,算法的核心就是“二分法”,记不住就写在脸上。。。。

1. 冒泡排序算法(Bubble Sort)

冒泡排序算法应该是程序中很常用的一种排序算法,主要是做相邻两个元素的大小比较,比较后 进行大小数的位置替换。

这个很像上边说的 :拼图板游戏,其实都是 用了一种替换算法。

2. 快速排序算法(quick sort)

快速排序是由东尼·霍尔所发展的一种排序算法。这个老外一定精通二分算法的精髓,知道多数据比较会有次数平方的困扰,所以就用了一个类似开发的方式来抵消: 由于多数据比较带来的比较次数过多的问题。

我们看一下原贴描述

“在平均状况下,排序 n 个项目要 Ο(nlogn) 次比较。在最坏状况下则需要 Ο(n2) 次比较,但这种状况并不常见。事实上,快速排序通常明显比其他 Ο(nlogn) 算法更快,因为它的内部循环(inner loop)可以在大部分的架构上很有效率地被实现出来。

快速排序使用分治法(Divide and conquer)策略来把一个串行(list)分为两个子串行(sub-lists)。”

所谓分治法,其实说白了就是二分法。把数据划分成不同的区域放进数组,就可以极大提高比较速度。

如果 数据比较是平方,那么二分法就是开方,“平方/开方”,相当于没有平方,这也就是快速排序明显比其他 Ο(nlogn) 算法更快的根本原因。

**说到这里,有小伙伴就会问了:

“既然二分法这么屌,为啥快速排序每次只取一个数做“基准”,不如每次分区后都建立两个数组,每个数组都继续使用二分法,最后把比较好的数据进行排序。相当于对这些数据不断的做2的开方,速度不是更快吗?”

这个问题提的非常好!

但是由于现实物理环境不同于数学环境,物理设备总是有这样那样的限制,比如计算机的内存,如果不够大,在不断建立数组对象的情况下就很难进行二分。

比如:大型数据统计一组数据就有上兆亿个数据,用二分法建立的数组可能就有上万个。此时,就会对计算机造成极大的内存消耗,一般的电脑根本吃不消。。。毕竟抽象的数学环境还是不同于现实的物理环境的,这也是为什么会出现如此多不同算法的

原因,让大家可以选择不同的物理环境进行选择。。。

**这么一说,这位小伙伴可能又要问了:

“既然这样,那为啥不重用某这个数组呢?这样不就行了吗?”

说的非常好,不过你能想到的,人家也能想到,意外不意外,惊喜不惊喜?

iOS 算法的前世今生:算法原理、常用算法(一)排序算法_第3张图片

下边就让我们进入第三个算法,讲解这个问题!

3. 选择排序算法(select sort)

分为

直接选择排序(straight select sort)

2堆排序(heap sort  这个 涉及到完全二叉树的概念,但 二叉树的本质就是“二分法”,不过在后来的应用中做了很多的延伸和变形来适应不同的问题)

看下边这段代码:

- (void)heapSortWithArray:(NSMutableArray *)array {
    //循环建立初始堆
    for (NSInteger i = array.count * 0.5; i >= 0; i--) {
        [self heapAdjustWithArray:array parentIndex:i length:array.count];
    }
    //进行n-1次循环,完成排序
    for (NSInteger j = array.count - 1; j > 0; j--) {
        //最后一个元素和第一个元素进行交换
        [array exchangeObjectAtIndex:j withObjectAtIndex:0];
        //筛选R[0]结点,得到i-1个结点的堆
        [self heapAdjustWithArray:array parentIndex:0 length:j];
        NSLog(@"第%ld趟:", array.count - j);
        [self printHeapSortResult:array begin:0 end:array.count - 1];
    }
}

- (void)heapAdjustWithArray:(NSMutableArray *)array
                parentIndex:(NSInteger)parentIndex
                     length:(NSInteger)length {
    NSInteger temp = [array[parentIndex] integerValue]; //temp保存当前父结点
    NSInteger child = 2 * parentIndex + 1; //先获得左孩子
    
    while (child < length) {
        //如果有右孩子结点,并且右孩子结点的值大于左孩子结点,则选取右孩子结点
        if (child + 1 < length && [array[child] integerValue] < [array[child + 1] integerValue]) {
            child++;
        }
        
        //如果父结点的值已经大于孩子结点的值,则直接结束
        if (temp >= [array[child] integerValue]) {
            break;
        }
        
        //把孩子结点的值赋值给父结点
        array[parentIndex] = array[child];
        
        //选取孩子结点的左孩子结点,继续向下筛选
        parentIndex = child;
        child = 2 * child + 1;
    }
    array[parentIndex] = @(temp);
}

- (void)printHeapSortResult:(NSMutableArray *)array
                      begin:(NSInteger)begin
                        end:(NSInteger)end {
    for (NSInteger i = 0; i < begin; i++) {

    }
    for (NSInteger i = begin; i <= end; i++) {
        
    }
    //打印堆排序
    NSLog(@"堆排序升序结果是--->%@",array);
}

第二行代码:这个 heapAdjustWithArray: parentIndex:  length:方法 就是不断的对传入的Array 数组进行重用!

4.  插入排序(insert sort)

说到这里,那么插入排序的核心是不是也是我们上边提到 的“二分法”呢?

恭喜你,答对了!

一般的插入排序其实就是两个数据进行比较,这个和冒泡算法差不多,但是,插入排序和冒泡排序一样,也有一种优化算法,叫做拆半插入。

何谓“ 拆半”?

其实就是二分嘛!

万变不离其宗,其核心还是“二分法”。

可见“你大爷永远是你大爷!”

iOS 算法的前世今生:算法原理、常用算法(一)排序算法_第4张图片

5. 归并排序(merge sort)

这个算法不用我讲,大家用屁股也能猜到,其核心还是“二分法”!

为啥?

下面我们来看一下帖子对其算法的描述:
“归并排序(Merge sort)是建立在归并操作上的一种有效的排序算法。该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。

作为一种典型的分而治之思想的算法应用,归并排序的实现由两种方法:

  • 自上而下的递归(所有递归的方法都可以用迭代重写,所以就有了第 2 种方法);
  • 自下而上的迭代;

在《数据结构与算法 JavaScript 描述》中,作者给出了自下而上的迭代方法。

和选择排序一样,归并排序的性能不受输入数据的影响,但表现比选择排序好的多,因为始终都是 O(nlogn) 的时间复杂度。代价是需要额外的内存空间。”

分治法(Divide and Conquer)?

这不还是说的“二分法”吗?为啥 需要“需要额外的内存空间”,不就是要多建立数组来存储分区数据吗?这才是其“表现比选择排序好的多”的根本原因!!!

6. 希尔排序(shell sort)

帖子描述:

“希尔排序,也称递减增量排序算法,是插入排序的一种更高效的改进版本。但希尔排序是非稳定排序算法。

希尔排序是基于插入排序的以下两点性质而提出改进方法的:

  • 插入排序在对几乎已经排好序的数据操作时,效率高,即可以达到线性排序的效率;
  • 但插入排序一般来说是低效的,因为插入排序每次只能将数据移动一位;

希尔排序的基本思想是:先将整个待排序的记录序列分割成为若干子序列分别进行直接插入排序,待整个序列中的记录“基本有序”时,再对全体记录进行依次直接插入排序。”

怎么样?

这个描述是不是很面熟!

“先将整个待排序的记录序列分割成为若干子序列”不就是“二分法”划分 不同数据区间的意思吗?

然后“二分”比较或者插入比较后,直接进行排序!

7. 基数排序(radix sort) VS 8. 计数排序(counting sort) VS 9. 桶排序(bucket sort)

------这3个算法比较有意思了,我下边重点讲一下---------

这三种排序法,不同于一般的数据“二分法”,或许应该就叫做“0-9 十分法”:

它 不是 对   所有数据进行个数的区间“二分”;而是对每一个数据进行位次上的“二分”!

有小朋友表示看不懂下边这个图,我们来讲解一下哈:大家打起精神,不要“神游天外思佳人,望着学究直愣神”

iOS 算法的前世今生:算法原理、常用算法(一)排序算法_第5张图片

 

如果按照“二分法”来讲:

第一次排序:其实是对“个位”为0 或非0的数进行的二分;所以50 和其他数分开了;

第二次排序:是对“十分位”为0或非0进行的二分;这里已经把2、3、4、5换成了“02、03、04、05”来进行比较;

**第三次排序:如果添加一个数:101,进行第三次排序,那么就会在“百分位”进行比较;所有数据就变成了“002、003、004、005、015、019....... 050、 101”,也就只有101 会在非0 位置。

如果按照“0-9 十分法”来讲:

第一次排序:是对“个位”的数进行的“0-9”划分;

第二次排序:是对“十分位”的数进行的“0-9”划分;其实已经把2、3、4、5换成了“02、03、04、05”来进行划分;

**第三次排序:如果添加一个数:101,那么就会在“百分位”进行划分;所有数据就变成了“002、003、004、005、015、019、 050...... 101”。

 

-----------------------结语----------------------

其实算法并不是很晦涩难懂的问题,我们不要有“望它高楼难下脚”的感觉,谁能想到,这么多高级的算法,本质就是小学学的“二分法”呢?

----------------------引伸----------------

如果说“二分法”是对数据比较的难度进行了开平方处理,那我们就可以想到:是不是还有开 “立方”处理呢?

结合大学学习的“矩阵算法”,我们是不是可以在三维上可以对数据算法进行发展呢?

三维算法可能是“0-9十分法”和“二分法”的组合。。。。

也可能是两个“0-9十分法”的组合。。。。

大家可以让思想飞翔,不拘一格的去研究。。。。

去吧骚年!

你可能感兴趣的:(iOS,系统)