冒泡排序、快速排序

冒泡排序

冒泡排序的思想

      冒泡排序的主要思想是:相邻两个元素进行比较,较大的元素放在后面。就像水泡一样,慢慢的浮到最后一个。我们通过一个例子,演示一下冒泡排序:

      举例说明冒泡排序:

      已知一个数组a = {5,9,2,6},如下图所示,我们开始演示一下冒泡排序:
在这里插入图片描述

      5比9小,不用交换位置

      然后比较9和2,9比2大,于是交换9和2的位置,如下图所示:
在这里插入图片描述

      然后比较9和6,9比6大,于是交换9和6的位置,如下图所示:
在这里插入图片描述

      此时我们发现,9已经被排到数组的最后,我们称上面的过程为一趟扫描。通过这一趟扫描后,我们可以确定数组中最大的元素。这个元素已经被排在我们数组的最后了。

      然后我们进行下一趟扫面, 5比2大,交换位置。如下图所示:
在这里插入图片描述
      然后比较5和6,5比6小不用交换。注意,由于我们第一趟已经将数组的最大值9放在最后了,我们在以后的几趟中,就无需再比较这些已经被我们排好序的元素了。

代码实现

代码实现:

    public static void BubbleSort(int[] a){
        for (int i = 0; i <a.length; i++) {
            for (int j = 0; j < a.length-i-1; j++) { //-1就是为了重复比较已经排好序的元素。
                if (a[j] >a[j+1]){
                    int tmp = a[j];
                    a[j] = a[j+1];
                    a[j+1] = tmp;
                }
            }
        }
    }

快速排序

快速排序的思想

      ① 在数组中随机找一个枢纽元素。(该枢纽元素用作比较)

      ② 将小于枢纽元素的放在左半区,将大于枢纽元素的放在右半区。

      ③ 对左右半区的元素,继续进行②③步。

普通快速排序(双向划分)

      举例快速排序

      给定一数组,我们选择数组的第一个元素作为枢纽元素。记作tag。并给定两个指针,分别记作left、right。
在这里插入图片描述

      通过判断left、right所指向的数据:

                  如果right所指向的数据大于tag,则right–,直到找到 ar[right] ≤ tag时,使得ar[left] = ar[right]

                  如果left所指向的数据小于tag,则left++,直到找到ar[left] ≥ tag时,使得ar[right] = ar[left];

                  当left == right时,我们让这个位置上的元素变为tag。

       一次划分的过程:

                  ① 判断right所指向的值 是否 小于 tag。如上图,ar[right] == 30 < tag ==78,所以我们让ar[left] = 30; 如下图:

在这里插入图片描述
                  ② 然后判断left所指向的值,是否大于tag,如果不大于,则让left++,直到找到为止。如图,直到left指向89时,我们的array[left]==89 > tag,然后将left所指向的值,赋给array[right]
在这里插入图片描述
                  ③ 再次移动right和left。重复①② 过程。
我们再次判断right所指向的值 是否小于tag,如果不小,则一直让right–,直到找到为止。如图,
当right指向45时,array[right] < tag。于是,right所指向的值 赋给 array[left]。
在这里插入图片描述
                  ④ 再去判断,left所指向的值,是否大于tag,如果没找到, 则让left++,如图,当left指向100时,array[left]==100 > tag,于是left所指的值,赋给array[right]
在这里插入图片描述
                  ⑤ 继续判断 right,right所指向的值是否小于tag,由上图得,array[right] == 100 > tag。所以让right–,此时left指向同一个元素。
这个时候,我们就不用再循环了,将tag赋给array[left]。这个时候,我们就发现,left之前的数据都是小于tag的,left之后的数据,都是大于tag的。

在这里插入图片描述
                  此时,我们将数组分为了左半部分和右半部分,左半部分的数都比tag小,右半部分都比tag大。于是对于左右两个部分,划分过程和上面类似。

      一次划分的代码实现:

  public static int partition(int[] ar,int left,int right){
        int i = left;
        int j = right;
        int tag = ar[i];
        while(i<j){
            while(i<j && ar[j] >tag) j--;
            if(i<j) {ar[i]= ar[j];}
            while(i<j && ar[i] < tag) i++;
            if(i<j) {ar[j] = ar[i];}
        }
        ar[i] = tag;
        return i;
    }

      多次划分的代码实现:

 public static void quickSortPass(int []ar,int left,int right){

        if(left < right){
            int pos = partition(ar,left,right);
            quickSortPass(ar,left,pos-1);
            quickSortPass(ar,pos+1,right);
        }

    }

      我们能不能将多次划分的代码,不用递归来实现呢? 当然可以,我们可以使用一个队列来实现,代码如下:

