排序算法一览

来源:互联网 发布:军事坐标地图软件 编辑:程序博客网 时间:2024/05/29 07:29
 

排序算法一览 - [算法]

版权声明:转载时请以超链接形式标明文章原始出处和作者信息及本声明
http://lzhshen.blogbus.com/logs/20025537.html

虽然现有的开发组件中对排序算法已经有很好的实现,但是通过研究这些算法的思路,对我们思维能力的提高还是很有帮助的,而且现在面试笔试时也常常会用到,所以自己在网上搜罗并研究实现了一下,以下都以升序为例,总结如下。
 
1.冒泡排序,最简单也最常用的一种(^_^不复习的情况下,笔试遇到排序问题,我只能记住它),思想是:每次将数组前N个中最大(升序)或最小(降序)的数交换到数组底部,每次数组大小N--,再进行如此操作,直到所有的数都已排序即N=1。这样循环比较的次数是(n-1)+(n-2)+(n-3)....... 约等于(n)(n+1)/2, 时间复杂度为O(n2),N的平方。C语言实现如下:

void bubble_sort(int*array, int size)
{
        int i, j, temp;
        for (i=size-1; i>1; i--)
                for (j=0; j<i; j++)
                        if (array[j]> array[j+1]){
                                temp = array[j+1];
                                array[j+1]= array[j];
                                array[j]= temp;
                        }
}

2.交换排序,原理是用第一个元素和第二个元素比较,若第一个元素大于第二个,则交换,交换后第一个元素在与第三个元素比较.....直到N个元素,然后再选择第二个元素和第三个元素比较,进行同样操作.....直到选择到N个元素。 这样操作完成后数组就变为升序了。其实说白了,原理就是第一次交换,确保第一个元素是最小的,第二次交换确保第二个元素是第二小的.... 以此类推。效率也很低,循环次数(n-1)(n-2)..即O(n2), n的平方,C语言实现如下:

void change_sort(int*array, int size)
{
        int i, j, temp;
        for (i=0; i<size; i++)
                for (j=i+1; j<size; j++)
                       if (array[i]>array[j]){
                               temp = array[j];
                               array[j]= array[i];
                               array[i]= temp;
                       }
}

3.选择排序,仔细看交换排序,发现其实没必要每次发现比自己小的元素就发生交换,只要和剩下的元素中最小的那个元素交换就可以了,即选择最小的元素进行交换,那么交换排序的扩展算法,选择排序就很容易得出来了,效率略比交换排序高,整体平均效率还是N的平方,C语言实现如下:

void select_sort(int*array, int size)
{
        int i, j, temp, pos;
        for (i=0; i<size; i++){
                for (j=i+1, temp=array[i], pos=i; j<size; j++)
                        if (temp > array[j]){
                                temp = array[j];
                                pos = j;
                        }
                array[pos]= array[i];
                array[i]= temp;
        }
}

4.插入排序,有点类似冒泡排序的逆操作,也是比较简单易懂的算法。原理是,现对数组第一个和第二个排序,这样前2个元素就是有序的了,再引入第三个元素,找到合适的位置插入。。。以此插入剩余元素。这种排序算法比起冒泡排序算法效率更高,有点类似冒泡排序的改进算法。虽然效率上比冒泡排序好,但是也是一种简单排序算法,循环次数为 1+2+3...+n-1,即n(n+1)/2, 平均情况下时间复杂度还是O(n2),n的平方。C语言实现如下:

void insert_sort(int*array, int size)
{
        int i, j, temp;
        for (i=1; i<size; i++){
                for (j=i, temp=array[i]; j>0&&temp<array[j-1];j--)
                        array[j]= array[j-1];
                array[j]= temp;
        }
}

小结,以上四种排序算法是常见的四种简单排序算法,其中选择排序比较像我们人对一副扑克牌排序一样,先选出最小的,然后选出次小的,以此类推。而且选择排序算是这四种算法中效率比较稳定的,虽然这四种简单排序的时间复杂度都趋近O(n2),下面我们介绍几种高级排序算法。
5.希尔排序,在所有这些排序算法中,唯一以人名命名的算法就是希尔排序(Donald shell),可见这个排序算法是比较特别的。很少有人能理解这个算法的原理并且证明它(^_^俺也不懂,好像证明过程很复杂,需要很强的数学功底),而且现在已经有更加快速的算法取代它了,深入研究也就没那么重要了。其思想是,避免大量的数据移动(即交换),每次对H1个序列进行插入排序,然后计算出一个增量K1,对连续的H2=K+H1个序列进行插入排序,重复计算增量K2,再对H3=H2+K2进行插入排序......因为我们知道,每次插入排序之后,这个序列就是有序序列了,在扩大这个序列进行排序的时候,需要移动的元素就少了,这样就能获得比插入排序更加高效的算法来。经过shell大人的证明,这个算法的时间复杂度小于O(N2),所以也被叫做亚二次方算法。第一次选择的序列为N/2,即后一半元素,每次计算增量的参考值用的是2.2(资料上说是没有理论基础,但是经过实际检验,估计是后来出现更优的算法,也就少有人去研究证明了吧^_^),C语言编码如下:

void shell_sort(int*array, int size)
{
  int gap, i, j,temp;
        for (gap= size/2; gap>0;
gap=(gap== 2 ?1:(int)(gap/2.2)))
               for (i=gap; i<size; i++){
                        for (j=i,temp=array[i];
j>=gap&& temp < array[j-gap]; j=j-gap)
                                array[j]= array[j-gap];
                                array[j]= temp;
              }
}

6.归并排序,采用的是分治算法,即将一组元素通过几次平分,分成若干组元素(最终单个元素为一组),这样就形成一个满二叉树形结构,叶子节点为单个元素,归属于同一父节点的两组元素排序后归并成一组元素,最终归属于根节点的两组元素再归并成一组元素,完成排序。这是典型的分治算法,如能理解分治算法的含义,那么也不难理解归并排序。这个算法的缺点是需要借助于排序元素相同大小的辅助数组,而且需要将数据反复的在原始数组和辅助数组之间拷贝,这些额外的工作大大降低了此算法的工作效率,时间复杂度为O(NlogN),C编码如下:

voidmerge(int*array, int *temp_array,int left,int right,int right_end)
{
        int left_end = right - 1, i;
        int tmp =left;
        int num = right_end- left+1;

        while (left<= left_end&& right <= right_end)
                if (array[left]<= array[right])
                        temp_array[tmp++]= array[left++];
                else
                        temp_array[tmp++]= array[right++];

        while (left<= left_end)
                temp_array[tmp++]= array[left++];
        while (right<= right_end)
                temp_array[tmp++]= array[right++];
        for (i=0; i<num; i++, right_end--)
                array[right_end]= temp_array[right_end];
}

void m_sort(int*array, int *temp_array,int left,int right)
{
        int center;
        if (left< right){
                center = (left+right)/2;
                m_sort(array, temp_array,left, center);
                m_sort(array, temp_array, center+1,right);
                merge(array, temp_array,left, center+1,right);
        }
}
void merge_sort(int*array, int size)
{
        int *temp= (int*)malloc(size);
        memset(temp, 0, size);
        m_sort(array, temp, 0, size-1);
        free(temp);
}

7.快速排序.实际应用中用的最多的,也是最常见的一种排序方法,其主要思想还是用了分治法,算法是,从数组中找到一个支点,通过交换,使得支点的左边都是小于支点的元素,支点的右边都是大于支点的元素,而支点本身所处的位置就是排序后的位置!然后把支点左边和支点右边的元素看成两个子数组,再进行如上支点操作直到所有元素有序!这是基本的算法思想,各种不同的实现可能和支点的选择有关,最简单的是选择第一个元素做支点,常用的是选择中间的元素做支点,而本文的实现代码是第一个元素,中间元素,最后一个元素,这三个元素的中值即满足(a<b<c)时,取b作为支点,这样能比纯粹的选第一个元素和中间元素更加合理。实现代码如下:

voidswap(int*a, int *b) //交换函数
{
        int temp =*a;
        *a =*b;
        *b = temp;
}

