美文网首页
冒泡排序

冒泡排序

作者: 点滴86 | 来源:发表于2024-07-28 23:29 被阅读0次

    冒泡排序

    冒泡排序只会操作相邻的两个数据。每次冒泡操作都会对相邻的两个元素进行比较,看是否满足大小关系要求。如果不满足就让它俩互换。一次冒泡会让至少一个元素移动它应该在的位置,重复n次,就完成了n个数据的排序工作。
    用一个例子,看下冒泡排序的整个过程。要对一组数据4,5,6,3,2,1,从小到大进行排序。第一次冒泡操作的详细过程如下:


    可以看出,经过一次冒泡操作之后,6这个元素已经存储在正确的位置上。要想完成所有数据的排序,只要进行6次这样的冒泡操作就行了。

    实际上,刚讲的冒泡过程还可以优化。当某次冒泡操作已经没有数据交换时,说明已经达到完全有序,不需要再继续执行后面的冒泡操作。这里还有另外一个例子,这里面给6个元素排序,只需要4次冒泡操作就可以了。

    冒泡排序算法的原理比较容易理解,代码实现如下:
    @interface DMSortDemo : NSObject
    
    @end
    
    @implementation DMSortDemo
    
    - (void)demo 
    {
        {
            NSMutableArray *dataArray = [[NSMutableArray alloc] init];
            [dataArray addObject:[NSNumber numberWithInteger:4]];
            [dataArray addObject:[NSNumber numberWithInteger:5]];
            [dataArray addObject:[NSNumber numberWithInteger:6]];
            [dataArray addObject:[NSNumber numberWithInteger:3]];
            [dataArray addObject:[NSNumber numberWithInteger:2]];
            [dataArray addObject:[NSNumber numberWithInteger:1]];
            [self bubblePort:dataArray];
        }
        {
            NSMutableArray *dataArray = [[NSMutableArray alloc] init];
            [dataArray addObject:[NSNumber numberWithInteger:3]];
            [dataArray addObject:[NSNumber numberWithInteger:5]];
            [dataArray addObject:[NSNumber numberWithInteger:4]];
            [dataArray addObject:[NSNumber numberWithInteger:1]];
            [dataArray addObject:[NSNumber numberWithInteger:2]];
            [dataArray addObject:[NSNumber numberWithInteger:6]];
            [self bubblePort:dataArray];
        }
    }
    
    - (void)bubblePort:(NSMutableArray *)array
    {
        NSLog(@"冒泡排序前数据:%@", array);
        NSInteger count = [array count];
        for (NSInteger i = 0; i < count; i++) {
            // 提前退出冒泡排序的标志位
            BOOL bExchange = NO;
            for (NSInteger j = 0; j < count - i - 1; j++) {
                NSNumber *firstNum = [array objectAtIndex:j];
                NSNumber *secondNum = [array objectAtIndex:j + 1];
                if ([firstNum integerValue] > [secondNum integerValue]) {
                    // 交换
                    [array replaceObjectAtIndex:j withObject:secondNum];
                    [array replaceObjectAtIndex:j + 1 withObject:firstNum];
                    // 表示有数据交互
                    bExchange = YES;
                }
            }
            if (bExchange == NO) {
                // 没有数据交互,提前退出
                break;
            }
        }
        NSLog(@"冒泡排序后数据:%@", array);
    }
    
    @end
    

    第一,冒泡排序是原地排序算法吗?

    冒泡的过程只涉及相邻数据的交换操作,只需要常量级的临时空间,所以它的空间复杂度为O(1),是一个原地排序算法。

    第二,冒泡排序是稳定的排序算法吗?

    在冒泡排序中,只有交换才可以改变两个元素的前后顺序。为了保证冒泡排序算法的稳定性,当有相邻的两个元素大小相等的时候,不做交换,相同大小的数据在排序前后不会改变顺序,所以冒泡排序是稳定的排序算法。

    第三,冒泡排序的时间复杂度是多少?

    最好情况下,要排序的数据已经是有序的了,只需要进行一次冒泡操作,就可以结束了,所以最好情况时间复杂度是O(n)。而最坏的情况是,要排序的数据刚好是倒序排列的,需要进行n次冒泡操作,所以最坏情况时间复杂度为O(n*n)。


    最好、最坏情况下的时间复杂度很容易分析,那平均情况下的时间复杂度是多少呢?
    对于包含n个数据的数组,这n个数据就有n!中排列方式。不同的排列方式,冒泡排序执行的时间肯定是不同的。比如前面举的例子,其中一个要进行6次冒泡,而另一个只需要4次。如果用概率论方法定量分析平均时间复杂度,涉及的数学推理和计算就会很复杂。这里还有一种思路,通过有序度和逆序度这两个概念来进行分析。
    有序度是数组中具有有序关系的元素对的个数。有序元素对用数学表达式表示就是下面这样。
    有序元素对:a[i] <= a[j], 如果i < j。
    

    对于一个倒序排列的数组,比如6,5,4,3,2,1,有序度是0;对于一个完全有序的数组,比如1,2,3,4,5,6,有序度就是n*(n-1)/2,也就是15。把这种完全有序的数组的有序度叫作满有序度。
    逆序度的定义正好跟有序度相反(默认从小到大为有序)。
    逆序元素对:a[i] > a[j], 如果i < j。
    

    关于这三个概念,可以得到一个公式:逆序度 = 满有序度 - 有序度。排序的过程就是一种增加有序度,减少逆序度的过程,最后达到满有序度,就说明排序完成了。
    还是拿前面举的那个冒泡排序的例子来说明。要排序的数组的初始状态是4,5,6,3,2,1,其中,有序元素对有(4,5)、(4,6)、(5,6),所以有序度是3。n = 6,所以排序完成之后终态的满有序度为n * (n - 1) / 2 = 15。


    冒泡排序包含两个操作原子,比较和交换。每交换一次,有序度就加1.不管算法怎么改进,交换次数总是确定的,即为逆序度,也就是n * (n - 1) / 2 - 初始有序度。此例中就是15 - 3 = 12,要进行12此交换操作。
    对于包含n个数据的数组进行冒泡排序,平均交换次数是多少呢?最坏情况下,初始状态的有序度是0,所以要进行n * (n-1)/2次交换。最好情况下,初始状态的有序度是n * (n-1)/2,就不需要进行交换。可以取个中间值n * (n-1)/4,来表示初始有序度既不是很高也不是很低的平均情况。
    换句话说,平均情况下,需要n * (n-1)/4次交换操作,比较操作肯定要比交换操作多,而复杂度的上限是O(n * n),所以平均情况下的时间复杂度就是O(n * n)。

    相关文章

      网友评论

          本文标题:冒泡排序

          本文链接:https://www.haomeiwen.com/subject/qfbihjtx.html