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

排序系列之——快速排序

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

基本思想是基于分治法:

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("\n");
 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:\n");
114     print_Arr(arr, N);
115 
116     QuickSort(arr, N);
117     printf("After\n");
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("\n");
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:\n");
88     print_Arr(arr, N);
89 
90     HeapSort(arr, N);
91     printf("After\n");
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("\n");
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:\n");
81     print_Arr(arr, N);
82 
83     MergeSort(arr, N);
84     printf("After\n");
85     print_Arr(arr, N);
86     return 0;
87 }
View Code

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

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

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

 

完毕。

posted @ 2014-11-13 23:32  Stephen_Hsu  阅读(799)  评论(0编辑  收藏  举报