zoukankan      html  css  js  c++  java
  • [原创] 基础中的基础(一):简单排序算法总结(附代码)

      又到了一年毕业生找工作的时候,回想起自己找工作的经历,那种紧张、期盼、失望和纠结的情景仍然历历在目。在这里跟大家分享一些作者在找工作时总结的基础知识,希望能够大家带来一点点帮助^_^。作者菜鸟一枚,即使总结这些简单的基础知识,也难免出错,还请各位不吝赐教!

      本文主要总结了一些很基础的排序算法。通过一张表格,将这些算法的部分要点总结在一起,以方便查找和对比。本文最后给出了一些排序算法的C语言实现供大家参考。本文并不对各个算法进行详细介绍,有需要的读者请自行查找(维基百科对每种算法讲的很详细,见本文参考资料)。

      本文中的代码在:https://github.com/icemoon1987/sort_algorithm

    一、简单排序算法总结

      表中简述部分的用语非常随意,很不专业。原因在于我并不赞成机械地背诵算法步骤,而是要理解算法处理方式和逻辑。此处的文字是在笔者理解算法的基础上,对于其中处理要点的口语化叙述,请大家参考。

      表中注意事项部分,是根据我在实现算法的过程中出现的错误总结的,同样非常个人化,很可能不全面(或是多余),仅供参考。

    序号

    名称

    简述

    复杂度

    特性

    注意事项

    改进

    1

    冒泡排序

    从数组最后开始,两两比较元素(n-1)

    如果顺序不对,则交换元素位置

    一轮过后,冒一个泡到当前首元素位置

    减小数组长度,进入下一轮(n)

    最好:O(n2)

    平均:O(n2)

    最差:O(n2)

    空间:O(1)

    稳定

    就地排序

    比较排序

    冒泡方向与比较方向相配合

    冒泡停止条件

    如果前一轮没有交换过数据,则立刻停止

    局部冒泡排序

    鸡尾酒排序(双向冒泡)

    奇偶排序(a[j],a[j+1]一轮j为奇数,一轮为偶数,在多处理器条件下,实现很快)

    梳排序(定义gap,每次减1)

    2

    选择排序

    每轮找到最小值(n-1)

    与本轮首元素交换

    减小数组长度进入下一轮(n)

    最好:O(n2)

    平均:O(n2)

    最差:O(n2)

    空间:O(1)

    不一定稳定

    就地排序

    比较排序

    每轮的首元素位置

    堆排序

    3

    插入排序

    从后边拿出一个元素

    与前边各个元素比较,如果不符合要求,则依次向后移动前边的元素(n)

    符合要求后,放下这个元素

    加长数组长度,进入下一轮(n)

    最好:O(n)

    平均:O(n2)

    最差:O(n2)

    空间:O(1)

    稳定

    就地排序

    比较排序

    插入元素的位置

    插入元素下标越界

    二分查找排序,通过二分查找,更快找到插入位置

    希尔排序,每次比较前进更多

    4

    希尔排序

    规定增量,下标为增量倍数的元素为一组

    各组进行插入排序

    选择更小的增量,进入下一轮,直到为1

    最好:O(n)

    平均:与增量有关

    最差:O(nlogn2)

    空间:O(1)

    不稳定

    就地排序

    比较排序

    下标加入增量后的越界

    插入元素位置

    增量序列选择

    1、4、10、23……

    5

    合并排序

    将当前数组分为左右两部分

    递归,分别排序左右两部分

    合并左右两部分,比较两堆中最上边的元素,哪个小就放入合并序列

    最好:Θ(n)

    平均:Θ(nlogn)

    最差:Θ(nlogn)

    空间:Θ(n)

    稳定

    非就地排序

    比较排序

    分治递归

    动态开辟空间、释放空间

    递归终止条件(p<=r)

    哨兵元素

    函数输入参数能否取到

    中间元素加入分组

    6

    堆排序

    将数组整理成堆

    交换根节点与最末尾元素交换,堆长减1

    在根节点重新生成堆

    重复,直到堆长为1

    最好:O(nlogn)

    平均:O(nlogn)

    最差:Θ(nlogn)

    空间:O(1)

    不稳定

    就地排序

    比较排序

    左右孩子下标

    下标是否超过堆长检查

    建树起始下标

    Max_heapify函数递归

    7

    快速排序

    选择基准元(最后元素)

    小于基准元的元素交换到前方

    将基准元与小于其的元素的后一个交换,实现大于基准元的元素都在后方

    分为两部分,递归调用

    最好:Θ(n2)

    平均:Θ(nlogn)

    最差:Θ(nlogn)

    空间:O(1)

    不稳定

    就地排序

    比较排序

    分治

    基准元本身不加入本组

    函数输入参数能否取到

    小于基准源元素起始下标

    基准元交换元素下标

    随机快速排序:随机选择基准元

    三数取中,选取基准元

    尾递归:用控制结构进行递归,而不是直接调用两次函数

    8

    计数排序

    假设元素都小于等于k(或找到最大值)

    开辟数组,记录各个元素出现的次数

    从最前边,两两相加,计算小于等于元素的个数

    从后向前遍历输入数组(保证稳定性),通过以上信息,直接将元素放入输出数组

    更新小于等于元素的个数

    最好:O(n+k)

    平均:O(n+k)

    最差:O(n+k)

    空间:O(n+k)

    稳定

    非就地排序

    非比较排序

    线性时间

    小于等于元素个数为1的数,放在第一个,下标为0

    动态开辟数组大小k+1

    下标越界

    对输入有要求

    9

    基数排序

    一位一位的进行排序(k)

    从低位向高位

    子排序算法需要稳定(例如计数排序)

    最好:O(kn)

    平均:O(kn)

    最差:O(kn)

    空间:O(kn)

    稳定

    非就地排序

    非比较排序

    线性时间

    分割数字算法

    10

    桶排序

    输入为[0,1]上的均匀分布

    将[0,1]分为n个桶,将输入序列分到各个桶中

    对每个桶进行插入排序

    合并各个桶的结果

    最好:

    平均:O(n+k)

    最差:O(n2)

    空间:O(n*K)

    稳定

    非就地排序

    非比较排序

    线性时间

    二、算法实现(C语言)

      1. 冒泡排序 

     1 int bubble_sort(int *input, const int len)
     2 {
     3     int i,j;
     4     int tem;
     5 
     6     if(NULL == input || len < 0)
     7     {
     8         return -1;
     9     }
    10 
    11     for(i=0 ; i<len ; ++i)
    12     {
    13         for(j = len-1 ; j>i ; --j)
    14         {
    15             if(input[j]<input[j-1])
    16             {
    17                 tem = input[j];
    18                 input[j] = input[j-1];
    19                 input[j-1] = tem;
    20             }
    21         }
    22     }
    23     return 0;
    24 }

      2. 插入排序 

     1 int insert_sort::sort(int *input, const int size)
     2 {
     3     int i;
     4 
     5     if(NULL == input || size < 0)
     6     {
     7         return -1;
     8     }
     9 
    10     for(i = 1 ; i < size ; ++i)
    11     {
    12         int key = input[i];
    13         int j = i-1;
    14         while(j>=0 && input[j]>key)
    15         {
    16             input[j+1] = input[j];
    17             --j;
    18         }
    19         input[j+1]=key;
    20     }
    21 
    22     return 0;
    23 }

       3. 合并排序

     1 // 合并排序:主函数
     2 void merge_sort(int *a, const int p, const int r)
     3 {
     4     if(p<r)
     5     {
     6         int q = (p+r)/2;
     7         sort(a,p,q);
     8         sort(a,q+1,r);
     9         merge_pp(a,p,q,r);
    10     }
    11 
    12     return ;
    13 }
    14 
    15 // 合并排序:合并函数
    16 void merge_pp(int *a, const int p, const int q, const int r)
    17 {
    18     vector<int> left;
    19     vector<int> right;
    20     int i;
    21 
    22     for(i = p ; i <= q ; ++i)
    23     {
    24         left.push_back(a[i]);
    25     }
    26 
    27     for(i = q+1 ; i<=r ; ++i)
    28     {
    29         right.push_back(a[i]);
    30     }
    31 
    32     i = p;
    33 
    34     while( (!left.empty()) && (!right.empty()) )
    35     {
    36         if(left.front() < right.front())
    37         {
    38             a[i++] = left.front();
    39             left.erase(left.begin());
    40         }
    41         else
    42         {
    43             a[i++] = right.front();
    44             right.erase(right.begin());
    45         }
    46     }
    47 
    48     while(!right.empty())
    49     {
    50         a[i++] = right.front();
    51         right.erase(right.begin());
    52     }
    53 
    54     while(!left.empty())
    55     {
    56         a[i++] = left.front();
    57         left.erase(left.begin());
    58     }
    59 
    60     return;
    61 }

      4. 快速排序

     1 // 快速排序:主函数
     2 void sort(int *input, const int p, const int r)
     3 {
     4     if(p<r)
     5     {
     6         // 将输入数组分割成两部分
     7         int q = partition(input, p, r);
     8 
     9         // 递归调用,分别排序两部分
    10         sort(input, p, q-1);
    11         sort(input, q+1, r);
    12     }
    13 
    14     return;
    15 }
    16 
    17 // 快速排序:分割函数
    18 int partition(int *input, const int p, const int r)
    19 {
    20     int key = input[r];
    21     int i = p-1;
    22     int j = p;
    23     int tem = 0;
    24 
    25     for(; j < r ; ++j)
    26     {
    27         if( input[j] < key )
    28         {
    29             ++i;
    30             tem = input[i];
    31             input[i] = input[j];
    32             input[j] = tem;
    33         }
    34     }
    35 
    36     tem = input[i+1];
    37     input[i+1] = key;
    38     input[r] = tem;
    39 
    40     return i+1;
    41 }

       5. 堆排序

     1 // 最大堆排序:获取左孩子函数
     2 int left(const int i)
     3 {
     4     return (2*i+1);
     5 }
     6 
     7 // 最大堆排序:获取右孩子函数
     8 int right(const int i)
     9 {
    10     return (2*i+2);
    11 }
    12 
    13 // 最大堆排序:在某一节点重新生成最大堆函数
    14 void max_heapify(int *input, const int i, const int heap_len)
    15 {
    16     int l = left(i);
    17     int r = right(i);
    18     int largest = i;
    19     int tem = 0;
    20 
    21     if( l < heap_len && input[l]>input[largest] )
    22     {
    23         largest = l;
    24     }
    25 
    26     if( r < heap_len && input[r]>input[largest] )
    27     {
    28         largest = r;
    29     }
    30 
    31     if( largest != i )
    32     {
    33         tem = input[largest];
    34         input[largest] = input[i];
    35         input[i] = tem;
    36         max_heapify(input,largest,heap_len);
    37     }
    38 
    39     return;
    40 }
    41 
    42 // 最大堆排序:构建最大堆函数
    43 void build_max_heap(int *input, const int len)
    44 {
    45     int i = len/2-1;
    46 
    47     for(; i >= 0 ; --i)
    48     {
    49         max_heapify(input, i , len);
    50     }
    51 
    52     return;
    53 }
    54 
    55 // 最大堆排序:主函数
    56 void maxheap_sort(int *input, const int len)
    57 {
    58     int tem = 0;
    59     int heap_len = len;
    60         
    61     // 将输入数组生成最大堆
    62     build_max_heap(input,len);
    63 
    64     while( heap_len > 0)
    65     {
    66         // 交换根节点与最末尾节点
    67         tem = input[0];
    68         input[0] = input[heap_len-1];
    69         input[heap_len-1] = tem;
    70 
    71         // 在根节点重新生成最大堆
    72         --heap_len;
    73         max_heapify(input, 0, heap_len);
    74     }
    75 
    76     return;
    77 }

      6. 计数排序

     1 // 计数排序:主函数
     2 void count_sort(int *input, int *output, const int len)
     3 {
     4     int max = find_max(input, len);
     5     int i;
     6 
     7     int *tem = (int *)malloc(sizeof(int) * (max + 1));
     8     for(i = 0; i < max+1 ; ++i)
     9         tem[i] = 0;
    10 
    11 // 记录各个元素出现次数
    12     for(i = 0 ; i < len ; ++i)
    13         tem[input[i]]++;
    14 
    15 // 对记录数据进行修改:从最前边,两两相加,计算小于等于某个元素的元素个数
    16     for(i = 1 ; i < max+1 ; ++i)
    17         tem[i] += tem[i-1];
    18 
    19 // 将元素直接放入输出数组
    20     for(i = 0 ; i < len ; ++i)
    21     {
    22         output[ tem[input[i]] - 1 ] = input[i];
    23         tem[input[i]]--;
    24     }
    25     return;
    26 }
    27 
    28 // 计数排序:寻找最大值函数
    29 int find_max(int *input, const int len)
    30 {
    31     int max = 0;
    32     int i;
    33 
    34     for(i = 0 ; i < len ; ++i)
    35         if( input[i]>max )
    36             max = input[i];
    37 
    38     return max;
    39 }

      好了,本文的主要内容就这些了,最后再跟大家分享几个学习算法的个人心得:

        1、学习算法不光是学习知识,更是学习一种思维方式。在积累了一定算法基础之后,相信您对新问题、新算法都能够有自己的感觉和思路。

        2、不要背诵算法步骤,要理解算法步骤后的方法和逻辑。单纯地背诵算法没有用处,一定要理解算法解决问题的方法。如果理解了方法,算法步骤不过是用一种有逻辑的方式将这种方法表达出来而已。理解算法对记忆算法也有极大帮助。

        3、算法学习和其他计算机知识学习一样,一定要注重实践。在实现的过程中,我们将发现很多算法中的具体问题,对算法的细节和思想都有可能有新的发现。

      

    本文参考资料:

        http://en.wikipedia.org/wiki/Sorting_algorithm  维基百科上的那些算法演示 gif 挺有趣的:)

        《算法导论》(第二版) 

  • 相关阅读:
    eclipse如何把多个项目放在一个文件夹下【eclipse中对项目进行分类管理】-图示详解
    hibernate中cache二级缓存问题
    Java处理正则验证手机号-详解
    Java处理手机号中间4位替换成*
    WPF:依赖属性的应用
    WPF:自定义路由事件的实现
    WPF:类型转换器的实现
    MFC中CString转化为char*
    Android4.4 往短信收件箱中插入自定义短信(伪造短信)
    OpenCV imread读取图片,imshow展示图片,出现cv:Exception at memory location异常
  • 原文地址:https://www.cnblogs.com/icemoon1987/p/3307608.html
Copyright © 2011-2022 走看看