冒泡排序和双向冒泡排序算法

冒泡排序算法应该算是每个开发者入门必学的基础算法,它逻辑清晰简单,代码实现也并不复杂,这里用自己的话语来总结一下。

(这些为了方便随便打开了一个node项目写的,其他Java ,PHP,C等也是一样的道理,算法讲究的只是一个思维模式,知道怎么去实现就行,不要死抠语言)

先说简单的普通冒泡排序:

冒泡排序既然是一个排序算法,那么它就一定是有一个循环排序的过程,其实它的原理很简单,就是两层for循环嵌套,外层for循环控制总的循环次数,内层则是不断拿前一个值和后一个值去比较,不断把小的放前面(当然你也可以把大的放前面)。那么这里有几个坑点是要注意的:
坑点1:外层for循环要记得下标和数组长度的对应,下标从0开始,那么0~9就有十个数了,千万别用正常计数思维想着只有九个数,在用.length方法的时候要注意,不然在最后一轮循环会出现undefined拿不到值或者null point之类的错误。

在这里插入图片描述

比如这里数组长度0~9乱序,就是有十个数,所以 list.length 长度就是10,在写判断条件时候如果写a <= list.length还跑循环的话,就会出现undefind了。

在这里插入图片描述

坑点2:很多初学者在两个数交换位置那里有点疑惑,这个是很简单一个数据交换小问题,就是要定义一个容器去装。两杯水互换,需要第三个临时容器一样的道理,交换的时候记得是数组下标的值互换,不是从数组对应下标拿出的值互换(那样子没改变数组下标元素),起不到重新排序效果。
错误示范:let tempBox;
let a = a[x];
let b = b [x];
if (a > b ) {
tempBox = a;
a = b;
b = tempBox;
}
乍看之下没问题啊,互换啊,为啥排序完跟没排序一样??那就是你只交换一个赋值后的变量,根本的数组对应下标的值你都没发生改动!!
正确示范:

if (waitSort[a] > waitSort[b]) {
tempBox = waitSort[a];
waitSort[a] = waitSort[b];
waitSort[b] = tempBox;
}

坑点三:记住外圈循环是从拿第一个元素开始,内圈循环是从第二个开始往后拿,当到第二轮循环时候,外圈从拿第二个元素开始,那么内圈就是第三个元素开始往后拿,也就是说,外圈是n开始,内圈循环是n+1开始一直往后拿!切记内圈的任务是拿剩下还没排序的进行排序而已。

在这里插入图片描述

坑点四:学算法光靠看和复制粘贴跑一次是学不懂的,一定要自己拿纸拿笔推演一遍!推演两遍三遍彻底明白它怎么实现的一个逻辑,自己再用代码去实现!这是最重要的一点坑!算法不能靠背诵!

下面上源码给大家:

在这里插入图片描述
流程解释:
1:外层先拿下标0的第一个元素9。内层开始比较9是不是最小的,内层一个个比较循环下来发现0才是最小的,于是放0在第一位,9在跟2比较的时候就被比下去了,所以被丢在2那个下标位置,2跟1比较时候又被丢下在1那个位置,拿着1去继续比较,1跟0PK的时候被丢下在0的位置,0上升到一开始9所在的下标位置。

2:外层再拿下标1的元素丢进内循环比较。然后继续上一步的循环比较,把大的丢在当前下标位置,小的拿着继续比较,内循环结束,拿到最小的值1丢在下标为1的位置。

3:接下来都是一样的操作,这里就不解释了。(如果看不懂,那就很正常,赶紧动手动笔画一画流程!!花不了十分钟!不然你看谁的都看不懂!!)

从控制台我们可以很清晰看到,每一次循环,都会把剩余的未排序的数组中最小的一个元素放到最前面,像汽水冒泡一样一个个冒泡上来,这就是简单的普通冒泡排序算法。

冒泡排序的时间复杂度(也就是花的时间)为:O(n²),最好的情况下是O(n);空间复杂度(也就是占用内存空间的大小)是O(1),属于稳定的排序算法。

下面说双向冒泡排序:

