zoukankan      html  css  js  c++  java
  • 那些年我们一起学过的“排序算法”

    排序算法是经常使用的算法,在STL中也有一个比较牛X的快速排序(sort),但是我们不能只会调用sort呀!?作为一个好学的同学,我们要知道各种排序的内部是怎么实现滴~~~提到排序算法我们要知道两个经常提到的概念:

    (1)排序算法的稳定性:所谓“稳定性”是指,在待排序数组出现的两个相同的元素,排序之后相对维持保持不变。比如:待排序数组为arr[] = {1,4,3,1},排序之后元素变为arr_new[] = {1,1,4,3},并且arr_new中的第一个是arr中的第一个1,arr_new中的第二个1是arr中的第二个1,这是我们就说这种排序时稳定的。

    (2)原地排序:所谓原地排序是指,不申请多余的空间来辅助完成排序算法,而是在原来的待排序的数据之上直接进行比较,交换,移动等操作。

    1.插入排序

    算法原理:将待排序的数组分为:有序区 和 无序区。然后每次从无序区取出第一个数据插入到有序区的正确位置,最终完成排序。

    算法代码:

    View Code
     1 #include <iostream>
     2 
     3 using namespace std;
     4 
     5 void insert_sort(int *arr,int n)
     6 {
     7     int i,j;
     8     for(i = 1 ; i < n ; ++i)
     9     {
    10         int tmp = arr[i];
    11         j = i - 1;
    12         while( j >= 0 && arr[j] > tmp)
    13         {
    14             arr[j+1] = arr[j];
    15             j--;
    16         }
    17         arr[j+1]  = tmp;
    18     }
    19 }
    20 
    21 int main()
    22 {
    23     int arr[] = {2,4,1,3,5,8,7,6,8};
    24     insert_sort(arr,9);
    25     for(int i = 0  ; i < 9 ; ++i)
    26     {
    27         cout<<arr[i]<<" ";
    28     }
    29     cout<<endl;
    30     return 0;
    31 }

    小结:看代码可以知道这种排序算法的时间复杂度是O(n^2),并且插入排序时稳定的,属于原地排序。那么什么时候使用插入排序比较好呢?那就是当数组中的大部分数据已经有序时,使用插入排序算法的效率比较高,这种情况下,所需要进行的数据移动较少,而数据移动正式插入排序算法的主要步骤~~~~

    2.冒泡排序

    算法原理:冒泡排序是经过n-1趟子排序完成的,第 i 趟子排序从第1个数至第 n-i+1 个数,若第 i 个数比第 i+1 个数大,则交换这两个数,实际上这样经过 i 次子排序就使得 第1个数至第 n-i +1个数之间最大的数交换到了n-i+1 的位置上了。实际上冒泡排序时可以优化的,那就是当第 i 次子排序并没有发生元素的交换时,就说明数组已经排好序了,以后的子排序就不用做了。

    算法代码:

    View Code
     1 #include <iostream>
     2 
     3 using namespace std;
     4 
     5 void swap(int &x,int &y)
     6 {
     7     x = x^y;
     8     y = x^y;
     9     x = x^y;
    10 }
    11 
    12 void bubble_sort(int *arr,int n)
    13 {
    14     int i,j;
    15     for(i = n - 1 ; i > 0 ; --i)
    16     {
    17         bool flag = true;
    18         for(j = 0 ; j < i ; ++j)
    19         {
    20             if(arr[j] > arr[j+1])
    21             {
    22                 flag = false;
    23                 swap(arr[j],arr[j+1]);
    24             }
    25         }
    26         if(flag) //数组已经排好序没必要在继续进行其他子排序了
    27             break;
    28     }
    29 }
    30 
    31 int main()
    32 {
    33     int arr[] = {2,1,4,3,8,7,5,6};
    34     bubble_sort(arr,8);
    35     for(int i = 0 ; i < 8 ; ++i)
    36     {
    37         cout<<arr[i]<<" ";
    38     }
    39     cout<<endl;
    40     return 0;
    41 }

    小结:冒泡排序算法的时间复杂度是O(n^2),同时冒泡排序也是稳定的,并且属于原地排序,排序的效率取决于逆序对的多少。采用一点小优化也加速了冒泡排序。

    3.选择排序

    算法原理:所谓选择排序经过 n-1 次选择,当进行第 i 次选择时,是从第1个元素到第 n-i+1 的元素中选择最大的元素和第 n-i+1 个位置的元素交换,这样做比如第1 次选择使得最大的元素到了数组的最后一个位置。注意哦,在选择排序中每次选择时只进行一次数据的交换。

    算法代码:

    View Code
     1 #include <iostream>
     2 
     3 using namespace std;
     4 
     5 void swap(int &x,int &y)
     6 {
     7     int tmp = x;
     8     x = y;
     9     y = tmp;
    10 }
    11 
    12 void select_sort(int *arr,int n)
    13 {
    14     int i,j;
    15     for(i = n-1 ; i > 0 ; --i)
    16     {
    17         int tmp = 0;
    18         for(j = 1 ; j <= i ; ++j)
    19         {
    20             if(arr[j] >= arr[tmp])//这里的“=”是保证选择排序稳定的关键
    21             {
    22                 tmp = j;
    23             }
    24         }
    25         swap(arr[i],arr[tmp]);
    26     }
    27 }
    28 int main()
    29 {
    30     int arr[] = {2,1,4,3,8,7,5,6};
    31     select_sort(arr,8);
    32     for(int i = 0 ; i < 8 ; ++i)
    33     {
    34         cout<<arr[i]<<" ";
    35     }
    36     cout<<endl;
    37     return 0;
    38 }

    小结:选择排序的思路非常的简单,实现起来也不难。时间复杂度是O(n^2),选择排序也是稳定的排序,并且也是原地排序。选择排序的时间基本不受数据的影响,因为不管怎样都要进行n-1次选择排序。

    4.归并排序

    算法原理:归并排序的思想是分治,将一个带排序的数组分成两个较小的数组,然后分别进行排序,组后将两个排好序的较小的数组合并起来,就得到了原来数组的排序后的结果。应该注意的是这种将两个排好序的数组合并有一个较好的算法,时间复杂度是O(n1+n2)的。n1、n2分别是两个小数组的长度。

    算法代码:

    View Code
     1 #include <iostream>
     2 
     3 using namespace std;
     4 
     5 void merge_sort(int *arr,int start,int end,int *temp)
     6 {
     7     if(end > start+1)
     8     {
     9         int mid = start + (end - start) / 2;
    10         merge_sort(arr,start,mid,temp);
    11         merge_sort(arr,mid,end,temp);
    12         int i = start , j = mid , k = start;
    13         while(i < mid || j < end)
    14         {
    15             if(j >= end || (i < mid && arr[i] <= arr[j]))
    16             {
    17                 temp[k++] = arr[i++];
    18             }
    19             else
    20             {
    21                 temp[k++] = arr[j++];
    22             }
    23         }
    24         for(i = start ; i < end ; ++i)
    25         {
    26             arr[i] = temp[i];
    27         }
    28     }
    29 }
    30 
    31 
    32 int main()
    33 {
    34     int temp[8];
    35     int arr[]  = {2,1,4,3,8,7,5,6};
    36     merge_sort(arr,0,8,temp);
    37     for(int i = 0 ; i < 8 ; ++i)
    38     {
    39         cout<<arr[i]<<" ";
    40     }
    41     cout<<endl;
    42     return 0;
    43 }

    小结:归并排序时稳定的排序,但是不属于原地排序,因为用了额外的O(n)的空间,时间复杂度降到了O(n*log n),并且对任意的数组进行排序时间复杂度都能控制在O(n*logn)。

    5.堆排序

    算法原理:所谓的堆排序是利用完全二叉树的思想实现的。首先应该提到的是最大堆,在最大堆中(完全二叉树二叉树)中每个父节点都大于等于两个儿子节点的值,这时候很明显堆顶是元素的最大值,然后把堆顶元素和堆中最后一个元素(分层遍历的节点编号最大的元素)交换,这样最大值就落到了数组的arr[n-1]的位置,然后把前n-1元素继续按照上面的方式处理,如此进行n-1次就完成堆排序。

    算法代码:

    View Code
     1 #include <iostream>
     2 
     3 using namespace std;
     4 
     5 void swap(int &x,int &y)
     6 {
     7     x =  x + y;
     8     y =  x - y;
     9     x =  x - y;
    10 }
    11 
    12 void restore(int *arr,int s,int e)
    13 {
    14     int i = s , m;
    15     while(i <= e/2)
    16     {
    17         if(2*i+1 <= e && arr[2*i] > arr[2*i-1])
    18         {
    19             m = 2 * i + 1;
    20         }
    21         else
    22         {
    23             m = 2 * i;
    24         }
    25         if(arr[i-1] < arr[m-1])
    26         {
    27             swap(arr[i-1],arr[m-1]);
    28             i = m;
    29         }
    30         else
    31         {
    32             i = e;
    33         }
    34     }
    35 }
    36 void heap_sort(int *arr,int n)
    37 {
    38     int i;
    39     for(i = n / 2 ; i > 0 ; --i)
    40     {    
    41         restore(arr,i,n);
    42     }
    43     for(i = n ; i > 1 ; --i)
    44     {
    45         swap(arr[0],arr[i-1]);
    46         restore(arr,1,i-1);
    47     }
    48 }
    49 
    50 int main()
    51 {
    52     int arr[] = {2,1,4,3,8,7,5,6};
    53     heap_sort(arr,8);
    54     for(int i = 0 ; i < 8 ; ++i)
    55     {
    56         cout<<arr[i]<<" ";
    57     }
    58     cout<<endl;
    59     return 0;
    60 }

    小结:堆排序是不稳定的排序,但是堆排序属于原地排序。时间复杂度是O(n*log n),并且不需要额外的辅助空间,也就是说堆排序是一种不错的排序算法哦~~~

    6.快速排序

    算法原理:快速排序时这样的一种排序,选取数组中的第一个元素arr[0]作为依据,遍历一遍数组后,使得数组中的第一个元素进入正确的位置,即在该位置左面的元素均小于等于arr[0],在该位置右面的元素均大于等于arr[0]。然后,在对该位置左面和右面的元素分别进行快速排序,如此一来完成整个数组的排序。

    算法代码:

    View Code
     1 #include <iostream>
     2 
     3 using namespace std;
     4 
     5 void swap(int &x,int &y)
     6 {
     7     x = x + y;
     8     y = x - y;
     9     x = x - y;
    10 }
    11 
    12 void quick_sort(int *arr,int s,int e)
    13 {
    14     if(s+1 < e)
    15     {
    16         int tmp = arr[s];
    17         int i = s+1;
    18         int j = e-1;
    19         while(i < j)
    20         {
    21             while(i <= j && arr[i] <= tmp)
    22             {
    23                 i++;
    24             }
    25             while(i <= j && arr[j] >= tmp)
    26             {
    27                 j--;
    28             }
    29             if(i < j)
    30             {
    31                 swap(arr[i],arr[j]);
    32             }
    33         }
    34         swap(arr[s],arr[i-1]);
    35         quick_sort(arr,s,i-1);
    36         quick_sort(arr,i,e);
    37     }
    38 }
    39 
    40 int main()
    41 {
    42     int arr[] = {2,1,4,3,8,7,5,6};
    43     quick_sort(arr,0,8);
    44     for(int i = 0 ; i < 8 ; ++i)
    45     {
    46         cout<<arr[i]<<" ";
    47     }
    48     cout<<endl;
    49     return 0;
    50 }

    小结:首先还是说明快速排序时不稳定的,但是是原地排序,不需要额外的空间,时间复杂度是O(nlog n),实际上,这种把第一个元素作为依据元素只是快速排序的一种,STL中的sort内部实现是根据排序到了不同的阶段选用不同的排序算法。当数据量大是采用quick_sort排序,当分段递归到了数据量小于某个数值时,为避免quick_sort的递归调用带来的额外开销,就改用insert_sort 了;如果递归层次过深,还会考虑使用heap_sort 。


    学习中的一点总结,欢迎拍砖哦^^


  • 相关阅读:
    解构赋值用在函数参数默认值中的一些情况
    obaa源码加注
    omi-mp-create源码加注
    小程序表情选择器
    小程序QQ版表情解析组件
    用TamperMonkey去掉cdsn中的广告
    小程序自定义tabbar
    Python基础入门——安装与运行
    LaTeX安装与入门
    Java 运行环境安装
  • 原文地址:https://www.cnblogs.com/BeyondAnyTime/p/2638070.html
Copyright © 2011-2022 走看看