赞
踩
为了查找方便,通常希望查找表是按关键字有序排序的,因此学习和研究各种排序的方法和对应的算法,有助于提高计算机对数据处理的工作效率。首先了解一些和排序相关的概念。
1.排序( Sorting )
将数越元素(或记录)的任意序列,重新排列成 个按关健字有序(递增或递减)的序列的过程称为排序。
2.排序过程中的两种基本操作
(1)比较两个关键字值的大小。
(2)根据比较结果,移动记录的位置。
3.对关键字排序的三个原则
(1)关键字值为数值型的,则按键值大小为依据。
(2)关键字值为ASCII码,则按键值的内码编排顺序为依据。
(3)关键字值为汉字字符串类型,则大多以汉字拼音的字典次序为依据。
4.待排序记录的三种存储方式
(1)待排序记录存放在地址连续的一组存储单元上。
(2)待排序记录存放在静态链表中。
(3)待排序记录存放在一组地址连续的存储单元, 同时另设个指示各 个记录存储位置的地址向量,在排序过程中不移动记录本身,而移动地址向量中这些记录的“地址”,在排序结束后,再按照地址向量中的值调整记录的存储位置。
5.内部排序
整个排序过程完全在内存中进行,称为内部排序。
6.外部排序
由于待排序记录数据量太大,内存无法容纳全部数据,排序需要借助外部存储设备才能完成,称为外部排序。
7.稳定排序和不稳定排序
假设Ki=Kj(1≤i≤n, 1≤j≤n, i不等于j),若在排序前的序列中R;领先于R;(即ikj),经过排序后得到的序列中Ri仍领先于Rj则称所用的排序方法是稳定的;反之,若相同关键字的先后次序在排序过程中发生变化,则称所用的排序方法是不稳定的。
定待排序的记录均是以顺序存储结构存放的,且假定记录的关键字均为整数。另外,假定待排序的记录均是以顺序存储结构存放的,且假定记录的关键字均为整数。另外,假定要排序的记录是按递增顺序进行排序。
排序记录的数据类型定义如下:
#include<stdio.h>
#include<stdlib.h>
#define MAX 100 //宏定义
typedef struct {//构造线性表
int data[MAX];
int length;
}sort;
■ 基本操作:有序插入
■ 在有序序列中插入一个元素,保持序列有序,有序长度不断增加。
■ 起初,a[0]是长度为1的子序列。然后,逐一将a[1]至a[n-1]插入到有序子序列中。
■ 在插入a[i]前,数组a的前半段(a[0]~a[i-1])是有序段,
后半段(a[i]~a[n-1])是停留于输入次序的"无序段”。
■ 插入a[i]使a[0]~a[i-1]有序,也就是要为a[i]找到有序位置j (0≤j≤i) ,
将a[i]插入在a[j]的位置上。
■ 直接插入排序——采用顺序查找法查找插入位置
1、复制插入元素 x=a[i];
2、记录后移,查找插入位置 for(j=i-1;j> =0&&x<a[j];j--)a[j+1]=a[j];
3、插入到正确位置 a[j-1]=x;
■ 算法分析
void DirectSort(sort *L){//直接插入排序法 printf("--直接插入排序--\n"); int i,j; for(i=2;i<=L->length;i++){ L->data[0]=L->data[i];//设置哨兵 j=i-1; while(L->data[0]<L->data[j]){ L->data[j+1]=L->data[j]; j=j-1; } L->data[j+1]=L->data[0]; } printf("直接插入排序后的线性表:"); for(i=1;i<L->length;i++){ printf("%5d",L->data[i]); } }
■ 效率分析
从空间角度来看,它只需要一个辅助空间r[0]。
从时间耗费角度来看,主要时间耗费在关键字比较和移动元素上。
向有序表中逐个插入记录的操作,进行了n-1 趟,每趟操作分为比较关键字和移动记录,而比较的次数和移动记录的次数取决于待排序列按关键字的初始排列。
最好情况下:即待排序列已按关键字有序,每趟操作只需1次比较2次移动(仅有的两次移动是将待插入的记录移动到监视哨,再从监视哨移出),算法的比较次数和移动次数分别为:
总比较次数=n-1;总移动次数=2 (n-1)
最坏情况下:如果待排序列是逆序的,将r(心]插入到适合位置,要进行i-1 次关键字的比较,记录移动次数为i- 1+2。算法的比较次数和移动次数达到最大值,分别为:
总比较次数=总移动次数=
因此,直接插入排序的时间复杂度为O(n2)。该算法只使用了存放监视哨的1个附加空间,它的空间复杂度为O(1)。
■ 折半插入排序大至与折半查找一样
void Halfsort(sort *L){//折半排序 printf("\n--折半排序--\n"); Initialization(L); Establish(L); int i,j,low,high,mid; for(i=2;i<L->length;++i){ L->data[0]=L->data[i];//设置哨兵 low=1;high=i-1; while(low<=high){ mid=(low+high)/2; if(L->data[0]<L->data[mid]){ high=mid-1; }else{ low=mid+1; } } for(j=i-1;j>=high+1;--j){ L->data[j+1]=L->data[j]; } L->data[high+1]=L->data[0]; } printf("折半排序后的线性表:"); for(i=1;i<L->length;i++){ printf("%5d",L->data[i]); } }
■ 折半插入排序–算法分析
折半查找比顺序查找快,所以折半插入排序就平均性能来说比直按插入排序要快;
它所需要的关键码比较次数与待排序对象序列的初始排列无关,仅依赖于对象个数。在插入第i个对象时,需要经过Llog2iJ + 1次关键码比较,才能确定它应插入的位置;
当n较大时,总关键码比较次数比直接插入排序的最坏情况要好得多,但比其最好情况要差;
在对象的初始排列已经按关键码排好序或接近有序时,直接插入排序比折半插入排序执行的关键码比较次数要少;
希尔排序又称为缩小增量排序,它也是种插入排序方法, 但在时间上比直接插入排序方法有较大的改进。
(1)希尔排序的基本思想
先将待排序的记录划分为若干子序列分别进行直接插入排序,当记录的排列已经基本有序,最后再对所有的记录进行一次直接插入排序 。希尔排序的具体方法如下:
void ShellSort(sort *L){//希尔排序 printf("\n--希尔排序--\n"); Initialization(L); Establish(L); int i,j,b; b=L->length/2; while(b>0){ for(i=b;i<=L->length;i++){ L->data[0]=L->data[i];//设置哨兵 j=i-b; while(j>=0&&L->data[0]<L->data[j]){ L->data[j+b]=L->data[j]; j=j-b; } L->data[j+b]=L->data[0]; j=j-b; } b=b/2; } printf("希尔排序后的线性表:"); for(i=1;i<L->length;i++){ printf("%5d",L->data[i]); } }
(3)希尔排序算法分析
希尔排序算法效率与增量序列的取值有关
void BubbleSort(Exchange *l){ Initialize(l); Assignment(l); printf("--冒泡排序--\n"); int i,j,team,flat=1; for(i=1;i<=l->length&&flat==1;i++){ flat=0; for(j=0;j<l->length-i;j++){ if(l->data[j]>l->data[j+1]){ team=l->data[j]; l->data[j]=l->data[j+1]; l->data[j+1]=team; flat=1; } } } printf("冒泡排序后的线性表:"); for(i=0;i<l->length;i++){ printf("%5d",l->data[i]); } printf("\n"); }
(3)冒泡排序算法分析
空间效率:O(1)
■ 冒泡排序最好时间复杂度是O(n)
■ 冒泡排序最坏时间复杂度为O(n2)
■ 冒泡排序平均时间复杂度为O(n2)
■ 冒泡排序算法中增加一个辅助空间temp,辅助空间为S(n)=O(1)
■ 冒泡排序是稳定的
■ 基本思想:通过一趟排序,将待排序记录分割成独立的两部分,其, 部分记录的关键字均比另一部分记录的关键字小, 则可分别对这两部分记录进行排序,以达到整个序列有序
■ 具体实现:选定一个中间数作为参考,所有元素与之比较,小的调到其左边,大的调到其右边。
(枢轴)中间数:可以是第一 个数、最后一个数、最中间一个数、任选一个数等。
■ 算法
void QuickSort(Exchange *l,int first,int end){ int team; int i,j,k; i=first;j=end;team=i; while(i<j){ while(i<j&&l->data[i]>=l->data[j]&&i!=0){ k=l->data[i]; l->data[i]=l->data[j]; l->data[j]=k; j--; break; } if(i==((l->length+1)/2)-1){ j--; } while(i<j&&l->data[i]>=l->data[j]){ k=l->data[j]; l->data[j]=l->data[i]; l->data[i]=k; break; } i++; } if(first<i-1){ QuickSort(l,first,i-1); } if(i+1<end){ QuickSort(l,i+1,end); } }
①每一趟的子表的形成是采用从两头向中间交替式逼近法;
②由于每趟中对各子表的操作都相似,可采用递归算法。
■ 快速排序算法分析
(1)基本思想:在待排序的数据中选出最大(小) 的元素放在其最终的位置。
(2)基本操作:
■首先通过n-1次关键字比较,从n个记录中找出关键字最小的记录,将它与第一一个记录交换
■再通过n-2次比较,从剩余的n-1个记录中找出关键字次小的记录,将它与第二个记录交换
■重复上述操作,共进行n-1趟排序后,排序结束
(3)算法
void SelectSort(choice *l){ printf("--直接选择排序--\n"); Establish(l); int i,j,k,x; for(i=1;i<=l->length;i++){ k=i; for(j=i+1;j<=l->length;++j){ if(l->data[j]<l->data[k]){ k=j; } } if(k!=i){ x=l->data[i]; l->data[i]=l->data[k]; l->data[k]=x; } } printf("直接排序后的结果:"); for(i=1;i<l->length;i++){ printf("%5d",l->data[i]); } }
(4)简单选择排序算法分析
时间复杂度
若在输出堆顶的最小值(最大值)后,使得剩余n-1个元素的序列重又建成-个堆,则得到n个元素的次小值(次大值) …如此反复,便能得到一个有序序列,这个过程称之为堆排序。
(1)堆的定义:
若n个元素的序列{a1 a2 … an}满足
则分别称该序列{a1 a2 … an}为小根堆和大根堆。从堆的定义可以看出,堆实质是满足如下性质的完全二叉树:二叉树中任一非叶子结点均小于(大于)它的孩子结点
(2)堆的调整
例3:如何在输出堆顶元素后,调整剩余元素为一个新的堆?
小根堆:
1.输出堆顶元素之后,以堆中最后一个元素替代之;
2.然后将根结点值与左、右子树的根结点值进行比较,并与其中小者进行交换;
3.重复上述操作,直至叶子结点,将得到新的堆,称这个从堆顶至叶子的调整过程为"筛选"
(3)筛选算法
void HeapAdjust(choice *l,int s,int m){
int j;
int tmp=l->data[s];
for(j=2*s;j<=m;j*=2){
if(j<m&&l->data[j]<l->data[j+1]){
++j;
}
if(tmp>=l->data[j]){
break;
}
l->data[s]=l->data[j];
s=j;
}
l->data[s]=tmp;
}
(4)建堆算法
void HeapSort(choice *l){ printf("\n--堆排序--\n"); Establish(l); int i,tmp,n; n=l->length; for(i=n/2;i>=1;i--){ HeapAdjust(l,i,n); } for(i=n;i>=1;i--){ tmp=l->data[1]; l->data[1]=l->data[i]; l->data[i]=tmp; HeapAdjust(l,1,i-1); } printf("堆排序后的结果:"); for(i=1;i<l->length;i++){ printf("%5d",l->data[i]); } }
(5)算法性能分析
排序法 | 最坏所需时间 | 平均所需时间 | 稳定性 | 所需的额外空间 |
---|---|---|---|---|
直接插入 | O(n^2) | O(n^2) | 是 | O(1) |
希尔排序 | O(n^2) | O(n^1.3) | 否 | O(1) |
冒泡排序 | O(n^2) | O(n^2) | 是 | O(1) |
快速排序 | O(n^2) | O(nlog2n) | 否 | O(nlog2n) |
直接选择排序 | O(n^2) | O(n^2) | 是 | O(1) |
堆排序 | O(nlog2n) | O(nlog2n) | 否 | O(1) |
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。