双向冒泡排序要比普通冒泡难,但是它的速度会更快,在数据量大的情况下,速度差距是几倍的,因为双向冒泡排序减少了for循环,并且两边已经排好序的数组元素在下一次循环中不会参与到比较,大大减少了很多无用操作。网上关于双向冒泡的资源很多,这里我根据个人的理解来进行讲解和分享源码展示。
双向冒泡其实就是在一次循环中进行两次内圈循环,分别找出最大的元素放到最后和找出最小的元素放到最前面。同时进行从前往后排和从后往前排,最后到达两者交汇点就证明全部数组元素都已经正确进行了排序(反之如果头尾两者没有相等,还是头 小于 尾,则证明头尾之间还夹着没排序好的元素);难点在于理清开始和结束的下标,因为记录前后下标每一次循环结束都会改变,下一次循环会直接从上一次排好序的下标位置开始对中间没有排序部分进行处理。(可能这样说很难理解,实际上真的是比较难理解,一定要自己动手画图实现,一定要自己理解后用代码再实现一遍!!)

首先是从前往后找部分,把最大的一个放到最后

在这里插入图片描述

接下来就是后往前找部分,找出未排序中最小的一个放到数组的前面

在这里插入图片描述

执行结果:

请重点看第一轮的9和第二轮的0,它们都是乱序中的最大和最小,它们都能够自觉的跑到自己该呆的位置,然后从此退出江湖,不再参与比较大小,而夹在它们二者中间那些乱序的,还迷茫的元素们,还是会进行不断的循环比较排序。直到rigthStart = leftStart 头尾相连的时候。
在这里插入图片描述
可以看到最终结果就是0 1 2 3 4 5 6 7 8 9;排序成功!

双向冒泡排序的坑点:

坑点一:从前往后第一轮容易,从后往前难;记住要准确记录上一次存放好数据的下标,记住那是下一次开始的地方。
坑点二: 只有在不需要再比较和换位置的时候!才能对下标记录赋值!不能把leftStart 和 rightStart 记录下标放在for循环里!不然条件b >= leftStart;之类的地方会永真!一直跑个不停!不要自己挖坑给自己跳!!
坑点三:记住第一轮结束后,右边倒序开始的边界界限就变成了原来的长度-1;第二轮结束后,左边正序的开始边界界限就变成了下标1;不再从下标0开始。这里定义的beginPoint;永远是临时存放左右边界开始的下标!!

// 等待排序的数组
const waitSort = [9, 2, 3, 1, 8, 6, 5, 7, 4, 0];

// 定一个临时容器盒子
let tempBox;

// 左边开始的标志
let leftStart = 0;

// 右边开始的标志
let rightStart = waitSort.length;

// 交汇点
let beginPoint = 0;

// 如果左边跟右边还没接触,证明中间还有未排序的数
while (leftStart < rightStart) {
  // 从左边第一个数开始一直比较到最后一个数,找到整个数组中最大那个放到最后
  for (let a = leftStart; a < waitSort.length - 1; a++) {
    if (waitSort[a] > waitSort[a + 1]) {
      tempBox = waitSort[a];
      waitSort[a] = waitSort[a + 1];
      waitSort[a + 1] = tempBox;
      // 循环结束后这里是从后往前的起点,因为a+1已经排好序了是最大的一个数,不需要再挪动
      beginPoint = a;
      console.log('这是前往后排序循环');
      console.log(waitSort);
    }
  }
  // 如果不需要换位置了,则设置右边的起点
  rightStart = beginPoint;

  // 从后往前找,直到找到整个数组最小的那个放在第一位。
  for (let b = rightStart; b >= leftStart; b--) {
    if (waitSort[b] < waitSort[b - 1]) {
      tempBox = waitSort[b];
      waitSort[b] = waitSort[b - 1];
      waitSort[b - 1] = tempBox;
      // 动
      beginPoint = b + 1;
      console.log('这是后向前排序循环');
      console.log(waitSort);
    }
  }
  // 如果不需要换位置了,则设置左边的起点
  leftStart = beginPoint;
}
可能我说的不太清楚,有疑惑的地方可以留言给我,还是记住一定要拿纸拿笔画流程;再自己动手敲一边才会更好理解哈;谢谢大家观察;鞠躬!

在这里插入图片描述

发布了13 篇原创文章 · 获赞 34 · 访问量 3726

猜你喜欢

转载自blog.csdn.net/whiteBearClimb/article/details/103743981