排序的分类:
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