算法基础

计算机是用来存储和处理数据的。自然界中物体不可能是独立的,必定和别的物体之间有联系。比如人离不开衣服一样(除非在家裸睡,那也得睡在床上啊),总之物体不可能独立存在。同样的,在计算机的世界里,数据之间也是存在某些联系的,我们把这种联系称为数据结构。数据结构有逻辑结构和物理结构。逻辑结构有:链表,树,图等。物理结构表示的是数据在内存中的存储结构,可以是连续的,比如数组。也可以是不连续的内存,比如链表结构,可以用一个指针指向另一段内存的起始位置。在写 C 语言的时候,有很多数据类型,比如 int、 float、struct 等,为什么会有这么多数据类型呢?打个比方,现实世界中,有别墅,有胶囊公寓,这些都是用来住人的,家里人多的话住别墅,人少的话住胶囊公寓就行。房子就好比是计算机中的内存,有了数据结构后就能给数据分配合适的存储空间,这样不会浪费,效率会高点儿。

时间复杂度
我们怎么评估一个算法写的好坏呢?在不同平台上都运行一下代码,看看这个算法所需要的时间和占用内存的大小来评估算法的好坏,这显然是不科学的。于是就引入了时间复杂度和空间复杂度这两个概念。时间复杂度来评估算法的时间,空间复杂度来评估算法所需要占用的空间。在进行算法分析时,语句总的执行次数 T ( n ) 是关于问题规模 n 的函数,记作:T ( n ) = O ( fn ( n ) ) 。这样用 O ( ) 来体现算法时间复杂度的表示方式,我们称之为大 O 记法。推导大 O 阶方法要记住下面这三条规则:1. 用常数 1 来代表运行时间中所有加法常数。2. 只保留最高阶项。3. 如果最高阶存在且不为 1 ,则去除与这个项相乘的常数。下面来举列子依次说明怎么求大 O 阶。

例 1 常数阶

int a = 1;  //执行一次
a = a + 1;  //执行一次
printf("%d",a); //执行一次

上面的算法语句一共执行了 3 次,根据第一条规则:用常数 1 来代表运行时间中所有加法常数,那么它的时间复杂度为 O (1),我们把这种时间复杂度称之为常数阶。

例2 线性阶

int n = 100;    //执行一次
for (int i = 0; i < n; i++) {
    printf("%d",i); //执行 n 次
}

上面的 for 循环里的 printf 被执行了 n 次,一共执行了 n + 1 次,根据第二条规则:只保留最高阶项,那么它的时间复杂度为 O (n), 我们把这种时间复杂度称之为线性阶。

例3 平方阶

 for (int i = 0; i < n; i++) {
    for (int j = 0; j < n; i++) {
        printf("%d",i);
    }
}

上面的 for 循环里的 printf 被执行了 n ^ 2 次,根据上面的规则,它的时间复杂度为 O ( n ^ 2) , 我们把这种时间复杂度称之为平方阶。

例4

 while (i < n) {
    i = i * 2;
}

上面的 while 里的语句被执行了 log2n 次,它的时间复杂度为 log2n。

总结:常用的时间复杂度所花费的时间从小到大依次是:

CalculateFun2.jpg

上面介绍了时间复杂度的推导方法。下面着重来学习下常用的排序方法,因为算法一直是编程的基础,而排序算法是学习算法的开始,排序也是数据处理的重要内容。排序的归类如下:

CalculateFun1.jpg

冒泡排序

基本思想:依次比较相邻的两个数,将小数放在前面,大数放在后面。

实现代码如下:

