常见排序算法分析

 

一.常见排序算法的实现

1.冒泡排序

冒泡排序是非常容易理解和实现,,以从小到大排序举例:

设数组长度为N。

1.比较相邻的前后二个数据,如果前面数据大于后面的数据,就将二个数据交换。

2.这样对数组的第0个数据到N-1个数据进行一次遍历后,最大的一个数据就“沉”到数组第N-1个位置。

3.N=N-1,如果N不为0就重复前面二步,否则排序完成。

按照定义很容易写出代码:

 

void bubbleSort(int arr[],int n)
{
    int i,j,t;
     for(i=0;i<n-1;i++)
        for(j=0;j<n-i-1;j++)
            if(arr[j+1]<arr[j])
            {
              t=arr[j+1];
              arr[j+1]=arr[j];
              arr[j]=t;
             }
}

 测试代码:

# include <stdio.h>
 
void bubbleSort(int arr[],int n)
{
    int i,j,t;
     for(i=0;i<n-1;i++)
        for(j=0;j<n-i-1;j++)
            if(arr[j+1]<arr[j])
            {
              t=arr[j+1];
              arr[j+1]=arr[j];
              arr[j]=t;
             }
}
 
void print(int arr[],int n)    //打印数组
{
    int i=0;
    for(;i<n;i++)
    {
        printf("%d  ",arr[i]);
    }
    printf("\n");
}
int main(void)
{
    int arr[]={49,15,52,64,98};    //测试数据
    print(arr,5);
    bubbleSort(arr,5);
        printf("排序后的结果:\n");
    print(arr,5);
    return 0;
}

 

 详细分析:见冒泡排序

 

2.快速排序

一趟快速排序的算法是:

   1)、设置两个变量I、J,排序开始的时候I:=1,J:=N;

   2)以第一个数组元素作为关键数据,赋值给X,即X:=A[1];

   3)、从J开始向前搜索,即由后开始向前搜索(J:=J-1),找到第一个小于X的值,两者交换;

   4)、从I开始向后搜索,即由前开始向后搜索(I:=I+1),找到第一个大于X的值,两者交换;

   5)、重复第3、4步,直到I=J;

   例如:待排序的数组A的值分别是:(初始关键数据X:=49)

                   A[1]     A[2]     A[3]     A[4]     A[5]      A[6]     A[7]:

                     49        38       65       97       76       13        27

