一.选择排序
选择排序的逻辑非常简单,就是循环遍历所有还未排序的数据,然后每轮循环取出最大或最小的数据置于已排序序列的末位,非常稳定,因为它不会申请额外空间,所以空间复杂度为O(1)。算法逻辑上只用两层循环,所以时间复杂度为O(n^2).
代码实现
template<class T> void SelectSort3(T* pData, size_t count, bool IsUpSort) { T swap; if (IsUpSort) { //升序 //从第一个元素开始,将它与后面的元素比较,遇到比它小的元素时便交换里面的数据,依次循环,直到末位; //一遍内循环后,第一个元素的值便是最小值,然后从第二个元素开始再次循环, //如此反复,直到外循环结束,数据便排序好了; for (size_t i=0;i<count;++i) { for (size_t j=i+1;j<count;++j) { if (pData[i]>pData[j]) { swap = pData[i]; pData[i] = pData[j]; pData[j] = swap; } } } } else { for (size_t i = 0; i < count; ++i) { for (size_t j = i + 1; j < count; ++j) { if (pData[i] < pData[j]) { swap = pData[i]; pData[i] = pData[j]; pData[j] = swap; } } } } }
选择排序耗时在每次找到合适的元素都要交换依次数据,可以上面代码轻微优化一下。
for (size_t i = 0; i < count- 1; ++i) //控制循环次数; { MinIndex = i; for (size_t j = i + 1; j < count;++j) //用来将当前数据与后面的数据比较,找出合适的值; { if (pData[MinIndex]>pData[j]) //记录下标; { MinIndex = j; } } swap = pData[MinIndex]; //交换数据; pData[MinIndex] = pData[i]; pData[i] = swap; }
只记录下标,这样每次内循环只交换一次。
二.冒泡排序
冒泡排序也很简单,它也是通过两层循环就可以简单实现的,与选择排序不同的是,它是相邻数据之间比较,条件合适便交换,就与它的名字一样,数组前面是水面,后面的元素就像气泡一样从后面比较冒上来。它的时间复杂度也是O(n^2),空间复杂度是O(1).
代码实现:
template<class T> void PopSort(T *pData, size_t count, bool IsUpSort) //冒泡排序,通过元素与下一个元素比较,每次比较两个元素来逐步排序; { //两层循环,一层用来控制循环次数,由元素个数决定,第二层循环将元素相互比较, //由第一个开始分别与下一个开始比较;满足条件就交换数据; //通过比较找到需要的值,进行值交换; T swap; if (IsUpSort) //升序 { for (size_t i = count - 1; i > 0; --i) //控制循环次数 { for (size_t j = 0; j < i ;++j) //控制交换比较次数; { if (pData[j]>pData[j+1]) { swap = pData[j]; pData[j] = pData[j + 1]; pData[j + 1] = swap; } } } } else //降序 { for (size_t i = count - 1; i > 0; --i) //控制循环次数 { for (size_t j = 0; j < i; ++j) //控制交换比较次数; { if (pData[j]<pData[j + 1]) { swap = pData[j + 1]; pData[j+1] = pData[j]; pData[j] = swap; } } } } }
简单优化一下,与选择排序相同
if (IsUpSort) { T swap; int MinIndex; for (size_t i = count - 1; i > 0;--i) //控制循环次数; { MinIndex = 0; for (size_t j = 0; j < i; ++j) //控制比较次数; { if (pData[MinIndex] < pData[j + 1]) //比较交换索引; { MinIndex = j + 1; } } swap = pData[MinIndex]; //根据索引交换元素; pData[MinIndex] = pData[i]; pData[i] = swap; } }
三.插入排序
插入排序的逻辑思想就是逐步插入元素,我们将第一个元素默认为第一个有序元素,将第二个元素与它比较,比它大就插入到第一个元素的前面,比他小就插入到它的后面,后面的元素逐个与它前面的元素比较,直到找到合适的位置插入。
代码实现
template<class T> void InsertSort(T *pData, size_t count, bool IsUpSort) //插入排序 将元素插入到已经排序好的序列里; { //先取第二个元素,与第一个元素比较,根据升降序来判断是否进行转换; //然后逐渐取后面的元素,从后面开始判断是否交换,如果要逐个交换找到位置。如果不要直接不动就是放入尾处; //这是提高效率的地方; T swap; if (IsUpSort) { for (size_t i = 1; i < count;++i) //循环控制从第二个元素考试取数; { for (size_t j = i; j>0; --j) //从尾开始有序序列的尾开始遍历; { if (pData[j] < pData[j - 1]) //能交换就循环找到位置 { swap = pData[j]; pData[j] = pData[j - 1]; pData[j - 1] = swap; } else //找不到进入下一个元素的循环以提高效率; { break; } } } } else //降序 { for (size_t i = 1; i < count; ++i) { for (size_t j = i; j>0; --j) { if (pData[j] > pData[j - 1]) { swap = pData[j]; pData[j] = pData[j - 1]; pData[j - 1] = swap; } else { break; } } } } }
插入排序的时间复杂度与空间复杂度与选择冒泡一样都是O(n^2)和O(1).break跳出循环的过程减少了比较多的交换与比较,所以在一般情况下它要比选择和冒泡快
简单优化一下
for (size_t i = 1; i < count; ++i) //控制循环次数; { swap = pData[i]; //定位自己; size_t j = i ; for (; j > 0; --j) //控制比较次数; { if (swap < pData[j-1]) //控制条件移动元素; { pData[j] = pData[j-1]; } else { break; } //插入最终位置; } pData[j] = swap; }
归并排序
归并排序的核心思想是分冶,我们先对数据进行划分,先将整个数据分成两半,再将这两份数据分成四份,逐步循环划分,直到数据被分到最小,然后开始归并,两两比较排序合并,逐渐合并成排序好的数组。归并排序的时间复杂度为O(nlogn),如果是非递归版只需要申请一次额外空间,它适合大量数据排序。并且归并排序在对链表的排序中有绝对的优势。
代码展示:
#ifndef SAFE_DELARR #define SAFE_DELARR(p) {if(p){delete []p;p=nullptr;}} #endif template<class T> void MereSort(T *pData, size_t count, bool IsUpSort = true) //归并排序, { //判断元素大小,对元素进行插分,把整个数据链插分成一个个元素; //将插分出来的元素当作最底层的构建,两两比较构成新的有序组件, //将做好的组件相互再次比较排序,形成新的组件,直到整个序列排序完; //排序的过程中减少了许多不必要的比较次数,大幅提高了效率 if (count>1) { //进行前后分组,将数组逐步分化成一个个的单位 int Fcount = count / 2; int Bcount = count - Fcount; //为分裂的数组分配新的空间; T *pfData = new T[Fcount]; T *pbData = new T[Bcount]; //转移数据 memcpy(pfData, pData, sizeof(T)*Fcount); memcpy(pbData, pData + Fcount, sizeof(T)*Bcount); //用递归分裂数据元素 MereSort(pfData, Fcount, IsUpSort); MereSort(pfData, Fcount, IsUpSort); /// //开始归并 int dataindex = 0; //定位原来数组空间的位置 int frontindex = 0;//分裂出来的前数组数据域 int backindex = 0;//分裂出来的后半组数据域 if (IsUpSort) { //将分裂出来的数组进行逐个比较排序, while (frontindex < Fcount&&backindex < Bcount) { if (pfData[frontindex] < pbData[backindex]) { pData[dataindex++] = pfData[frontindex++]; } else { pData[dataindex++] = pbData[backindex++]; } } } else { while (frontindex < Fcount&&backindex < Bcount) { if (pfData[frontindex] > pbData[backindex]) { pData[dataindex++] = pfData[frontindex++]; } else { pData[dataindex++] = pbData[backindex++]; } } } //将还未排序,即节省出来的操作空间给补上去; while (frontindex<Fcount) { pData[dataindex++] = pfData[frontindex++]; } while (backindex < Bcount) { pData[dataindex++] = pbData[backindex++]; } //释放临时数据空间; SAFE_DELARR(pbData); SAFE_DELARR(pfData); } }
非递归版
template<class T> void MereSort(T *pData, size_t count, bool IsUpSort = true) //归并函数的非递归版; { //相比较与递归版; //非递归版的方法是用两个范围来划定区域; //一个是从二开始逐步扩大,直到包括整个容器的范围; //一个是对上一个范围的分裂,对其排序合成大区; //大区分成小区,小区排序好和成大区,大区翻倍再分小区,直到排好序合成整个大区; //两个范围; int bigblocksize = 2; int samllblocksize; //用来合并原数据; T *_pTempData = new T[count]; while (bigblocksize < (int)count * 2) //通过大区大小判断是否全部排序; { samllblocksize = bigblocksize / 2; //划分小块; for (int bbh = 0; bbh < (int)count; bbh += bigblocksize) { //原数据的索引,前后区域的索引; int dataindex = bbh; int findex = bbh; int bindex = bbh + samllblocksize; //如果两个数据的数据域都没有超出,就排序; //如果超出了就说明有一个区域已经排好了; while (((findex - bbh) < samllblocksize) && (findex < (int)count) && (bindex - (bbh + samllblocksize) < samllblocksize) && (bindex < (int)count)) { if (pData[findex] < pData[bindex]) { _pTempData[dataindex++] = pData[findex++]; } else { _pTempData[dataindex++] = pData[bindex++]; } } //将剩余的前小块直接合并 while (((findex - bbh) < samllblocksize) && (findex < (int)count)) { _pTempData[dataindex++] = pData[findex++]; } //将剩余的后小块直接合并 while ((bindex - (bbh + samllblocksize) < samllblocksize) && (bindex < (int)count)) { _pTempData[dataindex++] = pData[bindex++]; } } //扩大大范围为之前的两倍 bigblocksize *= 2; //将合并好的缓冲区数据拷回原数据区 memcpy(pData, _pTempData, sizeof(T)* count); } SAFE_DELARR(_pTempData); //删除空间; }
桶排序
也叫基排序,比起归并排序,桶排序在分冶上做的更加的彻底,它适合小范围的大量数据;
逻辑思想是:将一个数据划分成多个范围,将他们依次倒入这些范围的桶内,这些桶是有序的,倒入时是按顺序倒入,在桶里面不用排序,再倒入下一个桶,从最后一个桶里出来后就全都排序好了。例如9,59,28,27四个数,先将个位倒入9和8,7三个桶中,再按顺序倒回来,就是27,28,9,59。再将10位倒入桶中,即0,2,5三个桶中。再按顺序倒出来,即9,27,28,59。其实就是一种简单的逐步按位排序。(此处为升序)
桶排序的时间复杂度为O(n),是一种用空间换时间的算法,需要注意的是桶排序的目标数据应该尽量均匀。最坏的情况下桶排序的数据全在一个桶里面,时间复杂度就会退化到O(nlogn).
代码展示:
template<class T> void RadixSort(T *pData, size_t size, size_t databit, bool IsUp = true) { int baserate = 1; T *Tong[10]; int TongIndex[10]; int _tongindex; int dataindex = 0; int _daoindex[10]; for (int i = 0; i < 10; ++i) { Tong[i] = new T[size]; } for (size_t sorttime = 0; sorttime < databit; ++sorttime) { for (int i = 0; i < 10; ++i) { TongIndex[i] = 0; } for (size_t i = 0; i < size; ++i) { _tongindex = (pData[i] / baserate) % 10; Tong[_tongindex][TongIndex[_tongindex]++] = pData[i]; } dataindex = 0; memset(_daoindex, 0, sizeof(_daoindex)); if (IsUp) { for (int t = 0; t < 10; ++t) { while (_daoindex[t] < TongIndex[t]) //桶里有东西 { pData[dataindex++] = Tong[t][_daoindex[t]++]; } } } else { for (int t = 9; t >= 0; --t) { while (_daoindex[t] < TongIndex[t]) { pData[dataindex++] = Tong[t][_daoindex[t]++]; } } } baserate *= 10; } for (int i = 0; i < 10; ++i) { SAFE_DELARR(Tong[i]); } }
希尔排序
希尔排序可以说是插入排序的一种进阶算法,相比较插入排序,希尔排序的插入是通过一种距离插入来实现的。在插入排序中,如果数据比较有序,那么效率会很高,我们通过“步长”调整数组,使得数组尽量有序,那么在步长为1即标准插入时就会快上许多。
template<class T> void ShellSort(T *pData, size_t count, bool IsUpSort = true) //希尔排序,更像是归并的变种 { //用步长来分裂数据元素,分为多个数组,每个数组进行一次插入排序后为一次排序; //步长每次缩小一半,再次排序;直到步长等于0; //比起归并,它减少了交换值的过程,节约了效率; int step = count / 2; T temp; if (IsUpSort) { while (step > 0) //通过步长来控制循环 { for (int current = step; current < (int)count;++current) //用步长来定位数据位置,控制循环; { temp = pData[current]; // 将数据逐步后移,当前位置就是要比较插入的位置 int frontindex = current - step; while (frontindex >= 0) { if (temp < pData[frontindex]) //如果他的前一个位置符合条件可以插入 { pData[frontindex + step] = pData[frontindex]; //将前面的数据后移; } else { break; } frontindex -= step; //往前再移动一步,直到找到位置 } pData[frontindex + step] = temp; //找到位置后将数据插入该位置;完成一次步长的排序 } step /= 2; //缩减排序; } } else { while (step > 0) //通过步长来控制循环 { for (int current = step; current < (int)count; ++current) //用步长来定位数据位置,控制循环; { temp = pData[current]; // 将数据逐步后移,当前位置就是要比较插入的位置 int frontindex = current - step; while (frontindex >= 0) { if (temp > pData[frontindex]) //如果他的前一个位置符合条件可以插入 { pData[frontindex + step] = pData[frontindex]; //将前面的数据后移; } else { break; } frontindex -= step; //往前再移动一步,直到找到位置 } pData[frontindex + step] = temp; //找到位置后将数据插入该位置;完成一次步长的排序 } step /= 2; //缩减排序; } } }
希尔排序在最好的情况下时间复杂度是O(ologn),最坏的情况下是n(1.5);即数据比较大时。
快速排序
快速排序的实现原理不难,他的逻辑思想是选定一个基准数数,以这个数为中心,将整个数据域分成两个部分,即都比他大或者都比它小的部分(等于看自己怎么写判断),这个就是一次快牌,接着我们再对分出来的两块数据进行同样的操作,选择基准数将其再分,直到每一块只有一个数据。
template<class T> void _QuickSort(T *pData, int low, int high, bool IsUpSort) //快速排序,对归并排序的优化, { //将传进来的数据用两个下标直线头尾; //选取一边的第一个数为key值用来划分数组, //将key插入到相应的位置,此时左边的恒比小于等于key,右边的大于等于key的值, //将插入的key值左边化成一个数组快排,右边也快排,直到每个数据排号; int first = low; //指向数据的左右边 int last = high; if (first>=last) //只有一个数据退出函数 { return; } T key = pData[first]; //key值为第一个数 while (first < last) { while (first<last) //从后比较key值,找到比key值小的元素,找到后将其付给fisrt位置,结束这次的last查找,或者first与last重叠退出 { if (pData[last] < key) { pData[first] = pData[last]; break; } else { --last; } } while (first < last) //再从前开始比较,找到大于等于key值的数,将其付给last,或者两个坐标重叠,退出此次查找; { if (pData[first] > key) { pData[last] = pData[first]; break; } else { ++first; } } } pData[first] = key; //将key值找到的位置 _QuickSort(pData, low, first - 1, IsUpSort); //归并类似,继续快排; _QuickSort(pData, first + 1, high, IsUpSort); } template<class T> void QuickSort(T *pData, size_t count, bool IsUpSort = true) //快速排序的函数接口; { _QuickSort(pData, 0, count - 1, IsUpSort); //调用写好的函数,传入合理的数据; }
快速排序最好的时间复杂度是O(nlogn),最差的情况是O(n^2),即逆序有序的情况下,针对这一点我们可以通过随机选择基数来优化这一点,也可有每一步多选基数进行多路快排。
堆排序,这个可以看下面这个博主的介绍: