几种排序算法的讲解(一)

来源:互联网 发布:苹果手机文档软件 编辑:程序博客网 时间:2024/05/19 23:00
看完一本基础的算法书后,很多知识仍然混乱,打算用博客的形式来慢慢整理自己所学所感的知识。写得可能不好,但是我会尽力地把这些知识用自己的话慢慢讲解,把他尽可能易于接受。教才是最好的学。

首先说一下经常能碰到的一些排序算法,有冒泡排序、直接插入排序、快速排序、希尔排序和堆排序等等。

基础入门级的排序应该就是是冒泡和直接插入两大排序了,然后难一点的是快速与希尔、进一步是用到图论知识的堆排序,还有其他的排序算法,如黑白树、桶排序等等。不过我现在所学到的排序有限,脑袋里留存的仅仅只有上述的5种。

当然,平时写代码的时候可能用不着这些排序,对于C和C++来说,用STL中的sort函数就能搞定排序,python用一个方法sort搞定,很多语言都有自己的排序函数。但是排序算法思维很重要,学习它是毋庸置疑的。

一、冒泡排序

冒泡排序属于入门级别,非常容易实现。缺点是太耗时,无法处理比较大的数据。下面对从小到大排序进行讲解。

冒泡排序核心是,每次对连续的两个数据进行比较,然后把大的数据放到右边。这个过程就像滚雪球一样,从第一个开始,然后不断滚向最后一个,遇到大的数,雪球就变大,到达最后一个位置时候雪球就是最大的了。当然,把数从上到下放置的话,就像一个气泡,从下面冒出,不断向上变大。
冒泡排序的操作如下:

  • 有两种区间,一种是混乱无比的区间,一种是排序整齐的区间。开始的时候都是混乱的区间,没有整齐的区间。
  • 有一个在混乱区间里总是从左往右移动的游标,然后每次把游标所指的数与后一位数做比较。
  • 每次比较后,都把大的数放到右边去,游标接着后移一位。
  • 游标从 cur = 1 移动到 cur = N-1 后( N为混乱区间长度),第N位数就是当前混乱区间最大的数了,那么这个数就划入到了排序整齐的区间里。此时,混乱区间的长度N = N-1,排序区间长度+1。
  • 游标又从新回到1位置,然后又一直移动到N-1位置,跟上面的操作一样,反反复复进行下去,直到混乱的区间长度N = 1,也就是游标不能移动为止(想想为什么)。

上面那些操作不断重复,直到重复Len - 1次后结束(Len 为数据个数)。

#include <bits/stdc++.h>using namespace std;const int maxn = 10000;int a[maxn];int main(){    int n;    while(cin>>n)    {        for(int i=1; i<=n; i++) cin>>a[i];        //重复操作n-1次        for(int i=1; i<=n-1; i++)        {            //指标cur,每一次从1移动到N-1处            //第一轮操作N-1 = n-1, 第二轮操作N-1 = n-2...即每一轮操作后N-1 = n-i。            for(int cur=1; cur<=n-i; cur++)            {                //游标每一次移动,都把大的数放到右边                if(a[cur]>a[cur+1])swap(a[cur], a[cur+1]);            }        }        for(int i=1; i<=n; i++)            cout<<a[i]<<" ";        cout<<endl;    }    return 0;}

二、直接插入排序

直接插入排序,顾名思义,就是每次取一个数,然后插入到一个排序好的区间里面,一旦插入某一个位置,后面的所有数的位置都要后移一位。
这里以从小到大排序讲解,排序思路如下:

  • 有两种区间,一种是排序整齐的区间, 一种是混乱无比的区间。开始的时候,第一个数划为排序整齐的区间,后面是混乱区间。
  • 排序整齐的区间里有个总是从从右向左移动的游标,即总是从cur = N(整齐区间长度)移动到cur = 1 处。
  • 取出混乱区间的第一个数,然后开始与指向N位置的游标所指的数比较,如果取出的数小了,那么游标向前移动一位,即cur = N-1。然后继续如此操作。
  • 当找到一个比取出的数小的位置,那么把取出的数插入到cur +1 的位置。如果是cur = 0,即没有找到一个合适的数,那么就把该数插入到第一个位置。后面的数都往后移动一位。
  • 此时整齐区间长度+1,N = N+1。混乱区间长度-1。如次反复操作Len - 1次,直到没有混乱区间,即 N = Len。

