快速排序和两路合并排序速度_经典排序算法——快速排序、归并排序、堆排序...

 快速排序、归并排序、堆排序

01

快速排序

算法思想

    快排(quicksort)过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。是一种分而治之思想在排序算法上的典型应用。本质上来看,快速排序应该算是在简单排序基础上的递归分治法。

算法步骤

  • 从数列中挑出一个元素,称为 “主元”(pivot)。

  • 重新排序数列,所有元素比主元值小的摆放在主元前面,所有元素比主元值大的摆在主元的后面(相同的数可以到任一边)。在这个分区退出之后,该主元就处于数列的中间位置。这个称为分区(partition)操作。

  • 递归地把小于主元值元素的子数列和大于主元值元素的子数列排序。

  

 过程图示

c69412692282aadc577eeae7f151d354.gif

算法复杂度

  • 最优情况

    • 分区每次都划分得很均匀,如果排序n个关键字,其递归树的深度就为[log2n]+1 ([x] 表示不大于 x 的最大整数),即仅需递归[log2n]次。第一次分区应该是需要对整个数组扫描一遍,做n次比较。然后,获得的枢轴将数组一分为二,那么各自还需要T(n/2)的时间(注意是最好情况,所以平分两半)。

    • 时间复杂度为:O(nlog2n)

  • 最差情况

    • 当待排序的序列为正序或逆序排列时为最糟糕情况下的快排。此时需要执行n‐1次递归调用,且第i次划分需要经过n‐i次关键字的比较才能找到第i个记录,也就是枢轴的位置,因此比较次数为:    

      5e12dc2f1b96def4d275adcfcb749266.png

    • 时间复杂度为:O(n²)

代码实现

//分区int partition(int arr[], int low, int high){ int pivot = arr[low]; //基准 while (low < high) { while (arr[high] >= pivot && low < high) --high; // 找到排在后面但是小于基准的最先元素 arr[low] = arr[high]; while (arr[low] <= pivot && low < high) ++low; arr[high] = arr[low]; } arr[low] = pivot; return low;}

//快速排序(递归)void Quick_Sort(int arr[], int low, int high){  int pivot;//主元  if (low < high)  {    pivot = partition(arr, low, high);    Quick_Sort(arr, low, pivot - 1);    Quick_Sort(arr, pivot + 1, high);  }}

//快速排序(非递归)void Quick_Sort(int arr[], int low, int high){ int pivotpos;//主元 std::stack<int> pos_stack; pos_stack.push(low); pos_stack.push(high); while (!pos_stack.empty()) { high = pos_stack.top(); // 注意出栈顺序 pos_stack.pop(); low = pos_stack.top(); pos_stack.pop(); if (low < high) { pivotpos = partition(arr, low, high); //左边序列起始、终止位置入栈 pos_stack.push(low); pos_stack.push(pivotpos - 1); //右边 pos_stack.push(pivotpos + 1); pos_stack.push(high); } }}

02

堆排序

算法思想

        堆排序(Heapsort)是指利用堆这种数据结构所设计的一种排序算法。堆积是一个近似完全二叉树的结构,并同时满足堆积的性质:即子结点的键值或索引总是小于(或者大于)它的父节点。堆排序可以说是一种利用堆的概念来排序的选择排序。本文采用大顶堆。

算法步骤

  • 创建一个堆 H[0……n-1];

  • 各个根节点与其叶子节点比较,最大者换为根节点

  • 把堆首(最大值)和堆尾互换;

  • 把堆的尺寸缩小1,并重新进行步骤2,目的是把新的数组顶端数据调整到相应位置;

  • 重复步骤 3,直到堆的尺寸为 1。

算法复杂度

    堆排序的主要阶段为:初始化建立堆和重建堆。因此堆排序的时间复杂度由这两部分组成。

  1. 初始化建立堆

    假如有N个节点,那么高度为h=logN,,最后一层每个父节点最多只需要下调1次,倒数第二层最多只需要下调2次,顶点最多需要下调h次,而最后一层父节点共有2^(h−1)个,倒数第二层公有2^(h-2) ,顶点只有一个所以总共的时间复杂度为:

    930be81d73a4c2087502ebfff3e262eb.png

    将h代入后s=2N−2−log2N,近似的时间复杂度就是O(N)。

  2. 重建堆

    重建的过程,需要循环 n -1 次,每次都是从根节点往下循环查找,所以每一次时间是 logn,总时间:logn(n-1) = nlogn - logn。

    故综合以上可以得出堆排序时间复杂度: O(nlog2n)  

