在计算机科学所使用的排序算法通常被分类为:
1-计算的复杂度(最差、平均、和最好性能),依据列表(list)的大小(n)。一般而言,好的性能是O(n log n),且坏的性能是O(n2)。对于一个排序理想的性能是O(n)。仅使用一个抽象关键比较运算的排序算法总平均上总是至少需要O(n log n)。
2-存储器使用量(以及其他电脑资源的使用)
3-稳定度:稳定排序算法会依照相等的关键(换言之就是值)维持纪录的相对次序。也就是一个排序算法是稳定的,就是当有两个有相等关键的纪录R和S,且在原本的列表中R出现在S之前,在排序过的列表中R也将会是在S之前。(简单来说,判断依据是一个在一个存在相同元素或者键值的数组里面排序,只要同一方向,他们之间排序后的前后位置不变,则这个排序是稳定的)
4-一般的方法:插入、交换、选择、合并等等。交换排序包含冒泡排序和快速排序。选择排序包含希尔排序和堆排序。
按照稳定性分
稳定排序:冒泡、插入、桶、归并、二叉树
不稳定排序:选择、希尔(改进版插入排序)、堆、快排
冒泡排序bubble sort 时间复杂度O(n2)
是稳定的,如 1 2 3 1 4 8 91中,排序后,第一个1和第二个1的前后位置不变
void bubbleSort(int ibubbleArr[],const int iArrSize){
for (int i = 0; i<iArrSize ; i++){
for (int j = iArrSize - 1; j>i; j--){
if (ibubbleArr[j] > ibubbleArr[j - 1]){
ibubbleArr[j] ^= ibubbleArr[j - 1];
ibubbleArr[j - 1] ^= ibubbleArr[j];
ibubbleArr[j] ^= ibubbleArr[j - 1];
}
}
}
}
插入排序 insertion sort时间复杂度O(n)
是稳定的,基于已经有序的数列中,插入一个元素,如 1 2 4 8 9 10中 插入4,只要插入方向一定,那么这两个4的位置也是一定的
int* insertSort(int *iInsertArr,int *iArrSrc,const int iArrSize){//原数组,目标数组,数组大小
int i = 0;
for (; i<iArrSize; i++){
iArrSrc[i] = iInsertArr[i];
for (int j= i; j>0; j--){
if(iArrSrc[j] > iArrSrc[j - 1]){
iArrSrc[j] ^= iArrSrc[j - 1];
iArrSrc[j - 1] ^= iArrSrc[j];
iArrSrc[j] ^= iArrSrc[j - 1];
}else break;
}
}
for ( i = 0; cout<<iArrSrc[i++]<<" ",i<iArrSize; ) ;cout<<endl;
return iArrSrc;
}
归并排序 merge sort O(n log n);需要O(n)额外空间
稳定的,归并排序把序列递归地分成短序列,递归出口是短序列只有1个元素(认为直接有序)或者2个序列(1次比较和交换),然后把各个有序的段序列合并成一个有序的长序列,不断合并直到原序列全部排好序。可以发现,在1个或2个元素时,1个元素不会交换,2个元素如果大小相等也没有人故意交换,这不会破坏稳定性。那么,在短的有序序列合并的过程中,稳定是是否受到破坏?没有,合并过程中我们可以保证如果两个当前元素相等时,我们把处在前面的序列的元素保存在结果序列的前面,这样就保证了稳定性。所以,归并排序也是稳定的排序算法。
void mergeSort(int i_src[],int i_rs[],int i_stIndex,int i_midIndex,int i_enIndex){//归并src这个数组的前+中和中+后合并
int i,j,k;
//int st,mid,en;st = i_stIndex,mid = i_midIndex,en = i_enIndex;
for (i = i_stIndex,j = i_midIndex + 1;i_stIndex <= i_midIndex && j <= i_enIndex; i++){
if (i_src[i_stIndex] < i_src[j]){
i_rs[i] = i_src[i_stIndex++];
}else{
i_rs[i] = i_src[j++];
}
}
if (i_stIndex <= i_midIndex){
for (k = 0; k <= i_midIndex - i_stIndex; k++){
i_rs[i+k] = i_src[i_stIndex + k];
}
}
if (j <= i_enIndex){
for (k = 0; k<= i_enIndex - j;k++){
i_rs[i + k] = i_src[j + k];
}
}
return;
}
void merge(int i_src[],int i_rs[],int i_stIndex,int i_enIndex){//划分阶段参数:原数组,存放结果的数组,起点,终点
int i_temprs[i_max_size + 100];
if (i_stIndex == i_enIndex){
i_rs[i_stIndex] = i_src[i_stIndex];
}else{
int i_midIndex = (i_stIndex + i_enIndex) / 2;
merge(i_src,i_temprs,i_stIndex,i_midIndex);
merge(i_src,i_temprs,i_midIndex + 1,i_enIndex);
mergeSort(i_temprs,i_rs,i_stIndex,i_midIndex,i_enIndex);
}
return;
}
桶排序bucket sort
O(n);需要O(k)额外空间
稳定的,因数组下标已经排好序,根据数组下标来排序
//桶排序
//
#include "stdafx.h"
#include <iostream>
using namespace std;
#define iBucketSize sizeof(iBucketSort)/sizeof(iBucketSort[0])
void bucketSort(int *iBucketSort,int *iBucketRs,const int size){
for (int i = 0; i<size; i++){
iBucketRs[iBucketSort[i]]++;
}//排序完毕
for (int i = 0; i< iBucketSize+1000; i++){
while (iBucketRs[i] > 0){
cout<<i<<" ";
iBucketRs[i]--;
}
}
cout<<endl;
return;
}
int main(){
int iBucketSort[] = {1,4,7,5,1,2,8};
int iBucketRs[iBucketSize + 1000];
memset(iBucketRs,0,iBucketSize + 1000);//初始化桶
bucketSort(iBucketSort,iBucketRs,iBucketSize);
return 0;
}
二叉查找树Binary treeO(n log n)期望时间; O(n2)最坏时间;需要O(n)额外空间
稳定的,规定了左右节点的大小,
#include "stdafx.h"
#include <iostream>
using namespace std;
#define iArrSize sizeof(iArr)/sizeof(iArr[0])
//二叉树中的节点编号,树顶是从1开始,则,每个节点的左孩子编号是父节点的编号值*2,右孩子的编号是*2+1,因为数组从0开始,所以,要再+1
void inertValue(int *iBinTree, int iNode, int iValue){
if (iBinTree[iNode] == 0){//默认设定不对0插入
iBinTree[iNode] = iValue;
}else{
if (iBinTree[iNode] > iValue){
inertValue(iBinTree,iNode * 2 + 2,iValue);
}else if (iBinTree[iNode] < iValue){
inertValue(iBinTree,iNode * 2 + 1,iValue);
}else return;//不插入已经存在的节点
}
非递归方法
//while(iNode < 10000){
// if (iBinTree[iNode] == 0){//默认设定不对0插入
// iBinTree[iNode] = iValue;
// return;
// }
// if (iBinTree[iNode] > iValue){
// iNode = iNode * 2 + 2;
// }else if (iBinTree[iNode] < iValue){
// iNode = iNode * 2 + 1;
// }else return;//不插入已经存在的节点
//}
return ;
}
void printRs(int *iArr,int iNode){
if (iArr[iNode] != 0){//默认设定不对0插入
printRs(iArr,iNode * 2 + 1);
cout<<iArr[iNode]<<" ";
printRs(iArr,iNode * 2 + 2);
}
return;
}
int main(){
int iArr[] = {9,10,200,3,400,12,-1};
int iArrRs[iArrSize+10000];
memset(iArrRs,0,iArrSize + 10000);
for (int i = 0;i<iArrSize; i++){
inertValue(iArrRs,0,iArr[i]);
}
//中序遍历
printRs(iArrRs,0);
cout<<endl;
return 0;
}
-----------------------------------------我是不稳定的分割线-----------------------------------------------------
选择排序selection sort O(n2)
不稳定的,选择排序是给每个位置选择当前元素最小的,比如给第一个位置选择最小的,在剩余元素里面给第二个元素选择第二小的,依次类推,直到第n-1个元素,第n个元素不用选择了,因为只剩下它一个最大的元素了。那么,在一趟选择,如果当前元素比一个元素小,而该小的元素又出现在一个和当前元素相等的元素后面,那么交换后稳定性就被破坏了。比较拗口,举个例子,序列5 8 5 2 9,我们知道第一遍选择第1个元素5会和2交换,那么原序列中2个5的相对前后顺序就被破坏了,所以选择排序不是一个稳定的排序算法。
void selectSort(int iselectArr[],const int iArrSize){
for (int i = 0; i<iArrSize ; i++){
for (int j = i+1; j>iArrSize; j++){
if (iselectArr[j] > iselectArr[i]){
iselectArr[j] ^= iselectArr[i];
iselectArr[i] ^= iselectArr[j];
iselectArr[j] ^= iselectArr[i];
}
}
}
}
希尔排序
shell sortO(n log n)如果使用最佳的现在版本
不稳定的。 希尔排序是按照不同步长对元素进行插入排序,当刚开始元素很无序的时候,步长最大,所以插入排序的元素个数很少,速度很快;当元素基本有序了,步长很小,插入排序对于有序的序列效率很高。所以,希尔排序的时间复杂度会比o(n^2)好一些。由于多次插入排序,我们知道一次插入排序是稳定的,不会改变相同元素的相对顺序,但在不同的插入排序过程中,相同的元素可能在各自的插入排序中移动,最后其稳定性就会被打乱,所以shell排序是不稳定的。
暂不实现
堆排序heapsort O(n log n)
不稳定的。我们知道堆的结构是节点i的孩子为2*i和2*i+1节点,大顶堆要求父节点大于等于其2个子节点,小顶堆要求父节点小于等于其2个子节点。在一个长为n 的序列,堆排序的过程是从第n/2开始和其子节点共3个值选择最大(大顶堆)或者最小(小顶堆),这3个元素之间的选择当然不会破坏稳定性。但当为n /2-1, n/2-2, ...1这些个父节点选择元素时,就会破坏稳定性。有可能第n/2个父节点交换把后面一个元素交换过去了,而第n/2-1个父节点把后面一个相同的元素没有交换,那么这2个相同的元素之间的稳定性就被破坏了
void BuildMaxHeap(int iArrList[] ,int iArrSize,int index){//原数组,大小,节点编号
int iLeftIndex = 2*index + 1;
int iRightIndex = 2*index + 2;
int largest = index;
if((iRightIndex < iArrSize) ){
//在建立大根堆时,如果父节点比两个子节点都小,则交换最大的一个子节点
if((iArrList[iLeftIndex] < iArrList[iRightIndex])){
largest = iRightIndex;
}else{
largest = iLeftIndex;
}
}else{
if(iLeftIndex < iArrSize){
largest = iLeftIndex;
}
}
if((iArrList[index] < iArrList[largest]) && (largest != index)){
iArrList[index] ^= iArrList[largest];
iArrList[largest] ^= iArrList[index];
iArrList[index] ^= iArrList[largest];
//如果交换了某个节点的值,则需要递归交换其子树的节点
BuildMaxHeap(iArrList,iArrSize,largest);
}
}
void HeapSort(int iArrList[],int iArrSize){
if(iArrSize < 0){
return ;
}
for(int i=0;i<iArrSize;i++){
for(int j = ((iArrSize - i)/2-1);j>=0;j--){//从最后一个孩子的父节点开始
BuildMaxHeap(iArrList,iArrSize - i,j);
}
int tmp = iArrList[0];
iArrList[0] = iArrList[iArrSize -1 - i];
iArrList[iArrSize -1 - i] = tmp;
/*
iArrList[0] ^= iArrList[iArrSize -1 - i];
iArrList[iArrSize -1 - i] ^= iArrList[0];
iArrList[0] ^= iArrList[iArrSize -1 - i];*/
for(int i=0;i<iArrSize;i++){
cout<<iArrList[i]<<" ";
}
cout<<endl;
}
return ;
}
快速排序 Quickly Sort O(n log n)期望时间, O(n2)最坏情况;对于大的、乱数列表一般相信是最快的已知排序
不稳定的。快速排序有两个方向,左边的i下标一直往右走,当a[i] <= a[center_index],其中center_index是中枢元素的数组下标,一般取为数组第0个元素。而右边的j下标一直往左走,当a[j] > a[center_index]。如果i和j都走不动了,i <= j, 交换a[i]和a[j],重复上面的过程,直到i>j。交换a[j]和a[center_index],完成一趟快速排序。在中枢元素和a[j]交换的时候,很有可能把前面的元素的稳定性打乱,比如序列为 5 3 3 4 3 8 910 11,现在中枢元素5和3(第5个元素,下标从1开始计)交换就会把元素3的稳定性打乱,所以快速排序是一个不稳定的排序算法,不稳定发生在中枢元素和a[j] 交换的时刻。
void quickly(int i_src[],int i_stIndex,int i_enIndex){//需要排序的数组
if ( i_stIndex >= i_enIndex){
return;
}else{
int i_pointIndex = i_stIndex;//以第一个元素为中间点
for ( int i = i_stIndex + 1 ; i <= i_enIndex ; i++){
if (i_src[i_pointIndex] < i_src[i]){//大于这个中间点的,就交换
int i_tempValue = i_src[i];
int i_temp = i;
while( i_temp > i_pointIndex){
i_src[i_temp ] = i_src[i_temp - 1];
i_temp--;
}
i_src[i_pointIndex] = i_tempValue;
i_pointIndex++;
//i_src[i_pointIndex] ^= i_src[i];
//i_src[i] ^= i_src[i_pointIndex];
//i_src[i_pointIndex] ^= i_src[i];
//i_pointIndex = i;
}
}
quickly(i_src,i_stIndex,i_pointIndex);//分治
quickly(i_src,i_pointIndex + 1,i_enIndex);
}
return ;
}