vlambda博客
学习文章列表

冒泡排序算法你真的会了吗

冒泡排序算法你真的会了吗


摘自:百度百科

冒泡排序算法是把较小的元素往前调或者把较大的元素往后调。这种方法主要是通过对相邻两个元素进行大小的比较,根据比较结果和算法规则对该二元素的位置进行交换,这样逐个依次进行比较和交换,就能达到排序目的。冒泡排序的基本思想是,首先将第1个和第2个记录的关键字比较大小,如果是逆序的,就将这两个记录进行交换,再对第2个和第3个记录的关键字进行比较,依次类推,重复进行上述计算,直至完成第(n一1)个和第n个记录的关键字之间的比较,此后,再按照上述过程进行第2次、第3次排序,直至整个序列有序为止。排序过程中要特别注意的是,当相邻两个元素大小一致时,这一步操作就不需要交换位置,因此也说明冒泡排序是一种严格的稳定排序算法,它不改变序列中相同元素之间的相对位置关系。

下面的我们就根据冒泡算法的原理,实现一下这个算法.

以升序为例进行实现

从原理中可以明确得知,该算法的核心是将一组数据完全遍历,然后将相邻的两个数进行比较,按照顺序进行大小交换位置.

如果左边数字大于右边数字,进行交换位置,第一遍比较后,最后一个数字就是最大的那个数字.但是前面的数字还是无序的,继续进行重复的操作,但是应该将最后一个数字排除.

原始数组[12,34,23,15,43,53,23]

第一遍排序完后[12,34,23,15,43,23,53]

第二遍排序完后[12,34,23,15,23,43,53]

.....

基本实现

<?php
/**
* User: Troy
* Date: 2020/5/7
* Time: 11:06 上午
*/

namespace sort\bubble;

/**
* 冒泡排序算法
*
* @package sort\bubble
*/
class BubbleSort
{
   private $sortArray = [];

   public $sortedArr = [];

   public function __construct($array = [])
  {
       $this->getArray();
  }

   /**
    * 生成一个大数组
    *
    * @author Chuang
    */
   private function getArray()
  {
       for ($i = 0; $i < 10000; $i++) {
           $tmp = rand(10, 50000);
           array_push($this->sortArray, $tmp);
      }
       var_dump('原始数组: ' . implode('-', $this->sortArray));
  }

   /**
    * 基本冒泡排序
    *
    * @param array $arrayParams
    * @author Chuang
    */
   public function bubble1($arrayParams = [])
  {
       $array = empty($arrayParams) ? $this->sortArray : $arrayParams;
       // 计时开始
       $t1 = microtime(true);
       for ($end = count($array) - 1; $end > 0; $end--) {
           for ($begin = 1; $begin <= $end; $begin++) {
               // 如果左边数字大于右边数字,交换位置
               if ($array[$begin] < $array[$begin - 1]) {
                   $tmp = $array[$begin];
                   // 交换位置
                   $array[$begin] = $array[$begin - 1];
                   $array[$begin - 1] = $tmp;
              }
          }
      }
       // 计时结束
       $t2 = microtime(true);
       $t = $t2 - $t1;

       var_dump(implode('-', $array));
       var_dump('执行时间: ' . number_format($t, 10));
       $this->sortedArr = $array;
       opcache_reset();
  }
}

$a = new BubbleSort();
$a->bubble1();

针对完全有序序列排序优化

但是现在的实现存在一个问题,如果待排序数组是一个完全有序的数组,在实际意义上讲是没有必要进行在重新排序的,但是上面的实现,不会区分待排序数组是否有序,都会完全遍历一遍.浪费计算资源.

如果是一个完全有序数组的话,其实在第一遍循环时,已经可以确定.如果确定是完全有效数组,就将程序停止,返回结果即可.

下面我们来优化一下.

<?php
/**
* User: Troy
* Date: 2020/5/7
* Time: 11:06 上午
*/

namespace sort\bubble;

/**
* 冒泡排序算法
*
* @package sort\bubble
*/
class BubbleSort
{
   private $sortArray = [];

   public $sortedArr = [];

   public function __construct($array = [])
  {
       $this->getArray();
  }

   /**
    * 生成一个大数组
    *
    * @author Chuang
    */
   private function getArray()
  {
       for ($i = 0; $i < 10000; $i++) {
           $tmp = rand(10, 50000);
           array_push($this->sortArray, $tmp);
      }
       var_dump('原始数组: ' . implode('-', $this->sortArray));
  }

   /**
    * 基本冒泡排序
    *
    * @param array $arrayParams
    * @author Chuang
    */
   public function bubble1($arrayParams = [])
  {
       $array = empty($arrayParams) ? $this->sortArray : $arrayParams;
       // 计时开始
       $t1 = microtime(true);
       for ($end = count($array) - 1; $end > 0; $end--) {
           for ($begin = 1; $begin <= $end; $begin++) {
               // 如果左边数字大于右边数字,交换位置
               if ($array[$begin] < $array[$begin - 1]) {
                   $tmp = $array[$begin];
                   // 交换位置
                   $array[$begin] = $array[$begin - 1];
                   $array[$begin - 1] = $tmp;
              }
          }
      }
       // 计时结束
       $t2 = microtime(true);
       $t = $t2 - $t1;

       var_dump(implode('-', $array));
       var_dump('执行时间: ' . number_format($t, 10));
       $this->sortedArr = $array;
       opcache_reset();
  }