代码实现

/* * index 调整开始位置*  length 数组长度范围*/void MaxHeap(int arr[], int index, int length){  int node = index;  int child_index = node * 2 + 1;  int current = arr[node];  for (; child_index <= length; node = child_index, child_index = node * 2 + 1)  {    if (child_index < length && arr[child_index] < arr[child_index + 1])      ++child_index;  // 子节点中的最大值    if (current > arr[child_index]) break;    else    {      arr[node] = arr[child_index];      arr[child_index] = current;    }  }}void Heap_Sort(int arr[], int n){  for (int i = n / 2 - 1; i >= 0; --i)  {    MaxHeap(arr, i, n - 1);  // 建立最大堆  }  for (int i = n - 1; i > 0; --i)  // 从最后开始调整  {    int temp = arr[0];    arr[0] = arr[i];    arr[i] = temp;    MaxHeap(arr, 0, i - 1);  // 数组长度范围减一  }}

03

归并排序

算法思想

        将序列每相邻两个数字进行归并操作(merge),形成floor(n/2+n%2)(floor向下取整函数)个序列,排序后每个序列包含两个元素将上述序列再次归并,形成floor(n/4)个序列,每个序列包含四个元素。重复步骤2,直到所有元素排序完毕。

算法步骤

  • 申请空间,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列;

  • 设定两个指针,最初位置分别为两个已经排序序列的起始位置;

  • 比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下一位置;

  • 重复步骤 3 直到某一指针达到序列尾

  • 将另一序列剩下的所有元素直接复制到合并序列尾。

算法复杂度

    归并排序需要不仅时时间还有空间上的辅助,因此从时间复杂度和空间复杂度进行分析。

  • 时间复杂度

      总时间=分解时间+解决问题时间+合并时间。分解时间就是把一个待排序序列分解成两序列,时间为一常数,时间复杂度o(1).解决问题时间是两个递归式,把一个规模为n的问题分成两个规模分别为n/2的子问题,时间为2T(n/2)。合并时间复杂度为O(n)。总时间T(n)=2T(n/2)+O(n) 这个递归式可以用递归树来解,其解是  O(nlogn)   此外在最坏、最佳、平均情况下归并排序时间复杂度均为  O(nlogn)。

  • 空间复杂度

      如之前的算法步骤第一步,需要申请空间,该空间的作用时用于存放合并后的序列。因此需要初始序列规模n的空间,故空间复杂度为 O(n)。

代码实现

int * temp = new int[len];// 合并操作void merge(int arr[], int low, int mid, int high){  int i, j, index;  for (int i = low; i <= high; ++i) //复制数组,空间复杂度为O(n)    temp[i] = arr[i];  for (i = low, j = mid + 1, index = low; i <= mid && j <= high; ++index)  {    if (temp[i] > temp[j])    {      arr[index] = temp[j];      ++j;    }    else    {      arr[index] = temp[i];      ++i;    }  }  while (i <= mid) arr[index++] = temp[i++];  while (j <= high) arr[index++] = temp[j++];  memset(temp, 0, sizeof(temp));}
void Merge_Sort(int arr[], int low, int high){  int mid;  if (low < high)  {    mid = (high + low) / 2;    Merge_Sort(arr, low, mid);    Merge_Sort(arr, mid + 1, high);    merge(arr, low, mid, high); // 归并  }}
//非递归void Merge_Sort_NonRecursive(int arr[], int n){  int step = 2, low, high, mid;  //二路归并步长  while (step <= n)  {    int curpos = 0;    while (curpos + step <= n)    {      high = curpos + step - 1;      low = curpos;      mid = curpos + step / 2 - 1;      merge(arr, low, mid, high);      curpos += step;    }    if (curpos < n - step / 2)  // 如过剩余个数比一个step长度还多,那么就在进行一次合并    {      mid = curpos + step / 2 - 1;      merge(arr, curpos, mid, n - 1);    }    step *= 2;  }  mid = step / 2 - 1;   merge(arr, 0, mid, n - 1);}

归并排序和堆排序GIF图片示意帧数太大无法上传,可在公众号内回复堆排序领取动态图片。

429ce732da7f1ce0a471a55320425099.png

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值