排序

内部排序:被排序的文件放在内存中,可以很容易访问任何元素。
外部排序:被排序的文件放在磁盘中,必须按顺序访问元素
(至少在大的数据块中是这样)。

排序算法的运行时间:
要避免内部循环中不必要的开销。
在可能时设法减小内部循环中的指令(缩短内部循环)。
排序算法的内存空间:
(1)在原位上排序,使用小堆栈或表
(2)使用链表表示,或用指针、数组来索引数据,需要额外的内存空间
(3)需要额外使用足够的内存空间来存储数据。

定义1:如果排序后有相同关键字的元素保持不变,那么排序方法是稳定的。

排序程序通常用两种方法来访问元素:
访问关键字进行比较。
访问整个元素来移动。
如果访问的元素比较大,通过间接排序的方法避免混洗数据(指针数组,每个指针指向一个元素)

#1.基本排序方法
通过学习基本排序方法,可以了解排序算法的相关术语和基本机制。
在许多排序应用中,简单排序方法比强大通用的人排序方法有效。
简答排序方法能扩展为更普遍实用的排序方法。

如果一个基本排序方法不比其他的数据处理,如数据读入和读出要慢,那么没有必要寻找一个更快的方法。
基本方法通常适用于小型文件(几十元素)。
##1.1选择排序
/*
1.关于函数
SelectSort:选择排序
ItermType:元素类型
left:左边的元素的下标
right:右边元素的下标
2.说明
先假定第一个数最小,然后依次把第一个数与后面的每一个数比较,如果发现比这个数小的,就替换最小数的值

void SelectSort(ItemType a[], int N)
{
    int i, j;
    int left = 0, right = N ;
    ItemType tmp;
    for (i = left; i < right; i++) {
        for (j = i + 1; j < right; j++) {
            if (a[j] < a[i]) {
                tmp = a[i];
                a[i] = a[j];
                a[j] = tmp;
            }
        }
    }
}
选择排序使用大概N^2/2次比较和N次交换操作
缺点:对文件中已有序十五部分依赖较少。
优点:元素比较大,关键字比较小的文件,适合这个方法,移动数据的步数较少。

##1.2插入排序
/*
1.关于函数
InsertSort:插入排序
ItermType:元素类型
left:左边的元素的下标
right:右边元素的下标
2.说明
第一个for循环的目的是确定最小元素并把它放到第一个位置。
改进后的插入排序的优点:
没有在第二层循环中做元素交换,这样减少开销。
元素插入到正确位置后就完成本次循环,不存在无用的操作。
内层循环是相邻间的元素的移位,这比交换元素开销小。

void InsertSort(ItemType a[], int N)
{
    int i, j;
    ItemType tmp;
    for (i = 1; i < N; i++) {
        j = i;
        tmp = a[i];
        while (tmp < a[j-1] && j > 0) {
            a[j] = a[j-1];
            j--;
        }
        a[j] = tmp;
    }
}

在平均的情况下,插入排序执行大约N^2/4 次比较操作和 N^2/4交换操作,而在最坏的情况下是两倍。

##1.3冒泡排序
/*
1.关于函数
BubbleSort:冒泡排序
ItermType:元素类型
left:左边的元素的下标
right:右边元素的下标
*/
void BubbleSort(ItemType a[], int N)
{
int i, j;
int left = 0, right = N;
int flag = 0;
ItemType tmp;
for (i = left; i < right; i++) {
flag = 0;
for (j = right; j > i; j–) {
if (a[j-1] > a[j]) {
tmp = a[j-1];
a[j-1] = a[i];
a[i] = tmp;
flag = 1;
}/如果a[j-1]大于a[j],就交换他们/
}
if (0 == flag)
break;
}
}
在平均和最坏的情况下,冒泡执行大约N2/2次比较和N2/2次交换操作。

##1.4希尔排序

希尔排序利用了快速排序的简单,同时又克服了快速排序每次只能消除一对逆序对的缺点。
如何定义增量序列? 互质的数,并且最小的值一定得是1。

void ShellSort(ItemType a[], int N)
{
    int i, j, h;
    int left = 0, right = N;
    ItemType t;
    for (h = 1; h <= (right - 1) / 9; h = 3 * h + 1) ;
    for (; h > 0; h /= 3) {
        for (i = left + h; i < right; i += h) {
            j = i;
            t = a[i];
            while ((t < a[j-h]) && j > left + h) {
                a[j] = a[j-h];
                j--;
            }
            a[j] = t;
        }
    }
}

#2.快速排序

快速排序最好的情况是每次划分都恰好把文件划分成两个大小完全相等的部分,此时为NlgN。
最坏的情况是恰好遇到一个已经有序的文件,那么比较的次数为N + (N - 1)+ ··· + 1 = (N + 1)N / 2。
平均情况下,比较2NlgN;

##2.1递归的快速排序
void QuikSort(ItemType a[], int N)
{
Quik(a, 0, N - 1);
}
/需要说明的是,当数据规模较小的时候(比如right - left <= 100),就应该考虑用别的排序,如插入排序替代/
int Quik(ItemType a[], int left, int right)
{
if (right - left <= 0)
return -1;
if (right - left == 1 && a[right] <= a[left]) {
Swap(&a[left], &a[right]);
return 0;
}
int center = (left + right) / 2;
if (a[left] > a[center])
Swap(&a[left], &a[center]);
if (a[left] > a[right])
Swap(&a[left], &a[right]);
if (a[center] > a[right])
Swap(&a[center], &a[right]);
int pivot = a[center];
Swap(&a[center], &a[right - 1]);

    int i = left, j = right - 1;
    while (1) {
        while (a[++i] < pivot) { }
        while (a[--j] > pivot) { 
            if (j == left)
                break; 
        }
        if (i < j)   
            Swap(&a[i], &a[j]);
        else 
            break;
    }
    Swap(&a[i], &a[right - 1]);
    
    Quik(a, left, i - 1);
    Quik(a, i + 1, right);
    return 0;
}

如果待排文件中有大量重复的数据,用前面划分的方法,重复元素会被频繁地移动,那么考虑三段划分更加好。

 int i = left, j = right - 1, p = left, q = right - 1;
	    while (1) {
	        while (a[++i] < pivot) { }
	        while (a[--j] > pivot) { 
	            if (j == left)
	                break; 
	        }
	        if (i >= j)   
	            break;
	        else 
	            Swap(&a[i], &a[j]);
	        if (a[i] == pivot) {
	            p++;
	            Swap(&a[p], &a[i]);
	        }
	        if (a[j] == pivot) {
	            q--;
	            Swap(&a[q], &a[j]);
	        }
	    }
	    Swap(&a[i], &a[right - 1]);
	    int k;
	    for (k = 1eft; k < p; k++, j--)
	        Swap(&a[k], &a[j]);
	    for (k = right - 1; k > q; k--, i++)
	        Swap(&a[k], &a[i]);

从左边扫描直到找到一个不小于pivot的元素,从右边扫描直到找到一个不大于pivot的元素,然后进行交换。如果左边的元素和pivot相等,那么将它交换到数组的最左边,右边类似。当把pivot放到该放的位置后,再交换回来。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值