经典排序算法综述 持续更新中

来源:互联网 发布:菜鸟网络 心怡科技 编辑:程序博客网 时间:2024/05/16 11:29

 

(如果对排序方法一点不清楚,可以先将步骤和每个方法下面的示例链接结合起来看更清楚,另外代码如有问题欢迎大家指正~)

 

直接/简单选择排序 Selection Sort

视觉直观感受7种常用排序算法

步骤:

1. 从未排序的数列中挑出最小元素,存放到排序数列的起始位置;
2. 从剩余未排序数列中继续寻找最小元素,然后添至排序数列末尾;
3. 以此类推,直到所有元素均添至排序数列。

 

void swap(int *arr, int i, int j)//后面的swap函数如无声明都是此函数

{

         inttemp=arr[i];

         arr[i]=arr[j];

         arr[j]=temp;

}

 

int selectMin(int *arr, int low, inthigh)

{

int MIN=arr[low];

int pos=low;

while(++low<=high)

          if(MIN>arr[low])

                   {

                            MIN=arr[low];

                            pos=low;

                   }

         returnpos;

}

 

void selectsort(int *arr, int n)

{

         intsorted_pos=0;

         while(sorted_pos<n-1)

         {

                   intselected_pos=selectMin(arr,sorted_pos,n-1);

                   if(selected_pos!=sorted_pos)

                            swap(arr,selected_pos,sorted_pos);

                   ++sorted_pos;

         }

}

 

时间复杂度:O(n*n)

空间复杂度:O(1)

 

示例:

http://www.cnblogs.com/kkun/archive/2011/11/23/2260281.html

 


堆排序 Heap Sort (选择排序) (不稳定)

视觉直观感受7种常用排序算法

堆积排序(Heapsort)是指利用堆这种数据结构所设计的一种排序算法。堆是一个近似完全二叉树的结构,并同时满足堆性质:即子结点的键值或索引总是小于(或者大于)它的父节点(大根堆/小根堆)。堆排序实际就是一个维护大根堆的过程:先将数组看作一个堆,然后生成一个大根堆(步骤1-2),此时堆顶的数总是最大值,取出堆顶的最大值然后重新生成新的大根堆,依次取出堆顶值即生成有序数列(步骤3-4)。


步骤:
1. 将未排序数列看成一颗完全二叉树的层次遍历,从而得到一颗完全二叉树(实质这一步没有做任何工作,并非真的写一棵树- - |||... 完全二叉树与数组的对应关系为:数组中下标为 i 的节点的子节点下表分别为 2i+1和 2i+2,下标以0开始,注意示例链接中是以1开始)。
2.生成大根堆:
从最后一个非叶子节点(arr[n/2-1])开始,将该节点与子节点的值比较:
a.若该节点的值 小于 子节点,则交换父节点和较大子节点的值(即生成一个局部大顶堆),然后对交换后的子节点递归重复这一步直到父节点大于子节点没有交换发生则返回;
b.若该节点的值 不小于 子节点,则对下一个非叶子节点(即该非叶子节点在数组前一个位置的值)重复2直至根节点;
当处理完根节点即生成一个大根堆,具体过程如下图所示:


3. 将堆顶的最大值(即此时数组中第一个值)与二叉树最后一个叶子节点的值(即数组未排序部分的最后一个值)交换,此时数组尾部的值即为排序后的值,然后对根节点调用步骤2.a,将新的根节点的值移到合适的位置,即生成一个新的未排序数列的大根堆。
4. 重复3直到堆中只剩下一个节点(即数组中第一个值),即排序完成,具体过程如下图所示:





void HeapAdjust_Recursive(int *arr, int i,int n)

{

         intleft=2*i+1;

         intright=2*i+2;

 

         if(left>n-1)

                   return;

         else if(left==n-1)

         {

                   if(arr[left]>arr[i])

                            swap(arr,left,i);

         }

         elseif(arr[left]>arr[i]&&arr[left]>=arr[right])

         {

                   swap(arr,left,i);

                   HeapAdjust_Recursive(arr,left,n);

         }

         elseif(arr[right]>arr[i]&&arr[right]>=arr[i])

         {

                   swap(arr,right,i);

                   HeapAdjust_Recursive(arr,right,n);

         }

}

void HeapAdjust(int *arr, int i, int n)

{

         intleft=2*i+1;

         intright=2*i+2;

         intmax=i;

         while(left<n)

         {

                   if(arr[left]>arr[max])

                   {

                            max=left;

                   }

                   if(right<n&&arr[right]>arr[max])

                   {

                            max=right;

                   }

                   if(i!=max)

                   {

                            swap(arr,i,max);

                            i=max;

                            left=2*i+1;

                            right=2*i+2;

                   }

                   else

                            break;

         }

}

void HeapSort(int *arr, int n)

{

         //BuildHeap

         intlast_nleaf=n/2-1;

         for(inti=last_nleaf;i>=0;--i)

                   HeapAdjust(arr,i,n);

        

         for(intlast_sorted=n-1;last_sorted>0;--last_sorted)

         {

                   swap(arr,last_sorted,0);

                   HeapAdjust(arr,0,last_sorted);//如写成BuildHeap(arr,0,last_sorted); 就会得到倒序排列,但只是个trick,不应通过此方法来得到倒序,效率问题,自己体会一下~

         }

}

 

时间复杂度:

最好情况O(nlogn)建堆时间O(1),调堆时间O(nlogn);

平均情况O(nlogn)平均情况接近最坏情况;

最坏情况O(nlogn)建堆时间O(n),调堆时间O(nlogn)。

因为建堆时间另外需要O(n),所以堆排序不适合大数据量的排序。

 

空间复杂度:

递归 O(logn)递归调用栈所需的空间;

非递归O(1) 原地算法,就地排序。

 

堆排序与简单选择排序的区别:

直接选择排序中,为了从Arr[1..n]中选出关键字最小的记录,必须进行n-1次比较,然后在Arr[2..n]中选出关键字最小的记录,又需要做n-2次比较。事实上,后面的n-2次比较中,有许多比较可能在前面的n-1次比较中已经做过,但由于前一趟排序时未保留这些比较结果,所以后一趟排序时又重复执行了这些比较操作。堆排序可通过树形结构保存部分比较结果,可减少比较次数。


示例:

原序列: 16,4,10,14,7,9,3,2,8,1

调整为大根堆:

Heap3

Heap1


堆排过程:

Heap4

Heap5

Heap6


http://www.cnblogs.com/kkun/archive/2011/11/23/2260286.html

http://www.cnblogs.com/zabery/archive/2011/07/26/2117103.html

 

 

快速排序 Quick Sort (交换排序) (不稳定)


视觉直观感受7种常用排序算法

步骤(分治思想):

1. 从数列中挑出一个元素,称为 “基准”(pivot);
2. 重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区(partition)操作。
3. 递归地把小于基准值元素的子数列和大于基准值元素的子数列排序。


void quicksort(int *arr, int low, int high)

{

   if(low>=high)   

       return;

   int pivot=arr[low];

   int bklow=low;

   int bkhigh=high;

   while(low<high)

    {

       while(low<high&&arr[high]>=pivot)

           --high;

       arr[low]=arr[high];

       while(low<high&&arr[low]<=pivot)

           ++low;

       arr[high]=arr[low];

    }

   arr[low]=pivot;

   //以上部分也可单独写个partition函数,返回low值作为下面递归的参数

   quicksort(arr,bklow,low-1);

   quicksort(arr,low+1,bkhigh);  

}

 

时间复杂度:

最好情况O(nlogn) 每次选择的pivot数值都为数组的中值;

平均情况 O(nlogn)一般比同为O(nlogn)复杂度的算法要快;

最坏情况 O(n*n)每次pivot都是数组最小的数,即数组已经有序的情况。

 

空间复杂度:O(logn)递归调用栈所需的空间。

 

示例:

无序数组[6 2 4 1 5 9]

a),先把第一项[6]取出来,

用[6]依次与其余项进行比较,

如果比[6]小就放[6]前边,2 4 1 5都比[6]小,所以全部放到[6]前边

如果比[6]大就放[6]后边,9比[6]大,放到[6]后边,//6出列后大喝一声,比我小的站前边,比我大的站后边,行动吧!霸气十足~

一趟排完后变成下边这样:

排序前 6 2 4 1 5 9

排序后 2 4 1 5 6 9

b),对前半拉[2 4 1 5]继续进行快速排序

重复步骤a)后变成下边这样:

排序前 2 4 1 5

排序后 1 2 4 5

前半拉排序完成,总的排序也完成:

排序前:[6 2 4 1 5 9]

排序后:[1 2 4 5 6 9]