下面看直接插入排序的实现代码:

#include <bits/stdc++.h>using namespace std;const int maxn = 10000;int a[maxn];int main(){    int n;    while(cin>>n)    {        for(int i=1; i<=n; i++) cin>>a[i];        //要从混乱的区间里取出第一个数,从第二个数开始取。        for(int i=2; i<=n; i++)        {            int cur;            //,每一次从混乱区间取第一个值(位置为i) 比较时候,此时的排序整齐的区间长度为i-1。            //游标从排序整齐区间的最后一个数往左移动直到第一位数            //当发现取出的值比游标当前所指的值大时,游标的任务完成,找到了插入的位置,循环终止。            for(cur=i-1; cur>=1; cur--)                if(a[cur]<a[i])break;            //如果游标移动了,那么执行值的插入,与值的后移。            if(cur!=i-1)            {                //此处就是把值后一一位,在后移动的同时,把要插入的值往前推一位。                //这样就实现了类似插入的效果。                for(int k=i-1; k>cur; k--)                    swap(a[k+1], a[k]);            }        }        for(int i=1; i<=n; i++)            cout<<a[i]<<" ";        cout<<endl;    }    return 0;}

三、快速排序

快速排序利用了“二分”的思想,优化了算法的复杂度(耗时)。
快速排序是每次定义第一位为“基准数”(这个专有名词第一次见的话可能理解吃力,姑且称它为划分区间的数,即分界数吧),然后把该分界数放到一列数据的中间位置某一处位置(不一定是正中间),然后把分界数左边那一块区间里 大于分界数的数 放到分界数右边那一块区间里,而右区间里小于分界数的数放到左区间里。
一句话就是说,把大的放在右区间,小的放在左区间。这么啰嗦地解释,是为了方便理解排序的操作。
下面说说快速排序的操作:

  • 有一个要排序的大区间,每一次把大区间里的第一个数定义为分界数。
  • 区间里有左右两个游标,left和right。开始的时候分别在区间的最左边与最右边。
  • 首先是right游标向左移动 , 找比分界数小的数,一旦找到就停止;然后才是left游标向右移动,找比分界数大的数,一旦找到就停止。然后把两个游标所指的数交换。
  • 如果right > left 即左游标在右游标左边的时候,一直重复上面的操作,直到不符合该条件为止。
  • 此时,两个游标重合,然后把分界数与游标所指的数交换,那么,现在就有了两个区间,数全小于或等于分界数的左区间,数全大于或等于分界数的右区间。两区间都不包含分界数。
  • 然后把左右两个区间都分别看成要排序的区间,重复以上操作,直到无法分割区间。

下面看快速排序的实现代码:

#include <bits/stdc++.h>using namespace std;const int maxn = 10000;int a[maxn];//利用递归来实现操作,开始对一个区间[L, R]进行操作void quicksort(int L, int R){    //当无法划分区间的时候停止    if(L > R) return;    //定义该区间的第一个数为分界数,然后放置左右两个游标。    int base = a[L], left = L, right = R;    //当右游标一直在左游标右边时候,一直循环,直到两个游标重合才停止    while(left < right)    {        //右游标开始移动,直到找到小于分界数的数才停止,否则,若不与左游标重合时一直往左移动        while(a[right]>=base && left<right) right--;        //左游标后移动,直到找到大于分界数的数才停止,否则,若不与右游标重合时一直往右移动        while(a[left]<=base && left<right) left++;        //当游标不重合时候,交换游标指的数。        if(left<right) swap(a[left], a[right]);    }    //最后把分界数与游标重合所指的数交换    swap(a[L], a[left]);    //开始排序分界数的左右两个区间。    quicksort(L, left-1);    quicksort(left+1, R);}int main(){    int n;    while(cin>>n)    {        for(int i=1; i<=n; i++) cin>>a[i];        quicksort(1, n);        for(int i=1; i<=n; i++)            cout<<a[i]<<" ";        cout<<endl;    }    return 0;}