归并排序&快速排序&堆排序

Posted 算法入门

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了归并排序&快速排序&堆排序相关的知识,希望对你有一定的参考价值。

根据以往找实习的经验,一般很多公司在算法笔试的时候,第一道题目就是让你手写快熟排序再讲一遍。 
可见,排序是多么基础的算法.....那么现在就复习一下归并排序、快速排序、和堆排序吧。 
归并排序 
归并排序应该是最容易理解的了,归并排序的结构大概是这样

 
   
   
 
  1. void merge_sort(vector<int>& v, int l, int r) {

  2.    if (l == r) return ;

  3.    int mid = (l + r) / 2;

  4.    merge_sort(v, l, mid);

  5.    merge_sort(v, mid+1, r);

  6.    merge(v, l, r, mid);//这里合并区间

  7. }


merge_sort()是不是挺像线段树的..就是二分区间,不断的递归,使得每次分开的两个区间都是有序的,然后再合并两个区间就好了。如何使区间有序? 
当区间是一个数的时候就是有序的啊,递归到最底层,回溯的时候再合并两个只有一位数的区间,这样就实现了排序。 
这里要注意一点:merge()合并函数就是合并两个有序的序列,得需要额外的空间开销,合并的方法是从后往前扫,比较a,b两个序列的大小,如果按照从小到大排序的话,那就是把先在新序列的末段index放a,b序列中较大的,然后index--,在找a,b序列中交到的放到新序列的index位置.....其实合并两个有序的序列为一个新的有序的序列,是一道leetcode题目,具体题号忘了... 
实现代码如下:

 
   
   
 
  1. #include <bits/stdc++.h>

  2. using namespace std;

  3. void merge(vector<int>& v, int l, int r, int mid) {

  4.    int index = r;

  5.    vector<int> a, b;

  6.    //for (int x = l; x <= mid; ++x) a.push_back(v[x]);

  7.    //for (int x = mid+1; x <= r; ++x) b.push_back(v[x]);

  8.    a.assign(v.begin()+l, v.begin()+mid+1);

  9.    b.assign(v.begin()+mid+1, v.begin()+r+1);

  10.    int i = a.size() - 1;

  11.    int j = b.size() - 1;

  12.    while(i >= 0 && j >= 0) {

  13.        if(a[i] > b[j]) v[index--] = a[i--];

  14.        else v[index--] = b[j--];

  15.    }

  16.    while(i >= 0) v[index--] = a[i--];

  17.    while(j >= 0) v[index--] = b[j--];

  18. }

  19. void merge_sort(vector<int>& v, int l, int r) {

  20.    if (l == r) return ;

  21.    int mid = (l + r) / 2;

  22.    merge_sort(v, l, mid);

  23.    merge_sort(v, mid+1, r);

  24.    //merge

  25.    merge(v, l, r, mid);

  26. }

  27. int main() {

  28.    int n;

  29.    srand(0);

  30.    int cnt = 0;

  31.    vector<int> v;

  32.    for (int i = 0; i < 100; ++i) {

  33.        v.clear();

  34.        for (int j = 0; j < 100; ++j) {

  35.            v.push_back(rand()%1000);

  36.        }

  37.        vector<int> w;

  38.        w.assign(v.begin(), v.begin()+v.size());

  39.        merge_sort(w, 0, w.size() - 1);

  40.        sort(v.begin(), v.end());

  41.        if (w == v) cnt++;

  42.    }

  43.    printf("correct %d\n",cnt);

  44.    return 0;

  45. }

时间复杂度O(n*logn) 
空间复杂度O(n)


快速排序 
快速排序和归并排序一般都是递归实现,但是快速排序递归的时候是,先进行划分。比如找到某个基准k,把小于k的放大k的左边,大于等于k的放在k的右边。 比如序列: 
{6,3,7,6,2,1,4,8} 
如果选6作为基准那么一次划分后得到 
{4,3,1,2,6,6,7,8} 
划分后会得到两个区间[0,4][5,7],那么接下来划分[0,4][5,6]区间.....依次下去最后划分区间中只有一个数的时候结束。 
代码实现如下:

 
   
   
 
  1. void quicksort(vector<int>&v,int l,int r){

  2.    if(r<=l)return ;

  3.    int k=v[l];

  4.    int low=l;

  5.    int high=r;

  6.    while(low<high){

  7.        while(low<high&&v[high]>=k)high--;

  8.        swap(v[low],v[high]);

  9.        while(low<high&&v[low]<k)low++;

  10.        swap(v[low],v[high]);

  11.    }

  12.    quicksort(v,l,low-1);

  13.    quicksort(v,low+1,r);

  14. }


