排序篇

排序有以下5类:

插入排序,交换排序,交换排序,并归排序,分配排序。

 

一、插入类排序

1、直接插入排序

思想:有数组R[1...n]。初始时,R[1]看做有序区,R[2...n],看做无序区,讲R[i](1<i<n+1)依次插入R[1...i-1]的有序区,直到生成有n条记录的有序区。

基本操作:增量法,将当前无序区的第1个记录R[i]插入到有序区R[1...i-1]中适当的位置,得到新的R[1...i]的有序区。

方法一:

(1)当前有序区R[1...i]中查找R[i]的正确插入位置k(1<=k<=i-1)。

(2)讲R[k...i-1]中的记录均后移一个位置,腾出k位置上的酷键插入R[i]。

方法二:

查找比较操作和记录移动操作交替进行。

将待插入记录R[j]的关键字从右向左依次与有序区中记录R[j](j=i-1,i-2,...,1)的关键字进行比较:

(1)如果R[j]的关键字大于R[i]的关键字,则将R[j]后移一位。

(2)如果R[j]的关键字小于或等于R[i]的关键字,则查找过程结束,j+1即为R[i]的插入位置。

 1 void insert_sort(int a[],int n)
 2 {
 3     int i,j,temp;
 4 
 5     for(i=1;i<n;i++)
 6     {
 7         temp=a[i];
 8      for(j=i-1;j>=0 && temp<a[j];j--)
 9      {
10          a[j+i]=a[j];
11      }
12      a[j+1]=temp;
13     }
14 }

 

2、希尔(Shell)排序

希尔排序(Shell Sort)实际上是分组插入排序。也称缩小增量排序,是直接插入排序算法的一种更高效的改进版本。希尔排序是非稳定排序算法。

思想:先取一个小于n的整数d1作为第一个增量,把文件的全部记录分组。所有距离为d1的倍数的记录放在同一个组中。先在各组内进行直接插入排序;然后,取第二个增量d2<d1重复上述的分组和排序,直至所取的增量 =1(<…<d2<d1),即所有记录放在同一组中进行直接插入排序为止。一般的初次取序列的一半为增量,以后每次减半,直到增量为1。

稳定性:由于分组的存在,相等的元素可能会分在不同组,导致他们的次序可能会发上变化,因此希尔排序是不稳定的。

 1 void shell_sort(int a[],int len)
 2 {
 3     int h,i,j,temp;
 4     for(h=len/2;h>0;h=h/2)
 5     {
 6         for(i=h;i<len;i++)
 7         {
 8             temp=a[i];
 9             for(j=i-h;j>=0 && temp<a[j];j-=h)
10             {
11                 a[j+h]=a[j];
12             }
13             a[j+h]=temp;
14         }
15     }
16 }

 

二、交换类排序

1、冒泡排序(Bubble Sort)

思想:它重复地走访过要排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来。走访数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。

冒泡排序时稳定排序,算法如下:

(1)初始状态下,A[1...n]为无序区。

(2)第一趟扫描:从无序区底部向上依次比较相邻的两个气泡的重量,若发现轻者在下、重者在上,则交换二者的位置。即依次比较(A[n],A[n-1]),(A[n-1],A[n-2]),...,(A[2],A[1]);对于每次气泡(A[j+1],A[j]),若A[j+1]<A[j],则交换A[j+1]和A[j]的内容。

  第一趟扫描完毕时,“最轻”的起泡酒漂浮到该区间的顶部,即关键字最小的纪录被放在最高位置A[1]上。

(3)第二趟扫描:扫描A[2...n]。扫描完毕时,“次轻”的气泡漂浮到A[2]的位置上。

(4)第i趟扫描:效果相同。

  最后,经过n-1唐扫描可得到有序区A[1...n]。

 1 void bubble_sort(int a[],int len)
 2 {
 3     int i=0;
 4     int j=0;
 5     int temp=0;
 6     int exchange=0;     //用于记录每次扫描时是否发生交换
 7     
 8     for (i=0; i<len-1; i++)
 9     {
10         exchange=0;
11         for (j=len-1; j>=i; j--)
12         {
13             if (a[j+i]<a[j])
14             {
15                 temp = a[j];
16                 a[j] = a[j+1];
17                 a[j+1] = temp;
18                 exchange = 1;       //发生交换,exchange置1。若不进入循环则值为0,未发生交换,说明已经是有序的,则退出
19             }
20         }
21         if (exchange != 1)
22         {
23             return;
24         }
25     }
26 }

 