http://www.cnblogs.com/kkun/archive/2011/11/23/2260270.html

 

冒泡排序 Bubble Sort (交换排序)

视觉直观感受7种常用排序算法

步骤:
1. 比较相邻的元素。如果第一个比第二个大,就交换他们两个。
2. 对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。在这一点,最后的元素应该会是最大的数。
3. 针对所有的元素重复以上的步骤,除了最后一个。
4. 持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。


void bubblesort(int *arr, int n)

{

   bool changed=true;

   int pos=0;

   while(pos<n-1&&changed)

    {

       changed=false;

       for(int i=0;i<n-pos-1;i++)

       {

           if(arr[i]>arr[i+1])

           {

                swap(arr,i,i+1);

                changed=true;

           }

       }

       ++pos;

    }

}

 

时间复杂度:

最好情况 O(n)

平均情况 O(n*n) 不考虑布尔标志changed时

最坏情况 O(n*n)

 

空间复杂度:O(1)

 

示例:

http://www.cnblogs.com/kkun/archive/2011/11/23/2260280.html

 

归并排序 Merge Sort (稳定)

 视觉直观感受7种常用排序算法


步骤(分治思想):
1. 申请空间,使其大小与原数组一致,该空间与原数组空间用来交替存放一次合并排序后的序列。
2. 设定两个指针,最初位置分别为两个已经排序序列的起始位置。
3. 比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下一位置,重复步骤3直到某一指针达到序列尾。
4. 将另一序列剩下的所有元素直接复制到合并序列尾。


void Merge(int *src_arr, int *dst_arr, ints1, int e1, int s2, int e2)

{

         intsorted_pos=s1;

         while(s1<=e1&&s2<=e2)

         {

                   if(src_arr[s1]<=src_arr[s2])

                            dst_arr[sorted_pos++]=src_arr[s1++];

                   else

                            dst_arr[sorted_pos++]=src_arr[s2++];

         }

         if(s1>e1)

                   while(s2<=e2)

                            dst_arr[sorted_pos++]=src_arr[s2++];

         else

                   while(s1<=e1)

                            dst_arr[sorted_pos++]=src_arr[s1++];

}

 

void MergeSort(int *&arr, int n)      //注意引用

{

         int*arr2=(int*)malloc(sizeof(int)*n);

         int*src_arr=arr;

         int*dst_arr=arr2;

 

         for(intinterv=1;interv<n;interv*=2)

         {

                   for(ints1=0;s1<n;s1+=2*interv)

                   {

                            inte1=(s1+interv-1<n?(s1+interv-1):(n-1));

                            ints2=e1+1;

                            inte2=(s2+interv-1<n?(s2+interv-1):(n-1));

                            Merge(src_arr,dst_arr, s1, e1 , s2, e2);

                   }

                   int*temp=src_arr;

                   src_arr=dst_arr;

                  dst_arr=temp;

         }

         arr=src_arr;

         deletedst_arr;

         //arr2=NULL;

         //src_arr=NULL;

         //dst_arr=NULL;

}


时间复杂度:

最好情况 O(nlogn)

平均情况 O(nlogn)

最坏情况 O(nlogn)

 

空间复杂度:O(n)

 

示例:

http://www.cnblogs.com/kkun/archive/2011/11/23/2260271.html



插入排序 Insertion Sort (稳定)



插入排序(Insertion Sort)的算法描述是一种简单直观的排序算法。它的工作原理是通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。插入排序在实现上,通常采用in-place排序(即只需用到O(1)的额外空间的排序),因而在从后向前扫描过程中,需要反复把已排序元素逐步向后挪位,为最新元素提供插入空间。

步骤:

1. 从第一个元素开始,该元素可以认为已经被排序;
2. 取出下一个元素,在已经排序的元素序列中从后向前扫描,如果该元素(已排序)大于新元素,将该元素移到下一位置;
3. 重复步骤2,直到找到已排序的元素小于或者等于新元素的位置,将新元素插入到该位置中;
4. 依次对剩余的未排序元素进行上述步骤,直到所有元素排序完成。


void InsertSort(int *arr, int n){for(int i=1;i<n;i++){int value=arr[i];int j=i;while(--j>=0&&arr[j]>value)arr[j+1]=arr[j];arr[j+1]=value;}}

时间复杂度:

最好情况 O(n) 已有序的情况

平均情况 O(n*n)

最坏情况 O(n*n) 倒序的情况


空间复杂度:O(1)

 

