排序其实很简单--常见排序算法的理解与实现

    最近对几个常见排序算法进行复习和总结,发现认真思考一下然后代码练习一下,这些排序基本上不会有太大难度。做任何事都是一样,难在你把它想得太难了,逼下自己,动脑思考一下,事情就会变得很简单。

    下面是我个人对常见几个排序算法的理解和代码实现,如有什么错误还请指出。

1. 最简单的排序 -- 冒泡排序

    原理就是:从第一个数开始,和后面的数两两进行比较;若前面的数比后面的数大,则将其后移。一轮下来,最后一个数一定是最大的。所以现在可以抛弃最后一个数,从第一个数开始,到倒数第二个数,重复以上步骤。依此类推,最后只剩下两个数比较交换完后,排序结束。

 

     图1.冒泡排序(动态图来自维基百科,下同)

    前面几个排序算法很容易理解和实现,故不详述。代码实现:

 1 #include <stdio.h>
 2 #include <stdlib.h>
 3 
 4 void BubbleSort(int *a, int len)
 5 {
 6     int i, j, temp;
 7     for (i = len - 1; i >= 0; i--)
 8     {
 9         for (j = 0; j < i; j++)
10         {
11             if (a[j] > a[j + 1])
12             {
13                 temp = a[j];
14                 a[j] = a[j + 1];
15                 a[j + 1] = temp;
16             }
17         }
18     }
19 }
20 
21 int main(int argc, char *argv[]) 
22 {
23     int a[10] = {12, 3, 16, 58, 18, 24, 6, 5, 64, 1};
24     BubbleSort(a, 10);
25     for (int i = 0; i < 10; i++)
26     {
27         printf("%d ", a[i]);
28     }
29     
30     return 0;
31 }

    算法时间复杂度:O(n²)。在排序算法中属最慢,不过好在算法容易理解、代码容易实现。

2. 从剩下的当中选最大的 -- 选择排序

    原理:遍历一遍数组找到最小的数放在首位,再遍历数组的剩余部分,找到最小的数放在第二位,依此类推。执行到最后一个数完成数组的排序。

                       图2. 选择排序动态图

    代码实现:

 

 1 #include <stdio.h>
 2 #include <stdlib.h>
 3 
 4 void SelectionSort(int *a, int len)
 5 {
 6     int i, j, min, temp;
 7     for (i = 0; i < len; i++)
 8     {
 9         min = i;
10         for (j = i + 1; j < len; j++)
11         {
12             if (a[j] < a[min])
13             {
14                 min = j;
15             }
16         }
17         temp = a[i];
18         a[i] = a[min];
19         a[min] = temp;
20     }
21 }
22 
23 int main(int argc, char *argv[]) 
24 {
25     int a[10] = {12, 3, 16, 58, 18, 24, 6, 5, 64, 1};
26     SelectionSort(a, 10);
27     for (int i = 0; i < 10; i++)
28     {
29         printf("%d ", a[i]);
30     }
31     return 0;
32 }

 

 

 

    时间复杂度同样为O(n²),不过速度比冒泡快。拿100000个倒序的数在我的电脑试了下,冒泡用了21秒左右,选择用了12秒左右。

