浅谈快速排序

浅谈归并排序
在上一篇文章中,简单介绍了归并排序。归并排序的时间复杂度为O(nlogn),空间复杂度为O(n)。今天来介绍另一种时间复杂度同为O(nlogn)的常见排序算法–快速排序。

快速排序的原理

快速排序与归并排序一样,利用的都为分治思想。如果不细看的话,归并排序和快速排序其实差别并不是很大。但是二者的思路却是完全不一样的。这里我们首先先来说一下快速排序的核心思想。
p
快排的思想是这样的:如果要对数组中下标为p到r之间的元素进行排序,我们并不是像归并排序一样,选择p到r的中间点作为分割点,而是从其中任选一个数据作为分区点(这里用pivot来表示,一般我们会选择区间[p,r]中的最后一个元素)。

接下来我们遍历p到r之间的数据,将小于pivot的元素放到左边,将大于pivot的元素放到右边,将pivot放到中间位置。经过分割后,数组被划分成了三部分,前面[p,pivot-1]的元素都是小于pivot的元素,中间是pivot元素,后边[pivot+1,r]都是大于pivot元素的元素。

分完区间之后,我们接下来就要用到递归了,继续按以上规则划分区间。直到每个区间的元素个数缩减为1,就说明所有的元素都有序了。具体的分区部分的代码如下

//a为参与排序的数组,[p,r]为数组a中参与排序的区间
  private static int partition(int[] a, int p, int r) {
    //取最后一个元素作为中间点
    int pivot = a[r];
    //这里用到了两个指针,指针i先用来替代pivot,最后与作为pivot的真正元素交换
    // j用来遍历分界点之后的元素
    int i = p;
    for(int j = p; j < r; ++j) {
      //当前j元素小于分界点元素时,如果两指针相等就都+1,否则i原地不动,j+1
      if (a[j] < pivot) {
        if (i == j) {
          ++i;
        } else {
          int tmp = a[i];
          a[i++] = a[j];
          a[j] = tmp;
        }
      }
    }
    //最后把i指针代表的元素与参与排序区间的最后一个元素交换
    int tmp = a[i];
    a[i] = a[r];
    a[r] = tmp;

    System.out.println("i=" + i);
    return i;
  }

递归的代码如下:

// 快速排序递归函数,p,r为下标
  private static void quickSortInternally(int[] a, int p, int r) {
    if (p >= r) return;

    int q = partition(a, p, r); // 获取分区点
    quickSortInternally(a, p, q-1);
    quickSortInternally(a, q+1, r);
  }

那么归并排序与插入排序的区别在哪里呢?首先归并排序分为拆分合并两个部分。它并不是原地的排序算法,它的处理过程是由下到上,先处理子问题,然后再合并。而快速排序恰好相反,它是原地的排序算法,处理过程是由上到下,先分区,然后再处理子问题。

快速排序的性能分析

首先快速排序相比归并排序的有点在于快速排序不占用额外的空间,只是运用了两个指针的相互交换操作就完成了排序。对于稳定性,我们举这样一个例子6、8、7、6、3、5、9、4。指针i和j开始在6,因为6、8、7、6都小于最后一个元素4,所以j指针一直后移,知道移动到3为止,3与第一个6发生交换,这样两个6的先后顺序就发生了改变。所以快速排序不是稳定的排序算法。

至于时间复杂度,如果每次选择的pivot都能把整个排序区间均匀的分成两个区间的话,那么时间复杂度就为O(nlogn)。但是在极端的情况下,如果数组有序,然后我们每次都选择最后一个元素作为分区点的话,那么时间复杂度就退化成了O(n²)。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值