示例:

Image(6)



希尔排序 Shell Sort (插入排序) (稳定)

视觉直观感受7种常用排序算法

希尔排序,也称递减增量排序算法,是插入排序的一种高速而稳定的改进版本。希尔排序是基于插入排序的以下两点性质而提出改进方法的:

1、插入排序在对几乎已经排好序的数据操作时, 效率高, 即可以达到线性排序效率;

2、插入排序对倒序的元素进行排序时效率极差,希尔排序对此进行了优化。


步骤:
1. 取增量d1,将序列中下标为d1倍数(包括0)的元素看作一个单独的序列,对该子序列进行插入排序。
2. 取增量d2,重复步骤1。(增量的取值规则: 第一次取总长度的一半,第二次取一半的一半,依次累推直到1为止)

4. 在对增量1进行插入排序之后,即完成希尔排序。


void ShellSort(int *arr, int n){for(int dim=n/2;dim>=1;dim/=2){for(int j=dim;j<n;j++){int value=arr[j];int m=j-dim;while(m>=0&&arr[m]>value){arr[m+dim]=arr[m];m=m-dim;}arr[m+dim]=value;}}}


时间复杂度:
最好情况 O(n)
平均情况 O(n^1.5) 
最坏情况 O(n*n)

空间复杂度:O(1)
 
示例:



****************************************************************************************************************************************************************************
以下是未整理的内容,后续会更新 ... ...



计数排序
假设:有n个数的集合,而且n个数的范围都在0~k(k = O(n))之间。
运行时间:Θ(n+k)





待排序数组A如图2.1所示,需要辅助数组B(存储最后排序结果),数组C(存储元素的个数)。基于上述的假设,数组C的大小为k,C[i]表示数组A中元素i(0 <= i < k)的个数(如图2.2所示),为了保证计数排序的稳定性,数组C变化为图2.3,C[i]表示小于或者等于i的个数。代码如下:
   1:/*
   2:    输入:待排序数组A,存储排序后的数组B,数组A的大小,数组C的大小
   3:    功能:计数排序
   4:*/
   5:void CountingSort(int A[], int B[], int len, int k)
   6:{
   7:     int *CountArr = new int[k];
   8:     int i;
   9:     for (i = 0; i < k; i++)
  10:     {
  11:         CountArr[i] = 0;
  12:     }
  13: 
  14:     for (i = 0; i < len; i++)
  15:     {
  16:         CountArr[A[i]]++;                
  17:     }
  18: 
  19:     for (i = 1; i < k; i++)
  20:     {
  21:         CountArr[i] += CountArr[i-1];
  22:     }
  23: 
  24:     // 从右至左保证算法的稳定性
  25:     for (i = len-1; i >=0; i--)
  26:    {
  27:         B[CountArr[A[i]]-1] = A[i];
  28:         CountArr[A[i]]--;
  29:     }
  30:}
9-12行和19-22行的运行时间Θ(k),14-17行和25-29行的运行时间为Θ(n),所以总的运行时间为Θ(2(n+k))= Θ(n+k)。
 
 
一. 基数排序 
基数排序:将所有待比较数值(正整数)统一为同样的数位长度,数位较短的数前面补零。然后,从最低位开始,依次进行一次排序。这样从最低位排序一直到最高位排序完成以后,数列就变成一个有序序列。
基数排序分为两种LSD和MSD。
LSD(Least significant digital):最低有效位优先,即从右向左开始排序。
MSD(Most significant digital):最高有效位优先,即从左往右开始排序。
以下是LSD方式的基数排序的伪代码
   1:RadixSort(A,d)
   2:     fori <- 1 to d
   3:         用稳定的排序算法排列数组A中元素的第i位


如图3:先牌个位,然后十位,最后百位。为数组的某一位排序的时候一定需要稳定的算法。
运行时间为Θ(d(n+k))。在基数排序中排列数组各位的算法是计数排序所以运行时间为Θ(n+k),又d是数组中数的最大位数。
 



参考:
http://blog.jobbole.com/11745/
http://www.cnblogs.com/kkun/archive/2011/11/23/2260312.html
http://www.cnblogs.com/zabery/archive/2011/07/30/2122128.html
http://blog.csdn.net/doc_sgl/article/details/884032
http://www.cnblogs.com/easonliu/archive/2012/10/19/2731358.html
http://baike.baidu.com/view/157305.htm



原创粉丝点击