3. 在输入的时候排序 -- 插入排序

    原理:从第二个数开始,逐个和前面的数进行比较,直到碰到比自己小的数或数组头部,则插入到其后面。再从下一个数开始,依此类推,直至最后一个数。这是对已有数组排序的情况,而在用户输入数组的情况下,可在输入过程中拿刚输入的数与已输入的数,如碰到比自己小的在插入到其后。

             图3. 插入排序动态图

    如:[6 5 1 4],5比6小,6后移,5插到6前面,得5614;1比5小,5和6后移,1插到5前面,得[1 5 6 4];4比5小,5和6后移,4插到1后面,[1 4 5 6]排序完成。

    代码实现:

 1 #include <stdio.h>
 2 #include <stdlib.h>
 3 
 4 void InsertionSort(int *a, int len)
 5 {
 6     int i, j, temp;
 7     for (int i = 1; i < len; i++)
 8     {
 9         temp = a[i];
10         for (j = i - 1; j >= 0 && temp < a[j]; j--)
11         {
12             a[j + 1] = a[j];
13         }
14         a[j + 1] = temp;
15     }
16 }
17 
18 int main(int argc, char *argv[]) 
19 {
20     int a[10] = {12, 3, 16, 58, 18, 24, 6, 5, 64, 1};
21     InsertionSort(a, 10);
22     for (int i = 0; i < 10; i++)
23     {
24         printf("%d ", a[i]);
25     }
26         
27     return 0;
28 }

 

    时间复杂度同样为O(n²)。同样拿100000个倒序的数在我的电脑试了下,插入排序用了15秒左右,似乎最坏情况插入排序要比选择排序慢。没用使用大量数据测试,所以不知道平均情况下上面三种排序谁快。

 4. 改良插入排序 -- 希尔排序

    原理:对插入排序的改良算法,通过设定步长(一般为n/2),将数组分成若干列,对每一列进行排序。再缩小步长,重复以上步骤,直至步长为1排序完成。 

                     图4. 希尔排序动态图

    我们假设有数组[ 13 14 94 33 82 25 59 94 65 23 45 27 73 25 39 10 ],现在以步长5排序,先将它们分列:

  13 14 94 33 82

  25 59 94 65 23

  45 27 73 25 39

  10

    然后各自排序:

10 14 73 25 23

13 27 94 33 39

25 59 94 65 82

45

    再变换步长3,完成排序:

10 14 13

25 23 33

27 25 59

39 65 73

45 94 82

94

   最后变换步长1,执行一次插入排序即完成排序。

   代码实现:

 1 #include <stdio.h>
 2 #include <stdlib.h>
 3 
 4 void ShellSort(int *a, int len)
 5 {
 6     int i, j, temp, step = len / 2;
 7     while (step >= 1)
 8     {
 9         step /= 2;
10         for (i = 1; i < len; i++)
11         {
12             temp = a[i];
13             j = i - step;
14             while (j >= 0 && temp < a[j])
15             {
16                 a[j + step] = a[j];
17                 j -= step;
18             }
19             a[j + step] = temp;
20         }
21     }
22 }
23 
24 int main(int argc, char *argv[]) 
25 {
26     int a[10] = {12, 3, 16, 58, 18, 24, 6, 5, 64, 1};
27     ShellSort(a, 10);
28     for (int i = 0; i < 10; i++)
29     {
30         printf("%d ", a[i]);
31     }
32     
33     return 0;
34 }

 

    时间复杂度为O(nlog²n),相比上面的算法速度有显著提升。

 5. 分成两半来排序 -- 归并排序

    原理: 使用二分法将数组分成若干子数组,再在合并的过程中进行排序。因为每次递归都要创建新数组,所以内存空间占用大。 

 

                    图5. 归并排序动态图

    说白了就是将数组对半分,排序完后合并起来。例如数组[13 14 94 33 82 25 59 94]可分为[13 14 94 33]和[82 25 59 94],前者又可分为[13 14]和[94 33],排完序后变成[13 14]和[33 94],按顺序合并成[13 14 33 94],其它部分执行相同步骤。

    代码实现:

 1 #include <stdio.h>
 2 #include <stdlib.h>
 3 
 4 void MergeSort(int *a, int len)
 5 {
 6     int i, j, k;
 7     int len1 = len / 2;
 8     int len2 = len - len1;
 9     int *a1 = (int *)calloc(sizeof(int), len1);
10     int *a2 = (int *)calloc(sizeof(int), len2);
11     if (len <= 1)
12     {
13         return;
14     }
15     for (i = 0; i < len1; i++)
16     {
17         a1[i] = a[i];
18     }
19     for (j = 0; j < len2; j++)
20     {
21         a2[j] = a[i++];
22     }
23     MergeSort(a1, len1);
24     MergeSort(a2, len2);
25     i = 0; j = 0;
26     //合并两个数组
27     for (k = 0; i < len1 && j < len2; k++)
28     {
29         if (a1[i] < a2[j])
30         {
31             a[k] = a1[i++];
32         }
33         else
34         {
35             a[k] = a2[j++];
36         }
37     }
38     //将剩余的数拷回原数组
39     while (i < len1)
40     {
41         a[k++] = a1[i++];
42     }
43     while (j < len2)
44     {
45         a[k++] = a2[j++];
46     }
47     free(a1);
48     free(a2);
49 }
50 
51 int main(int argc, char *argv[]) 
52 {
53     int a[10] = {12, 3, 16, 58, 18, 24, 6, 5, 64, 1};
54     MergeSort(a, 10);
55     for (int i = 0; i < 10; i++)
56     {
57         printf("%d ", a[i]);
58     }
59     
60     return 0;
61 }

    时间复杂度为O(nlogn),比希尔排序快。