进行第一次交换后:   27        38       65       97       76       13        49

                   ( 按照算法的第三步从后面开始找

进行第二次交换后:   27        38       49       97       76       13        65

                  ( 按照算法的第四步从前面开始找>X的值,65>49,两者交换,此时I:=3 )

进行第三次交换后:   27        38       13       97       76       49        65

( 按照算法的第五步将又一次执行算法的第三步从后开始找

进行第四次交换后:   27        38       13       49       76       97        65

( 按照算法的第四步从前面开始找大于X的值,97>49,两者交换,此时J:=4 )

      此时再执行第三不的时候就发现I=J,从而结束一躺快速排序,那么经过一躺快速排序之后的结果是:27        38       13       49       76       97        65,即所以大于49的数全部在49的后面,所以小于49的数全部在49的前面。

 

int quicksort(vector<int> &v, int left, int right){
        if(left < right){
 
                int key = v[left];
                int low = left;
                int high = right;
 
                while(low < high){
                        while(low < high && v[high] > key){
                                high--;
                        }
 
                        v[low] = v[high];
 
                        while(low < high && v[low] < key){
                                low++;
                        }
 
                        v[high] = v[low]
 
                }
 
                v[low] = key;
                quicksort(v,left,low-1);
                quicksort(v,low+1,right);
 
        }
}

  详细分析:见快速排序

 

3.直接插入排序

直接插入排序(straight insertion sort)的作法是:
     每次从无序表中取出第一个元素,把它插入到有序表的合适位置,使有序表仍然有序。

  • 第一趟比较前两个数,然后把第二个数按大小插入到有序表中;
  • 第二趟把第三个数据与前两个数从前向后扫描,把第三个数按大小插入到有序表中;
  • 依次进行下去,进行了(n-1)趟扫描以后就完成了整个排序过程。

代码实现:

void InsertSortArray()
{
    for(int i=1;i<n;i++)//循环从第二个数组元素开始,因为arr[0]作为最初已排序部分
    {
        int temp=arr[i];//temp标记为未排序第一个元素
        int j=i-1;
        while (j>=0 && arr[j]>temp)/*将temp与已排序元素从大到小比较,寻找temp应插入的位置*/
        {
            arr[j+1]=arr[j]; 
            j--;
        }
        arr[j+1]=temp;
    }
} 

详细分析见直接插入排序

 

4.选择排序

思想:首先设置一个变量保存元素下标,将该元素与其他元素进行比较。若大于其他元素则变换其下标,比较完后再判断其下标是否发生变化,若变化则交换两个元素的值,否则不变。经过一次比较,即可得出数列中的最小元素并将其放在数列的首位。一次类推,经过n-1次比较即可得到n个元素从小到大的排列。

int i , temp;
for(i = 0 ; i < 8 ; ++i)
	scanf("%d" , &x[i]);
for(i = 0 ; i < 7 ; ++i)
{
	k = i;
	for(j = i + 1 ; j < 8 ; ++j)
		if(x[i] > x[j])
			k = j;
	if(k != i)
	{
		temp = x[k];
		x[k] = x[i];
		x[i] = temp;
	}
}

 

二.其他算法的分析

1 快速排序(QuickSort)

快速排序是一个就地排序,分而治之,大规模递归的算法。从本质上来说,它是归并排序的就地版本。快速排序可以由下面四步组成。

(1) 如果不多于1个数据,直接返回。
(2) 一般选择序列最左边的值作为支点数据。
(3) 将序列分成2部分,一部分都大于支点数据,另外一部分都小于支点数据。
(4) 对两边利用递归排序数列。

快速排序比大部分排序算法都要快。尽管我们可以在某些特殊的情况下写出比快速排序快的算法,但是就通常情况而言,没有比它更快的了。快速排序是递归的,对于内存非常有限的机器来说,它不是一个好的选择。

2 归并排序(MergeSort)

归并排序先分解要排序的序列,从1分成2,2分成4,依次分解,当分解到只有1个一组的时候,就可以排序这些分组,然后依次合并回原来的序列中,这样就可以排序所有数据。合并排序比堆排序稍微快一点,但是需要比堆排序多一倍的内存空间,因为它需要一个额外的数组。

3 堆排序(HeapSort)

堆排序适合于数据量非常大的场合(百万数据)。

堆排序不需要大量的递归或者多维的暂存数组。这对于数据量非常巨大的序列是合适的。比如超过数百万条记录,因为快速排序,归并排序都使用递归来设计算法,在数据量非常大的时候,可能会发生堆栈溢出错误。

堆排序会将所有的数据建成一个堆,最大的数据在堆顶,然后将堆顶数据和序列的最后一个数据交换。接下来再次重建堆,交换数据,依次下去,就可以排序所有的数据。

4 Shell排序(ShellSort)

Shell排序通过将数据分成不同的组,先对每一组进行排序,然后再对所有的元素进行一次插入排序,以减少数据交换和移动的次数。平均效率是O(nlogn)。其中分组的合理性会对算法产生重要的影响。现在多用D.E.Knuth的分组方法。

Shell排序比冒泡排序快5倍,比插入排序大致快2倍。Shell排序比起QuickSort,MergeSort,HeapSort慢很多。但是它相对比较简单,它适合于数据量在5000以下并且速度并不是特别重要的场合。它对于数据量较小的数列重复排序是非常好的。

5 插入排序(InsertSort)

插入排序通过把序列中的值插入一个已经排序好的序列中,直到该序列的结束。插入排序是对冒泡排序的改进。它比冒泡排序快2倍。一般不用在数据大于1000的场合下使用插入排序,或者重复排序超过200数据项的序列。

6 冒泡排序(BubbleSort)

冒泡排序是最慢的排序算法。在实际运用中它是效率最低的算法。它通过一趟又一趟地比较数组中的每一个元素,使较大的数据下沉,较小的数据上升。它是O(n^2)的算法。

7 交换排序(ExchangeSort)和选择排序(SelectSort)

这两种排序方法都是交换方法的排序算法,效率都是 O(n2)。在实际应用中处于和冒泡排序基本相同的地位。它们只是排序算法发展的初级阶段,在实际中使用较少。

8 基数排序(RadixSort)

基数排序和通常的排序算法并不走同样的路线。它是一种比较新颖的算法,但是它只能用于 整数的排序,如果我们要把同样的办法运用到浮点数上,我们必须了解浮点数的存储格式,并通过特殊的方式将浮点数映射到整数上,然后再映射回去,这是非常麻 烦的事情,因此,它的使用同样也不多。而且,最重要的是,这样算法也需要较多的存储空间。

9 总结

下面是一个总的表格,大致总结了我们常见的所有的排序算法的特点。

 

名称复杂度说明备注
冒泡排序 BubbleSortO(N*N)

将待排序的元素看作是竖着排列的 气泡 ,较小的元素比较轻,从而要往上浮


插入排序 InsertionSort O(N*N) 逐一取出元素,在已经排序的元素序列中从后向前扫描,放到适当的位置 起初,已经排序的元素序列为空
选择排序 SelcetionSort O(N*N) 首先在未排序序列中找到最小元素,存放到排序序列的起始位置,然后,再从剩余未排序元素中继续寻找最小元素,然后放到排序序列末尾。以此递归。
快速排序 QuickSort O(n*log2 (n)) 先选择中间值,然后把比它小的放在左边,大的放在右边(具体的实现是从两边找,找到一对后交换)。然后对两边分别使用这个过程(递归)。
堆排序 HeapSort O(n*log2 (n))

利用堆( heaps )这种数据结构来构造的一种排序算法。堆是一个近似完全二叉树结构,并同时满足堆属性:即子节点的键值或索引总是小于(或者大于)它的父节点。

近似完全二叉树
希尔排序 ShellSort

O(n1+ 0< <1

选择一个步长 (Step) , 然后按间隔为步长的单元进行排序 . 递归 , 步长逐渐变小 , 直至为 1.


箱排序 BinSort O(n)

设置若干个箱子,把关键字等于   k   的记录全都装入到第   k   个箱子里   (   分配   )   ,然后按序号依次将各非空的箱子首尾连接起来   (   收集   )  

分配排序的一种:通过     分配         收集     过程来实现排序。

桶排序 BucketSort O(n)

桶排序的思想是把   [0     1)   划分为   n   个大小相同的子区间,每一子区间是一个桶。

分配排序的一种:通过     分配         收集     过程来实现排序。

 

排序法平均时间最差情形稳定度额外空间备注
冒泡O(n2)    O(n2)稳定O(1)n小时较好
交换    O(n2)    O(n2)不稳定O(1)n小时较好
选择O(n2)O(n2)不稳定O(1)n小时较好
插入O(n2)O(n2)稳定O(1)大部分已排序时较好
基数O(logRB)O(logRB)稳定O(n)

B是真数(0-9),

R是基数(个十百)

ShellO(nlogn)O(ns) 1<s<2不稳定O(1)s是所选分组
快速O(nlogn)O(n2)不稳定O(nlogn)n大时较好
归并O(nlogn)O(nlogn)稳定O(1)n大时较好
O(nlogn)O(nlogn)不稳定O(1)n大时较好
点赞