//冒泡排序
- (NSMutableArray *)bubbleSort:(NSMutableArray *)array {
    NSInteger n = array.count;
    for (NSInteger i = 0; i < n; i++) {
        //这里注意下,一定是 n - 1 -i,要不会数组越界
        for (NSInteger j = 0; j < n - 1 - i; j++) {
            NSInteger a = [[array objectAtIndex:j] intValue];
            NSInteger b = [[array objectAtIndex:j+1] intValue];
            if (a > b) {
                [array replaceObjectAtIndex:j withObject:[NSString stringWithFormat:@"%ld",b]];
                [array replaceObjectAtIndex:j+1 withObject:[NSString stringWithFormat:@"%ld",a]];
            }
        }
    }
    return array;
}

复杂度为:n^2。

优化方法:

快速排序

基本思想是:

1.先从数列中取出一个数作为基准数(一般选择第一个)。
2.分区过程,将比这个数大的数全放到它的右边,小于或等于它的数全放到它的左边。
3.再对左右区间重复第二步,直到各区间只有一个数。

实现代码如下:

//快速排序
-(NSMutableArray *)quickSortWithArray:(NSMutableArray *)array left:(NSInteger)left right:(NSInteger)right{
    if (right > left) {
        NSInteger x = [[array objectAtIndex:left] integerValue];
        NSInteger i = left;
        NSInteger j = right;
        while (i < j) {
            //从右向左找第一个小于x的数
            while (i < j && [[array objectAtIndex:j] integerValue] >= x) {
                j--;
            }
            if (i < j) {
                [array replaceObjectAtIndex:i++ withObject:[array objectAtIndex:j]];
            }

            //从左向右找第一个大于等于x的数
            while (i < j && [[array objectAtIndex:i] integerValue] < x) {
                i++;
            }
            if (i < j) {
                [array replaceObjectAtIndex:j-- withObject:[array objectAtIndex:i]];
            }
        }

        [array replaceObjectAtIndex:i withObject:[NSString stringWithFormat:@"%ld",x]];
        [self quickSortWithArray:array left:left right:i-1];
        [self quickSortWithArray:array left:i+1 right:right];
    }
    return array;
}

直接插入排序

基本思想:直接插入排序,顾名思义,把一个数插入一个已经排好序的数据序列里。我们通常把第一数认为是已经排好序的一个数据序列。

实现代码如下:

//直接插入排序
- (NSMutableArray *)insertSort:(NSMutableArray *)array {
    //假定第一个是排好序的,所以 i 从 1 开始
    for (NSInteger i = 1; i < array.count; i++) {
        NSInteger j = i;
        NSInteger target = [[array objectAtIndex:i] integerValue];

        while (j > 0 && target < [[array objectAtIndex:j - 1] integerValue]) {
            [array replaceObjectAtIndex:j withObject:[array objectAtIndex:j - 1]];
            j--;
        }
        [array replaceObjectAtIndex:j withObject:[NSString stringWithFormat:@"%ld",target]];
    }
    return array;
}

复杂度为:n^2。

希尔排序

基本思想:希尔排序(Shell Sort)是插入排序的一种。也称缩小增量排序,是直接插入排序算法的一种更高效的改进版本。希尔排序是非稳定排序算法。该方法因DL.Shell于1959年提出而得名。
希尔排序是把记录按下标的一定增量分组,对每组使用直接插入排序算法排序;随着增量逐渐减少,每组包含的关键词越来越多,当增量减至1时,整个文件恰被分成一组,算法便终止。

实现代码如下:

//希尔排序
- (NSMutableArray *)shellSort:(NSMutableArray *)array {
    NSInteger gap = array.count / 2;
    while (1 <= gap) {
        for (NSInteger i = gap; i < array.count; i++) {
            NSInteger j;
            NSInteger tmp = [[array objectAtIndex:i] integerValue];
            for (j = i - gap; j >= 0 && tmp < [[array objectAtIndex:j] integerValue]; j -= gap) {
                [array replaceObjectAtIndex:j + gap withObject:[array objectAtIndex:j]];
            }
            [array replaceObjectAtIndex:j + gap withObject:[NSString stringWithFormat:@"%ld",tmp]];
        }
        gap = gap / 2;
    }

    return array;
}

复杂度为:n^2。