• 排序系列之——快速排序、堆排序、归并排序


    排序系列之——快速排序

    快速排序是对冒泡排序的一种改进。

    基本思想是基于分治法:

    1、在待排序列表L[1···n]中任取一个元素pivot作为基准,通过一趟排序将待排序列表划分为独立的两部分L[1···k-1],L[k+1···n],使得L[1···k-1],中的所有元素都小于等于privot,L[k+1···n]中所有元素大于等于privot。则privot放在了其最终位置L(k)上。这个过程称之为一趟快速排序。

    2、而后,分别递归对两个子表重复上述过程,直至每部分内只有一个元素或空为止,即所有元素都放在了其最终位置上。

    快速排序里最主要的的就是其分治法,

    以下程序采用两种分治方法。

    方法一较为简单,代码如下:

     1 //8 12 4 13 18
     2 int Partion(int *arr, int len)//(基本分割)
     3 {
     4     int left = 0;
     5     int right = len -1;
     6 
     7     int key = arr[0];//以第一个元素为枢轴值,对表进行划分--->对于随机数这是可行的。
     8     while( left < right)
     9     {
    10         while( left < right && arr[right] >= key)  --right;  //走到 4
    11         arr[left] = arr[right]; //将4赋值给arr[left]
    12         
    13         if( left >= right)
    14             break;
    15 
    16         while( left < right && arr[left] <= key)   ++left;  //走到 12
    17         arr[right] = arr[left];
    18     }
    19     arr[left] = key;//交换8 4之后8的位置为left
    20     return left;
    21 }
    View Code

    若有初始序列:3,、8、7、1、2、5、6、4,则排序过程大致如下:

     1 初始:3 8 7 1 2 5 6 4 
     2 2_ 8 7 1 2_ 5 6 4 
     3                <-
     4 2 8_ 7 1 8_ 5 6 4
     5   ->
     6 2 1_ 7 1_ 8 5 6 4 
     7          <-
     8 2 1 7_ 7_ 8 5 6 4 
     9    ->
    10 一趟排序之后:
    11 2 1 3_ 7 8 5 6 4 //arr[high] == arr[left] 
    View Code

    方法二采用两个指针索引一前一后逐步向后扫描的方法,代码如下:

     1 /方法二:
     2 //利用两个指针索引一前一后逐步向后扫描的方法。
     3 //3 8 7 1 2 5 6 4
     4 int Partion2(int *arr, int len)
     5 {
     6     int key = arr[0];
     7     int slow = 0, fast;
     8     
     9     for(fast = slow; fast < len; ++fast)
    10     {
    11         //当快指针所指向的元素值小于枢轴值3 的时候,慢指针位置向前移,同时交换各自的元素值
    12         if( arr[fast] < key) 
    13         {
    14             ++slow;
    15             swap( &arr[slow], &arr[fast]);
    16         }
    17     }
    18     //此处不可以是swap(&key, arr[slow]) 因为key为局部变量。程序结束,key即失效。
    19     swap(&arr[0], &arr[slow]); 
    20     return slow;
    21 }
    View Code

    若有初始序列:3,、8、7、1、2、5、6、4,则排序过程大致如下:

     1          slow  
     2 初始序列:3    8_   7   1_   2    5   6   4
     3          high ->  ->  ->|
     4 
     5               ->slow
     6 for循环 :3    1_   7   8_   2    5   6   4
     7                        high->|  
     8 
     9                  ->slow
    103    1    2_   8   7_   5   6   4
    11                             high->   ->   ->
    12 
    13 一次结束: 2_   1    3_   8   7    5   6   4  (swap(&arr[0], &arr[slow]))

    快速排序代码如下:

      1 #include <stdio.h>
      2 #include <stdlib.h>
      3 #include <string.h>
      4 #include <time.h>
      5 #define N 41
      6 
      7 void print_Arr(int *arr, int len)
      8 {
      9     int i;
     10     for (i = 0; i < len; ++i)
     11     {
     12         printf("%4d", arr[i]);
     13     }
     14     printf("
    ");
     15 }
     16 
     17 void init_Arr(int *arr, int len)
     18 {
     19     int i = 0;
     20     while( i < len)
     21     {
     22         arr[i] = rand()%1000 ;
     23         ++i;
     24 
     25     }
     26 }
     27 
     28 void swap(int* left, int *right)
     29 {
     30     int tmp = *left;
     31     *left = *right;
     32     *right = tmp;
     33 }
     34 //方法一:
     35 //8 12 4 13 18
     36 int Partion1(int *arr, int len)//(基本分割)
     37 {
     38     int left = 0;
     39     int right = len -1;
     40 
     41     int key = arr[0];//以第一个元素为枢轴值,对表进行划分--->对于随机数这是可行的。
     42     while( left < right)
     43     {
     44         while( left < right && arr[right] >= key)  --right;  //走到 4
     45         arr[left] = arr[right]; //将4赋值给arr[left]
     46         
     47         if( left >= right)
     48             break;
     49 
     50         while( left < right && arr[left] <= key)   ++left;  //走到 12
     51         arr[right] = arr[left];
     52     }
     53     arr[left] = key;//交换8 4之后8的位置为left
     54     return left;
     55 }
     56 //方法二:
     57 //利用两个指针索引一前一后逐步向后扫描的方法。
     58 //3 8 7 1 2 5 6 4
     59 int Partion2(int *arr, int len)
     60 {
     61     int key = arr[0];
     62     int slow = 0, fast;
     63     
     64     for(fast = slow; fast < len; ++fast)
     65     {
     66         //当快指针所指向的元素值小于枢轴值3 的时候,慢指针位置向前移,同时交换各自的元素值
     67         if( arr[fast] < key) 
     68         {
     69             ++slow;
     70             swap( &arr[slow], &arr[fast]);
     71         }
     72     }
     73     //此处不可以是swap(&key, arr[slow]) 因为key为局部变量。程序结束,key即失效。
     74     swap(&arr[0], &arr[slow]); 
     75     return slow;
     76 }
     77 
     78 void QuickSort(int *arr, int len)
     79 {
     80     if( len <= 1)
     81         return;
     82     if( len <= 10) //长度小于10的时候,插入排序效率较高
     83     {    
     84         int pos, index;
     85         int key;
     86         for(pos = 1; pos < len; ++pos)
     87         {
     88             key = arr[pos];
     89             for(index = pos-1; index >= 0; --index)
     90             {
     91                 if( arr[index] > key)
     92                     arr[index +1] = arr[index];
     93                 else
     94                     break;
     95             }
     96             arr[index +1] = key;
     97         }
     98     }
     99     else //len >=10
    100     {
    101         int k = Partion2(arr, len);//分离点
    102         QuickSort(arr, k); //0~k-1
    103         QuickSort(arr+k+1, len-k-1);//k+1 ~ len-1
    104     }
    105 }
    106 
    107 int main(int argc, char const *argv[])
    108 {
    109     srand(time(NULL));
    110     
    111     int arr[N];
    112     init_Arr(arr, N);
    113     printf("Before:
    ");
    114     print_Arr(arr, N);
    115 
    116     QuickSort(arr, N);
    117     printf("After
    ");
    118     print_Arr(arr, N);
    119     return 0;
    120 }
    View Code

    性能分析:

    空间效率:由于快排是递归的,需要借助一个递归工作栈来保存每一层递归调用的必要信息。 最好情况下为向上取整log(N+1),最坏情况下要进行n-1次递归调用,所以栈的深度为O(n)。因而,空间复杂度在最坏的情况下为O(n),最好情况下为O(logN)。

    时间复杂度:快速排序的运行时间与划分是否对称有关。而后者又与具体使用的划分算法有关,一般情况下为O(N*logN).

    最坏情况下:初始待排序列基本有序或者基本逆序时,最坏的时间复杂度为O(N*N);

    改进:当递归过程中划分得到的子序列的规模叫小时,不要再用快速排序,可以采用直接插入排序完成。

    稳定性:不稳定。(相对次序有可能发生变化)

    快速排序时所有内部排序算法中平均性能可能最优的排序算法。

    快速排序一次排序的应用:快速排序——一次快排的应用(笔试&面试)

    排序系列之——堆排序

    堆排序是一种树形选择排序算法。它的特点是:在排序过程中,将L[1···n]视为一棵完全二叉树的顺序存储结构,利用完全二叉树中双亲结点和孩子结点之间的内在关系,在当前无需区中选择关键字最大的元素。

    大顶堆:最大元素存放于根结点中,且 对其任一非根结点,它的值小于等于双亲结点值。 小顶堆的定义刚好与大顶堆相反。

    1、堆排序的关键是构造初始堆。对于初始序列建堆,就是一个反复帅选的过程。n个结点的完全二叉树,最后一个结点是第[n/2]个结点的孩子。对第[n/2]个结点为根的子树筛选。使孩子树成为堆。

    2、之后向前依次对各结点为根的子树进行筛选。即看该结点值是否大于等于其左右孩子的结点值。若不是,将左右孩子结点中较大值与值交换。但是交换之后可能会破坏下一级的堆,与实践继续采用上述方法构造下一级的堆,直到树根位置。

    3、建立大顶堆之后,使堆顶元素与当前堆的最大一个元素值进行交换。

    4、交换之后,当前堆结点数要减 1,并且需要调整以树根根结点的堆。

    堆排代码如下:

     1 #include <stdio.h>
     2 #include <stdlib.h>
     3 #include <string.h>
     4 #include <time.h>
     5 #define N 23
     6 
     7 //打印函数
     8 void print_Arr(int *arr, int len)
     9 {
    10     int i;
    11     for (i = 0; i < len; ++i)
    12     {
    13         printf("%4d", arr[i]);
    14     }
    15     printf("
    ");
    16 }
    17 //初始化函数
    18 void init_Arr(int *arr, int len)
    19 {
    20     int i = 0;
    21     while( i < len)
    22     {
    23         arr[i] = rand()%1000 ;
    24         ++i;
    25     }
    26 }
    27 //交换数据
    28 void swap(int* left, int *right)
    29 {
    30     int tmp = *left;
    31     *left = *right;
    32     *right = tmp;
    33 }
    34 
    35 //调整大顶堆
    36 void AdjustDown(int *arr, int i, int len)//i代表结点
    37 {
    38     int left = 2*i+1;//左孩子
    39     int flag = 0;//标记最大值结点的位置
    40 
    41     while(left < len)
    42     {
    43         if(arr[left] > arr[i])//左孩子结点值较大
    44             flag = left;
    45         else//父结点值较大
    46             flag = i;
    47 
    48         if( left+1 < len && arr[left+1] > arr[flag]) //存在右孩子,并且右孩子值大于父亲结点与左孩子之间的较大值
    49             flag = left+1;
    50 
    51         if( flag != i) //若最大值结点不等于附近结点-->调整之
    52         {
    53             swap(&arr[flag], &arr[i]);
    54             i = flag;//继续调整堆
    55             left = 2*flag + 1; //继续调整堆
    56         }
    57         else
    58             break;
    59     }
    60 }
    61 
    62 //堆排序
    63 void HeapSort(int *arr, int len)
    64 {
    65     //建立初始堆
    66     int p = 0;
    67     for(p = len/2-1; p >= 0; --p) //从第一个有孩子的结点建堆
    68     {
    69         AdjustDown(arr, p, len-1);
    70     }
    71 
    72     int end = len-1;//堆的最后一个元素的位置
    73     while(end >= 0) 
    74     {    
    75         swap(&arr[0], &arr[end]); //将大顶堆堆顶元素与堆的最后一个元素交换
    76         --end; //长度减1
    77         AdjustDown(arr, 0, end);//调整为大顶堆
    78     }
    79 }
    80 
    81 int main(int argc, char const *argv[])
    82 {
    83     srand(time(NULL));
    84     
    85     int arr[N];
    86     init_Arr(arr, N);
    87     printf("Before:
    ");
    88     print_Arr(arr, N);
    89 
    90     HeapSort(arr, N);
    91     printf("After
    ");
    92     print_Arr(arr, N);
    93     return 0;
    94 }
    View Code

    堆排序性能分析如下:

    空间效率:仅使用于了常数个辅助单元,所以空间复杂度为O(1);

    时间效率:在最好、最坏和平均情况下,算法的时间复杂度都为O(N*logN).

    稳定性不稳定的排序算法.

    堆的应用【大顶堆的应用】【剑指offer】面试题30:最小的 K 个数

    排序系列之——归并排序

    归并的含义是将两个或者两个以上的有序表组合成一个新的有序表。

    步骤:

    1、假定待排表含有n个记录,则可以视为 n 个有序的子表,每个表的长度为1,然后两两归并,得到n/2个长度为2或1的有序表;

    2、再两两归并····,直到合并成一个长度为n的有序表为止,这种排序方法称为二路归并排序

    示例如下:

    1 初始关键字:(49)  (38)  (65)  (97)  (76)  (13)  (27)
    2 
    3 一趟归并后: (38   49)   (65  97)    (13   76)  (27)
    4 
    5 二趟归并后:  (38   49   65  97)      (13   27  76)
    6 
    7 三趟归并后:    ( 13  27  38  49  65  76  97 )

    归并程序代码如下:

     1 #include <stdio.h>
     2 #include <stdlib.h>
     3 #include <string.h>
     4 #include <time.h>
     5 const int N = 21;
     6 
     7 //打印函数
     8 void print_Arr(int *arr, int len)
     9 {
    10     int i;
    11     for (i = 0; i < len; ++i)
    12     {
    13         printf("%4d", arr[i]);
    14     }
    15     printf("
    ");
    16 }
    17 //初始化函数
    18 void init_Arr(int *arr, int len)
    19 {
    20     int i = 0;
    21     while( i < len)
    22     {
    23         arr[i] = rand()%1000 ;
    24         ++i;
    25     }
    26 }
    27 //交换数据
    28 void swap(int* left, int *right)
    29 {
    30     int tmp = *left;
    31     *left = *right;
    32     *right = tmp;
    33 }
    34 
    35 //合并元素
    36 void Merge(int *arr, int low, int mid, int high)
    37 {
    38     int *B = (int*)malloc( (N+1)*sizeof(int) ); //申请辅助数组B
    39 //表arr的两段arr[low, ```, mid],arr[mid+1, ```, high]各自有序,将它们合并成一个有序表    
    40     int k =0;
    41     for(k = low; k <= high; ++k)
    42         B[k] = arr[k];//将arr中所有的元素复制到数组B中
    43     
    44     int i=0, j=0;
    45     for(i = low, j = mid+1, k=i; i<=mid && j<=high; k++ )
    46     {
    47         if(B[i] < B[j]) //比较B的左右两段中的元素
    48             arr[k] = B[i++]; //将较小者复制回arr中
    49         else
    50             arr[k] = B[j++];
    51     }
    52     while( i <= mid)//若,第一个表未检测完,复制
    53         arr[k++] = B[i++];
    54     while( j <= high)
    55         arr[k++] = B[j++];
    56 }
    57 
    58 //归并排序
    59 void MergeSort(int *arr, int len)//
    60 {
    61     int low = 0;
    62     int high = len-1;
    63     if( low < high)
    64     {
    65         int mid =(low + high)/2; //从中间划分两个子序列
    66         //注意这里的第二个参数为mid+1,第二个参数的意义在于归并排序的长度
    67         //(由于中间位置的元素放在左侧归并数组中,因此长度+1)
    68         MergeSort(arr, mid+1); //对左侧的mid个子序列进行递归排序
    69         MergeSort(arr+mid+1, len-mid-1);//对右侧的len-mid-1个子序列元素进行递归排序
    70         Merge(arr, low, mid, high);//合并元素
    71     }    
    72 }
    73 
    74 int main(int argc, char const *argv[])
    75 {
    76     srand(time(NULL));
    77     
    78     int arr[N];
    79     init_Arr(arr, N);
    80     printf("Before:
    ");
    81     print_Arr(arr, N);
    82 
    83     MergeSort(arr, N);
    84     printf("After
    ");
    85     print_Arr(arr, N);
    86     return 0;
    87 }
    View Code

    归并排序性能分析如下:
    空间效率:Merge()操作中,由于辅助空间刚好要占用 n 个单元,凡是一趟归并后这些空间就被释放了,所以归并排序的空间复杂度为 O(N)。

    时间效率:每一堂归并的时间复杂度为O(N),共需要进行 logN趟排序,所以算法的时间复杂度为O(N*logN).

    稳定性:由于Merge()操作不会改变相同关键字记录的相对次序,所以二路归并排序算法是一个稳定的排序算法.

    完毕。

  • 相关阅读:
    php类型转换
    PHP标记
    使用PHP从web访问mysql数据库
    javascript string对象的属性与方法
    linux vim 常用命令
    添加事件监听兼容IE6-8
    js-jQuery对象与dom对象相互转换
    js 数组
    js正则表达式
    选择排序
  • 原文地址:https://www.cnblogs.com/xfxu/p/4093853.html
Copyright © 2020-2023  润新知