2、快速排序(Quick Sort)

思想:通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。

采用的是分治法:将原问题分解为若干个规模更小的但结构与原问题相似的子问题。递归的解这鞋子问题,然后将这些子问题的解组合为原问题的解。

一趟快速排序的算法:

(1)设置两个变量i、j,排序开始的时候:i=0,j=N-1;

(2)以第一个数组元素作为关键数据,赋值给key,即key=A[0];

(3)从j开始向前搜索,即由后开始向前搜索(j--),找到第一个小于key的值A[j],将A[j]和A[i]互换;

(4)从i开始向后搜索,即由前开始向后搜索(i++),找到第一个大于key的A[i],将A[i]和A[j]互换;

(5)重复第3、4步,直到i=j; (3,4步中,没找到符合条件的值,即3中A[j]不小于key,4中A[i]不大于key的时候改变j、i的值,使得j=j-1,i=i+1,直至找到为止。找到符合条件的值,进行交换的时候i, j指针位置不变。另外,i==j这一过程一定正好是i+或j-完成的时候,此时令循环结束)。
 1 void quick_sort(int a[], int low, int high)
 2 {
 3     int i,j,pivot;
 4     if (low < high)
 5     {
 6         pivot = a[low];
 7         i = low;
 8         j = high;
 9         while (i<j)
10         {
11             while (i<j && a[j]>=pivot)
12                 j--;
13             if (i<j)
14                 a[i++] = a[j];          //将比pivot小的元素移到低端
15             
16             while (i<j && a[i]<=pivot)
17                 i++;
18             if (i<j)
19                 a[j--] = a[i];          //将比pivot大的元素移到高端
20         }
21         a[i] = pivot;                   //pivot移到最终位置
22         quick_sort(a, low, i-1);        //对左区间递归排序
23         quick_sort(a, i+1, high);       //对右区间递归排序
24     }
25 }

 

三、选择类排序
1、直接选择排序
思想:n个记录的直接选择排序可经过n-1趟直接学安泽排序得到有序结果。
(1)初始状态:无序区为R[1..n],有序区为空。
(2)第一趟排序:在无序区R[1..n]中选出关键字最小的记录R[k],将它与无序区的第1个记录R[1]交换,使R[1..1]和R[2..n]分别变为记录个数增加1个的新有序区和记录个数减少1个的新无序区。
(3)第i趟排序:
第i趟排序开始时,当前有序区和无序区分别为R[1..i-1]和R(i..n)。该趟排序从当前无序区中选出关键字最小的记录 R[k],将它与无序区的第1个记录R交换,使R[1..i]和R分别变为记录个数增加1个的新有序区和记录个数减少1个的新无序区。
稳定性:直接选择排序是不稳定的。
 1 void select_sort(int a[], int len)
 2 {
 3     int i,j,x,l;
 4     for (i=0; i<len; i++)
 5     {
 6         x = a[i];
 7         l = i;
 8         for (j=i; j>len; j++)
 9         {
10             if (a[j]<x)
11             {
12                 x = a[j];       //x保存每次便利搜索道德最小数
13                 l = j;          //l记录最小数的位置
14             }
15         }
16         a[l] = a[i];            //把最小元素与a[i]进行交换
17         a[l] = x;
18     }
19 }
 
2、堆排序(Heap sort)
 堆排序是指利用堆积树(堆)这种数据结构所设计的一种排序算法,它是选择排序的一种。可以利用数组的特点快速定位指定索引的元素。堆分为大根堆和小根堆,是完全二叉树。
大根堆:所有子节点都小于其父节点,即Ai>=A2i且Ai>=A2i+1。
小根堆:所有子节点都大于其父节点,即Ai<=A2i且Ai<=A2i+1。
 大根堆排序的思想:
(1)先将初始文件R[1..n]建成一个大根堆,此堆为初始的无序区
(2)再将关键字最大的记录R[1](即堆顶)和无序区的最后一个记录R[n]交换,由此得到新的无序区R[1..n-1]和有序区R[n],且满足R[1..n-1].keys≤R[n].key
(3)由于交换后新的根R[1]可能违反堆性质,故应将当前无序区R[1..n-1]调整为堆。然后再次将R[1..n-1]中关键字最大的记录R[1]和该区间的最后一个记录R[n-1]交换,由此得到新的无序区R[1..n-2]和有序区R[n-1..n],且仍满足关系R[1..n-2].keys≤R[n-1..n].keys,同样要将R[1..n-2]调整为堆。
(4)重复上面的交换,直到无序区只有一个元素为止。
大根堆排序算法的基本操作:
(1)建堆:建堆是不断调整堆的过程,从len/2处开始调整,一直到第一个节点,此处len是堆中元素的个数。建堆的过程是线性的过程,从len/2到0处一直调用调整堆的过程,相当于o(h1)+o(h2)…+o(hlen/2) 其中h表示节点的深度,len/2表示节点的个数,这是一个求和的过程,结果是线性的O(n)。
(2)调整堆:调整堆在构建堆的过程中会用到,而且在堆排序过程中也会用到。利用的思想是比较节点i和它的孩子节点left(i),right(i),选出三者最大(或者最小)者,如果最大(小)值不是节点i而是它的一个孩子节点,那边交互节点i和该节点,然后再调用调整堆过程,这是一个递归的过程。
(3)堆排序:堆排序是利用上面的两个过程来进行的。首先是根据元素构建堆。然后将堆的根节点取出(一般是与最后一个节点进行交换),将前面len-1个节点继续进行堆调整的过程,然后再将根节点取出,这样一直到所有节点都取出。
/*堆调整函数*/
void sift(int R[],int low, int high)        //因为R[]是一颗完全二叉树,所以元素的存储必须从1开始。
{
    int i=low,j=2*i;
    int temp=R[i];
    while (j<=high)
    {
        if (j<high && R[j]<R[j+1])
            ++j;
        if (temp<R[j])
        {
            R[i]=R[j];      //将R[j]调整到双亲节点的位置上
            i=j;            //修改i与j的值,以便继续向下调整
            j=2*i;
        }
        else
            break;
    }
    R[i]=temp;              //被调整节点的值放入最终位置
}

/*堆排序函数*/
void heapSort(int R[], int n)
{
    int i;
    int temp;
    for (i=n/2; i>=1; --i)      //初始化堆
        sift(R, i, n);
    for (i=n; i>=2; --i)        //进行n-1次循环完成堆排序
    {
        /*以下三行换出了根节点中元素,将其放入最终位置*/
        temp=R[1];
        R[1]=R[i];
        R[i]=temp;
        sift(R, 1, i-1);        //对栈顶元素进行调整
    }
}

 

四、归并排序(Merge Sort)

思想:将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并;类似的,还有k(>2)路归并。
实现方法:自底向上和自顶向下。
自底向上的归并排序:归并排序主要是完成将若干个有序子序列合并成一个完整的有序子序列;自底向上的排序是归并排序的一种实现方式,将一个无序的N长数组切个成N个有序子序列,然后再两两合并,然后再将合并后的N/2(或者N/2 + 1)个子序列继续进行两两合并,以此类推得到一个完整的有序数组。

自顶向下的归并排序:设当前区间是A[low...high]。采用分治法进行自顶向下的程序设计方式,分治法的核心思想就是分解、求解、合并。

(1)分解:将当前区间一分为二,即求分裂点。

(2)求解:递归的对两个自取件A[low...mid]和A[mid+1...high]进行并归排序。

(3)组合:将已排序的两个子区间A[low...mid]和A[mid+1...high]归并为一个有序的区间R[low...high]。

(4)递归的终结条件:子区间长度为1(一个记录自然有序)。

 1 #include <iostream>
 2 using namespace std;
 3 
 4 /*区间归并排序*/
 5 void Merge(int a[], int tmp[], int lPos, int rPos, int rEnd)
 6 {
 7     int i,lEnd,NumElements,tmpPos;
 8     lEnd = rPos-1;
 9     tmpPos = lPos;
10     NumElements = rEnd - lPos +1;       //数组长度
11     
12     while(lPos <= lEnd && rPos <= rEnd)
13     {
14         if (a[lPos] <= a[rEnd])         //比较两端元素
15             tmp[tmpPos++]=a[lPos++];  //把较小的之先放入tmp临时数组
16         else
17             tmp[tmpPos++]=a[rPos++];
18     }
19     while (lPos <= lEnd)                //把左端上与元素放入tmp
20         tmp[tmpPos++] = a[lPos++];
21     
22     while (rPos <= rEnd)
23         tmp[tmpPos++] = a[rPos++];
24     
25     for (i=0; i<NumElements; i++,rEnd--)
26         a[rEnd] = tmp[rEnd];            //把临时数组拷贝到原始数组
27 }
28 
29 void msort(int a[], int tmp[], int low, int high)
30 {
31     if (low >= high)        //结束条件
32         return;
33     
34     int middle = (low + high)/2;        //计算分裂点
35     msort(a, tmp, low, middle);         //对子区间[low,middle]递归做归并排序
36     msort(a, tmp, middle+1, high);      //对子区间[middle+1,high]递归做归并排序
37     Merge(a, tmp, low, middle, high);   //组合,把两个有序区合并为一个有序区
38 }
39 
40 void merge_sort(int a[], int len)
41 {
42     int *tmp = NULL;            //分配临时数组空间
43     tmp = new int[len];
44     if (tmp != NULL) {
45         msort(a, tmp, 0, len-1);//调用归并排序
46         delete []tmp;           //释放临时数组内存
47     }
48 }
49 
50 int main()
51 {
52     int a[8] = {8,6,1,3,5,2,7,4};
53     merge_sort(a, 8);
54     return 0;
55 }

 

五、基数排序

基数排序是箱排序的改进和推广。箱排序也称桶排序(Bucket Sort),其思想是“多关键字排序”。

方法:

最高位优先(Most Significant Digit,MSD)

最低位优先(Least Significant Digit,LSD)

 1 #include <iostream>
 2 #include <math.h>
 3 using namespace std;
 4 
 5 int find_max(int a[],int len)       //查找长度为len的数组的最大元素
 6 {
 7     int max = a[0];
 8     for (int i = 1; i<len; i++) {
 9         if (max < a[i]) {
10             max = a[i];
11         }
12     }
13     return max;
14 }
15 
16 int digit_number(int number)        //计算number有多少位
17 {
18     int digit =0;
19     do
20     {
21         number /=10;
22         digit++;
23     } while (number !=0);
24     return digit;
25 }
26 
27 int kth_digit(int number,int kth)   //返回number上第kth位的数字
28 {
29     number /= pow(10,kth);
30     return number%10;
31 }
32 
33 void radix_sort(int a[],int len)    //对长度为len的数组进行基数排序
34 {
35     int *temp[10];                  //指针数组,每一个指针表示一个箱子
36     int count[10] = {0,0,0,0,0,0,0,0,0,0};      //用于存储每个箱子装有多少元素
37     int max = find_max(a, len);
38     int maxDigit = digit_number(max);
39     int i,j,k;
40     for (i=0; i<10; i++)
41     {
42         temp[i] = new int[len];                 //使每一个箱子能装下len个int元素
43         memset(temp[i], 0, sizeof(int)*len);    //初始化为0
44     }
45     for (i = 0; i<maxDigit; i++)
46     {
47         memset(count, 0, sizeof(int)*10);       //每次装箱前把count清空
48         for (j=0; j<len; j++)
49         {
50             int xx = kth_digit(a[j], i);        //将数据安装位数放入到暂存数组中
51             temp[xx][count[xx]] = a[j];
52             count[xx]++;                        //此箱子的计数递增
53         }
54         int index = 0;
55         for (j=0; j<10; j++)                    //将数据从暂存数组中取回,放入原始数组中
56         {
57             for (k=0; k<count[j]; k++)
58             {
59                 a[index++] = temp[j][k];
60             }
61         }
62     }
63 }
64 
65 int main()
66 {
67     int a[] = {22,32,19,53,47,29};
68     radix_sort(a, 6);
69     return 0;
70 }

 

各种排序算法的时间复杂度比较:(引自:http://blog.sina.com.cn/s/blog_771849d301010ta0.html

 

 
posted @ 2016-04-21 21:21  define_ray  阅读(201)  评论(0编辑  收藏  举报