int partition(int*array, int low, int high)
{
        int middle = (low+high)/2, temp, pivot,i,j;
        //选择第一个元素,最后一个元素,中间元素中的中间值作为支点

        if (array[middle]< array[low])
                swap(&array[middle],&array[low]);
        if (array[high]< array[low])
                swap(&array[high],&array[low]);
        if (array[high]< array[middle])
                swap(&array[high],&array[middle]);

        pivot = array[middle];// 选中支点

        swap(&array[middle],&array[high-1]);//将支点值换到倒数第二个位置

        for (i=low, j=high-1;;) {
                while (array[++i]<pivot){} //找到一个大于支点的元素
                while (pivot<array[--j]){} //找到一个小于支点的元素
                if (i < j){ //交换两个元素
                        temp = array[j];
                        array[j]=array[i];
                        array[i]=temp;
                } else
                        break;
        }
        swap(&array[i],&array[high-1]);//将支点换回i点, 第一次分组结结束

        return i;
}

void quicksort(int*array, int low, int high)
{
        int piovt_pos;
        if (low< high) {
                piovt_pos = partition(array, low, high);//分组
                quicksort(array, low, piovt_pos-1);
                quicksort(array, piovt_pos+1, high);
        }
}

void quick_sort(int*array, int size)
{
        quicksort(array, 0, size-1);
}

8.堆排序,堆排序在最坏的情况下其时间复杂度也只为O(NlogN), 平均复杂度O(NlogN)这是于快速排序相比最大的优势,不过堆排序需要借助大小为N的辅助存储。基本原理是,通过一个大小为N的辅助数组,建立一个二叉堆(大顶堆或小顶堆),所谓大顶堆指的是根节点为数组中最大的元素,用来降序,所谓小顶堆指的是根节点为数组中最小的元素,用来升序。具体代码实现如下:

void heapinsert(int*array, int pos, int data)//构建小顶堆,升序排列

{
        int i;
        for (i=pos; i/2>0; i/=2){
                if (array[i/2]> data) //与父节点比较,大于父节点则交换位置

                        array[i]= array[i/2];
                else
                        break;
        }
        array[i]= data; //插入

}

int heapdel(int*array, int len)
{
        int min, last, i, j;

        min = array[1];//堆顶元素删除

        last = array[len--];

        for (i=1; i*2<=len; i=j) {
                j = i*2;//i的左儿子

                if ((j!=len)&& (array[j+1]<array[j]))//看看哪个儿子可以填补空白

                        j++;
                if (last > array[j])
                        array[i]= array[j];
                else
                        break;

        }
        array[i]= last; //将最后一个元素填入空白

        return min;

}

void heap_sort(int*array, int size)
{
        int *temp= (int*)malloc(size+1);//方便编码,下标0的不用,从1开始

        int i;

        for (i=0; i<size; i++)
                heapinsert(temp, i+1, array[i]);
        for (i=0; i<size; i++)
                array[i]= heapdel(temp, size-i);
        array[size-1]= temp[1];
        free(temp);
}

9.基数排序 这种排序算法区别于以上的所有排序算法,以上的排序算法都是通过单个关键字的比较,移动元素位置来达到形成有序序列的目的。基数排序是利用将自身分解成多个关键字,通过依次对单个关键字的进行排序来达到整体的有序。比如扑克牌的排序,可以将扑克牌的关键字分为花色和点数两个关键字。基数排序需要借助额外的存储空间,算法的时间复杂度为O(d(n+rd)),其中r,d的大小和关键字的个数和额外存储的大小有关。这里以N个正整数为例,可以将正整数的每一位作为关键字,即个位,十位,百位。。。分别作为关键字,借助10个链表,标号0~9,,第一轮用个位关键字,遍历数组,以个位为标准,依次插入相应的链表,即个位0的插入0号链,个位1的插入1号链,数组元素全部插入后,又依次从头部删除0~9号链,删除的元素依次存入原数组,这样完成第一轮操作,然后进行第二轮操作,以十位为标准,重复以上操作,直到轮数等于最大数的位数。 为了简化代码实现,用STL中的LIST实现如下:

int get_radix(int num,int radix)//取得基数位的数
{
        int temp;
        for(int i=0; i<radix; i++){
                temp = num%10;
                num = num/10;
        }
        return temp;
}

void radix_sort(int*array, int size)
{
        list<int> temp_list[10];
        int max, radix, i, j, k;
        char temp_buf[20];
        list<int>::iterator iter;

        memset(temp_buf, 0,sizeof(temp_buf));
        max = array
</

}