排序算法:快速排序

快速排序

快速排序由 C. A. R. Hoare 在1962年提出。它的基本思想是:通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。

快速排序!都叫快速排序了,它能不快吗?快速排序是排序算法中效率最快的!!是对冒泡排序的一种改进。

思路一:左右指针交换法

快速排序的思路简单来说就是一趟快速排序下来,能够保证至少有一个数字到达它应该在的位置上。而后面的排序过程,这个数都不参与。

在排序之前,首先确定一个值,这个值就是排序后到达其所属位置的值。然后定义两个指针,分别从头尾开始遍历,前面的找到一个比刚才那个值大的停止,后面的从后往前遍历,找到一个比刚才那个值小的停止,接着交换这两个值。周而复始,最终找到这个值的合适位置。可能用文字表达的不清晰,我们画图来看!

这里写图片描述

这个时候以 cur_value 左侧为一个新的区域,右侧也为一个新的区域继续进行排序,直至每个区域内元素只有一个,代表排序完毕。

实现

int ExchangeValue(int array[], int beg, int end) // 左右指针交换法
{
  int cur_value = array[end];
  int left = beg;
  int right = end;

  while (left < right) {
    while (left < right && array[left] <= cur_value) {
      ++left;
    }
    while (left < right && array[right] >= cur_value) {
      --right;
    }
    if (left < right) {
      Swap(&array[left], &array[right]);
    }
  }
  Swap(&array[left], &array[end]);
  return left;
}

void _QuickSort(int array[], int beg, int end)
{
  if (beg >= end) {
    return;
  }

  int mid = ExchangeValue(array, beg, end);
  // int mid = DigValue(array, beg, end);
  _QuickSort(array, beg, mid - 1);
  _QuickSort(array, mid + 1, end);
  return;
}

void QuickSort(int array[], int size)
{
  if (size <= 1) {
    return;
  }

  int beg = 0;
  int end = size - 1;
  _QuickSort(array, beg, end);
  return;
}

思路二:挖坑法

挖坑法与左右指针交换法最大的区别就是,在左右进行遍历的时候,一旦找到一个比 cur_value 大的或小的就开始填坑,而这个坑就是你之前保存的 cur_value 的位置,此时这个位置就是个坑!!!我们要做的就是填坑!!! 知道光用文字叙述没什么 luǎn 用! 接下来还是图解!!

这里写图片描述

其实到这里,思路大致就清晰了许多,其实所谓的挖坑法,就是相当于把上一个思路左右指针交换法中,在 right 查找到比 cur_value 大的值后停下,在 left 找到比 cur_value 小的值停下,此时交换 rightleft 的这个过程,分步进行,之前是交换,现在变成了直接 “挖坑” “填坑” 的过程。

实现

int DigValue(int array[], int beg, int end) // 挖坑法
{
  int cur_value = array[end];
  int left = beg;
  int right = end;

  while (left < right) {  // 下面这个过程就是挖坑填坑的过程
    while (left < right && array[left] <= cur_value) {
      ++left;
    }
    if (left < right) {
      array[right] = array[left];
      --right;
    }

    while (left < right && array[right] >= cur_value) {
      --right;
    }
    if (left < right) {
      array[left] = array[right];
      ++left;
    }
  }
  array[right] = cur_value;
  return right;
}



void _QuickSort(int array[], int beg, int end)
{
  if (beg >= end) {
    return;
  }

  int mid = ExchangeValue(array, beg, end);
  // int mid = DigValue(array, beg, end);
  _QuickSort(array, beg, mid - 1);
  _QuickSort(array, mid + 1, end);
  return;
}


非递归的快速排序

在清楚了前面的思路后,发现其实快速排序的核心思想就是一次排序过后,让某一个值前面的所有内容都比它小,后面的所有内容都比它大。并且将每个小区间都这样进行调整,直至区间只有一个元素。不难想到,递归正是解决这个问题的一个简单方式,利用递归实现区间的划分,进而调整。接下来,就是非递归版本的快速排序了。其实大体思路都是一致的,只需要利用循环的方式将各个区间划分完毕,最后再进行上面的两个思路即可。

所以,现在要解决的问题就是如何划分空间。

思路:栈

提到栈,我觉得聪明的大家应该都有思路了吧! 其实就是每次同时入栈一个区间,出栈同时出,每次出栈两次,将一个区间全部出栈,接着处理这个区间,返回的值为 mid 利用这个值划分区间,继续入栈。直至栈里没有元素为止。
说到这里,我觉得还是画图吧,毕竟画图最实际!

这里写图片描述

实现

void QuickSortByLoop(int array[], int size)
{
  if (size <= 1) {
    return;
  }

  SeqStack stack;
  SeqStackInit(&stack);
  int beg = 0;
  int end = size - 1;
  SeqStackPush(&stack, beg);
  SeqStackPush(&stack, end);

  while (1) {
    int ret = SeqStackGetFront(&stack, &end);
    if (ret == 0) {
      break;
    }
    SeqStackPop(&stack);
    SeqStackGetFront(&stack, &beg);
    SeqStackPop(&stack);

    int mid = ExchangeValue(array, beg, end);
    SeqStackPush(&stack, beg);
    SeqStackPush(&stack, mid - 1);
    SeqStackPush(&stack, mid + 1);
    SeqStackPush(&stack, end);
  }
  return;
}

这里就不贴栈的基本操作了,大家肯定也都会,如果大家有疑问的话可以去我前面的博客中看。主要的非递归快速排序算法思路就是这样。

栈的基本操作的实现


总结

其实快速排序主要浪费时间的地方就是在区间的划分过程中,而这个区间划分好与坏也取决于你选的 cur_value 的值,倘若每次选择的值恰好都是最大或最小值,那么时间复杂度将会达到与一个最差情况也就是 O (N ^ 2)。所以在对 cur_value 进行选择的时候,就要避免这个问题,我们可以在选择 cur_value 的时候多选几个数,然后在这几个数中选择一个中间的数,比如说是选择三个 cur_value 然后最终的值选择这三个当中中间的那个,这样的话我们选出的 cur_value 一定不是整个区间的最值。由于快速排序需要递归或者是利用栈来实现,那么它的空间复杂度就高了,是 O ( N )。同时快速排序是不稳定的排序算法。


欢迎大家共同讨论,如有错误及时联系作者指出,并改正。谢谢大家!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值