各种排序算法的总结
概要:
这是本人以前学习数据结构时,写的一些排序算法。今天整理一下,方便以后查阅。
本文实现的排序算法包括:直接插入排序、折半插入排序、2路插入排序、希尔排序、冒泡排序、快速排序、简单选择排序、堆排序、归并排序
一、具体实现
关于各算法的特点和思想原理,本文不作介绍,需要了解的,可查阅数据结构书籍,或直接google
直接插入排序
//直接插入排序:参数:待排序数据,数据个数 void InsertSort( int nData[], int nNum) { int temp= 0; //哨兵 int i,j; for (i = 1;i<nNum;i++) { if (nData[i] < nData[i-1]) { temp = nData[i]; //for (j = i-1; j>=0 && temp < nData[j]; j--) for (j = i-1; j>=0; j--) { if (temp < nData[j]) nData[j+1] = nData[j]; else break; } nData[j+1] = temp; //插入数据 } } }
折半插入排序
//折半插入排序 void BInsertSort(int nData[],int nNum) { int i,j,low,high; int mid;//中间值 int temp;//哨兵 for (i = 1;i<nNum; i++) { /////我觉得最好加入判断:if (nData[i] < nData[i-1]),这可减少比较次数 temp = nData[i]; low = 0; high = i-1; while (low <= high) { mid = (low+high)/2; //折半 if (temp < nData[mid]) high = mid-1; //插入点在低半区 else low = mid+1; //插入点在高半区 }//找到插入点位置,为high。 for (j = i-1;j>= high+1;j--) nData[j+1]=nData[j]; nData[j+1] = temp;//将新数据插入到high+1 } }
2路插入排序
//2路插入排序 void P2_InsertSort(int nData[], int nNum) { int i=0; int first = 0,final = 0; int *d = new int[nNum]; d[0] = nData[0];//第0个元素 for (i = 1; i<nNum; i++) { if (nData[i]<d[first]) //插入到最前面 { first = (first-1+nNum)%nNum; //当first为0时,插入位置是最后一个。所以要加nNum d[first] = nData[i]; } else if(nData[i]>d[final]) //插入到最后面 { final = (final+1)%nNum; d[final] = nData[i]; } else { int j = final++; while (nData[i] < d[j])//移动尾部数据 { d[(j+1)%nNum] = d[j]; j = (j-1+nNum)%nNum; } d[j+1] = nData[i];//进行插入 } } for (i=0 ;i<nNum;i++)//把数据赋给原数组 { nData[i] = d[first]; first = (first+1)%nNum; } delete []d; }
希尔排序
////////////////////////////////////////////////////////////////////////// //希尔排序 //第一种写法 //其中的一组排序 void ShellInsert_1(int nData[], int nNum, int nBegin,int nStep) { int i,j; int temp; for (i = nBegin + nStep;i<nNum; i+=nStep)//从nBegin点的下一个点,开始进行插入 { temp = nData[i]; if (temp < nData[i-nStep]) { for (j= i-nStep; j>=nBegin&&temp<nData[j]; j-=nStep)//j>=nBegin { nData[j+nStep] = nData[j]; } nData[j+nStep] = temp; //插入 } } } void ShellSort_1(int nData[],int nNum) { int i,nStep; //以nStep分组,nStep每次减为原来的一半 for(nStep = nNum/2; nStep>0; nStep /=2 )//趟数 { //对每个组进行排序 for (i=0; i<nStep; i++) { ShellInsert_1(nData,nNum,i,nStep); } } } //第2种写法 /*一趟希尔插入排序*/ void ShellInsert_2(int nData[],int nNum,int nStep) { int i,temp; for (i = nStep;i<nNum;i++) //i++ { if (nData[i]<nData[i-nStep]) { temp = nData[i]; for (int j=i-nStep;j>=0&&temp<nData[j]; j-=nStep )//由于数组是从0开始取,所以j>=0 { nData[j+nStep] = nData[j]; } nData[j+nStep] = temp; } } } void ShellSort_2(int nData[],int nNum) { int nStep; for (nStep = nNum/2; nStep>0; nStep /= 2) { ShellInsert_2(nData,nNum,nStep); } }
冒泡排序
//冒泡排序 void BubbleSort(int nData[],int nNum) { int i,j; int temp;//用于交换 bool flag = false;//标志:是否有交换 for (i=0; i<nNum-1; i++)//nNum-1趟 { flag = false; for(j=0; j<nNum-1-i; j++) { if (nData[j]>nData[j+1]) { temp = nData[j]; nData[j] = nData[j+1]; nData[j+1] = temp; flag = true; //有交换,标志置1 } } if (!flag) { break; } } printf("\n总共排了 %d 趟\n",i+1); }
快速排序
//快速排序 //第一种写法:是参照《数据结构基础(第二版c语言)》写的 void QuickSort_1(int nData[],int left,int right)//选取第一个作为枢纽 { int pivot,i,j; int temp; if (left < right) { i = left; j = right+1; pivot = nData[left]; do { do { i++; } while (nData[i] < pivot); do { j--; } while (nData[j] > pivot); if(i<j) //交换 nData[i]与nData[j] { temp = nData[i]; nData[i] = nData[j]; nData[j] = temp; } }while (i<j); nData[left] = nData[j];//交换nData[left]与nData[j] nData[j] = pivot; QuickSort_1(nData,left,j-1);//递归排左半部分 QuickSort_1(nData,j+1,right);//递归排右半部分 }// end if(left<right) } //第二种写法:参照《严蔚敏:数据结构(c语言版)》写的 void QuickSort_2(int nData[],int left,int right)//选取第一个作为枢纽 { int i,j,pivot; // int temp; if (left < right) { pivot = nData[left];//先把枢纽值备份 i = left; j = right; while (i<j) { while(i<j && nData[j]>=pivot) j--;//找到右边的小数的位置 nData[i] = nData[j];//把右边的小数放到左边,即放到nData[i] while(i<j && nData[i]<=pivot) i++;//找到左边的大数的位置 nData[j] = nData[i];//把左边的大数放到右边,即放到nData[j] } nData[i] = pivot;//把枢纽值填进来 // printf("\n%d %d\n",i,j);//此时i,j的值相等 QuickSort_2(nData,left,i-1); QuickSort_2(nData,i+1,right); } }
接下来的快排是参照: 公开课: MIT 算法导论 中 快速排序
MIT-算法导论-快速排序
/************************************************************************/ /* 公开课: MIT 算法导论 中 快速排序 * Partion()方法 * /************************************************************************/ void exchange(int &x,int &y)//交换 { int temp; temp =x; x = y; y =temp; } int Partion(int a[],int start,int end) { int pivot = a[start]; //枢轴 int i = start;//i 用来存比pivotkey小的一系列数的最后一个数的下标 for (int j= start+1 ; j<=end ;j++) //一次遍历,找到枢轴所在的位置 { if ( a[j] < pivot) //把所有小的数,移到前面 { i =i+1; exchange(a[j],a[i]); } } exchange(a[i],a[start]); return i; //i是枢轴的位置 } void QSORT(int a[],int start,int end) { if (start < end) { int pivotloc = Partion(a,start,end); QSORT(a,start,pivotloc-1); QSORT(a,pivotloc+1,end); } } void QS(int a[],int n) { QSORT(a,0,n-1); }
简单选择排序
void SelectSort(int nData[],int nNum) { int i,j,temp; for (i = 0;i < nNum-1; i++) for(j = i+1; j<nNum;j++) { if(nData[i] > nData[j]) { temp = nData[i]; nData[i] = nData[j]; nData[j] = temp; } } }
堆排序
//堆排序:(包括两个步骤:建堆和排序) //第一步:建堆(大根堆),二叉树的标号是从1,2,…,nNum void HeapAdjust(int nData[],int s, int nNum) //s表示父节点的标号,nNum表示总结点数 { int temp,j;//j表示左孩子的二叉树标号 int t;//用t记录结点对应的数组下标,则t=s-1 t=s-1; temp = nData[t];//temp记录父结点s的值 for (j = 2*s; j<=nNum; j*=2) { t = j-1;//此时t表示左孩子的数组下标 if (j<nNum && nData[t]<nData[t+1]) //左孩子比右孩子小 { ++j; //用j记录较大孩子的二叉树下标 } if (temp >= nData[j-1]) //如果当前父节点比最大孩子的值还大,说明已经是最大堆 { break; //那么跳出循环 } else { nData[s-1] = nData[j-1];//把最大孩子的值赋给当前父节点 s=j;//把父节点更新到最大的孩子,进入下一轮判断 } } nData[s-1] = temp; //把原父节点s的值插入到大根堆中 } //第二步:堆排序 void HeapSort(int nData[],int nNum) { int i,temp;//i表示二叉树的标号,取值范围为1:nNum int j;//表示对应的数组下标,取值范围为:0:nNum-1,故j=i-1; for (i = nNum/2;i>0; i--)//对标号从1到nNum的结点进行建堆 { HeapAdjust(nData,i,nNum);//从最后一个非终端结点开始,建堆 } for (i = nNum;i>1; i--) //i表示二叉树的标号 { temp = nData[i-1]; //交换,即把根(即最大值),放到数组的最后一个元素中 nData[i-1] = nData[0]; nData[0] = temp; HeapAdjust(nData,1,i-1);//将剩下的i-1个结点重新调整为大根堆 } }
归并排序
//归并排序: //第一种写法:递归形式。(包括两个步骤: 归并和递归排序) //第一步 归并: //将nData[left..center]和nData[center+1..len], 从小到大,归并为有序的nData[left..len] void Merge(int nData[],int left,int center, int len) { int *temp = new int [len+1-left]; int k = 0;//k是temp数组的下标索引 int i,j;//i是nData[left..center]数组的下标索引 //j是nData[center+1..len]数组的下标索引 for (i=left,j=center+1; i<=center&&j<=len; k++) { if (nData[i] <= nData[j]) temp[k] = nData[i++]; else temp[k] = nData[j++]; } while (i<= center)//归并剩余的部分 { temp[k++] = nData[i++]; } while (j <= len) { temp[k++] = nData[j++]; } for (i=left,j=0; i<=len&&j<k; i++)//temp[]中的元素复制回原数组nData[] { nData[i] = temp[j++]; } delete []temp; } //第二步:递归排序 //参数:nData[]待排序的数组,left起点的数组下标,right终点的数组下标 void MSort(int nData[],int left,int right) { if (left < right) { int center;//原数组进行划分,一分为二。center为中点 center = (left+right)/2; MSort(nData,left,center);//左边部分进行递归的排序 MSort(nData,center+1,right);//右边 Merge(nData,left,center,right);//将排序的左右两边进行归并 } } //归并排序,第二种写法,非递归形式(non-recursive) //参数:nData[]待排数据,nNum:nData[]的元素个数 void Msort_NRecur(int nData[],int nNum) { int pre_Len = 1; //合并前的序列长度 int after_Len = 1; //合并后的序列长度 for (; after_Len<nNum; pre_Len = after_Len )//合并后的长度小于nNum,则继续合并 { after_Len = pre_Len*2;//合并后的长度是合并前的2倍 int i=0;//合并的序列下标,从0开始 for (; i+after_Len-1 <= nNum-1; i+=after_Len)//元素下标范围是0..nNum-1 { Merge(nData,i,i+pre_Len-1,i+after_Len-1); } if (i+pre_Len <= nNum-1)//把最后那段长度不足after_Len,但超过pre_Len的序列合并 { Merge(nData,i,i+pre_Len-1,nNum-1); } } }
主函数main
void main() { // int a[max]={4,4,9,5,17,6,20,45,30,54,25}; int a[max]={3,13,56,34,14,46,34,6,9,56,20}; printf("直接插入排序\n\n请输入 %d 个待排序的整数:\n",max); int i; // for (i=0;i<max;i++) // { // scanf("%d",&a[i]); // } printf("\n\n原始数据 待排序的整数为:\n"); for ( i=0;i<max;i++) { printf("%d\t",a[i]); } // InsertSort(a,max); // BInsertSort(a,max); // P2_InsertSort(a,max); // ShellSort_2(a,max); // BubbleSort(a,max); QuickSort_2(a,0,max-1); // QS(a,max); // SelectSort(a,max); // HeapSort(a,max); // MSort(a,0,max-1); // Msort_NRecur(a,max); printf("\n直接插入排序,结果为:\n"); for ( i=0;i<max;i++) { printf("%d\t",a[i]); } }
二、总结
实现一遍后,对各算法都理解了。不知为啥,项目中一碰到要排序,立马就是冒泡!why?