/*排序算法总结
以实现从小到大的序列为例
*/
#include<iostream>
using namespace std;
#define MAXSIZE 10
void swap(int a[], int i, int j)
{
int temp=a[i];
a[i]=a[j];
a[j]=temp;
}
void print(int a[], int len)
{
int i;
for(i=0;i<len; i++){
cout<< a[i] <<" ";
}
cout<< endl;
}
/*冒泡排序 O(n^2)
两两比较相邻记录的关键字,如果反序则交换,直到没有反序的记录为止
*/
void BubbleSort(int a[], int len)
{
int i,j;
for(i=0; i<len; i++) {
for(j=len-1;j>=i;j--) { //注意是j从后面开始往前循环 此时较小的数字如同气泡一样慢慢浮到上面,故命名为冒泡排序
if(a[j-1]> a[j]) { //若前者大于后者则交换
swap(a,j-1,j);
}
}
}
}
//当你的序列本身就是 局部有序时,上述程序就会浪费很多的比较时间 ,为此我们增加一个标记变量flag来实现这一算法的改进
//flag 为true时表示有数据交换, c初始化为false
//当循环一次没有发生任何交换时,表明序列已经有序,此时flag会维持初始状态,从而直接退出循环不再进行比较
void BubbleSort1 (int a[], int len)
{
int i,j;
int flag=true;
for(i=0; i<len&& flag; i++) { //若flag为false则退出循环
flag=false; //初始状态为false
for(j=len-1;j>=i; j--) {
if(a[j-1]> a[j]){
swap(a,j-1,j);
flag=true;
}
}
}
}
/*简单选择排序 O(n^2)
通过n-i次关键字之间的比较,从 n-i+1个记录中选出关键字最小的记录,并和第i(1<=i<=n) 个记录交换。
即在排序时找到合适的关键字再做交换,并且只移动一次就能完成相应关键字的排序定位
*/
void SelectSort(int a[], int len)
{
int i,j,min;
for(i=0; i<len; i++) {
min=i; //将当前下标定义为最小值下标
for(j=i+1; j<len ;j++) { //循环剩下的n-i个数据
if(a[j]< a[min]){ //若第j个数比min对应的最小值 小,则将该值的小标赋给min
min=j;
}
}
if(i!=min) { //若min不等于i,说明找到n-i+1个数中的最小值 ,交换
swap(a,i,min);
}
}
}
/*直接插入排序 O(n^2)
将一个记录插入到已经排好序的有序表中,从而得到一个新的、记录数增1的有序表
即假设前i-1个数已经排好序,现在需要插入第i个
假设 第i个数比i-1大,则无需交换,若比第i-1个数小,则需要将其插入到前i-1个数中合适的位置 。
插入的过程就是从后往前比较,将比第i个数大的值后移一位,直至找到第i个数应该存放的位置。
在记录数少或者基本有序时效率很高效
*/
void InsertSort(int a[], int len)
{
int i,j;
int m; //用于存放第i个数,作为哨兵判断哪些数需要后移
for(i=1; i<len; i++) {
if(a[i]<a[i-1]){ //需将a[i]插入到前面
m=a[i]; //设置哨兵
for(j=i-1; a[j]>m; j--){
a[j+1]=a[j]; //把比a[i]大的数后移
}
a[j+1]=m; //插入到正确位置
}
}
}
/*希尔排序(shell排序) O(n^3/2)
将相距某个“增量”的记录组成一个子序列,然后对每一个子列内分别进行直接插入排序。
然后得到的结果是基本有序使,载对全体记录进行一次直接插入排序
即将整体分割成部分,然后部分进行直接插入排序后,组合回去再进行直接插入排序
关键并不是随便分组后各自排序,而是将相隔某个“增量”的记录组成一个子序列,实现跳跃式的移动,使得排序的效率提高
*/
void ShellSort(int a[], int len)
{
int i,j;
int m; //用于存放需要插入的数字
int increment=len; //初始化增量为长度
do{
increment= increment/3+1; //增量序列,直至为1。此时相隔increment的数形成了一个子列,排序在这个子列里面发生。子列逐渐缩小,直至只剩一个数
for(i=increment; i<len; i++) {
if(a[i]< a[i-increment]) { //需要将a[i]插入有序增量子表
m=a[i];
for(j=i-increment; j>=0 && m<a[j]; j-=increment) { //此时j的增量不为1,而是设置的跳跃的增量值
a[j+increment]=a[j];
}
a[j+increment]= m; //把a[i]放入正确位置
}
}
} while(increment>1);
}
/* 堆排序 O(nlogn)
对简单选择排序进行的一种改进
堆一种完全二叉树。大顶堆:每个结点的值大于或者等于其左右孩子结点的值。小顶堆: 每个结点的值小于或者等于其左右孩子结点的值
基本思想:将待排序序列构造成一个大顶堆。此时,整个序列的最大值就是堆顶的根节点。
将他移走(其实就是将其与堆数组的末尾元素交换,此时末尾元素就是最大值 ),然后将剩余n-1个序列重新构成一个堆,这样就会得到n个元素中的此最大值。
此反复执行,便能得到一个有序序列
*/
void HeapAdjust(int a[], int m, int n)
{
int temp,j;
temp=a[m];
for(j=2*m+1; j<n; j=2*j+1) { //沿关键字较大的孩子结点向下筛选
if(j+1<n && a[j]<a[j+1]) {
++j; //j为孩子结点值较大的记录的下标
}
if(temp>=a[j]) {
break;
}else {
//此处实现了父节点与子结点的交换,但是只是将父结点的值改变,子结点由于还要进行跟其子结点比较,所以并未直接将现父节点的值赋给它
a[m]=a[j]; //将更大的值赋给父节点
m=j; //将大值的下标赋给m,往下以m结点为父节点进行调节
}
}
a[m]=temp; //将最初的最小的值赋给最后子结点的位置
}
void HeapSort(int a[],int len)
{
int i;
//完成的是将现在待排序序列构建成一个大顶堆 。即从下往上、从右往左,将每个非终端结点(非叶结点)当做根节点,将其及其子树调整成大顶堆。
for(i=len/2-1; i>=0; i--) { //n个结点,其最后一个非叶结点的为[n/2]。注意数组的下标从0开始,所以为[n/2]-1
HeapAdjust(a,i,len);
}
//完成的是逐步将每个最大值的根结点与末尾元素交换,并且再调整其成为大顶堆
for(i=len-1; i>0; i--) {
swap(a,0,i);
HeapAdjust(a,0,i); //此处为i而不是i-1,传入的是数组删除顶部数据剩下的数据数目
}
}
/*归并排序 O(nlogn)
利用归并的思想,假设初始序列有n个记录,则可以看成是n个有序的子序列,每个子序列的长度为1,
然后两两合并,得到[n/2] 个长度为2或1的有序子序列;
再两两合并 ,...,如此重复,直至得到一个长度为n的有序序列为止
*/
//将 a2[s...m]与 a2[m+1...t]归并为有序的a1[s...t]
void Merge(int a2[], int a1[], int s, int m, int t)
{
int i,j,k,l;
//两个子序列一一比较,哪个序列的元素小就放进a1序列里面,然后位置加1,再与另外一个序列原来位置的元素比较,
//如此反复,可以把两个有序的序列合并成一个有序的序列
for(i=s,j=m+1,k=s;i<=m,j<=t;k++) { //k代表a1从s到t的下标; i代表a2从s到m; j代表a2中从m+1到n
if(a2[i]<a2[j]) {
a1[k]=a2[i++];
}else {
a1[k]=a2[j++];
}
}
//将两个子序列的剩下部分都放入a1序列中
if(i<=m) {
for(l=0; l<=m-i;l++){
a1[k+l]=a2[i+l]; //将剩余a2[i...m]复制到a1
}
}
if(j<=t) {
for(l=0; l<=t-j;l++){
a1[k+l]=a2[j+l]; //将剩余a2[j...n]复制到a1
}
}
}
//将a(s..t)归并排序为a1(s..t)
void Msort(int a[], int a1[], int s, int t)
{
int m,i;
int a2[MAXSIZE+1] ;
if(s==t) {
a1[s]=a[t];
} else {
m=(s+t)/2; //将a[s..t]平分为a[s...m]和a[m+1...t]
Msort(a, a2, s, m); //递归将a[s...m]归并排序为a2[s...m]
Msort(a, a2, m+1, t); //递归将a[s...m]归并排序为a2[m+1...t]
Merge(a2, a1, s, m, t); //将 a2[s...m]与 a2[m+1...t]归并到a1[s...t]
}
for(i=s;i<=t;i++){
a[i]=a1[i];
}
}
void MergeSort(int a[], int len)
{
int a1[MAXSIZE];
Msort(a,a1,0,len-1);
}
/*快速排序 O(nlogn)
通过一趟排序x选取一个轴值(pivot),将待排序记录分割成独立的两部分,左侧的元素均小于轴值,右侧的元素均大于或等于轴值,
然后分别对这两部分记录继续进行排序,以达到整个序列有序的目的
**/
//分割求轴值函数,主要用于选取关键字,并将其放置合适的位置,使得它左边的值都比图小,右边的都比它大 ,并返回其所在位置
int Partition(int a[], int low, int high)
{
while(low<high) { //从表的两端交替向中间扫描
while(low<high && a[high]>= a[low]){ //从右边开始,向左遍历
high--;
}
//跳出循环说明low>high或者a[high]小于pivotkey
//前者说明pivotkey右边的数都比其大,故无需进行交换,后者说明出现比pivotkey小的数,那就需要将其交换到低端
swap(a, low, high);
//对另外一边进行同样的操作
while(low<high && a[low]<= a[high]) {
low ++;
}
swap(a ,low, high);
}
return low; //返回轴值所在位置
}
void Qsort(int a[], int low, int high)
{
int pivot; //用于记录轴值的下标
if(low<high){
pivot=Partition(a, low, high); //将a一分为二,算出轴值的所在位置
//对轴值左右进行递归
Qsort(a, low, pivot-1);
Qsort(a, pivot+1, high);
}
}
void QuickSort(int a[], int len)
{
Qsort(a,0,len-1);
}
int main()
{
int a[MAXSIZE];
int i;
for(i=0; i<MAXSIZE; i++){
cin>>a[i];
}
// BubbleSort(a,MAXSIZE);
// SelectSort(a,MAXSIZE);
// InsertSort(a,MAXSIZE);
// ShellSort(a,MAXSIZE);
// QuickSort(a,MAXSIZE);
// HeapSort(a,MAXSIZE);
MergeSort(a,MAXSIZE);
print(a,MAXSIZE);
}
各种算法的复杂度及稳定性统计
参考资料:《大话数据结构》