6. 用二叉树来排序 -- 堆排序

    原理:使用近似完全二叉树的堆结构(不同于内存的“堆”),利用最大堆/最小堆原理,使用一个保持最大堆/最小堆状态的维护函数来排序。

                          图6. 堆排序动态图

    首先要理解堆结构,最大堆是一颗根结点比其它子结点都大的二叉树,一般用数组保存:

                                                                                            图7. 堆结构示意图

    结构的实现并不难,难点在于堆的维护,一个保持最大堆的维护函数。首先先写一个最大堆的实现,保证父结点比任何子结点都大,a[0]存放堆大小:

void MaxHeapify(int *a, int i)
{
    int largest = i;
    int l = LEFT(i);
    int r = RIGHT(i);
    if (l <= a[0] && a[l] >= a[i])
    {
        largest = l;
    }
    if (r <= a[0] && a[r] >= a[largest])
    {
        largest = r;
    }
    if (largest != i)
    {
        int temp = a[i];
        a[i] = a[largest];
        a[largest] = temp;
        MaxHeapify(a, largest);
    }
}

    然后实现将普通数组转换为最大堆:

void BuildHeapSort(int *a)
{
    int i;
    for (i = a[0] / 2; i > 0; i--)
    {
        MaxHeapify(a, i);
    }
}

    再然后则是递归实现最大堆的根结点放至数组最后,最后一个元素脱离最大堆,然后重新维护最大堆,继续以上步骤直至最大堆只剩下一个结点:

void HeapSort(int *a)
{
    int i;
    BuildHeapSort(a);
    for (i = a[0]; i > 1; i--)
    {
        int temp = a[i];
        a[i] = a[1];
        a[1] = temp;
        a[0]--;
        MaxHeapify(a, 1);
    }
}

 

    完整代码实现:

 1 #include <stdio.h>
 2 #include <stdlib.h>
 3 
 4 #define LEFT(x) (x * 2)
 5 #define RIGHT(x) (x * 2 + 1)
 6 
 7 void MaxHeapify(int *a, int i)
 8 {
 9     int largest = i;
10     int l = LEFT(i);
11     int r = RIGHT(i);
12     if (l <= a[0] && a[l] >= a[i])
13     {
14         largest = l;
15     }
16     if (r <= a[0] && a[r] >= a[largest])
17     {
18         largest = r;
19     }
20     if (largest != i)
21     {
22         int temp = a[i];
23         a[i] = a[largest];
24         a[largest] = temp;
25         MaxHeapify(a, largest);
26     }
27 }
28 
29 void BuildHeapSort(int *a)
30 {
31     int i;
32     for (i = a[0] / 2; i > 0; i--)
33     {
34         MaxHeapify(a, i);
35     }
36 }
37 
38 void HeapSort(int *a)
39 {
40     int i;
41     BuildHeapSort(a);
42     for (i = a[0]; i > 1; i--)
43     {
44         int temp = a[i];
45         a[i] = a[1];
46         a[1] = temp;
47         a[0]--;
48         MaxHeapify(a, 1);
49     }
50 }
51 
52 int main(int argc, char *argv[]) 
53 {
54     int a[11] = {10, 12, 3, 16, 58, 18, 24, 6, 5, 64, 1};
55     HeapSort(a);
56     for (int i = 1; i < 11; i++)
57     {
58         printf("%d ", a[i]);
59     }
60     
61     return 0;
62 }

 

    时间复杂度为O(nlogn),相对于归并排序稳定且快,不过代码实现麻烦。

