Sorting Algorithms

用到排序算法的时候,还是用STL比较方便,而且能保证最优的O(nlogn)的复杂度。 STL的排序算法sort实现的是快速排序(经过优化的),因此是不稳定的,即是说不能保证相等的元素在排序后仍然保持原始的先后次序。 为了实现稳定的排序,需要使用stable_sort,他使用的是归并排序,因此是可以做到稳定的。

vector<T> v;
bool less(const T &t1, const T &t2);
sort(v.begin(), v.end(), less);
stable_sort(v.begin(), v.end(), less);

基于比较的排序算法的复杂度(比较次数)下届为nlogn。 因为n个数据的可能排序有n!种,而每一次比较可以将可能性减少一半, 因此需要log(n!)次比较,近似为nlogn

快速排序

快速排序是将某一个元素(通常是第一个,后者最后一个,也可以随机选取一个)作为基准,将待排序数组划分为小于该元素和大于该元素的两部分,然后递归的对两部分进行快速排序。在最坏情形的效率是,比如元素本来就是有序的,总选择第一个元素进行划分,每次都会得到最差的划分。

quick_sort(a[], lo, hi)
  if (lo >= hi) then return
  pivot = a[lo]
  p = lo
  for i = lo...hi
    if (a[i] < pivot) then swap(a[i], a[++p])
  end-for
  swap(a[lo], a[p])
  quick_sort(a, lo, p)
  quick_sort(a, p + 1, hi)

归并排序

归并排序是将待排序数组等分为两段,递归的排好两段后,进行合并。 因此,该方法有稳定的时间效率,但是要占用额外的线性数量的空间。 归并排序通常是不能原地(in-place)完成的,in-place版本的归并排序效率为

merge_sort(a[], lo, hi)
  if (lo >= hi) then return a[lo...hi]
  m = (lo + hi) / 2
  merge_sort(a, lo, m)
  merge_sort(a, m + 1, hi)
  b = copy(a)
  p = lo
  q = m + 1
  for i = lo...hi
    if (q > hi || b[q] > b[p]) then 
      a[i] = b[p++] 
    else 
      a[i] = b[q++]
    end-if
  end-for

堆排序

堆排序是一种in place的高效排序方式,具有最坏情况O(nlogn)的时间复杂度。 堆是一种满二叉树,可以使用数组进行方便的存储和访问。 堆有一个约束:每个树节点要大于等于(对于最大堆)它的子节点。 维护这个约束的方法叫做下筛(sift down):假设只有根元素不满足堆约束, 那么,就可以逐层向下调整该元素,使得最终堆约束得到满足。 这一过程的复杂度是O(log n),即堆的层数。 从数组开始初始化一个堆,只要从堆的底层元素开始,逐层向上进行下筛调整, 就可以使得整个堆满足约束。

从堆中弹出根元素(最大堆的最大元素),也仅需要一次调整。 即用堆数组的最后一个元素替换根元素,然后进行下筛调整。 因此,堆常常被用于高效的优先级队列。 利用这个特性,依次将堆中的所有元素弹出,并放置在堆后面,就可以实现排序。

heap_sort(a[])
  n = a.size
  for i = (n - 2) / 2 downto 0
    sift_down(a, i, n)
  end-for
  for i = n - 1 downto 1
    swap(a[0], a[i])
    sift_down(a, 0, i)
  end-for
sift_down(a[], i, n)
  while i <= (n - 2) / 2
    l = i * 2 + 1
    r = i * 2 + 2
    if (a[l] >= a[i] && (r >= n || a[l] >= a[r])) then
      swap(a[l], a[i])
      i = l
    else if (r < n && a[r] >= a[i]) then
      swap(a[r], a[i])
      i = r
    else
      break
    end-if
  end-while

冒泡排序

冒泡排序通过比较和交换相邻元素实现排序。

bubble_sort(a[], n)
  for i = 1...n-1
    for j = 0...n-1-i
      if a[j+1] > a[j] then swap(a[j+1], a[j])
    end-for
  end-for

希尔排序

希尔排序是插入排序的一种。 由于插入排序在接近有序的数据上具有较好的性能, 因此,希尔排序通过进行有间隔(gap)的插入排序,来逐步将数据调整到有序。 间隔通常是一组不断缩小的序列, 当缩小为1时,希尔排序就是执行插入排序, 但是此时由于数据已经接近有序,因此可以期望有较好的性能。 希尔排序的优点是:原地排序,不需要额外的空间(归并需要线性的空间,快速排序需要对数级别的栈空间),实现的代码比较简单。

shell_sort(a[], n)
  gaps = [701, 301, 132, 57, 23, 10, 4, 1]
  for gap in gaps
    for i = gap...n-1
      temp = a[i]
      j = i
      while (j >= gap && a[j - gap] > temp)
        a[j] = a[j - gap]
        j = j - gap
      end-while
      a[j] = temp
    end-for
  end-for

桶排序和基数排序

桶排序的方法是先将数据按照顺序大致分在若干桶里面, 然后对桶内的数据再执行排序。 如果桶的数量是2,桶内递归的执行桶排序,那么就演变为快速排序。

基数排序主要用于排序字符串或者表示为字符串的整数。 排序的方法是逐位数字执行桶排序。