时间复杂度O(n*logn),最坏的复杂度是O(n^2)就是当原序列是有序的时候,这时递归树就变成链了... 
最优的情况下空间复杂度为O(logn):每一次都平分数组的情况 最差的情况下空间复杂度为O(n):退化为冒泡排序的情况


堆排序 
堆排序,感觉很高级的样子,首先得知道堆是什么。 
堆的定义如下:n个关键字序列L[n]成为堆,当且仅当该序列满足: 
①L(i) <= L(2i)且L(i) <= L(2i+1) 或者 ②L(i) >= L(2i)且L(i) >= L(2i+1) 其中i属于[1, n/2]。 
满足第①种情况的堆称为小根堆(小顶堆),满足第②种情况的堆称为大根堆(大顶堆)。 
如下图就是一个大根堆 
图片来源谷歌 
你会发现左边一个数组就可以表示右边的一个二叉树了。 
这里得扩展一下二叉树的知识,二叉树又分为完全二叉树(complete binary tree)和满二叉树(full binary tree) 一棵满二叉树 归并排序&快速排序&堆排序 
一棵完全二叉树 
 
完全二叉树的一个“优秀”的性质是,除了最底层之外,每一层都是满的,这使得堆可以利用数组来表示(普通的一般的二叉树通常用链表作为基本容器表示),每一个结点对应数组中的一个元素。 
本质上讲,堆排序是一种选择排序,每次都选择堆中最大的元素进行排序。只不过堆排序选择元素的方法更为先进,时间复杂度更低,效率更高。 
整体的思路就是,从最后一个父节点进行调整,先比较两个子节点选最大的,然后和父节点进行比较,交换,保证父节点是最大的。 
如果父节点被交换了,那么就要再次更新父节点以下的点,因为交换节点后可能存在不满足条件的子树。 如果父节点没被交换,那直接跳出函数即可。 
这样一次调整结束。把堆顶的最大元素拿出来(就是与最后一个元素交换),接着对剩下的n-1个数,进行调整。具体实现见代码。

 
   
   
 
  1. #include <bits/stdc++.h>

  2. using namespace std;

  3. void print(vector<int>& v) {

  4.    for (auto x : v) {

  5.        cout << x << " ";

  6.    }

  7.    cout << endl;

  8. }

  9. void max_heapify(vector<int>& v, int start, int end) {

  10.    //建立父节点和子节点

  11.    int dad = start;

  12.    int son = dad * 2 + 1;

  13.    while (son <= end) { //只有当子节点在范围内才做比较

  14.        if (son + 1 <= end && v[son] < v[son + 1]) //先比较两个子节点的大小,选最大的

  15.            son++;

  16.        if (v[dad] > v[son]) //如果父节点大于子节点,调整结束

  17.            return;

  18.        else { //否则在交换父子节点,然后在进行调整

  19.            swap(v[dad], v[son]);

  20.            dad = son;

  21.            son = dad * 2 + 1;

  22.        }

  23.    }

  24. }

  25. void heap_sort(vector<int>& v, int len) {

  26.    //初始化,i从最后一个父节点调整

  27.    //print(v);

  28.    for (int i = len / 2 - 1; i >= 0; i--)

  29.        max_heapify(v, i, len - 1);

  30.    //现将第一个元素和已经排好的元素前一位做交换,再重新调整,直到排序完成

  31.    for (int i = len - 1; i > 0; i--) {

  32.        swap(v[0], v[i]);

  33.        max_heapify(v, 0, i - 1);

  34.    }

  35.    //print(v);

  36. }

  37. int main() {

  38.    vector<int>v;

  39.    for (int i = 10; i >= 0; --i) {

  40.        v.push_back(i);

  41.    }

  42.    heap_sort(v, v.size());

  43.    return 0;

  44. }

空间复杂度O(1) 
时间复杂度O(nlogn)


 
欢迎关注:)


以上是关于归并排序&快速排序&堆排序的主要内容,如果未能解决你的问题,请参考以下文章

8大排序算法---我熟知3(归并排序/快速排序/堆排序)

算法系列01:快速排序&&归并排序

归并排序 && 快速排序

归并排序 && 快速排序

常见排序算法的实现(归并排序快速排序堆排序选择排序插入排序希尔排序)

排序 & 常用算法