[置顶] 各种排序算法的讲解与实现
排序的分类:
1 内部排序
内部排序:在整个排序过程中不需不访问外存便能完成,称这样的排序问题为内部排序;
1.1 插入排序
插入排序: 将无序序列中的一个或几个记录“插入”到有序的序列中,从而增加记录的有序序列的长度。
主要思想是将第一个元素看做是有序的,从第二个元素起将待排序的元素插入到有序序列中,使序列逐渐扩大,直到所有的元素都插入到有序序类中。
直接插入排序
基本思想是将记录R[i]插入到有序序列R[1..i-1],使记录的有序序列从R[1..i-1]变为R[1..i]。
直接插入排序算法最好情况下的时间复杂度为O(n),最坏情况的时间复杂度和平均时间复杂度为O(n^2)。
#include "stdafx.h" #include "stdafx.h" #include <iostream> using namespace std; void StrInsSort (int number ,int r[]) { int temp; for(int i=1;i<=number;i++) { temp=r[i];int j=i-1; while(temp<r[j]) { r[j+1]=r[j]; j--; } r[j+1]=temp; } } int _tmain(int argc, int argv[]) { std::cout<<"输入数组大小"<<endl; std::cin>> argc; std::cout<<"输入数组内数据"<<endl; for(int j=0;j<argc;j++) { std::cin>>argv[j]; } StrInsSort (argc ,argv); for(int j=0;j<argc;j++) { std::cout << argv[j] << std::endl; } system("pause"); return 0; }
次待排序数组是从0开始的,先假定第0个元素是有序的,从第一个元素起与前边的有序序列进行比较,T先和下标为i-1的数组比较,(生成的整个序列是从小到达排列的)如果
r[i]>r[i-1],则直接放在r[i]的位置,否则如果T<r[i-1],则将将i--,直到找到一个比T小的,把T放在该数的后边;
折半插入排序
折半插入与直接插入比较,当第i 个记录要插入到前i-1个记录序列时,可以利用折半查找方式确定插入位置,以减少比较次数。
折半查找明显减少了关键字的“比较”次数,单记录的移动次数不变,故时间复杂度仍为O(n^2)。
void BinSort (int count, int R[]) { for (int i=1;i<=count;i++) { int left=0; int right=i-1; int temp; while (right>=left) { temp=R[i] ; int mid =(left+right)/2; if (temp>R[mid]) { left=mid+1; } else { right=mid-1; } } for (int j=i-1;j>=right+1;j--) { R[j+1]=R[j]; } R[right+1]=temp; } };
函数写过程中,首先确定有多少个元素要参加排序,由于次数组是从下表0开始的,所以和直接插入排序一样,先假设第0个元素是有序的,则从下标为1的元素开始执行循环,即从1到count个元素都要执行对比循环过程;其循环内部基本思路是,把取到的第i个元素插入到前0到i-1个元素中,因为前i个元素是有序的,所以二分查找是拿第i元素和和前面有序数列中的第mid =(left+right)/2个元素比较,即有序数列中中间的那个元素,因为排序后要生成一个从小到大排序的数列,即升序数列,所以如果temp>R[mid]如下图所示,
0 | 2 | 4 | 6 | 7 | 9 | 11 |
则要是插入 temp后整个数列依然有序,则temp必须在6的后面,所以要最左侧的数为left=mid+1;才能保证在后半部分寻找数据;
然后就要判断循环什么时候终止,因为在整个循环过程中不断缩小循环的范围,即left和right的距离越来越近,当left==right时,这时mid==left==righ, 这时,
如果
if (temp>R[mid])
{
left=mid+1;
}
如上图所示,mid在6的位置,此时的temp是>6而<7 的,应该把>=left的或>=mid+1的或>=right+1的元素向后移一位;
而如果
else
{
right=mid-1;
}
如上图所示,mid应该<6且>4,即放在4和6之间,这样更应该把>=left的后>=mid的或>=right+1的元素向后移一位;
for (int j=i-1;j>=right+1;j--)
{
R[j+1]=R[j];
} 即对应这段程序;
经过上边分析,同样也可以写成下面程序,即把>=right+1,改成>=left;
for (int j=i-1;j>=left;j--)
{
R[j+1]=R[j];
}
R[left]=temp;
二路插入排序
基本思想是另设一数组d,将R[1]复制给d[1],并将d[1]看做排好序的“中间”记录,从第二个起依次将关键字小于d[1]的记录插入到d[1]之前的有序序列中,将关键字大于d[1]的记录插入到d[1]之后的有序序列中。这里借助两个变量first和final来指示排序过程中有序序列第一个记录和最后一个记录在d中的位置。
int BiInsertSort() { //二路插入排序算法 int iRawBuff[6] ={0,9,6,7,3,2}; int final = 0; int first = 0; const int iLenght = 5; int iTempBuff[iLenght] = {0}; iTempBuff[0] = iRawBuff[0]; for (int i = 1; i <= iLenght;i++) { if(iRawBuff[i] > iTempBuff[final]) { //大于当前最大值,后插 final++; iTempBuff[final] = iRawBuff[i]; } if(iRawBuff[i]< iTempBuff[first]) { //小于当前最小值,前插 first = (first-1+iLenght)%iLenght; iTempBuff[first] = iRawBuff[i]; } if(iRawBuff[i] < iTempBuff[final]&&iRawBuff[i] > iTempBuff[first]) { //大于当前最小值,小于当前最大值,中间插 int j = final++; while (iRawBuff[i] < iTempBuff[j]) { iTempBuff[(j+1)%iLenght] = iTempBuff[j]; j = (j-1+iLenght)%iLenght; } iTempBuff[j+1] = iRawBuff[i]; } printf("第%d趟:\n",i-1); for(int k = 0; k < iLenght; k++) { std::cout<<iTempBuff[k]<<"\t"; } std::cout<<std::endl; } //导入输入到原始数组中 for (int k = 0; k < iLenght; k++) { iRawBuff[k+1] = iTempBuff[(first++)%iLenght]; } return 0; }
运行结果:
由于first在增加增加的过程中,没有最大值的限制,为了防止生成的数组发生越界,所以对这些数取iLenght的余数,即用%iLenght。
表插入排序
为了减少在排序过程中“移动”记录的操作,必须改变排序过程中采用的存储结构。利用静态链表进行排序,并在排序之后,一次性地调整各个记录之间的位置,即将每个记录都调整到他们应该在的位置上,这样的排序方法称为表插入排序。
基本思想:
使头结点的next域始终指示最小的那个元素,然后依次向下:每一个元素的next域都指示比它稍大的那个元素。最大的元素的next域指示头结点。
下面分三步给出具体的代码:
1、用数组初始化表结构。
2、修改next域形成有序的循环链表。
3、根据next域信息调整表结构中的数组,是数据从小到大排列。
#include "stdafx.h" #define INT_MAX 100 #define number 100 #include<iostream> #include<time.h> #include<stdlib.h> #include<iomanip> #include<math.h> //排序后按从小到大输出 using namespace std; typedef struct { int key; int other; //记录其他数据域 int next; }STListType; STListType SL[number+1]; void ListInsSort(STListType SL[],int n) { //对记录序列SL[1..n]进行表插入排序 SL[0].key = 10000; SL[0].next=1; SL[1].next=0; //初始化,假定第一个记录有序 int k; for (int i=2; i<n ; i++) //查找插入位置 { int j=0; for (k=SL[0].next;SL[k].key<SL[i].key;) //保证[k].key>=[i].key { j=k, k=SL[k].next; //保证是SL[k]里的总是最大的 } SL[j].next=i; //结点i插入在结点j和结点k之间 SL[j]<=SL[i]<=SL[k] SL[i].next =k; cout<<"ListInsSort-key:\n"; for(int count=0;count<n;count++) cout<<setw(4)<<SL[count].key; cout<<endl; cout<<"ListInsSort-next:\n"; for(int count=0;count<n;count++) cout<<setw(4)<<SL[count].next; cout<<endl; } } void Arrange(STListType SL[],int n) { //根据静态表SL中各节点的指针值调整记录位置,使得SL中的记录关键字非递减有序顺序排列 //p指示第i个记录的当前位置;i指示第i个记录应在的位置;q指示第i+1个记录的当前位置; int p=SL[0].next; //p指示第一个记录的当前位置 int q; for (int i=1;i<n;i++) { //SL[1..i-1]中的记录关键字有序排列,第i个记录在SL中的当前位置应不小于i //为了保证<i 的元素都是有序的 while(p<i) //找到第i个记录,并用p指示其在SL中的当前位置 p=SL[p].next; q=SL[p].next; //q指示尚未调整的表尾 if (p!=i) { STListType temp; temp = SL[p]; SL[p] = SL[i]; SL[i] = temp; //交换记录,使第i个记录到位 SL[i].next=p; //指向被移走的记录,使得以后可以由while循环找到 } p=q; //p指示尚未调整的表尾,为找第i+1个记录作准比 cout<<"Arrange-key:\n"; for(int count=0;count<n;count++) cout<<setw(4)<<SL[count].key; cout<<endl; cout<<"Arrange-next:\n"; for(int count=0;count<n;count++) cout<<setw(4)<<SL[count].next; cout<<endl; } } int _tmain() { STListType SL[100]; int n; cout<<"ListInsSort.cpp运行结果:\n"; int b[100],i; srand(time(0)); cout<<"输入待排序元素个数n:";cin>>n; for(i=1;i<n;i++) { b[i]=rand()%100; SL[i].key=b[i]; } cout<<"排序前数组:\n"; for(i=1;i<n;i++) cout<<setw(4)<<b[i]; cout<<endl; ListInsSort(SL,n); Arrange(SL,n); cout<<"排序后数组:\n"; for(i=1;i<n;i++) cout<<setw(4)<<SL[i].key; cout<<endl; system("pause"); }
希尔插入排序
希尔排序又称缩小增量排序,(适用于待排序数列很无序的情况下);基本思想是将待排序的记录划分成几组,从而减少参与直接插入排序的数据量,经过几次分组排序后,记录的排序已经基本有序,在对所有的记录实施最后的直接插入排序。
对于希尔排序,具体步骤可以描述如下:假设待排序的记录为n个,先取整数d<n,例如d=n/2,将所有距离为d的记录构成一组,从而将整个待排序记录分割成为d个子序列(d组),对每个分组进行直接插入排序,然后再缩小间隔d,例如,取d=d/2,重复上述分组,再对每个分组分别进行直接插入排序,直到最后取d=1,即将所有的记录放在一组进行一次直接插入排序,最终将所有记录重新排列成按关键字有序的序列。
Shell提出的选法:d1=n/2 , di+1=di/2
#include<iostream> #include<iomanip> #include<stdlib.h> #include<time.h> using namespace std; #define MAXI 11 typedef int KeyType; typedef int ElemType; struct rec { KeyType key; ElemType data; }; typedef rec sqlist[MAXI]; void shellsort(sqlist b,int n) { int i,j,gap,k; rec x; gap=n/2; while(gap>0) { for(i=gap+1;i<n;i++) { j=i-gap; while(j>0) if(b[j].key>b[j+gap].key) { x=b[j]; b[j]=b[j+gap]; b[j+gap]=x; j=j-gap; } else j=0; for(k=1;k<n;k++) cout<<setw(4)<<b[k].key; cout<<endl; } gap=gap/2; } } void main() {cout<<"运行结果:\n"; sqlist a;int i,n=MAXI; srand(time(0)); for(i=1;i<n;i++) {a[i].key=rand()%80; a[i].data=rand()%100;} cout<<"排序前数组:\n"; for(i=1;i<n;i++) cout<<setw(4)<<a[i].key; cout<<endl; cout<<"数组排序过程演示:\n"; shellsort(a,n); cout<<"排序后数组:\n"; for(i=1;i<n;i++) cout<<setw(4)<<a[i].key; cout<<endl;cin.get();}
1.2 交换排序
交换排序:通过“交换”无序序列中的相邻记录从而得到其中关键字最小或最大的记录,并将他们加入到有序序列中,以增加记录的有序序列长度。
主要思想是在排序过程中,通过比较待排序记录序列中元素的关键字,如果发现次序相反,则将存储位置交换来达到排序目的。
起泡排序
它的基本思想是对所有相邻记录的关键字值进行比较,如果是逆序(a[j]>a[j+1]),则将其交换,最终达到有序。
#include "stdafx.h" #include "stdafx.h" #include <iostream> using namespace std; int bubbleSort (int number ,int r[]) { int temp; for (int i =0;i<=number-1;i++) { for (int j=0;j<=number-i-1;j++) { if (r[j]>r[j+1]) { temp=r[j]; r[j]=r[j+1]; r[j+1]=temp; } } } return 1; } int _tmain(int argc, int argv[]) { std::cout<<"输入数组大小"<<endl; std::cin>> argc; std::cout<<"输入数组内数据"<<endl; for(int j=0;j<argc;j++) { std::cin>>argv[j]; } bubbleSort (argc ,argv); for(int j=0;j<argc;j++) { std::cout << argv[j] << std::endl; } system("pause"); return 0; }
在排序过程中,比较相邻的元素,如果第前个比后一个大,就交换他们两个。对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。在这一点,最后的元素应该会是最大的数。针对所有的元素重复以上的步骤,除了最后一个。
for (int i =0;i<=number-1;i++)循环是指(一个元素向上冒泡,直到它找到合适的位置)这个过程的次数,即有多少个元素要进行冒泡操作;冒到上边的是最大的;
for (int j=0;j<=number-i-1;j++)循环是指进行冒泡操作的每一个元素,要经过做少次对比,才能找到合适的位置;
快速排序
快速排序的基本思想:首先将待排序记录序列中的所有记录作为当前待排序区域,从中任选一个记录(通常可选取第一个记录),以它的关键字作为枢纽,凡其关键字小于枢纽的记录均移到该记录之前,反之,凡关键字大于枢纽的记录均移至该纪录之后,这样一趟排序之后,记录的无序序列R[s..t]将分割成两部分:R[s..i-1]和R[i+1..t],且R[j].key<=R[i].key<=R[k].key
#include "stdafx.h" #include "stdafx.h" #define number 100 #include<iostream> #include<time.h> #include<stdlib.h> #include<iomanip> #include<math.h> //排序后按从小到大输出 using namespace std; typedef struct { int key; int other; //记录其他数据域 int next; }RecType; RecType S[number+1]; int Partirion(RecType R[],int l,int h) { //交换记录子序列R[1..h]中的记录,使枢纽记录交换到正确的位置,并返回其所在的位置 int i=l; int j=h; //用变量i,j记录待排序记录的首尾位置 R[0]=R[i]; //以子表的第一个记录作为枢轴,将其暂存到记录R[0]中 int x=R[i].key; //用变量x存放枢纽记录的关键字 while (i<j) { //从表的两端交替地向中间扫描 while (i<j&&R[j].key>=x) { j--; } R[i]=R[j]; //将比枢纽小的记录移到低端 while (i<j&&R[i].key<=x) { i++; } R[j]=R[i]; //将比枢纽大的记录移到高端 } R[i]=R[0]; //枢纽记录到位 return i; //返回枢纽位置 } void QuickSort(RecType R[],int s,int t) { //对记录序列R[s..t]进行快速排序 if (s<t) { int k,i; k=Partirion(R,s,t); QuickSort(R,s,k-1); QuickSort(R,k+1,t); cout<<"QuickSort:\n"; for(i=2;i<=t;i++) cout<<setw(4)<<R[i].key; cout<<endl; } } int _tmain() { RecType SL[100]; int n; cout<<"QuickSort.cpp运行结果:\n"; int b[100],i; srand(time(0)); cout<<"输入待排序元素个数n:";cin>>n; for(i=1;i<n;i++) { b[i]=rand()%100; SL[i].key=b[i]; } cout<<"排序前数组:\n"; for(i=1;i<n;i++) cout<<setw(4)<<b[i]; cout<<endl; QuickSort(SL,1,n); cout<<"排序后数组:\n"; for(i=2;i<=n;i++) cout<<setw(4)<<SL[i].key; cout<<endl; system("pause"); }
运行结果:
1.3 选择排序
选择排序:从记录的无序序列中“选择”关键字最小或最大的记录,并将他加入到有序子序列中,以增加记录的有序序列的长度。
基本思想是依次从待排序记录中选择出关键字值最小(或最大)的记录、关键字值次之的记录、...并分别将它们定位到序列左侧(或右侧)的第1位置、第2位置、...,从而使待排序的记录序列成为按关键字值由小到大(或有大到小)排列的有序序列。
直接选择排序
有序序列中所有记录的关键字均小于无序序列中记录的关键字,则第i趟直接选择排序是从无序序列R[i..n]的n-i+1个记录中选择关键字最小的记录加入有序序列的末尾。
#include "stdafx.h" #define number 100 #include<iostream> #include<time.h> #include<stdlib.h> #include<iomanip> #include<math.h> //排序后按从小到大输出 using namespace std; typedef struct { int key; int other; //记录其他数据域 int next; }RecType; RecType S[number+1]; void SelectSort(RecType R[],int n) { //对记录序列R[1..n]进行直接选择排序 for (int i=1;i<n;i++) { int k=i; for (int j=i+1;j<=n;j++) { if (R[k].key>R[j].key) {k=j;} } if (i!=k) { RecType temp; temp=R[i]; R[i]=R[k]; R[k]=temp; } } } int _tmain() { RecType SL[100]; int n; cout<<"SelectSort.cpp运行结果:\n"; int b[100],i; srand(time(0)); cout<<"输入待排序元素个数n:"; cin>>n; for(i=1;i<n;i++) { b[i]=rand()%100; SL[i].key=b[i]; } cout<<"排序前数组:\n"; for(i=1;i<n;i++)cout<<setw(4)<<b[i]; cout<<endl;SelectSort(SL,n); cout<<"排序后数组:\n"; for(i=2;i<=n;i++) cout<<setw(4)<<SL[i].key; cout<<endl; system("pause"); }
树形选择排序
基本思想:首先是对n个待排序记录的关键字进行两两比较,从中选出[n/2]个较小者再两两比较,直到选出关键字最小的记录为止,此为一趟排序。
#include "stdafx.h" //锦标赛排序法 #include<iostream> #include<iomanip> #include<math.h> #include<stdlib.h> #include<time.h> using namespace std; class DataNode //胜者树结点的类定义 {public: int data;//数据值 int index;//树中的结点号 int active;//参选标志 }; //锦标赛排序中的调整算法;i是表中当前 //最小元素的下标,即胜者.从它开始向上调整 void UpdataTree(DataNode *tree,int i) {int j; if(i%2==0) //i为偶数,对手为左结点 tree[(i-1)/2]=tree[i-1];//i为奇数,对手为右结点 else tree[(i-1)/2]=tree[i+1]; i=(i-1)/2; //i上升到双亲结点位置 while(i) {if(i%2==0) j=i-1;//确定i的对手为左结点还是右结点 else j=i+1; if(!tree[i].active||!tree[j].active)//比赛对手中间有一个空 if(tree[i].active) tree[(i-1)/2]=tree[i]; else tree[(i-1)/2]=tree[j]; //非空者上升到双亲结点 else //比赛对手都不为空 if(tree[i].data<tree[j].data) tree[(i-1)/2]=tree[i]; else tree[(i-1)/2]=tree[j];//胜者上升到双亲结点 i=(i-1)/2; //i上升到双亲结点 }} //建立树的顺序存储数组tree,将数组a[]中的元素复制到胜者树中, //对它们进行排序,并把结果返回数组中,n是待排序元素个数 void TournmentSort(int a[],int n) {DataNode *tree; //胜者树结点数组 DataNode item; int m,i,j=0; for(int k=0;k<n;k++)//计算满足>=n的2的最小次幂的数 { m=(int)pow((double) 2,(double)k); if(m>=n) break; } int bottomRowSize=m; int TreeSize=2*bottomRowSize-1;//计算胜者树的大小:内结点+外结点数 int loadindex=bottomRowSize-1;//外结点开始位置 tree=new DataNode[TreeSize]; //动态分配胜者树结点数组空间 for(i=loadindex;i<TreeSize;i++)//复制数组数据到树的外结点中 {tree[i].index=i;//下标 if(j<n) {tree[i].active=1;tree[i].data=a[j++];}//复制数据 else tree[i].active=0; //后面的结点为空的外结点 } i=loadindex; //进行初始比较寻找最小的项 while(i) {j=i; while(j<2*i) //处理各对比赛者 {if(!tree[j+1].active||tree[j].data<tree[j+1].data) tree[(j-1)/2]=tree[j]; else tree[(j-1)/2]=tree[j+1];//胜者送入双亲 j+=2; //下一对参加比较的项 } i=(i-1)/2;//i退到双亲,直到i=0为止 } for(i=0;i<n-1;i++)//处理其他n-1个元素 {a[i]=tree[0].data;//当前最小元素送数组a tree[tree[0].index].active=0;//该元素相应外结点不再比赛 UpdataTree(tree,tree[0].index);//从该处向上修改 } a[n-1]=tree[0].data; } //锦标赛排序法的测试 void main() {cout<<"JinBiaoSai.cpp运行结果:\n"; int n,b[100],i; srand(time(0)); cout<<"输入待排序元素个数n:";cin>>n; for(i=0;i<n;i++) b[i]=rand()%100; cout<<"排序前数组:\n"; for(i=0;i<n;i++) cout<<setw(4)<<b[i]; cout<<endl; TournmentSort(b,n); cout<<"排序后数组:\n"; for(i=0;i<n;i++) cout<<setw(4)<<b[i]; cout<<endl; cin.get();cin.get(); }
树形选择排序:
#include "stdafx.h" #include<iostream> #include<string.h> #include<ctype.h> #include<malloc.h> #include<limits.h> #include<stdio.h> #include<stdlib.h> #include<io.h> #include<math.h> using namespace std; #define MAX_SIZE 20 typedef int KeyType; typedef int InfoType; //定义其他数据类型 struct RedType{ KeyType key; InfoType otherinfo; }; struct SqList { RedType r[MAX_SIZE]; int length; }; void print(SqList L) { int i; for(i=1;i<=L.length;i++) cout<<"("<<L.r[i].key<<","<<L.r[i].otherinfo<<")"; cout<<endl; } void TreeSort(SqList &L) { int i,j,j1,k,k1,l; float n=L.length; RedType *t; l=(int)ceil(log(n)/log(2.0))+1; //完全二叉树的层数;ceil取上整,返回比x大的最小整数 k=(int)pow(2.0,l)-1; //k为l层完全二叉树的结点总数 k1=(int)pow(2.0,l-1)-1; //k1为l-1层二叉完全二叉树的结点总数 t=(RedType*)malloc(k*sizeof(RedType)); //二叉树采用顺序存储 for(i=1;i<=n;i++) //将L.r赋值给叶子结点 t[k1+i-1]=L.r[i]; for(i=k1+n;i<k;i++) //给多余的叶子结点的关键字赋无穷大值 t[i].key=INT_MAX; j1=k1; j=k; while(j1) { //给非叶子结点赋值 for(i=j1;i<j;i+=2) { t[i].key<t[i+1].key?(t[(i+1)/2-1]=t[i]):(t[(i+1)/2-1]=t[i+1]); } j=j1; j1=(j1-1)/2; cout<<"给非叶子结点赋值:"<<endl; //循环一次,找到最小的 print(L); } for(i=0;i<n;i++) { L.r[i+1]=t[0]; //键当前最小值赋给L.r[i] j1=0; for(j=1;j<l;j++) //沿树根找到结点t[0]在叶子结中的序号j1 t[2*j1+1].key==t[j1].key?(j1=2*j1+1):(j1=2*j1+2); t[j1].key=INT_MAX; while(j1) { j1=(j1+1)/2-1; //序号为j1的节点的双亲节点序号 t[2*j1+1].key<=t[2*j1+2].key?(t[j1]=t[2*j1+1]):(t[j1]=t[2*j1+2]); } cout<<"循环排序:"<<endl; print(L); } free(t); } #define N 8 void main() { RedType d[N]={{49,1},{38,2},{65,3},{97,4},{76,5},{13,6},{27,7},{49,8}}; SqList l; int i; for(i=0;i<N;i++) l.r[i+1]=d[i]; l.length=N; cout<<"排序前:"<<endl; print(l); TreeSort(l); cout<<"排序后:"<<endl; print(l); system("pause"); }
排序过程中t[k1+i-1]=L.r[i];把L里的数据存储t[k1+i-1]中,为排序需要
k=(int)pow(2.0,l)-1; //k为l层完全二叉树的结点总数
t=(RedType*)malloc(k*sizeof(RedType)); //二叉树采用顺序存储
的存储空间;
运行结果:
堆排序
其左右子树分别是堆,任何一个结点的值不大(或不小于)左右孩子节点,则最顶端元素必是序列中的最大值或最小值,分别称为小顶堆或大顶堆(小堆或大堆)。堆排序是利用堆的特性对记录序列进行排序的一种排序算法。其基本思想是:先建一个堆,即先选一个关键字最大或最小的记录,然后与序列中的最后一个记录交换,之后将序列中前n-1个记录重新调整为一个堆(调堆的过程称为“筛选”),再将堆顶记录和第n-1个记录交换,如此反复至排序结束。注意:第i 次筛选的前提是从2到n-i 是一个堆,即筛选是对一棵左/右子树均为堆的完全二叉树“调整”根节点使整棵二叉树为堆的过程。
#include "stdafx.h" #include <iostream> using namespace std; void sift(int R[],int i,int m) //R[]要排序的数组;i是指是从堆的第几行的首元素 即i=1 2 4 8 16...;m是指数组中一共多少个元素 { //设R[i+1..m]中各元素满足堆的定义,本算法调整R[i]使序列R[i..m]中的元素满足堆的性质 int temp = R [i]; for (int j=2*i;j<=m;j*=2) { //在堆的同一层进行比较,如果右边的大于左边的,则继续向右查找 ,为了保证下面和temp比较的R[j],是这一层上最大的一个 if ((j<m)&&(R[j]<R[j+1])) { j++; } if (temp<R[j]) //temp和每一层最大的数比较,如果小于最大的,则把最大的的值赋给R[i] { R[i]=R[j]; i=j; //修改i的值,此时R[i]=R[j]的值相同 } else { break; } } R[i]=temp; //最初要调整的节点放在正确的位置 } void HeapSort( int R[],int n) { int i ; int nCreateTime=1; for (i=n/2;i>0;i--) { sift(R,i,n); //建堆时i从n/2起递减,是指i从从最底层起依次循环向顶层排序 std::cout<<"建堆"<<nCreateTime<<endl; for(int j=1;j<9;j++) { std::cout << R[j]; } nCreateTime++; std::cout<<endl; } int nAdjustTime=1; for (i=n;i>1;i--) { int temp ; temp=R[i]; R[i]=R[1]; R[1]=temp; sift(R,1,i-1); std::cout<<"重新调整"<<nAdjustTime<<endl; for(int j=1;j<9;j++) { std::cout << R[j]; } nAdjustTime++; std::cout<<endl; } } int _tmain(int argc, _TCHAR* argv[]) { int data[9]={0,6,9,3,1,5,8,9}; std::cout<<"初始序列"<<endl; for(int j=1;j<9;j++) { std::cout << data[j]; } std::cout<<endl; HeapSort( data,8); std::cout<<"生成的有序序列"<<endl; for(int j=1;j<9;j++) { std::cout << data[j]; } std::cout<<endl; system("pause"); return 0; }
运行结果:
1.4 归并排序
归并排序:通过“归并”两个或两个以上的有序序类,逐步增加有序序列的长度。
归并排序 2-路归并排序
其基本思想是:将一个具有n个待排序记录的序列看成是n个长度为1的有序序列,然后进行两两归并,得到n/2个长度为2的有序序列,在进行两两归并,得到n/4个长度为4的有序序列,如此重复,直至得到一个长度为n的有序序列为止。
#include "stdafx.h" #define number 100 #include<iostream> #include<time.h> #include<stdlib.h> #include<iomanip> #include<math.h> //排序后按从小到大输出 using namespace std; typedef struct { int key; int other; //记录其他数据域 int next; }RecType; RecType S[number+1]; void Merge(RecType R[],RecType R1[],int i,int l,int h) { //将有序的R[i..l]和R[l+1..h]归并为有序的R1[i..h] int j; int k; for (j=l+1,k=i;i<=l && j<=h;k++) { //将R[]中记录由小到大地并入R1 if (R[i].key<=R[j].key) { R1[k]=R[i++]; } else { R1[k]=R[j++]; } } //for if(i<=l) { R1[k++]=R[i++]; } if(j<=h) { R1[k++]=R[j++]; //将剩余的R[j..h]复制到R1 } }//Merge void Msort(RecType R[],RecType R1[],int s,int t) { //将R[s..t]2-路归并排序为R1[s..t] RecType R2[100]; if (s==t) { R1[s]=R[s]; } else { int m=(s+t)/2; //将R[s..t]平分为R[s..m]和R[m+1..t] Msort(R,R2,s,m); //递归的将R[s..m]归并为有序的R2[s..m] Msort(R,R2,m+1,t); //递归的将R[m+1..t]归并为有序的R2[m+1..t] Merge(R2,R1,s,m,t); //将R2[s..m]和R2[m+1..t]归并到R1[s..t] }//if }//Msort int _tmain() { int n; cout<<"MergingSort.cpp运行结果:\n"; int b[100],i; RecType R1[100]; srand(time(0)); cout<<"输入待排序元素个数n:";cin>>n; RecType SL[100]; for(i=1;i<=n;i++) { b[i]=rand()%100; SL[i].key=b[i]; } cout<<"排序前数组:\n"; for(i=1;i<=n;i++) cout<<setw(4)<<b[i]; cout<<endl; Msort(SL,R1,1,n); cout<<"排序后数组:\n"; for(i=1;i<=n;i++) cout<<setw(4)<<R1[i].key; cout<<endl; system("pause"); }
1.5 分配排序
前面讨论的排序算法都是基于关键字之间的比较,通过比较判断出大小,然后进行调整。而分配排序则不然,它是利用关键字的结构,通过“分配”和“收集”的办法来实现排序。
分配排序:通过对无序序列中的记录进行反复的“分配”和“收集”操作,逐步是无序序列变为有序序列。
分配排序分为:桶排序和基数排序两类。
计数排序
计数排序的过程类似小学选班干部的过程,如某某人10票,作者9票,那某某人是班长,作者是副班长
大体分两部分,第一部分是拉选票和投票,第二部分是根据你的票数入桶
看下具体的过程,一共需要三个数组,分别是待排数组,票箱数组,和桶数组
var unsorted = new int[] { 6, 2, 4, 1, 5, 9 }; //待排数组
var ballot = new int[unsorted.Length]; //票箱数组
var bucket = new int[unsorted.Length]; //桶数组
最后再看桶数组,先看待排数组和票箱数组
初始状态,迭代变量i = 0时,待排数组[i] = 6,票箱数组[i] = 0,这样通过迭代变量建立了数字与其桶号(即票数)的联系
待排数组[ 6 2 4 1 5 9 ] i = 0时,可以从待排数组中取出6
票箱数组[ 0 0 0 0 0 0 ] 同时可以从票箱数组里取出6的票数0,即桶号
拉选票的过程
首先6出列开始拉选票,6的票箱是0号,6对其它所有数字说,谁比我小或与我相等,就给我投票,不然揍你
于是,2 4 1 5 分别给6投票,放入0号票箱,6得四票
待排数组[ 6 2 4 1 5 9 ]
票箱数组[ 4 0 0 0 0 0 ]
接下来2开始拉选票,对其它人说,谁比我小,谁投我票,不然弄你!于是1投了一票,其他人比2大不搭理,心想你可真二
于是2从1那得到一票
待排数组[ 6 2 4 1 5 9 ]
票箱数组[ 4 1 0 0 0 0 ]
再然后是,
4得到2和1的投票,共计两票
1得到0票,没人投他
5得到2,4,1投的三张票
9是最大,得到所有人(自己除外)的投票,共计5票(数组长度-1票)
投票完毕时的状态是这样
待排数组[ 6 2 4 1 5 9 ]
票箱数组[ 4 1 2 0 3 5 ]
入桶的过程
投票过程结束,每人都拥有自己的票数,桶数组说,看好你自己的票数,进入与你票数相等的桶,GO
6共计5票,进入5号桶
2得1票,进入1号桶,有几票就进几号桶
4两票,进2号桶,5三票进3号桶,9有5票,进5号桶
待排数组[ 6 2 4 1 5 9 ]
票箱数组[ 4 1 2 0 3 5 ]
-----------------------
入桶前 [ 0 1 2 3 4 5 ] //里边的数字表示桶编号
入桶后 [ 1 2 4 5 6 9 ] //1有0票,进的0号桶
排序完毕,顺序输出即可[ 1 2 4 5 6 9]
可以看到,数字越大票数越多,9得到除自己外的所有人的票,5票,票数最多所以9最大,
每个人最多拥有[数组长度减去自己]张票
1票数最少,所以1是最小的
#include "stdafx.h" #include <iostream> #include <cstdio> #include <cstdlib> #include <cmath> #include <cstring> #include <stdlib.h> #include<time.h> #include<iomanip> #include<math.h> using namespace std; void CountingSort(int *A,int *B,int *Order,int N,int K) { int *C=new int[K+1]; int i; memset(C,0,sizeof(int)*(K+1)); for (i=1;i<=N;i++) //把A中的每个元素分配 C[A[i]]++; for (i=2;i<=K;i++) //统计不大于i的元素的个数 { C[i]+=C[i-1]; } for (i=N;i>=1;i--) { B[C[A[i]]]=A[i]; //按照统计的位置,将值输出到B中,将顺序输出到Order中 Order[C[A[i]]]=i; C[A[i]]--; } } int main() { int *A,*B,*Order,N=15,K=10,i; A=new int[N+1]; B=new int[N+1]; Order=new int[N+1]; for (i=1;i<=N;i++) A[i]=rand()%K+1; //生成1..K的随机数 cout<<"Before CS:\n"; for (i=1;i<=N;i++) cout<<setw(4)<<A[i]; cout<<endl; CountingSort(A,B,Order,N,K); printf("\nAfter CS:\n"); for (i=1;i<=N;i++) cout<<setw(4)<<B[i]; cout<<endl; cout<<"\nOrder:\n"; for (i=1;i<=N;i++) cout<<setw(4)<<Order[i]; cout<<endl; system("pause"); return 0; }
运行结果:
桶排序
桶排序(Bucket Sort)也称箱排序(Bin Sort),其基本思想是:设置若干个桶,依次扫描待排序记录R[1..n],把关键字等于k的记录全部都装到第k个桶里(分配),按序号依次将各非空的桶首尾连接起来(收集)。桶的个数取决于关键字的取值范围。
桶排序 (Bucket sort)或所谓的箱排序,是一个排序算法,工作的原理是将数组分到有限数量的桶子里。
基本思路:
1.设置一个定量的数组当作空桶子。
2.寻访串行,并且把项目一个一个放到对应的桶子去。
3.对每个不是空的桶子进行排序。
4.从不是空的桶子里把项目再放回原来的串行中。
无序数组有个要求,就是成员隶属于固定(有限的)的区间,如范围为[0-9](考试分数为1-100等)
例如待排数字[6 2 4 1 5 9]
准备10个空桶,最大数个空桶
[6 2 4 1 5 9] 待排数组
[0 0 0 0 0 0 0 0 0 0] 空桶
[0 1 2 3 4 5 6 7 8 9] 桶编号(实际不存在)
1,顺序从待排数组中取出数字,首先6被取出,然后把6入6号桶,这个过程类似这样:空桶[ 待排数组[ 0 ] ] = 待排数组[ 0 ]
[6 2 4 1 5 9] 待排数组
[0 0 0 0 0 0 6 0 0 0] 空桶
[0 1 2 3 4 5 6 7 8 9] 桶编号(实际不存在)
2,顺序从待排数组中取出下一个数字,此时2被取出,将其放入2号桶,是几就放几号桶
[6 2 4 1 5 9] 待排数组
[0 0 2 0 0 0 6 0 0 0] 空桶
[0 1 2 3 4 5 6 7 8 9] 桶编号(实际不存在)
3,4,5,6省略,过程一样,全部入桶后变成下边这样
[6 2 4 1 5 9] 待排数组
[0 1 2 0 4 5 6 0 0 9] 空桶
[0 1 2 3 4 5 6 7 8 9] 桶编号(实际不存在)
0表示空桶,跳过,顺序取出即可:1 2 4 5 6 9
/* 桶排序实现 */ #include "stdafx.h" #include <iostream> #include <cstdio> #include <cstdlib> #include <cmath> #include <cstring> #include <stdlib.h> #include<time.h> #include<iomanip> #include<math.h> using namespace std; void BucketSort(int *A,int *B,int N,int K) { int *C=new int[K+1]; int i,j,k; memset(C,0,sizeof(int)*(K+1)); for (i=1;i<=N;i++) //把A中的每个元素按照值放入桶中 C[A[i]]++; for (i=j=1;i<=K;i++,j=k) //统计每个桶元素的个数,并输出到B for (k=j;k<j+C[i];k++) B[k]=i; } int main() { int *A,*B,N=100,K=100,i; A=new int[N+1]; B=new int[N+1]; cout<<"排序前:"<<endl; for (i=1;i<=N;i++) { A[i]=rand()%K+1; //生成1..K的随机数 cout<<setw(4)<<A[i]; } cout<<endl; BucketSort(A,B,N,K); cout<<"排序后:"<<endl; for (i=1;i<=N;i++) { cout<<setw(4)<<B[i]; } cout<<endl; system("pause"); return 0; }
运行结果:
这种特殊实现的方式时间复杂度为O(N+K),空间复杂度也为O(N+K),同样要求每个元素都要在K的范围内。更一般的,如果我们的K很大,无法直接开出O(K)的空间该如何呢?
首先定义桶,桶为一个数据容器,每个桶存储一个区间内的数。依然有一个待排序的整数序列A,元素的最小值不小于0,最大值不超过K。假设我们有M个桶,第i个桶Bucket[i]存储iK/M至(i+1)K/M之间的数,有如下桶排序的一般方法:
1.扫描序列A,根据每个元素的值所属的区间,放入指定的桶中(顺序放置)。
2.对每个桶中的元素进行排序,什么排序算法都可以,例如快速排序。
3.依次收集每个桶中的元素,顺序放置到输出序列中。
对该算法简单分析,如果数据是期望平均分布的,则每个桶中的元素平均个数为N/M。如果对每个桶中的元素排序使用的算法是快速排序,每次排序的时间复杂度为O(N/Mlog(N/M))。则总的时间复杂度为O(N)+O(M)O(N/Mlog(N/M)) = O(N+ Nlog(N/M)) = O(N + NlogN - NlogM)。当M接近于N是,桶排序的时间复杂度就可以近似认为是O(N)的。就是桶越多,时间效率就越高,而桶越多,空间却就越大,由此可见时间和空间是一个矛盾的两个方面。
/*桶排序实现 */ #include "stdafx.h" #include <iostream> #include <cstdio> #include <cstdlib> #include <cmath> #include <cstring> #include <stdlib.h> #include<time.h> #include<iomanip> #include<math.h> using namespace std; struct linklist { linklist *next; int value; linklist(int v,linklist *n):value(v),next(n){} ~linklist() {if (next) delete next;} }; inline int cmp(const void *a,const void *b) { return *(int *)a-*(int *)b; } /* 为了方便,我把A中元素加入桶中时是倒序放入的,而收集取出时也是倒序放入序列的,所以不违背稳定排序。 */ void BucketSort(int *A,int *B,int N,int K) { linklist *Bucket[101],*p; //建立桶 int i,j,k,M; M=K/100; memset(Bucket,0,sizeof(Bucket)); for (i=1;i<=N;i++) { k=A[i]/M; //把A中的每个元素按照的范围值放入对应桶中 Bucket[k]=new linklist(A[i],Bucket[k]); } for (k=j=0;k<=100;k++) { i=j; for (p=Bucket[k];p;p=p->next) B[++j]=p->value; //把桶中每个元素取出,排序并加入B delete Bucket[k]; qsort(B+i+1,j-i,sizeof(B[0]),cmp); } } int main() { int *A,*B,N=100,K=100,i; A=new int[N+1]; B=new int[N+1]; for (i=1;i<=N;i++) { A[i]=rand()%K+1; //生成1..K的随机数 cout<<setw(4)<<A[i]; } cout<<endl; BucketSort(A,B,N,K); cout<<"排序后:"<<endl; for (i=1;i<=N;i++) { cout<<setw(4)<<B[i]; } cout<<endl; system("pause"); return 0; }
运行结果:
基数排序
基数排序(Radix Sort)是对桶排序的改进和推广。
基本思想:将一个关键字分解成多个“关键字”,再利用多关键字排序的思想对记录序列进行排序。基数排序就是借助“多关键字排序”的思想来实现“单关键字排序”的算法。
假设有n个记录的待排序序列{R1,R2,...,Rn},每个记录Ri中含有d个关键字,则称上述记录序列对关键字有序,是指对于序列中的任意两个记录Ri和Rj(1<=i<j<=n)都满足下列(词典)有序关系;
具体做法为:
(1)待排序的记录以指针相链,构成一个链表。
(2)“分配”时,按当前“关键字位”的取值,将记录分配到不同的“链队列”中,每个队列中记录的“关键字位”相同。
(3)“收集”时,按当前关键字取值从小到大将各队首尾相链接成一个链表;对每个关键字位均重复2)和3)两步。如下所示:
p->369->367->167->239->237->138->230->139
第一次分配得到:
B[0].f->230<-B[0].e
B[7].f->367->167->237<-B[7].e
B[8].f->138<-B[8].e
B[9].f->369->239->139<-B[9].e
第一次收集得到:
p->230->367->167->237->138->369->239->139
第二次分配得到:
B[3].f->230->237->138->239->139<-B[3].e
B[6].f->367->167->369<-B[6].e
第二次收集得到:
p->230->237->138->239->139->367->167->369
第三次分配得到:
B[1].f->138->139->167<-B[1].e
B[2].f->230->237->239<=B[2].e
B[3].f->367->369<-B[3].e
第三次收集之后便得到记录的有序序列:
p->138->139->167->230->237->239->367->369
#include "stdafx.h" #include <iostream> #include<stdlib.h> #include<iomanip> #include<math.h> using namespace std; int maxbit(int data[],int n) //辅助函数,求数据的最大位数 { int d = 1; //保存最大的位数 int p =10; for(int i = 0;i < n; ++i) { while(data[i] >= p) { p *= 10; ++d; } } return d; } void radixsort(int data[],int n) //基数排序 { int d = maxbit(data,n); int * tmp = new int[n]; int * count = new int[10]; //计数器 int i,j,k; int radix = 1; for(i = 1; i<= d;i++) //进行d次排序 { for(j = 0;j < 10;j++) count[j] = 0; //每次分配前清空计数器 for(j = 0;j < n; j++) { k = (data[j]/radix)%10; //统计每个桶中的记录数 count[k]++; } for(j = 1;j < 10;j++) count[j] = count[j-1] + count[j]; //将tmp中的位置依次分配给每个桶 for(j = n-1;j >= 0;j--) //将所有桶中记录依次收集到tmp中 { k = (data[j]/radix)%10; tmp[count[k]-1] = data[j]; count[k]--; } for(j = 0;j < n;j++) //将临时数组的内容复制到data中 data[j] = tmp[j]; radix = radix*10; } delete [] tmp; delete [] count; } int _tmain(int argc, _TCHAR* argv[]) { int data[10]={267,6,9,679,0,999,99,9,22,11}; std::cout<<"初始序列"<<endl; for(int j=0;j<10;j++) { std::cout <<setw(4)<< data[j]; } std::cout<<endl; radixsort( data,10); std::cout<<"生成的有序序列"<<endl; for(int j=0;j<10;j++) { std::cout << setw(4)<<data[j]; } std::cout<<endl; system("pause"); system("pause"); return 0; }
运行结果:
各种排序算法比较
排序法 | 平均时间 | 最差情形 | 稳定度 | 额外空间 | 备注 |
冒泡 | O(n2) | O(n2) | 稳定 | O(1) | n小时较好 |
交换 | O(n2) | O(n2) | 不稳定 | O(1) | n小时较好 |
选择 | O(n2) | O(n2) | 不稳定 | O(1) | n小时较好 |
插入 | O(n2) | O(n2) | 稳定 | O(1) | 大部分已排序时较好 |
基数 | O(logRB) | O(logRB) | 稳定 | O(n) |
B是真数(0-9), R是基数(个十百) |
Shell | O(nlogn) | O(ns) 1<s<2 | 不稳定 | O(1) | s是所选分组 |
快速 | O(nlogn) | O(n2) | 不稳定 | O(nlogn) | n大时较好 |
归并 | O(nlogn) | O(nlogn) | 稳定 | O(1) | n大时较好 |
堆 | O(nlogn) | O(nlogn) | 不稳定 | O(1) | n大时较好 |
2 外部排序
外部排序:若参加排序的记录数量很大,整个排序过程不能一次在内存中完成,则称此类排序问题为外部排序;
以上博客,如有参考使用,请标明出处;
参考:
《算法与数据结构》 第二版 陈守孔 著
参考的网上内容:
http://blog.csdn.net/onedreamer/article/details/6745006
http://blog.csdn.net/zhangkaihang/article/details/7394806
http://blog.csdn.net/yincheng01/article/category/1269370/1
http://blog.csdn.net/yincheng01/article/details/8205037
http://www.chinadmd.com/file/wt63reerwwo6wcpxeiv3twx6_1.html
https://www.byvoid.com/blog/sort-radix
http://www.cnblogs.com/kkun/archive/2011/11/23/2260299.html
http://www.cnblogs.com/kkun/archive/2011/11/23/2260267.html
http://blog.csdn.net/hkx1n/article/details/3922249