常见的排序算法实现、分析和比较
排序算法实现与分析
1、插入排序
2、交换排序(冒泡排序)
3、选择排序
4、快速排序
快速排序的基本思想是,通过一轮的排序将序列分割成独立的两部分,其中一部分序列的关键字(这里主要用值来表示)均比另一部分关键字小。继续对长度较短的序列进行同样的分割,最后到达整体有序。在排序过程中,由于已经分开的两部分的元素不需要进行比较,故减少了比较次数,降低了排序时间。
我们附设两个指针(下角标)i 和 j, 通过 j 从当前序列的有段向左扫描,越过不小于基准值的记录。当遇到小于基准值的记录时,扫描停止。通过 i 从当前序列的左端向右扫描,越过小于基准值的记录。当遇到不小于基准值的记录时,扫描停止。交换两个方向扫描停止的记录 a[j] 与 a[i]。 然后,继续扫描,直至 i 与 j 相遇为止。
i = j = 3, 这样序列就这样分割成了两部分,左边部分{15, 30, 17} 均小于 基准值(46);右边部分 {56, 90,95,82},均大于基准值。这样子我们就达到了分割序列的目标。在接着对子序列用同样的办法进行分割,直至子序列不超过一个元素,那么排序结束,整个序列处于有序状态。
#include <stdio.h>
3 #define MAX_NUM 80
5 void quicksort(int* a, int p,int q)
6 {
7 int i = p;
8 int j = q;
9 int temp = a[p];
10
11 while(i < j)
12 {
13 // 越过不小于基准值的数据
14 while( a[j] >= temp && j > i ) j--;
15
16 if( j > i )
17 {
18 a[i] = a[j];
19 i++;
21 // 越过小于基准值的数据
22 while(a[i] <= temp && i < j ) i++;
23 if( i < j )
24 {
25 a[j] = a[i];
26 j--;
27 }
28 }
29 }
30 a[i] = temp;
// 用于打印观察
32 for(int k = p; k <= q;k++)
33 {
34 if( k == i )
35 {
36 printf("(%d) ",a[k]);
37 continue;
38 }
39 printf("%d ",a[k]);
40 }
41 printf("\n");
// 递归调用
43 if( p < (i-1)) quicksort(a,p,i-1);
44 if((j+1) < q ) quicksort(a,j+1,q);
45 }
47 int main(int argc, char* argv[])
48 {
49 int a[MAX_NUM];
50 int n;
52 printf("Input total numbers: ");
53 scanf("%d",&n);
55 if( n > MAX_NUM ) n = MAX_NUM;
57 for(int i = 0; i < n;i++)
58 {
59 scanf("%d",&a[i]);
60 }
62 printf("Divide sequence:\n");
63 quicksort(a,0,n-1);
65 printf("The sorted result:\n");
66 for(int i = 0; i < n;i++)
67 {
68 printf("%d ",a[i]);
69 }
70 printf("\n");
74 return 0;
75 }
5、归并排序(递归算法)
归并排序是利用递归和分而治之的技术将数据序列划分成为越来越小的半子表,再对半子表排序,最后再用递归步骤将排好序的半子表合并成为越来越大的有序序列,归并排序包括两个步骤1)划分子表 2)合并半子表
然后将临时变量中的值按照索引位置,拷贝回向量v中,就完成了对向量v的归并排序
public void Merger(int[] v, int first, int mid, int last)
{
Queue<int> tempV = new Queue<int>();
int indexA, indexB;
//设置indexA,并扫描subArray1 [first,mid]
//设置indexB,并扫描subArray2 [mid,last]
indexA = first;
indexB = mid;
//在没有比较完两个子标的情况下,比较 v[indexA]和v[indexB]
//将其中小的放到临时变量tempV中
while (indexA < mid && indexB < last)
{
if (v[indexA] < v[indexB])
{
tempV.Enqueue(v[indexA]);
indexA++;
}
else
{
tempV.Enqueue(v[indexB]);
indexB++;
}
}
//复制没有比较完子表中的元素
while (indexA < mid)
{
tempV.Enqueue(v[indexA]);
indexA++;
}
while (indexB < last)
{
tempV.Enqueue(v[indexB]);
indexB++;
}
int index = 0;
while (tempV.Count > 0)
{
v[first+index] = tempV.Dequeue();
index++;
}
}
public void MergerSort(int[] v, int first, int last)
{
if (first + 1 < last)
{
int mid = (first + last) / 2;
MergerSort(v, first, mid);
MergerSort(v, mid, last);
Merger(v, first, mid, last);
}
}
6、堆排序
堆分为最大堆和最小堆,其实就是完全二叉树。最大堆要求节点的元素都要大于其孩子,最小堆要求节点元素都小于其左右孩子,两者对左右孩子的大小关系不做任何要求,处于最大堆的根节点的元素一定是这个堆中的最大值。其实我们的堆排序算法就是抓住了堆的这一特点,每次都取堆顶的元素,将其放在序列最后面,然后将剩余的元素重新调整为最大堆,依次类推,最终得到排序的序列。
堆排序将所有的待排序数据分为两部分,无序区和有序区。无序区也就是前面的最大堆数据,有序区是每次将堆顶元素放到最后排列而成的序列。每一次堆排序过程都是有序区元素个数增加,无序区元素个数减少的过程。当无序区元素个数为1时,堆排序就完成了。本质上讲,堆排序是一种选择排序,每次都选择堆中最大的元素进行排序。只不过堆排序选择元素的方法更为先进,时间复杂度更低,效率更高。
int adjust_heap(vector<int> &v, int length, int i){
int left = 2 * i;
int right = 2 * i + 1;
int largest = i;
int temp;
while(left < length || right < length){
if (left < length && v[largest] < v[left]){
largest = left;
}
if (right < length && v[largest] < v[right]){
largest = right;
}
if (i != largest){
temp = v[largest];
v[largest] = v[i];
v[i] = temp;
i = largest;
left = 2 * i;
right = 2 * i + 1;
}
else{
break;
}
}
}
int build_heap(vector<int> &v, int length){
int i;
int begin = length/2 - 1; //get the last parent node
for (i = begin; i>=0; i--){
adjust_heap(v,length,i);
}
}
int heap_sort(vector<int> &v){
int length = v.size();
int temp;
printline("before sort:",v);
build_heap(v,length);
while(length > 1){
temp = v[length-1];
v[length-1] = v[0];
v[0] = temp;
length--;
adjust_heap(v,length,0);
}
printline("after sort:",v);
}
7、基排序
简略概述:基数排序是通过“分配”和“收集”过程来实现排序。
(2)我们把扑克牌的排序看成由花色和面值两个数据项组成的主关键字排序。
要求如下:
花色顺序:梅花<方块<红心<黑桃
面值顺序:2<3<4<…<10<J<Q<K<A
那么,若要将一副扑克牌排成下列次序:
梅花2,…,梅花A,方块2,…,方块A,红心2,…,红心A,黑桃2,…,黑桃A。
有两种排序方法:
<1>先按花色分成四堆,把各堆收集起来;然后对每堆按面值由小到大排列,再按花色从小到大按堆收叠起来。----称为"最高位优先"(MSD)法。
<2>先按面值由小到大排列成13堆,然后从小到大收集起来;再按花色不同分成四堆,最后顺序收集起来。----称为"最低位优先"(LSD)法。
【2】代码实现
(1)MSD法实现
最高位优先法通常是一个递归的过程:
<1>先根据最高位关键码K1排序,得到若干对象组,对象组中每个对象都有相同关键码K1。
<2>再分别对每组中对象根据关键码K2进行排序,按K2值的不同,再分成若干个更小的子组,每个子组中的对象具有相同的K1和K2值。
<3>依此重复,直到对关键码Kd完成排序为止。
<4> 最后,把所有子组中的对象依次连接起来,就得到一个有序的对象序列。
示例代码如下:
1 #include<iostream>
2 #include<malloc.h>
3 using namespace std;
4
5 int getdigit(int x,int d)
6 {
7 int a[] = {1, 1, 10}; //因为待排数据最大数据也只是两位数,所以在此只需要到十位就满足
8 return ((x / a[d]) % 10); //确定桶号
9 }
10
11 void PrintArr(int ar[],int n)
12 {
13 for(int i = 0; i < n; ++i)
14 cout<<ar[i]<<" ";
15 cout<<endl;
16 }
17
18 void msdradix_sort(int arr[],int begin,int end,int d)
19 {
20 const int radix = 10;
21 int count[radix], i, j;
22 //置空
23 for(i = 0; i < radix; ++i)
24 {
25 count[i] = 0;
26 }
27 //分配桶存储空间
28 int *bucket = (int *) malloc((end-begin+1) * sizeof(int));
29 //统计各桶需要装的元素的个数
30 for(i = begin;i <= end; ++i)
31 {
32 count[getdigit(arr[i], d)]++;
33 }
34 //求出桶的边界索引,count[i]值为第i个桶的右边界索引+1
35 for(i = 1; i < radix; ++i)
36 {
37 count[i] = count[i] + count[i-1];
38 }
39 //这里要从右向左扫描,保证排序稳定性
40 for(i = end;i >= begin; --i)
41 {
42 j = getdigit(arr[i], d); //求出关键码的第d位的数字, 例如:576的第3位是5
43 bucket[count[j]-1] = arr[i]; //放入对应的桶中,count[j]-1是第j个桶的右边界索引
44 --count[j]; //第j个桶放下一个元素的位置(右边界索引+1)
45 }
46 //注意:此时count[i]为第i个桶左边界
47 //从各个桶中收集数据
48 for(i = begin, j = 0;i <= end; ++i, ++j)
49 {
50 arr[i] = bucket[j];
51 }
52 //释放存储空间
53 free(bucket);
54 //对各桶中数据进行再排序
55 for(i = 0;i < radix; i++)
56 {
57 int p1 = begin + count[i]; //第i个桶的左边界
58 int p2 = begin + count[i+1]-1; //第i个桶的右边界
59 if(p1 < p2 && d > 1)
60 {
61 msdradix_sort(arr, p1, p2, d-1); //对第i个桶递归调用,进行基数排序,数位降 1
62 }
63 }
64 }
65
66 void main()
67 {
68 int ar[] = {12, 14, 54, 5, 6, 3, 9, 8, 47, 89};
69 int len = sizeof(ar)/sizeof(int);
70 cout<<"排序前数据如下:"<<endl;
71 PrintArr(ar, len);
72 msdradix_sort(ar, 0, len-1, 2);
73 cout<<"排序后结果如下:"<<endl;
74 PrintArr(ar, len);
75 }
排序前数据如下:
12 14 54 5 6 3 9 8 47 89
排序后结果如下:
3 5 6 8 9 12 14 47 54 89
(2)LSD法实现
最低位优先法首先依据最低位关键码Kd对所有对象进行一趟排序,
再依据次低位关键码Kd-1对上一趟排序的结果再排序,依次重复,直到依据关键码K1最后一趟排序完成,就可以得到一个有序的序列。使用这种排序方法对每一个关键码进行排序时,不需要再分组,而是整个对象组。
示例代码如下:
1 #include<iostream>
2 #include<malloc.h>
3 using namespace std;
4
5 #define MAXSIZE 10000
6
7 int getdigit(int x,int d)
8 {
9 int a[] = {1, 1, 10, 100}; //最大三位数,所以这里只要百位就满足了。
10 return (x/a[d]) % 10;
11 }
12 void PrintArr(int ar[],int n)
13 {
14 for(int i = 0;i < n; ++i)
15 {
16 cout<<ar[i]<<" ";
17 }
18 cout<<endl;
19 }
20 void lsdradix_sort(int arr[],int begin,int end,int d)
21 {
22 const int radix = 10;
23 int count[radix], i, j;
24
25 int *bucket = (int*)malloc((end-begin+1)*sizeof(int)); //所有桶的空间开辟
26
27 //按照分配标准依次进行排序过程
28 for(int k = 1; k <= d; ++k)
29 {
30 //置空
31 for(i = 0; i < radix; i++)
32 {
33 count[i] = 0;
34 }
35 //统计各个桶中所盛数据个数
36 for(i = begin; i <= end; i++)
37 {
38 count[getdigit(arr[i], k)]++;
39 }
40 //count[i]表示第i个桶的右边界索引
41 for(i = 1; i < radix; i++)
42 {
43 count[i] = count[i] + count[i-1];
44 }
45 //把数据依次装入桶(注意装入时候的分配技巧)
46 for(i = end;i >= begin; --i) //这里要从右向左扫描,保证排序稳定性
47 {
48 j = getdigit(arr[i], k); //求出关键码的第k位的数字, 例如:576的第3位是5
49 bucket[count[j]-1] = arr[i]; //放入对应的桶中,count[j]-1是第j个桶的右边界索引
50 --count[j]; //对应桶的装入数据索引减一
51 }
52
53 //注意:此时count[i]为第i个桶左边界
54
55 //从各个桶中收集数据
56 for(i = begin,j = 0; i <= end; ++i, ++j)
57 {
58 arr[i] = bucket[j];
59 }
60 }
61 free(bucket);
62 }
63
64 void main()
65 {
66 int br[10] = {20, 80, 90, 589, 998, 965, 852, 123, 456, 789};
67 cout<<"原数据如下:"<<endl;
68 PrintArr(br,10);
69 lsdradix_sort(br, 0, 9, 3);
70 cout<<"排序后数据如下:"<<endl;
71 PrintArr(br, 10);
72 }
原数据如下:
20 80 90 589 998 965 852 123 456 789
排序后数据如下:
20 80 90 123 456 589 789 852 965 998
8、希尔排序
希尔(Shell)排序又称为缩小增量排序,它是一种插入排序。它是直接插入排序算法的一种威力加强版。把记录按步长 gap 分组,对每组记录采用直接插入排序方法进行排序。
随着步长逐渐减小,所分成的组包含的记录越来越多,当步长的值减小到 1 时,整个数据合成为一组,构成一组有序记录,则完成排序。
在第一趟排序中,我们不妨设 gap1 = N / 2 = 5,即相隔距离为 5 的元素组成一组,可以分为 5 组。
接下来,按照直接插入排序的方法对每个组进行排序。
在第二趟排序中,我们把上次的 gap 缩小一半,即 gap2 = gap1 / 2 = 2 (取整数)。这样每相隔距离为 2 的元素组成一组,可以分为 2 组。
按照直接插入排序的方法对每个组进行排序。
在第三趟排序中,再次把 gap 缩小一半,即gap3 = gap2 / 2 = 1。 这样相隔距离为 1 的元素组成一组,即只有一组。
按照直接插入排序的方法对每个组进行排序。此时,排序已经结束。
需要注意一下的是,图中有两个相等数值的元素 5 和 5 。我们可以清楚的看到,在排序过程中,两个元素位置交换了。
所以,希尔排序是不稳定的算法。
public void shellSort(int[] list) {
int gap = list.length / 2;
while (1 <= gap) {
// 把距离为 gap 的元素编为一个组,扫描所有组
for (int i = gap; i < list.length; i++) {
int j = 0;
int temp = list[i];
// 对距离为 gap 的元素组进行排序
for (j = i - gap; j >= 0 && temp < list[j]; j = j - gap) {
list[j + gap] = list[j];
}
list[j + gap] = temp;
}
System.out.format("gap = %d:\t", gap);
printAll(list);
gap = gap / 2; // 减小增量
}
}