   /**
    * 优化如果待排序数组为完全有序序列就提前结束执行
    *
    * @param array $arrayParams
    * @author Chuang
    */
   public function bubble2($arrayParams = [])
  {
       $array = empty($arrayParams) ? $this->sortArray : $arrayParams;
       $t1 = microtime(true);
       for ($end = count($array) - 1; $end > 0; $end--) {
           $sorted = true;
           for ($begin = 1; $begin <= $end; $begin++) {
               if ($array[$begin] < $array[$begin - 1]) {
                   $tmp = $array[$begin];
                   $array[$begin] = $array[$begin - 1];
                   $array[$begin - 1] = $tmp;
                   $sorted = false;
              }
          }
           if ($sorted) {
               break;
          }
      }

       $t2 = microtime(true);
       $t = $t2 - $t1;

       var_dump(implode('-', $array));
       var_dump('执行时间: ' . number_format($t, 10));
       opcache_reset();
  }
}

$a = new BubbleSort();

$a->bubble1();

$a->bubble2();
// 完全有序数组
$a->bubble1($a->sortedArr);
// 完全有序数组
$a->bubble2($a->sortedArr);

执行结果

string(26) "执行时间: 3.1422049999"  # 无序数组排序消耗时间
string(26) "执行时间: 3.0795838833"  # 无序数组优化算法排序消耗时间
string(26) "执行时间: 1.7149028778"  # 完全序数组排序消耗时间
string(26) "执行时间: 0.0003330708"  # 完全有序数组优化算法排序消耗时间

但是在实际应用中这种完全有序的情况还是比较少的.

从上面的执行结果中可以明显看出,优化后的逻辑比优化前针对完全有序的数组性能提升还是比较明显的.

针对局部有序序列进行优化

如果待排序的序列尾部是有序序列或者经过几轮排序后待排序的序列尾部成为有序状态,那么有序的那段数据是没有必要进行在排列的.这种场景出现的概率还是比较大的.

例如: [2,12,8,13,15,17]这个待排序数据中后半部分是有序状态,是不需要进行排列的.在第一遍循环时,只要确定最后一次进行交换数据的位置为分界线,后面的数据就可以排除,分界线前面的数据继续进行排列.

<?php
/**
* User: Troy
* Date: 2020/5/7
* Time: 11:06 上午
*/

namespace sort\bubble;

/**
* 冒泡排序算法
*
* @package sort\bubble
*/
class BubbleSort
{
   private $sortArray = [];

   public $sortedArr = [];

   public function __construct($array = [])
  {
       $this->getArray();
  }

   /**
    * 生成一个大数组
    *
    * @author Chuang
    */
   private function getArray()
  {
       for ($i = 0; $i < 5; $i++) {
           $tmp = rand(10, 50000);
           array_push($this->sortArray, $tmp);
      }
       var_dump('原始数组: ' . implode('-', $this->sortArray));
  }

   /**
    * 基本冒泡排序
    *
    * @param array $arrayParams
    * @author Chuang
    */
   public function bubble1($arrayParams = [])
  {
       $array = empty($arrayParams) ? $this->sortArray : $arrayParams;
       // 计时开始
       $t1 = microtime(true);
       for ($end = count($array) - 1; $end > 0; $end--) {
           for ($begin = 1; $begin <= $end; $begin++) {
               // 如果左边数字大于右边数字,交换位置
               if ($array[$begin] < $array[$begin - 1]) {
                   $tmp = $array[$begin];
                   // 交换位置
                   $array[$begin] = $array[$begin - 1];
                   $array[$begin - 1] = $tmp;
              }
               var_dump('----');
          }
      }
       // 计时结束
       $t2 = microtime(true);
       $t = $t2 - $t1;

       var_dump('执行时间: ' . number_format($t, 10));
       $this->sortedArr = $array;
       opcache_reset();
  }

   /**
    * 优化如果待排序数组是尾部有序序列
    *
    * @param array $arrayParams
    * @author Chuang
    */
   public function bubble3($arrayParams = [])
  {
       $array = empty($arrayParams) ? $this->sortArray : $arrayParams;
       $t1 = microtime(true);
       for ($end = count($array) - 1; $end > 0; $end--) {
           // 初始交换位置
           $key = 1;
           for ($begin = 1; $begin <= $end; $begin++) {
               if ($array[$begin] < $array[$begin - 1]) {
                   $tmp = $array[$begin];
                   $array[$begin] = $array[$begin - 1];
                   $array[$begin - 1] = $tmp;
                   // 记录最后的交换位置
                   $key = $begin;
              }
               var_dump('----');
          }
           // 重新赋值结束位置
           $end = $key;
      }

       $t2 = microtime(true);
       $t = $t2 - $t1;

       var_dump(implode('-', $array));
       var_dump('执行时间: ' . number_format($t, 10));
       opcache_reset();
  }
}

$a = new BubbleSort();

$a->bubble1([12,8,13,15,17]);

$a->bubble3([12,8,13,15,17]);

执行结果

# bubble1执行结果
string(4) "----"
string(4) "----"
string(4) "----"
string(4) "----"
string(4) "----"
string(4) "----"
string(4) "----"
string(4) "----"
string(4) "----"
string(4) "----"
string(26) "执行时间: 0.0000360012"

# bubble3执行结果
string(4) "----"
string(4) "----"
string(4) "----"
string(4) "----"
string(13) "8-12-13-15-17"
string(26) "执行时间: 0.0000109673"

从上面的执行结果中可以看出,优化后的逻辑比优化前的逻辑少执行了6次循环,并且执行时间也缩短了许多.