各排序算法总结(简单选择,冒泡,快速,希尔排序,堆排序)

 1)冒泡排序bubble_sort
        1.原理
            假设对a[N]进行排序
            依次比较相邻两个数,小数放前,大数放前。
            *1    从头开始进行第一轮比较,则得到最后一个位置是所有数中的最大的一个数;       
                需要比较的次数是N-1,为什么是N-1?因为,总共是N个数,数组下标是从0开始,
                如果比较最后两个数据,判断条件:if(a[N-1-1] > a[N-1]),a[N-1]就是数组的最后一个数了,
                如果比较次数是N,则执行该论最后一对数据比较时就是if(a[N-1] > a[N]),
                众所都知,a[N]中是没有a[N],所以次数会得到一个意想不到的排序,里面会多一个垃圾值,垃圾值的产生就是a[N]。
                所以此时必须用N-1。
            *2    从头开始进行第二轮比较,则得到倒数第二个位置是所有数中的次最大数;       
                需要比较的次数是(N-1)-1,为什么是(N-1)-1?因为,解释同上。
            *3    从头依次进行第三轮比较,则得到倒数第三个位置是所有数中的次次最大数;       
                需要比较的次数是N-1-1-1,为什么是(N-1)-1-1?因为,解释同上。
            *N-1    依次从头开始比较,直至比较完。
        2.代码实现过程
            void bubble_sort(int a[], int N)            //N是数组a的长度
            {
                int i, j, tmp;

                for(i = 0; i < N-1; i++)            //进行比比较的趟数,此时的N-1如果换成N,对程序影响不大,只不过多一次循环而已
                {
                    for(j = 0; j < N-i-1; j++)        //开始进行比较,此时的N-i-1不能换成N-i,如果写成N-i,会产生一个垃圾值,解释在*1中
                    {
                        if(a[j] > a[j+1])
                        {
                            tmp    = a[j];
                            a[j]   = a[j+1];
                            a[j+1] = tmp;
                        }
                    }
                }
               
                return;
            }
   
    2)简单选择排序select_sort
        1.原理
            假设对a[N]进行排序
            假定选一个最小的,依次把后面的数与它进行比较,如果后面的比假定选的最小的还小,则进行交换这两个数。每进行一轮就要找出一个最小的数。
            *1    第一轮    假定第一个数是“最小的”,然后依次把后面的数与这个“最小的”的进行比较,如果比这个“最小的”小,首先标记该数为“最小的”,
                    然后把这个“最小的”与前面的“最小的”交换值,目的是保证第一个数是后面所有数中最小的一个。
            *2    第二轮    因为已经知道第一个是最小的,这时从第二个开始寻找最小的,假定第二个是最小的,然后依次把后面的数与这个“最小的”的进行比较,
                    如果比这个“最小的”小,首先标记该数为“最小的”,然后把这个“最小的”与前面的“最小的”交换值,目的是保证第二个数是后面所有数中最小的一个。

            *N    第N轮    步骤同上。
           
            说明    是进行N轮比较还是N-1轮比较,影响不大,原因是:该数组中共有N个数,如果对N-1个数进行了排序处理,即第N-1个数是后面中最小的一个数,
                剩余的唯一的第N个数肯定是所有数中最大的,显然它应该放在最后一个位置上,没必要再进行一次循环。如果是进行N轮比较,无非是程序多执行一次循环,
                其实实际上这次循环也没有执行,因为在子循环中无法满足循环条件,不能执行循环体。

        2.代码实现过程
            void select_sort(int a[], int N)        //N是数组a的长度
            {
                int i, j, min, tmp;

                for(i = 0; i < N; i++)            //从头开始比较
                {
                    min = i;            //把第i个数当作最小的,把下标标记为min
                    for(j = i+1; j < N; j++)    //把a[min]依次和它后面的数进行比较
                    {
                        if(a[j] < a[min])    //如果后面的某个值比a[min]小
                        {
                            min = j;    //重新标记最小值下标
                        }
                        tmp = a[min];        //交换
                        a[min] = a[i];        //原来min = i的,如果没有找到比a[min]小的,就不进行交换(如果没找到,i还是和min一样,执行该三条语句没作用)
                        a[i] = tmp;        //只有a[j] < a[min]成立,执行了min = j,这时才能交换(这时min的已经改为j,不再是i了,所以执行才有作用)
                    }                //自己表达欠缺,上面三句表达不够明确,只要自己研读程序,因该能明白其中奥妙
                }

                return;
            }

    3)快速排序quick_sort
        1.原理
            假定对a[N]排序
            快速排序使用分治法(Divide and conquer)策略来把一个序列(list)分为两个子序列(sub-lists)。
            实现快速排序需要两个函数,分别是:partition()和quick_sort()
           
            函数:            int partition(int a[], int i, int j)
            函数功能:        把数组分为两段,左段都是比基准小的数,但是左段的数是无序的;右段都是比基准大的数,但是右段的数是无序的。
            函数大概实现过程:       
                    *1    选取一个基准(pivot),基准一般选取第一个数a[0];

                    *2    定义两个变量i、j;
                        令j从数组尾部向左扫描,直到遇到一个数比基准小的,进行交换。目的是:使比基准小的都放在基准左边。
                        令i从数组开头向右扫描,直到遇到一个数比基准大的,进行交换。目的是:使比基准大的都放在基准右边。
                        进行多次循环后,最终i的值就是基准应该放的位置。
                        原因是:我们假定的基准是数组的第一个数a[0],a[0]也许既不是数组中最大的数也不是数组中最小的数,
                        也就是基准的位置应该在数组中的某个位置,经过多次循环后,i不断往后递增,当不满足循环条件时,i就停止了,此时i就是基准的位置
                        (表达欠缺,还需要自己揣摩)
               
            函数:            void quick_sort(int a[], int left, int right)
            函数功能:        实现排序
            函数大概实现过程:
                        递归的对每一段进行排序

        2.代码实现过程
            int partition(int a[], int i, int j)
            {           
                int pivot;                //基准
       
                pivot = a[i];               
                while(i < j)
                {
                    while(i < j && a[j] > pivot);   
                    {
                        j--;            //如果右边的比基准大,左移j
                    }
                    a[i] = a[j];            //如果遇到了右边的比基准小,和基准交换
               
                    while(i < j && pivot <= a[i]);
                    {
                        i++;            //如果左边的基准小,右移i
                    }
                    a[j] = a[i];            //如果遇到了左边的比基准大,和基准交换
                }
                a[i] = pivot;

                return i;
            }

            void quick_sort(int a[], int left, int right)
            {
                int pivottag;
           
                if(left < right)
                {
                    pivottag = partition(a, left, right);
                    quick_sort(a, 0, pivottag-1);   
                    quick_sort(a, pivottag+1, right);
                }

                return;
            }

 

  4)希尔排序
   在直接插入排序算法中,每次插入一个数,使有序序列只增加1个节点, 并且对插入下一个数没有提供任何帮助。

  如果比较相隔较远距离(称为 增量)的数,使得数移动时能跨过多个元素,则进行一次比较就可能消除 多个元素交换。

   算法先将要排序的一组数按某个增量d分成若干组,每组中 记录的下标相差d.对每组中全部元素进行排序,然后再用一个较小的增量 对它进行,在每组中再进行排序。

   当增量减到1时,整个要排序的数被分成 一组,排序完成。
   下面的函数是一个希尔排序算法的一个实现,初次取序列的一半为增量, 以后每次减半,直到增量为1。

 void shell_sort(int *x, int n)

 {

    int h, j, k, t;

     for (h=n/2; h>0; h=h/2) /*控制增量*/

   {  

     for (j=h; j<n; j++) /*这个实际上就是上面的直接插入排序*/  

      {     

         t = *(x+j);   

         for (k=j-h; (k>=0 && t<*(x+k)); k-=h)

        {      

          *(x+k+h) = *(x+k);

        }     

            *(x+k+h) = t;     

      }

  }

}

5)堆排序

堆排序是一种树形选择排序,是对直接选择排序的有效改进。 堆的定义如下:具有n个元素的序列(h1,h2,...,hn),当且仅当 满足(hi>=h2i,hi>=2i+1)或(hi<=h2i,hi<=2i+1)(i=1,2,...,n/2) 时称之为堆。在这里只讨论满足前者条件的堆。

由堆的定义可以看出,堆顶元素(即第一个元素)必为最大项。完全二叉树可以 很直观地表示堆的结构。堆顶为根,其它为左子树、右子树。 初始时把要排序的数的序列看作是一棵顺序存储的二叉树,调整它们的存储顺序, 使之成为一个堆,这时堆的根节点的数最大。然后将根节点与堆的最后一个节点 交换。然后对前面(n-1)个数重新调整使之成为堆。依此类推,直到只有两个节点 的堆,并对它们作交换,最后得到有n个节点的有序序列。

从算法描述来看,堆排序需要两个过程,一是建立堆,二是堆顶与堆的最后一个元素 交换位置。所以堆排序有两个函数组成。一是建堆的渗透函数,二是反复调用渗透函数 实现排序的函数。

堆排序是不稳定的。算法时间复杂度O(nlog2n)。

 功能:渗透建堆 输入:数组名称(也就是数组首地址)、参与建堆元素的个数、从第几个元素开始

void sift(int *x, int n, int s)

{

   int t, k, j;

   t = *(x+s);   /*暂存开始元素*/

     k = s;      /*开始元素下标*/

   j = 2*k + 1;   /*右子树元素下标*/

  while (j<n) {

      if (j<n-1 && *(x+j) < *(x+j+1))         /*判断是否满足堆的条件:满足就继续下一轮比较,否则调整。*/   

          {      j++;     }

        if (t<*(x+j))          /*调整*/   

           {    

               *(x+k) = *(x+j);      k = j;     /*调整后,开始元素也随之调整*/    

                j = 2*k + 1;   

            }   

         else          /*没有需要调整了,已经是个堆了,退出循环。*/   

          {     

              break;

            }

     }
          *(x+k) = t;    /*开始元素放到它正确位置*/

      }

/* 功能:堆排序 输入:数组名称(也就是数组首地址)、数组中元素个数 */

void heap_sort(int *x, int n) {

    int i, k, t; int *p;

       for (i=n/2-1; i>=0; i--) {  

            sift(x,n,i);       /*初始建堆*/

        }
       for (k=n-1; k>=1; k--) {  

             t = *(x+0);      /*堆顶放到最后*/  

            *(x+0) = *(x+k);   

           *(x+k) = t;  

           sift(x,k,0); /*剩下的数再建堆*/

           }

         }

 

posted @ 2013-08-31 21:40  lysxc  阅读(1296)  评论(0编辑  收藏  举报