7. 要的就是快 -- 快速排序

    原理:选择一个元素作为基准,比它大的元素放在其后面,比它小的元素放在其前面。由于不断改变基准元素的位置需要耗费大量时间,所以代码实现需要用到两个变量,一个表示基准元素应该所在的下标i,一个表示下一个元素的下标j。碰到比基准元素小的元素则i增加1并交换i下标元素和j下标元素,然后j1;碰到比基准变量大的元素则只有j加一。比较晚所有元素后,交换i+1下标元素和基准元素的位置并开始新一轮递归。

           图8. 快速排序示意图

                          图9. 快速排序动态图

    想法比较难理解,不过代码实现很简单。代码实现:

 1 #include <stdio.h>
 2 #include <stdlib.h>
 3 
 4 void swap(int *x, int *y)
 5 {
 6     int temp = *x;
 7     *x = *y;
 8     *y = temp;
 9 }
10 
11 void QuickSort(int *a, int start, int end)
12 {
13     int i = start - 1, j = start;
14     if (start >= end)
15     {
16         return;
17     }
18     while (j < end && i < end)
19     {
20         if (a[j] <= a[end])
21         {
22             i++;
23             swap(&a[i], &a[j]);
24         }
25         j++;
26     }
27     swap(&a[i + 1], &a[end]);
28     QuickSort(a, start, i);
29     QuickSort(a, i + 1, end);
30 }
31 
32 int main(int argc, char *argv[]) 
33 {
34     int a[10] = {12, 3, 16, 58, 18, 24, 6, 5, 64, 1};
35     QuickSort(a, 0, 9);
36     for (int i = 0; i < 10; i++)
37     {
38         printf("%d ", a[i]);
39     }
40     
41     return 0;
42 }

 

    快速排序的时间复杂度为:O(nlogn),在大量数据的情况下是最快的排序算法,但及其不稳定,不适合小数据排序。对于快速排序来说,需要排序的元素越乱,速度越快。通常需要排序的元素并不会太乱,这时可为其增加一个随机基准元素,从而提高算法速度和稳定性。代码如下:

 1 #include <stdio.h>
 2 #include <stdlib.h>
 3 #include <time.h>
 4 
 5 void swap(int *x, int *y)
 6 {
 7     int temp = *x;
 8     *x = *y;
 9     *y = temp;
10 }
11 
12 void QuickSort(int *a, int start, int end)
13 {
14     int r, i = start - 1, j = start;
15     time_t t;
16     if (start >= end)
17     {
18         return;
19     }
20     srand((unsigned) time(&t));
21     r = rand() % (end - start) + start;
22     swap(&a[r + 1], &a[end]);
23     while (j < end && i < end)
24     {
25         if (a[j] <= a[end])
26         {
27             i++;
28             swap(&a[i], &a[j]);
29         }
30         j++;
31     }
32     swap(&a[i + 1], &a[end]);
33     QuickSort(a, start, i);
34     QuickSort(a, i + 1, end);
35 }
36 
37 int main(int argc, char *argv[]) 
38 {
39     int a[10] = {12, 3, 16, 58, 18, 24, 6, 5, 64, 1};
40     QuickSort(a, 0, 9);
41     for (int i = 0; i < 10; i++)
42     {
43         printf("%d ", a[i]);
44     }
45     
46     return 0;
47 }

 

 

 

 

 

转载于:https://www.cnblogs.com/joyingx/p/3404836.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值