private static void niceQuickPass(int[]a,int left,int right){
        Queue<Integer> queue = new LinkedList<>();
        queue.offer(left);
        queue.offer(right);
        while(!queue.isEmpty()){
            left = queue.poll();
            right = queue.poll();
            int pos = partition(a,left,right);
            if (pos-1 > left){
                queue.offer(left);
                queue.offer(pos-1);
            }

            if (pos+1 < right){
                queue.offer(pos+1);
                queue.offer(right);
            }
        }
    }

快排的改进:随机划分

      我们递归实现快排的最好情况下的时间复杂度为: O(nlogn),最坏情况下的时间复杂度为 O(n²)

      快速排序的最坏的情况下是:
                  ① 当数组已经是排好序的。

                  ② 数组中所有的元素都相同。

      我们解决这些情况的办法是,让数组进行随机划分。上述我们的快速排序是以始终以数组的最左边的元素进行划分的。

      快速排序的随机划分

 public static int randomQuickPartition(int[] a,int left,int right){
        Random random = new Random();
        int index = random.nextInt((right-left+1)) + left;
        int tmp = a[index];
        a[index] = a[left];
        a[left] = tmp;
        return partition(a,left,right);
    }

      注意上面 我们在计算index时,为什么要再加上left呢?我们举个例子来说明这个问题。

      这是我们已经完成了一次划分的数组
在这里插入图片描述
      对于右半部分来说,如果我们随机得到的数是1,相当于我们右半部分数组的index == 1,如果我们不加left值的话,这个1就不是我们的绝对物理下标。什么意思呢? 就是说,我们随机得到的这个1,是相对于右半部分来说,是在我们右半部分数组中index = 1 的数字,但是对于我整个数组来说,这个数字的绝对物理下标应该加上left。这样才是我们随机产生index的绝对物理下标。

快排的划分:单向划分

      上面我们介绍了快速排序的随机划分、双向划分,现在我们来看一看单向划分,什么是单向划分?单向划分就是,我们只从一个方向和枢纽元素进行比较。

      具体思路如下:

                  ① 选择一个枢纽元素,我们这里就选择a[left] 为枢纽元素。

                  ② 定义两个指针,分别记作i,j。i指向left,j指向i+1。

                  ③ 判断ar[j] 是否大于tag,若大于,让j++,直到ar[j] ≤ tag,此时i++,并交换i和j位置上的值 。

                  ④ 当 j 超过数组长度时,此时将 a[i] = tag。

      图示:

      ① 定义两指针i,j,分别指向第一个元素和第二个元素。
在这里插入图片描述
      ② 比较a[j] > tah。如上图,a[j] == 90 > tag,于是j++,得到下图:
在这里插入图片描述
      ③ 再比较arr[j] > tag。如上图所示,arr[j] == 88 > tag。让j++,得到下图:在这里插入图片描述
      ④ 再比较arr[j] > tag。如上图所示,arr[j] == 15 < tag。于是先让i+1,然后交换i和j位置上的值,交换完后如下图所示:
在这里插入图片描述

      ⑤ 再比较arr[j] > tag。如上图所示,arr[j] == 90 >tag。让j++。得到下图:
在这里插入图片描述
      
⑥ 再比较arr[j] > tag。如上图所示,arr[j]==32 <tag,于是先让i+1,再交换i和j位置上的值
在这里插入图片描述
      
⑦ 再比较arr[j]>tag。如上图所示,arr[j] == 88 >tag。于是让j++。得到下图:
在这里插入图片描述
      
⑧ 再比较arr[j]>tag。如上图所示,arr[j] == 23 < tag。于是先让i+1,再交换i和j位置上的值。
在这里插入图片描述
      
⑨ 再比较arr[j] > tag。如上图所示,arr[j] ==90 > tag。于是j++。得下图:
在这里插入图片描述

      ⑩ 再比较arr[j] > tag。如上图所示:arr[j] == 99 > tag,于是j++。得下图:
在这里插入图片描述
      11.再比较arr[j] > tag。如上图所示,arr[j] == 47 < tag。于是让i+1,交换i和j位置上的值。
在这里插入图片描述
      再比较arr[j] > tag。如上图所示,arr[j] == 88 > tag。让j++,此时j已经超过了right的范围。此时划分结束。将i位置和left位置上的值进行交换,得下图:
在这里插入图片描述
      此时我们发现,在i的右边都是比tag小的数,在i的左边都是比tag大的数。此时单向划分一次划分的过程就结束了。

      代码实现:

    public static int singlePartition(int[] a,int left,int right){
        int i = left;
        int j = i+1;
        int tag = a[i];
        while(j <= right){
            if(a[j]<=tag){
                i++;
                change(a,i,j);
            }
            j++;
        }
        change(a,i,left);
        return i;
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值