几种常见排序及优化版本

来源:互联网 发布:mysql解压版安装配置 编辑:程序博客网 时间:2024/06/09 20:26

       排序算法是很常见的当然也是相当重要的算法之一,排序又分了好几种算法,在不同的场景下我们应该明白如何去选择用哪一种,而这就要综合很多种因素,包括时间,空间复杂度以及所给数据的具体情况,这里就先来介绍一下算法的分类:



1.冒泡排序

这种排序应该是我们接触最早的,它的思想就是:比较相邻的两个关键字,如果反序则交换,直到没有反序的记录为止。如果我们有n个元素,那么需要进行n-1趟的冒泡,而每一趟比较的元素个数为n-i-1,两个for循环就解决了。这里就不再详细缀诉了,下面是实现的优化版本:

void BubbleSort(int* arr,size_t sz){assert(arr);bool flag=true;         //作为标志位for(int i=0;i<sz-1;++i)   //控制趟数{flag=true;    for(int j=0;j<sz-i-1;++j)  //控制每趟需要比较的次数{if(arr[j]>arr[j+1]){std::swap(arr[j],arr[j+1]);flag=false;/*int tmp=arr[j];arr[j]=arr[j+1];arr[j+1]=tmp;*/}}if(flag)break;}}

这是以前写的一篇比较详细的冒泡排序:

http://blog.csdn.net/qq_29503203/article/details/51581315

我们重点看一下时间复杂度的分析:
最好的情况,也就是要排序的数据本身有序,我们需要进行n-1次的比较,没有数据交换,时间复杂度为O(n).
最坏的情况下,就是逆序的时候,此时需要比较n*(n-1)/2次(等差数列),并作等数量级的移动,所以时间复杂度为O(n^2).

2.选择排序
简单来说,选择排序就是通过n-i次关键字之间的比较,从n-i-1个记录中选出关键字最小或最大的记录,并和第i个(1<=i<=n)个记录交换。

void SelectSort1(int* arr,size_t sz){assert(arr);int i=0;int min=0;for(i=0;i<sz;++i){min=i;     //将当前下标定义为最小下标for(int j=i+1;j<sz;++j)  //从后面的数据选择小的{if(arr[j]<arr[min])min=j;}if(min!=i){std::swap(arr[i],arr[min]);}}}

无论最好还是最坏的情况,比较次数都是一样多,都为n*(n-1)/2次,通俗的讲就是每一趟都需要遍历一遍找到最小数的正确位置上;交换次数最好的情况下为0次,最坏的情况下为n-1次,最终的排序时间是比较次数与交换次数的和,因此总的时间复杂度为O(n^2),性能略优于冒泡。

优化:每次遍历的同时选出最大的和最小的数据,将两者放在正确位置上,这样遍历的次数就减少一半,性能就提高了一些,但时间复杂度忽略常数后依然为O(n^2)。

void SelectSort2(int* arr,size_t sz){//优化:一次选择两个数,小的放在左边,大的放在右边assert(arr);int left=0;int right=sz-1;while(left<right){int min=left;int max=left;for(int i=left;i<=right;++i){if(arr[i]<arr[min])min=i;if(arr[i]>arr[max])max=i;}std::swap(arr[min],arr[left]);if(max==left)max=min;std::swap(arr[max],arr[right]);++left;--right;}}


详细分析过程还可看:
http://blog.csdn.net/qq_29503203/article/details/51615789


3.直接插入排序
它的思想大概是:将一个记录插入到已经排好序的的有序表中,从而得到一个新的,记录数增1的有序表。

void InsertSort(int* arr,size_t sz){assert(arr);for(int index=1;index<sz;++index)  //先默认下标为0的元素有序{int pos=index-1;int tmp=arr[index];while(pos>=0 && tmp < arr[pos]){std::swap(arr[pos],arr[pos+1]);--pos;}arr[pos+1]=tmp;}}

不太明白还可以看详解哦:
http://blog.csdn.net/qq_29503203/article/details/51615337

时间复杂度分析:
最好的情况,待排序序列本身为有序的,需要进行n-1次比较,没有移动,时间复杂度为O(n).
最坏的情况,待排序序列为逆序的,此时需比较(n+2)*(n+1)/2,而移动次数也达到(n+4)*(n-1)/2次,可以举个例子去理解一下,总的来说,忽略常数时间复杂度依然为O(n^2),但待排序序列是随机的,那么平均比较和移动的次数约为(n^2)/4,同样的O(n^2)直接插入排序还是比冒泡和选择排序性能要优一些。

4.希尔排序

如图分析:


希尔排序实际上是在优化直接插入排序,它的目的是让较大数据尽可能快的调到序列后边,让较小数据尽可能快的调到前面来,让序列快速趋于基本有序。但是这里比较麻烦的是它的gap该如何去取,目前还是个数学难题,这里先采取gap=gap/3 +1的方式。
这种算法的时间复杂度为O(n^(3/2)).要优于直接插入排序。

void ShellSort(int* arr,size_t sz){assert(arr);int gap=sz;while(gap > 1)  {gap=gap/3 +1;  //保证最后一个增量值为1 for(int index=gap;index<sz;++index)  //先默认下标为0的元素有序    {int pos=index-gap;int tmp=arr[index];while(pos>=0 && tmp < arr[pos]){std::swap(arr[pos],arr[pos+gap]);pos-=gap;}arr[pos+gap]=tmp;    }}}



5.堆排序
这种排序以前也是写过的,这里就不再介绍它的原理了,不明白的可以去看一下:
http://blog.csdn.net/qq_29503203/article/details/52799915

void AdjustDown(int *a, size_t root, size_t size)  {       size_t parent = root;      size_t child = parent * 2 + 1;  //先指向左孩子    while (child < size)      {          if (child + 1 < size && a[child] < a[child + 1])          {              ++child;          }          if (a[parent] < a[child])          {              std::swap(a[parent], a[child]);              parent = child;              child = parent*2 + 1;          }          else          {              break;          }      }  }  void HeapSort(int *arr,int sz){assert(arr);//升序建大堆for(int i=(sz-2)/2;i>=0;--i)   //从倒数第一个非叶子节点开始向下调整{AdjustDown(arr,i,sz);}for (size_t i=0;i<sz;++i)          {          std::swap(arr[0],arr[sz-1-i]);           AdjustDown(arr,0,sz-1-i);      }  }

时间复杂度分析:
在建堆的过程中,时间复杂度为O(n*lgn),调堆也是O(n*lgn),所以总的时间复杂度为O(n*lgn).效率确实比上面的几种都要高,但他也有缺陷的地方,一般堆排序适用于数组存储的数据,而对链表存储的数据时没办法的;其次,在序列接近有序的时候,插入排序会更优一些。

有关快排,归并算法会在下一篇继续介绍……




0 0