排序(Sort)是将无序的记录序列(或称文件)调整成有序的序列。
为了方便讨论,在此首先要对排序下一个确切的定义:
假设含有n个记录的序列为
{ R1、R2、,。。。Rn }
其相应的关键字序列为
{K1、K2,。。。。Kn}
需确定1,2,。。。,n的一种排列 p1,p2,。。。pn,使其相应的关键字满足如下的非递减(或非递增)关系
Kp1 <= Kp2 <=........<= Kpn
即使这个序列称为一个按关键字有序的序列
{Rp1,Rp2,...., Rpn}
这样的操作称为排序。
排序分类:
1、稳定排序和非稳定排序
假设关键字 Ki = Kj (1 <= i <= n , 1<= j <= n , i != j ),且在排序前的序列中 Ri 领先于 Rj (即 i < j)。若在排序后的序列中,Ri 仍领先于 Rj ,则称所用的排序方法是稳定的;反之,若可能使排序后的序列中Rj 领先于 Ri ,则称所用的排序方法是不稳定的的。
2、内排序和外排序
内排序指的是待排序记录存放在计算机随机存储器中进行的排序过程;外排序是指待排序记录的数量很大,以致内存依次不能容纳全部记录,在排序过程中尚需对外存进行访问的排序过程。
总之,在排序的过程中需进行下列两种基本操作:1) 比较两个关键字的大小 ;2)将记录从一个位置移动到另一个位置;
下面讨论内排序:
一、插入排序
1、直接插入排序
直接排序是一种最简单的排序方法,它的基本操作是将一个记录插入到已排好序的有序表中,从而得到一个新的、记录数增1的有序表;
设待排文件f = (R1 R2 ........... Rn)相应的 key 集合为k = {k1 k2 ........kn },其排序方法是:
现将文件中的(R1)看成只含一个记录的有序子文件,然后从R2起,逐个将 R2 至 Rn按key插入到当前有序子文件中,最后得到一个有序的文件。插入的过程是一个key的比较过程,即每插入一个记录时,将其key 与当前有序字表中的key进行比较,找到待插入记录的位置后,将其插入即可。
- #define maxsize 1024 //文件最大长度
- typedef int key_t; //设key为整数
- typedef struct //记录类型
- {
- key_t key; //记录关键字
- ..... //记录其他域
- }Retype;
- typedef struct //文件或表的类型
- {
- Retype R[maxsize + 1]; //文件存储空间
- int len; //当前记录数
- }sqfile;
若说明sqfile F ; 则(F.R[1],F.R[2],。。。F.R[F.len] )为待排文件,它是一种顺序结构;文件长度 n = F.len ;F.R[0] 为工作单元,起到“监视哨”的作用;记录的关键字ki 写作 F.R[i].key 。
算法描述:
- void Insort(sqfile F)
- {
- int i,j;
- for(i = 2;i <= F;i++) //插入n - 1个记录
- {
- F.R[0] = F.R[i]; //带插入记录先存于监视哨
- j = i - 1;
- while(F.R[0].key < F.R[j].key) //key 比较
- {
- F.R[j + 1] = F[j]; //记录顺序后移
- j--;
- }
- F.R[j + 1] = F.R[0]; //原R[i]插入j + 1位置
- }
- }
排序的时间复杂度取耗时最高的量级,故直接插入排序的T(n) = O(n*n).
2、折半插入排序
排序算法的T(n) = O(n*n) ,是内排序时耗最高的时间复杂度。随着文件记录数n的增大,效率降低较快,下面的一些插入排序的方法,都是围绕着改善算法的时间的复杂度而展开的。另外,直接插入排序是稳定排序。
为了减少插入排序过程中key的比较次数,可采用折半插入排序。
排序方法:
同直接插入排序,先将(R[1])看成一个子文件,然后依次插入R[2] .....R[n] 。但在插入R[i] 时,子表(R[1] 。。。R[i-1])已是有序的,查找R[i] 在子表中位置可按折半查找方法进行,从而降低key 的比较次数。
算法描述:
- void Binsort(sqfile F) //对文件F折半插入排序的算法
- {
- int i,j,high,low,mid;
- for(i = 2;i <= F.len,i++) //插入n-1个记录
- {
- F.R[0] = F.R[i];//待插入记录存入监视哨
- low = 1;
- high = i - 1;
- while(low < high) //查找 R[i]的位置,即low = high 时;
- {
- mid = (low + high)/2;
- if(F.R[0].key >= F.R[mid].key)
- low = mid + 1; //调整下界
- else
- high =mid - 1;//调整上界
- }
- for(j = i - 1;j >= low;j--)
- F.R[j + 1] = F.R[j]; //记录瞬移
- F.R[low] = F.R[0]; //原R[i]插入low位置
- }
- }
二、交换排序
1、起泡排序
设待排文件 f = (R1 R2 。。。Rn),相应key集合k = {k1 ,k2, ......kn}。
排序方法
从k1起,两两key比较,逆序时交换,即:
k1~k2,若 k1 > k2 ,则R1 与 R2 交换;
.....
k n-1 ~ kn,若kn-1 > kn ,则Rn-1 与 Rn 交换。
经过一趟比较之后,最大key的记录沉底,类似水泡。接着对前n-1个记录重复上述过程,直到排序完毕。
起泡排序的市价你复杂度 T(n) = O(n*n) 。
算法描述:
- void Bubsort(sqfile F)
- {
- int i,flag; //flag 作为记录交换的标志
- Retype temp;
- for(i = F.len;i > 2;i--) //最多n - 1次排序
- {
- flag = 0;
- for(j = 1; j <= i - 1;j++) //一趟起泡排序
- {
- if(F.R[j].key > F.R[j + 1].key) //两两比较
- {
- temp = F.R[j]; //交换
- F.R[j] = F.R[j + 1];
- F.R[j + 1] = temp;
- flag = 1;
- }
- flag = 1;
- }
- if(flag == 0) //无记录交换时排序完毕
- break;
- }
- }
2、快速排序
快速排序 的基本思想是:通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。
排序的数组是A[0]……A[N-1],首先任意选取一个数据(通常选用数组的第一个数)作为关键数据,然后将所有比它小的数都放到它前面,所有比它大的数都放到它后面,这个过程称为一趟快速排序。值得注意的是,快速排序不是一种稳定的排序算法,也就是说,多个相同的值的相对位置也许会在算法结束时产生变动。
一趟快速排序的算法是:
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)一趟快排算法
- int Partition(sqfile *L,int low ,int high)
- {
- key_t pivotkey; //基准key
- L->r[0] = L->r[low]; //存入基准记录
- pivotkey = L->r[low].key; //存入基准key
- while(low < high)
- {
- while(low < high && L->r[high].key >= pivotkey) //逆序比较
- high--;
- if(low < high)
- L->r[low] = L->r[high]; //比 pivotkey小的key左移
- while(low < high && L->r[low].key <= pivotkey) //正序比较
- low++;
- if(low < high)
- L->r[high] = L->r[low]; //比 pivotkey 大的右移
- }
- L->r[low] = L->r[0]; //基准记录存入第i位置
- return low; //返回基准位置
- }
2)对文件L快速排序的算法(递归)
- void QSort(sqfile *L,int low ,int high)
- {
- int pivotloc;
- int i;
- if(low < high)
- {
- pivotloc = Partition(L,low,high); //将L分成两部分
- QSort(L,low,pivotloc - 1); //对左部再排序
- QSort(L,pivotloc + 1,high); //对右部再排序
- }
- }
下面是个测试程序:
- #include <stdio.h>
- #define maxsize 1024
- typedef int key_t;
- typedef struct
- {
- key_t key;
- }Retype;
- typedef struct
- {
- Retype r[maxsize + 1];
- int len;
- }sqlist;
- int Partition(sqlist *L,int low ,int high)
- {
- key_t pivotkey;
- L->r[0] = L->r[low];
- pivotkey = L->r[low].key;
- while(low < high)
- {
- while(low < high && L->r[high].key >= pivotkey)
- high--;
- if(low < high)
- L->r[low] = L->r[high];
- while(low < high && L->r[low].key <= pivotkey)
- low++;
- if(low < high)
- L->r[high] = L->r[low];
- }
- L->r[low] = L->r[0];
- return low;
- }
- void QSort(sqlist *L,int low ,int high)
- {
- int pivotloc;
- int i;
- if(low < high)
- {
- pivotloc = Partition(L,low,high);
- QSort(L,low,pivotloc - 1);
- QSort(L,pivotloc + 1,high);
- }
- }
- int main()
- {
- int i;
- sqlist L;
- L.len = 8;
- key_t a[8] = {50,36,66,76,36,12,25,95};
- for(i = 1; i < 9; i++)
- {
- L.r[i].key = a[i - 1];
- }
- printf("Begin sorting!\n");
- QSort(&L,1,8);
- for(i = 1; i < 9; i++)
- {
- printf("%d ",L.r[i].key);
- }
- printf("\n");
- return 0;
- }
执行结果如下:
- fs@ubuntu:~/qiang/sort$ ./quicksort
- Begin sorting!
- 12 25 36 36 50 66 76 95