驯鹿的响铃和雪花的相遇..........................................................................................................
文章目录
前言
本篇介绍了数据结构中排序的相关知识,欢迎观看和指正。
一、【排序的相关介绍】
1.概念:
所谓排序,就是使一串记录,按照其中的某个或某些关键字的大小,递增或递减的排列起来的操作。
2.稳定性:
假定在待排序的记录序列中,存在多个具有相同的关键字的记录,若经过排序,这些记录的相对次序保持不变,即在原序列中,r[i]=r[j],且r[i]在r[j]之前,而在排序后的序列中,r[i]仍在r[j]之前,则称这种排序算法是稳定的;否则称为不稳定的。
3.分类
内部排序:数据元素全部放在内存中的排序。
外部排序:数据元素太多不能同时放在内存中,根据排序过程的要求不能在内外存之间移动数据的排序。
4.排序相关的算法:
二、【常见的八种排序】
1.【直接插入排序】
1.【相关概念及介绍】
【直接插入排序是一种简单的插入排序法,其基本思想是】:
把待排序的数据按其关键码值的大小逐个插入到一个已经排好序的有序序列中,直到所有的记录插入完为止,得到一个新的有序序列 。这里可以联想到我们玩过的扑克牌。
【其实现思路为】:
当插入第i(i>=1)个元素时,前面的arr[0],arr[1],…,array[i-1]已经排好序,此时用arr[i]的值与arr[i-1],arr[i-2],…的值进行顺序比较,找到插入位置即将arr[i]插入,再将原来位置上的元素顺序后移
【具体如图】:
【直接插入排序的特性总结】:
1. 元素集合越接近有序,直接插入排序算法的时间效率越高
2. 时间复杂度:O(N^2)
3. 空间复杂度:O(1),它是一种稳定的排序算法
4. 稳定性:稳定值得一提的是,插入排序是n^2里的守门员,意思就是,插入排序过不去的其他n^2的排序算法也过不去。
2.【直接插入排序的实现】
代码部分:
void InsertSort(int* arr, int size)//传入数组,数组大小 { for (int i = 0; i < size - 1; i++) { int end=i;//从数组第一个元素开始插入排序,将数组第一个元素视为有序,将其之后的元素进行插入 int tmp=arr[end+1];//tmp表示要插入的元素,end表示有序元素的界限和范围。 while (end >= 0)//这里存在极端情况,就是要插入的元素tmp比数组任意一个元素都要小,这样就会 //导致end越界,所以循环只在end>=0时进行。 { if (tmp < arr[end])//如果要插入的元素tmp比end对应的元素要小,就顺序挪动元素,并--end { //以达到依次比较。 arr[end + 1] = arr[end]; --end; } else//一旦出现比end所对应的元素小的情况就在end对应元素之后插入 { break; } } arr[end + 1] = tmp;//即使出现极端情况,end越界,也能让tmp顺利插入 } }
2.【希尔排序】
1.【相关概念及介绍】
希尔排序法又称缩小增量法。希尔排序法的基本思想是:先选定一个整数gap一般取数组大小,然后按gap=gap/3+1(或者按gap=gap/2)来减小gap,这样就能使得组间间隔越来越小,从而使数组更接近有序(这里的+1是为了防止gap出现0的情况比如当gap<3时gap/3=0)然后把待排序数组中所有数据按步长为gap分成多个组,所有距离为gap的记录分在同一组内,并对每一组内的记录进行排序。然后,取gap=gap/3+1(或者按gap=gap/2),重复上述分组和排序的工作。直到gap=1时此时就是将数组中的数据每个数据当成一个组,就变为了插入排序,总的来说,希尔排序主要分为两步:预排序(利用gap!=1的步长将数组排成近似有序)和直接插入排序(当gap减为1时,就变为了直接插入排序)。
实现思路:
先对数组中的元素进行分组,假定gap首先为10/2=5,则会将【9,4】,【1,8】,【2,6】,【5,3】,【7,5】如下图相同元素的数据所示,分完组以后组内进行插入排序,即第一组为【9,4】,将9当作排好序元素,4当作待插入元素,进行插入排序,之后减小gap为2,再进行分组和组内排序【4,1,5,8,5】,【1,3,9,6,7】,将4当成排好序元素,【1,5,8,5】进行插入,.......直到gap减为1,这时候彻底变为插入排序,直接进行即可。
【希尔排序的特性总结】:
1. 希尔排序是对直接插入排序的优化。
2. 当gap > 1时都是预排序,目的是让数组更接近于有序。当gap == 1时,数组已经接近有序的了,这样就会很快。这样整体而言,可以达到优化的效果。我们实现后可以进行性能测试的对比。
3. 希尔排序的时间复杂度不好计算,因为gap的取值方法很多,导致很难去计算,因此在好些树中给出的希尔排序的时间复杂度都不固定:这里的gap是按照Knuth提出的方式取值的,而且Knuth进行了大量的试验统计,我们暂时就按照:O(n的1.25次方)到O(1.6乘n的1.25次方) 来计算。
4. 稳定性:不稳定,由于每次排序的难度不同,造成这种排序算法并不稳定。(由于相同元素之间可能会分到不同的组织间,间接导致相同元素之间的相对位置发生了改变)。2.【希尔排序的实现】
void ShellSort(int* arr, int size) { int gap=size;//首先将间隔定为数组长度,便于后续调整 while (gap > 1)//当gap为1时跳出循环,注意当gap为2时能进入循环,进入循环以后gap变为1 { gap = gap / 3 + 1;//减小gap使数组越来越接近有序,当gap越大,数组元素调整越快。 for (int i = 0; i < size - gap; i++)//i<size-gap的原因是防止tmp越界,利用i来实现分组 { //进而进行组内插入排序 int end = i; int tmp = arr[end + gap];//下面就是不同步长的插入排序 while (end >= 0) { if (tmp < arr[end]) { arr[end + gap] = arr[end]; end -= gap; } else { break; } } arr[end + gap] = tmp; } } }
3.【选择排序】
1.【选择排序的概念及介绍】
基本思想:
每一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,直到全部待排序的数据元素排完 。直接选择排序:
在元素集合arr[i]--arr[n-1]中选择关键码最大(小)的数据元素分别记为maxi和mini
若它不是这组元素中的最后一个(第一个)元素,则将它与这组元素中的最后一个(第一个)元素进行交换,将这两个元素排除,继续挑选剩下元素中最大和最小的两个元素,循环往复。
在剩余的arr[i]--arr[n-2](arr[i+1]--arr[n-1])集合中,重复上述步骤,直到集合剩余1个元素排序就进行完成了。直接选择排序的特性总结:
1. 直接选择排序思考非常好理解,但是效率不是很好。实际中很少使用
2. 时间复杂度:O(N^2)
3. 空间复杂度:O(1)
4. 稳定性:不稳定(当选的最大和最小元素出现相等的情况时,可能会导致相对位置发生改变)2.【选择排序的实现】
void Swap(int* p1, int* p2) { int tmp = *p1; *p1 = *p2; *p2 = tmp; } void SelectSort(int* arr, int size) { int begin = 0;//数组第一个元素位置 int end = size - 1;//数组最后一个元素位置 int maxi = begin;//假设数组中最大的元素在在数组开始位置,这里有极端情况,即当数组中的最大元 //素就在开始位置 int mini = begin;//同maxi while (begin < end)//当数组开端和末尾重合即排序完成 { for (int i = begin; i <= end; i++)//从数组开始位置到末尾,遍历数组找出最大和最小元素 { if (arr[i] > arr[maxi]) { maxi = i; } if (arr[i] < arr[mini]) { mini = i; } } Swap(&arr[begin], &arr[mini]);//将最小元素放在数组开端 if (maxi == begin)//若出现极端情况,这是由于最小元素和开端已经进行交换,这里maxi的位置 { //处放置的是mini,只需要修正maxi的位置即可 maxi = mini; } Swap(&arr[end], &arr[maxi]);//再将最大元素放在数组末端 begin++;//每次替换完最大最小元素,更新数组的开端和末尾 end--; } }
4.【冒泡排序】
由于之前说过,详情请看之前的【C语言——冒泡排序】
这里仅附代码部分:
冒泡排序是稳定的,由于出现相等元素时数据之间并不会进行交换。
void BubbleSort(int* arr, int size) { for (int i = 0; i < size-1; i++) { bool exchange = false; for (int j = 0; j < size - 1 - i; j++) { if (arr[j] > arr[j + 1]) { int tmp = arr[j]; arr[j] = arr[j + 1]; arr[j + 1] = tmp; exchange = true; } } if (exchange == false) { break; } } }
5.【快速排序】
快速排序人如其名,是一种效率很快的排序算法,其常见的有三种实现方式:
需要注意的是,在快速排序的过程中,正在进行第i趟排序,则待排数组中,就有i个元素已经位于其最终位置。
1.【霍尔实现法(HOARE)】
快速排序是Hoare于1962年提出的一种二叉树结构的交换排序方法,其基本思想为:任取待排序元素序列中的某元素作为基准值(记为keyi),按照基准值将待排序集合分割成两子序列,左子列中所有元素均小于基准值,右子序列中所有元素均大于基准值,然后对左右子序列重复该过程,直到所有元素都排列在其相应位置上为止。
这里通过图片详解,假设待排数组为:
1.
2.
3.
4.
动态演示如下:
到这里,就完成了一次单趟排序,接下来只需要想二叉树那样将整个区间分为3段分别为:
然后递归各个区间即可:
需要注意的是,这里递归的结束条件:
代码部分:
int partsort_hoare(int* arr, int left, int right)//单趟排序的函数 { int keyi = left; while (left < right) { while (right > left && arr[right] >= arr[keyi])//右边找小 { right--; } while (left < right && arr[left] <= arr[keyi])//左边找大 { left++; } Swap(&arr[left], &arr[right]);//找到了就交换 } Swap(&arr[left], &arr[keyi]);//当left和right相遇时,将keyi与相遇的位置交换即可,这样就能 keyi = left;//使这一次keyi的位置处于其最终位置(左边的值都比其小,右边的值都比其大) return keyi;//最后返回这个keyi即可 } void QuickSort_Hoare(int* arr, int begin, int end) { int keyi = partsort_hoare(arr, begin, end);//通过单趟排序得到keyi if (begin >= end)//递归结束条件 { return; } QuickSort_Hoare(arr, begin, keyi - 1);//得到第一个keyi后将整个区间分为3部分 QuickSort_Hoare(arr, keyi+1, end); }
这里有两个问题:
1.为什么left不能从key+1的位置往后查找?
这是因为,如果key位置的值小于其之后的所有值,那么right和left就会在left的位置(key+1)的位置相遇,就会导致key和key+1的位置交换,使key查找错误。
2.为什么left和right先后与的位置一定小于key位置的值?
这是因为,我们在设计时故意这样设计即如果让左边作key就让右边先走,理由如下:
2.【挖坑实现法(HOLE)】
挖坑法指的是,先找到一个用来衡量数据大小的标准(key)并将其位置作为坑(hole)(这个标准key一般选取数组的第一个元素),之后类似于HOARE通过left和right来分别找左边大于hole位置的值,右边找小于hole位置的值,找到以后将其置于坑的位置,并将其位置当作新的坑,如此往复,直到left与right相遇,相遇的位置一定是坑,这时候将先前选的标准放到这里即可,当left于right相遇时所处的位置就是key最终存放的位置,即这个位置左边的数据都小于key,右边都大于key,同HOARE一样,这个标准(key)也来到了它最终的位置。
1.
2.
3.
4.
5.
同HOARE,只需要将整个区间分为3段即可:
代码:
int partsort_hole(int* arr, int left, int right)//单趟排的函数 { int key = arr[left];//挖坑,先保存坑位的值 int hole = left;//记录坑的位置 while (left < right)//循环结束条件 { while (right > left && arr[right] >= key) {//右边找大 right--; } arr[hole] = arr[right];//为坑位赋值的同时更新坑位 hole = right; while (right > left && arr[left] <= key) {//左边找小 left++; } arr[hole] = arr[left];//为坑位赋值的同时更新坑位 hole = left; } arr[hole] = key;//最终将坑位赋值为开始选的标准 return hole; } void QuickSort_Hole(int* arr, int begin, int end)//同HOARE { int keyi = partsort_hole(arr, begin, end); if (begin >= end) { return; } QuickSort_Hole(arr, begin, keyi - 1); QuickSort_Hole(arr, keyi + 1, end); }
3.【前后指针实现法(FOLLOWER)】
前后指针实现法指的是,定义两个指针,再选定数据比较大小的标准keyi,让这两个指针分别从keyi(对应指针为prev)和keyi+1(对应指针为cur),开始向后遍历,prev在当其遍历的数据小于keyi对应的值时,向后移动,当遍历到大于keyi对应的值时,停止移动,而cur无论其便利的值是小于keyi还是大于keyi,都向后移动,但是,当cur与prev拉开距离以后,若cur再次遍历到小于keyi的值,则需要根prev的下一个值进行交换这样可以使大于keyi和小于keyi的值各自连续的放在一块,直到cur遍历完了数组,此时,只需要将keyi对应的值与prev进行交换即可,这样就能使得keyi左边都是小于其的值,右边都是大于其的值。
1.
2.
3.
4.
动图:
同HOARE和HOLE,代码部分为:
int partsort_follower(int* arr, int left,int right) { int prev = left; int cur = prev + 1; int keyi = left; while (cur <= right) { if (arr[cur] < arr[keyi] && ++prev != cur) { Swap(&arr[cur], &arr[prev]); } cur++; } Swap(&arr[keyi], &arr[prev]); keyi = prev; return keyi; } void QuickSort_Follower(int* arr, int begin, int end) { int keyi = partsort_follower(arr, begin, end); if (begin >= end) { return; } QuickSort_Follower(arr, begin, keyi - 1); QuickSort_Follower(arr, keyi + 1, end); }
4.【快速排序的非递归形式】
利用非递归的形式,实现快速排序,我们知道,快速排序是通过每次调整数组从而得到keyi来实现排序的,而每次调整都是通过区间进行调整的,即【left,key-1】,【keyi】,【key+1,right】,所以可以通过控制区间的传入来实现非递归形式的快速排序。这里采用“栈”,首先将数组的两端入栈,在出栈,利用这两个端点来获得第一个keyi,接着通过【left,keyi-1】和【key+1,right】,这两个区间分别入栈如此往复,就能通过控制区间的被取顺序来实现类似于递归的效果。
代码部分:
void QuickSort(int* arr, int begin, int end) { ST st;//创建栈 STInit(&st);//初始化栈 STPush(&st,end);//先入整个区间 STPush(&st,begin); while (!STEmpty(&st))//栈不为空,就一直取 { int left = STTop(&st); STPop(&st); int right = STTop(&st); STPop(&st); int keyi = partsort_hoare(arr, left, right); if (keyi + 1 < right) { STPush(&st, right); STPush(&st, keyi + 1); } if (keyi - 1 > left) { STPush(&st, keyi - 1); STPush(&st, left); } } STDestroy(&st); }
5.【快速排序的效率及其优化】
由上述三种方法可以看出,快速排序的核心要点在于key的选取,其表现为:
所以key的选取对于提高快速排序的效率至关重要,这里我们可以采用三数取中的方法来选取key,从而提高排序的效率。也可以当递归到小的子区间时,考虑使用插入排序
【三数取中的介绍】
三数取中顾名思义就是在三个数里找中间大的数字
代码:
int GetMidIndex(int* a, int left, int right) { int mid = (left + right) / 2;//先假定中间数位于整个区间的中间位置 if (a[left] < a[mid])//如果中间数大于起始值 { if (a[mid] < a[right])//并且中间数小于末尾值 { return mid;//说明其就是中间数 } else if (a[left] < a[right])//如果大于起始值,又大于末尾值,并且末尾值大于起始值,说明 { //末尾值为中间数 return right; } else//如果大于起始值,又大于末尾值,并且末尾值小于于起始值,说明起始值为中间数 { return left; } } else // a[left] > a[mid],中间数小于起始值 { if (a[mid] > a[right])//并且大于末尾值 { return mid;//说明其就是中间数 } else if (a[left] > a[right])//如果小于起始值,又小于末尾值,并且末尾值小于起始值 { return right;//说明末尾值为中间数 } else { return left;//如果小于起始值,又小于末尾值,并且末尾值大于起始值,说明起始值为中间数 } } }
在使用时只需:
int GetMidIndex(int* a, int left, int right) { int mid = (left + right) / 2; if (a[left] < a[mid]) { if (a[mid] < a[right]) { return mid; } else if (a[left] < a[right]) { return right; } else { return left; } } else // a[left] > a[mid] { if (a[mid] > a[right]) { return mid; } else if (a[left] > a[right]) { return right; } else { return left; } } } int partsort_hoare(int* arr, int left, int right) { int mid = GetMidIndex(arr, left, right); Swap(&arr[left], &arr[mid]); int keyi = left; while (left < right) { while (right > left && arr[right] >= arr[keyi]) { right--; } while (left < right && arr[left] <= arr[keyi]) { left++; } Swap(&arr[left], &arr[right]); } Swap(&arr[left], &arr[keyi]); keyi = left; return keyi; } void QuickSort_Hoare(int* arr, int begin, int end) { if (begin >= end) { return; } int keyi = partsort_hoare(arr, begin, end); QuickSort_Hoare(arr, begin, keyi - 1); QuickSort_Hoare(arr, keyi+1, end); }
【三路划分的介绍】
当存在大量数据,并且数据中有较多相等的值时,可以采用三路划分的思想具体表现为将数据分为三部分分别是,小于key的,等于key的和大于key的
具体体现为:
代码:
void Swap(int* p1, int* p2) { int tmp = *p1; *p1 = *p2; *p2 = tmp; } int GetMidIndex(int* arr, int left, int right) { int mid = (left + right) / 2; if (arr[left] < arr[mid]) { if (arr[mid] < arr[right]) { return mid; } else if (arr[left] < arr[right]) { return right; } else { return left; } } else // a[left] > a[mid] { if (arr[mid] > arr[right]) { return mid; } else if (arr[left] > arr[right]) { return right; } else { return left; } } } void QuickSort(int* arr, int begin, int end) { if (begin >= end) { return; } int left = begin; int right = end; int cur = left + 1; int midi = GetMidIndex(arr, left,right); Swap(&arr[left], &arr[midi]); int key = arr[left]; while (cur <= right) { if (arr[cur] < key) { Swap(&arr[cur], &arr[left]); left++; cur++; } else if (arr[cur] > key) { Swap(&arr[cur], &arr[right]); right--; } else { cur++; } } QuickSort(arr, begin, left - 1); QuickSort(arr, right+1, end); }
【快速排序的特性总结】:
1. 快速排序整体的综合性能和使用场景都是比较好的,所以才敢叫快速排序
2. 时间复杂度:O(N*logN)3. 空间复杂度:O(logN)
4. 稳定性:不稳定
6.【堆排序】
同冒泡排序,堆排序在【数据结构中的树】中有详细讲解,这里仅附代码:
void Swap(int* p1, int* p2) { int tmp = *p1; *p1 = *p2; *p2 = tmp; } void AdjustDown(int* arr, int size, int parent) { int child = 2 * parent + 1; while (child < size) { if (child + 1 < size && arr[child + 1] > arr[child]) { child++; } if (arr[child] > arr[parent]) { Swap(&arr[child], &arr[parent]); parent = child; child = parent * 2 + 1; } else { break; } } } void HeapSort(int* arr, int size) { for (int i = (size - 1 - 1) / 2; i >= 0; i--) { AdjustDown(arr, size, i); } int end = size - 1; while (end > 0) { Swap(&arr[0], &arr[end]); AdjustDown(arr, end,0); end--; } }
7.【归并排序】
什么是归并排序?简单来说就是先将数组里的元素拆分为一个一个组,然后将各个组之间进行归并,直到最后归并成两个有序的序列,再通过这两个组里的数据大小关系依次尾插到新数组里,完成最后一次归并,简单来说归并就是两个数据根据大小关系取取小的尾插。
【基本思想】:
归并排序(MERGE-SORT)是建立在归并操作上的一种有效的排序算法,该算法是采用分治法(Divide andConquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。【归并排序核心步骤】:
代码部分:
void _MergeSort(int* arr, int begin, int end, int* tmp)//子函数 { if (begin >= end)//递归终止条件,当递归到只有两个值时,进行归并即可 { return; } / 小区间优化 /*if (end - begin + 1 < 10) { InsertSort(a+begin, end - begin + 1); return; }*///当数据集很大且递归到小区间时,比如10个数字以内,就可以利用插入排序进行排序,这样就不用再 //进行归并和尾插了。 int mid = (begin + end) / 2;//先进行分组,分成左右两组 _MergeSort(arr, begin, mid , tmp);//递归调用左边,确保左边有序 _MergeSort(arr, mid+1, end, tmp);//递归调用左边,确保左边有序 int begin1 = begin;//确定归并区间,区间分为【begin,mid】,【mid+1,end】 int end1 = mid ; int begin2 = mid + 1; int end2 = end; int i = begin;//利用归并的开始区间,来确定尾插的位置 while (begin1 <=end1 && begin2<= end2)//取小的进行尾插 { if (arr[begin1] <= arr[begin2]) { tmp[i++] = arr[begin1++]; } else { tmp[i++] = arr[begin2++]; } } while (begin1 <= end1) { tmp[i++] = arr[begin1++]; } while (begin2 <= end2) { tmp[i++] = arr[begin2++]; } memcpy(arr + begin, tmp + begin, sizeof(int) * (end - begin + 1));//最后将尾插完成的有 //序数组,复制到原数组,其中复制范围由归并区间决定,实现了归并一段复制一段 } void MergeSort(int* arr, int n)//归并排序主函数 { int* tmp = (int*)malloc(sizeof(int) * n);//创建用来归并的数组接受尾插的数据 _MergeSort(arr, 0, n - 1, tmp);//调用子函数,以便递归进行 free(tmp); }
【非递归形式】
非递归形式,相比较于递归形式实现起来难度加大,这里简单来说,首先类似于希尔排序定义一个gap,用来对数组进行分组,开始时令gap为1,即单个数据之间进行归并,接着将gap变为2,使得两个数据构成1个小组于其他小组进行归并,接着gap变为4,4个数据为1组进行归并........,直到gap增长为数组大小时即体制归并。
代码部分:
void MergeSortNonr(int* arr, int n) { int* tmp = (int*)malloc(sizeof(int) * n); int gap = 1;//首先组间元素定义为1,即单个元素进行归并 while (gap < n) { int j = 0;定义j来确定尾插的位置 for (int i = 0; i < n; i+=2*gap) {//定义i来控制数组中的数据,让各个小组之间存在间隔 int begin1 = i;//这里通过gap来控制小组中的数据个数 int end1 = i + gap - 1; int begin2 = i + gap; int end2 = i + gap * 2 - 1; if (begin2 >= n || end1 >= n) { break;//防止越界,出现越界,进行修正 } if (end2 >= n) {//进行修正 end2 = n-1; } while (begin1 <= end1 && begin2 <= end2)//取小的进行尾插 { if (arr[begin1] <= arr[begin2]) { tmp[j++] = arr[begin1++]; } else { tmp[j++] = arr[begin2++]; } } while (begin1 <= end1) { tmp[j++] = arr[begin1++]; } while (begin2 <= end2) { tmp[j++] = arr[begin2++]; } memcpy(arr + i, tmp + i, sizeof(int)*(end2 - i + 1));//归并一段拷贝一段,这里的 //end2是各区间末尾位置,i为各区间开始位置,end2-i能够确定拷贝范围。 } gap *= 2;//迭代gap } free(tmp); }
【归并排序的特性总结】:
1. 归并的缺点在于需要O(N)的空间复杂度,归并排序的思考更多的是解决在磁盘中的外排序问题。
2. 时间复杂度:O(N*logN)
3. 空间复杂度:O(N)
4. 稳定性:稳定
8.【计数排序】
【思想】:
计数排序又称为鸽巢原理,是对哈希直接定址法的变形应用。简单来说就是确定待排数组的区间范围比如给定数组;102 100 104 104 106 107 107 100 109 110 104 102这12个数字,数组的区间范围就是100-110一共11个数字,那么就可以建立一个大小为11的数组,下标为0-10,先找到这组数字中最小和最大的元素,即为100和110,然后遍历该数组,用每次遍历的数据减去最小数,从而确定该数据在新创立数组中的位置,每出现先一次该数据就在确定好的位置处+1,比如这组数对应的新数组为2 0 2 0 3 0 1 2 0 1 1对应的下标分别为:
0,1,2,3,4,5,6,7,8,9,10
然后再通过下标和最小值来确定代表原数组中的什么数据,比如下表为0的+100=100,下标为0处代表100,而其存放的值为2,则从原数组下标为0处开始连续存放两个100,而由于没有101,所以101直接跳过,因为2+100=102,在原数组的剩下位置即下标为2,3的地方存放102,以此类推.........
数组最终变为:
100 100 102 102 104 104 104 106 107 107 109 110
这样就排好序了,不难发现计数排序并没有数据之间的比较,这是因为我们利用了数组的下标,而数组的下标本质上就是一连串排好序的数字。
【操作步骤】:
1. 统计相同元素出现次数
2. 根据统计的结果将序列回收到原来的序列中【计数排序的特性总结】:
1. 计数排序在数据范围集中时,效率很高,但是适用范围及场景有限。
2. 时间复杂度:O(MAX(N,范围))
3. 空间复杂度:O(范围)4. 稳定性:稳定
代码部分:
void CountSort(int* arr, int n) { int min = arr[0]; int max = arr[0];//找出数组中最大和最小的值 for (int i = 0; i < n; i++) { if (arr[i] < min) { min = arr[i]; } } for (int i = 0; i < n; i++) { if (arr[i] > max) { max = arr[i]; } } int range = max - min + 1;//确定开辟数组的下表区间范围 int* Count_arr = (int*)malloc(sizeof(int) * range); memset(Count_arr, 0, sizeof(int) * range);//初始化数组确保其中存放全为0 for (int i = 0; i < n; i++) { Count_arr[arr[i] - min]++;//计数 } int k = 0; for (int j = 0; j < range; j++)//排序 { while (Count_arr[j]--) { arr[k++] = j + min; } } }
三、【各排序算法的效率及其稳定性分析】
1.【稳定性和复杂度分析】
要看一个排序算法是否稳定,就是看其在排序前后各相同数据之间的相对位置是否发生改变,比如这里在进行选择排序时,待排数据为 5 5 1 1 ,这里max取5,min取 1,第一次排序完成后
就会变为1 5 1 5,这里对于红色的1来说,未排序之前其位于黑色的1之前,排序以后其仍然位于黑色的1之前,但是对于黑色的5来说其排序之前其位于红色5之前,完成排序以后其位于红色5之后,相对位置发生了变化,所以选择排序是不稳定的。
2.【效率的综合比较】
这里将这些排序放在一起比较一下性能:
可以看出像快排,计数排序,堆排,希尔,还有归并以及三路归并,这些排序在100000个数据下,效率还是很快的,其次就是像,插入排序,选择排序,冒泡排序,这些效率就要略低一些,其中插入排序算n^2里效率最高的排序算法了。
最后再附上代码:
sort.h部分:
#define _CRT_SECURE_NO_WARNINGS #include <stdio.h> #include <stdlib.h> #include <stdbool.h> #include <string.h> #include <time.h> void PrintArray(int* arr, int size); void InsertSort(int* arr, int size); void ShellSort(int* arr, int size); void BubbleSort(int* arr, int size); void SelectSort(int* arr, int size); void HeapSort(int* arr, int size); void QuickSort_Hoare(int* arr, int begin, int end); void QuickSort_Hole(int* arr, int begin, int end); void QuickSort_Follower(int* arr, int begin, int end); int GetMidIndex(int* a, int left, int right); void MergeSort(int* arr, int n); void CountSort(int* arr, int n); void QuickSort_ThreeRoad(int* arr, int begin, int end);
sort.c部分:
#define _CRT_SECURE_NO_WARNINGS #include "sort.h" void PrintArray(int* arr, int size) { for (int i = 0; i < size; i++) { printf("%d ", arr[i]); } printf("\n"); } void InsertSort(int* arr, int size) { for (int i = 0; i < size - 1; i++) { int end = i; int tmp = arr[end + 1]; while (end >= 0) { if (tmp < arr[end]) { arr[end + 1] = arr[end]; --end; } else { break; } } arr[end + 1] = tmp; } } void ShellSort(int* arr, int size) { int gap = size; while (gap > 1) { gap = gap / 3 + 1; for (int i = 0; i < size - gap; i++) { int end = i; int tmp = arr[end + gap]; while (end >= 0) { if (tmp < arr[end]) { arr[end + gap] = arr[end]; end -= gap; } else { break; } } arr[end + gap] = tmp; } } } void BubbleSort(int* arr, int size) { for (int i = 0; i < size-1 ; i++) { bool exchange = false; for (int j = 0; j < size - 1 - i; j++) { if (arr[j] > arr[j + 1]) { int tmp = arr[j]; arr[j] = arr[j + 1]; arr[j + 1] = tmp; exchange = true; } } if (exchange == false) { break; } } } void Swap(int* p1, int* p2) { int tmp = *p1; *p1 = *p2; *p2 = tmp; } void SelectSort(int* arr, int size) { int begin = 0; int end = size - 1; int max = begin; int min = begin; while (begin < end) { for (int i = begin; i <= end; i++) { if (arr[i] > arr[max]) { max = i; } if (arr[i] < arr[min]) { min = i; } } Swap(&arr[begin], &arr[min]); if (max == begin) { max = min; } Swap(&arr[end], &arr[max]); begin++; end--; } } void AdjustDown(int* arr, int size, int parent) { int child = 2 * parent + 1; while (child < size) { if (child + 1 < size && arr[child + 1] > arr[child]) { child++; } if (arr[child] > arr[parent]) { Swap(&arr[child], &arr[parent]); parent = child; child = parent * 2 + 1; } else { break; } } } void HeapSort(int* arr, int size) { for (int i = (size - 1 - 1) / 2; i >= 0; i--) { AdjustDown(arr, size, i); } int end = size - 1; while (end > 0) { Swap(&arr[0], &arr[end]); AdjustDown(arr, end, 0); end--; } } int GetMidIndex(int* a, int left, int right) { int mid = (left + right) / 2; if (a[left] < a[mid]) { if (a[mid] < a[right]) { return mid; } else if (a[left] < a[right]) { return right; } else { return left; } } else // a[left] > a[mid] { if (a[mid] > a[right]) { return mid; } else if (a[left] > a[right]) { return right; } else { return left; } } } int partsort_hoare(int* arr, int left, int right) { int mid = GetMidIndex(arr, left, right); Swap(&arr[left], &arr[mid]); int keyi = left; while (left < right) { while (right > left && arr[right] >= arr[keyi]) { right--; } while (left < right && arr[left] <= arr[keyi]) { left++; } Swap(&arr[left], &arr[right]); } Swap(&arr[left], &arr[keyi]); keyi = left; return keyi; } void QuickSort_Hoare(int* arr, int begin, int end) { if (begin >= end) { return; } int keyi = partsort_hoare(arr, begin, end); QuickSort_Hoare(arr, begin, keyi - 1); QuickSort_Hoare(arr, keyi + 1, end); } int partsort_hole(int* arr, int left, int right) { int mid = GetMidIndex(arr, left, right); Swap(&arr[left], &arr[mid]); int key = arr[left]; int hole = left; while (left < right) { while (right > left && arr[right] >= key) { right--; } arr[hole] = arr[right]; hole = right; while (right > left && arr[left] <= key) { left++; } arr[hole] = arr[left]; hole = left; } arr[hole] = key; return hole; } void QuickSort_Hole(int* arr, int begin, int end) { if (begin >= end) { return; } int keyi = partsort_hole(arr, begin, end); QuickSort_Hole(arr, begin, keyi - 1); QuickSort_Hole(arr, keyi + 1, end); } int partsort_follower(int* arr, int left, int right) { int mid = GetMidIndex(arr, left, right); Swap(&arr[left], &arr[mid]); int prev = left; int cur = prev + 1; int keyi = left; while (cur <= right) { if (arr[cur] < arr[keyi] && ++prev != cur) { Swap(&arr[cur], &arr[prev]); } cur++; } Swap(&arr[keyi], &arr[prev]); keyi = prev; return keyi; } void QuickSort_Follower(int* arr, int begin, int end) { if (begin >= end) { return; } int keyi = partsort_follower(arr, begin, end); QuickSort_Follower(arr, begin, keyi - 1); QuickSort_Follower(arr, keyi + 1, end); } void _MergeSort(int* arr, int begin, int end, int* tmp) { if (begin >= end) { return; } int mid = (begin + end) / 2; _MergeSort(arr, begin, mid, tmp); _MergeSort(arr, mid + 1, end, tmp); int begin1 = begin; int end1 = mid; int begin2 = mid + 1; int end2 = end; int i = begin; while (begin1 <= end1 && begin2 <= end2) { if (arr[begin1] <= arr[begin2]) { tmp[i++] = arr[begin1++]; } else { tmp[i++] = arr[begin2++]; } } while (begin1 <= end1) { tmp[i++] = arr[begin1++]; } while (begin2 <= end2) { tmp[i++] = arr[begin2++]; } memcpy(arr + begin, tmp + begin, sizeof(int) * (end - begin + 1)); } void MergeSort(int* arr, int n) { int* tmp = (int*)malloc(sizeof(int) * n); _MergeSort(arr, 0, n - 1, tmp); free(tmp); } void CountSort(int* arr, int n) { int min = arr[0]; int max = arr[0]; for (int i = 0; i < n; i++) { if (arr[i] < min) { min = arr[i]; } } for (int i = 0; i < n; i++) { if (arr[i] > max) { max = arr[i]; } } int range = max - min + 1; int* Count_arr = (int*)malloc(sizeof(int) * range); memset(Count_arr, 0, sizeof(int) * range); for (int i = 0; i < n; i++) { Count_arr[arr[i] - min]++; } int k = 0; for (int j = 0; j < range; j++) { while (Count_arr[j]--) { arr[k++] = j + min; } } } void QuickSort_ThreeRoad(int* arr, int begin, int end) { if (begin >= end) { return; } int left = begin; int right = end; int cur = left + 1; int midi = GetMidIndex(arr, left, right); Swap(&arr[left], &arr[midi]); int key = arr[left]; while (cur <= right) { if (arr[cur] < key) { Swap(&arr[cur], &arr[left]); left++; cur++; } else if (arr[cur] > key) { Swap(&arr[cur], &arr[right]); right--; } else { cur++; } } QuickSort_ThreeRoad(arr, begin, left - 1); QuickSort_ThreeRoad(arr, right + 1, end); }
test.c部分:
#define _CRT_SECURE_NO_WARNINGS #include "sort.h" void TestInsertSort() { int a[] = { 4,7,1,9,3,6,5,8,3,2,0 }; PrintArray(a, sizeof(a) / sizeof(int)); InsertSort(a, sizeof(a) / sizeof(int)); PrintArray(a, sizeof(a) / sizeof(int)); } void TestSelectSort() { int a[] = { 4,7,1,9,3,6,5,8,3,2,0 }; PrintArray(a, sizeof(a) / sizeof(int)); SelectSort(a, sizeof(a) / sizeof(int)); PrintArray(a, sizeof(a) / sizeof(int)); } void TestBubbleSort() { int a[] = { 4,7,1,9,3,6,5,8,3,2,0 }; PrintArray(a, sizeof(a) / sizeof(int)); BubbleSort(a, sizeof(a) / sizeof(int)); PrintArray(a, sizeof(a) / sizeof(int)); } void TestShellSort() { int a[] = { 4,7,1,9,3,6,5,8,3,2,0 }; PrintArray(a, sizeof(a) / sizeof(int)); ShellSort(a, sizeof(a) / sizeof(int)); PrintArray(a, sizeof(a) / sizeof(int)); } void TestHeapSort() { int a[] = { 4,7,1,9,3,6,5,8,3,2,0 }; PrintArray(a, sizeof(a) / sizeof(int)); HeapSort(a, sizeof(a) / sizeof(int)); PrintArray(a, sizeof(a) / sizeof(int)); } void TestQuickSort_Hoare() { int a[] = { 4,7,1,9,3,6,5,8,3,2,0 }; PrintArray(a, sizeof(a) / sizeof(int)); QuickSort_Hoare(a, 0,sizeof(a) / sizeof(int)-1); PrintArray(a, sizeof(a) / sizeof(int)); } void TestQuickSort_Hole() { int a[] = { 4,7,1,9,3,6,5,8,3,2,0 }; PrintArray(a, sizeof(a) / sizeof(int)); QuickSort_Hole(a, 0,sizeof(a) / sizeof(int)-1); PrintArray(a, sizeof(a) / sizeof(int)); } void TestQuickSort_Follower() { int a[] = { 4,7,1,9,3,6,5,8,3,2,0 }; PrintArray(a, sizeof(a) / sizeof(int)); QuickSort_Follower(a, 0,sizeof(a) / sizeof(int)-1); PrintArray(a, sizeof(a) / sizeof(int)); } void TestMergeSort() { int a[] = { 4,7,1,9,3,6,5,8,3,2,0 }; PrintArray(a, sizeof(a) / sizeof(int)); MergeSort(a, sizeof(a) / sizeof(int)); PrintArray(a, sizeof(a) / sizeof(int)); } void TestCountSort() { int a[] = { 4,7,1,9,3,6,5,8,3,2,0 }; PrintArray(a, sizeof(a) / sizeof(int)); CountSort(a, sizeof(a) / sizeof(int)); PrintArray(a, sizeof(a) / sizeof(int)); } void TestQuickSort_ThreeRoad() { int a[] = { 4,7,1,9,3,6,5,8,3,2,0 }; PrintArray(a, sizeof(a) / sizeof(int)); QuickSort_ThreeRoad(a, 0, sizeof(a) / sizeof(int) - 1); PrintArray(a, sizeof(a) / sizeof(int)); } void TestOP() { srand(time(0)); const int N = 100000; int* a1 = (int*)malloc(sizeof(int) * N); int* a2 = (int*)malloc(sizeof(int) * N); int* a3 = (int*)malloc(sizeof(int) * N); int* a4 = (int*)malloc(sizeof(int) * N); int* a5 = (int*)malloc(sizeof(int) * N); int* a6 = (int*)malloc(sizeof(int) * N); int* a7 = (int*)malloc(sizeof(int) * N); int* a8 = (int*)malloc(sizeof(int) * N); int* a9 = (int*)malloc(sizeof(int) * N); int* a10 = (int*)malloc(sizeof(int) * N); int* a11 = (int*)malloc(sizeof(int) * N); for (int i = 0; i < N; ++i) { a1[i] = rand(); a2[i] = a1[i]; a3[i] = a1[i]; a4[i] = a1[i]; a5[i] = a1[i]; a6[i] = a1[i]; a7[i] = a1[i]; a8[i] = a1[i]; a9[i] = a1[i]; a10[i] = a1[i]; a11[i] = a1[i]; } int begin1 = clock(); InsertSort(a1, N); int end1 = clock(); int begin2 = clock(); ShellSort(a2, N); int end2 = clock(); int begin3 = clock(); BubbleSort(a3, N); int end3 = clock(); int begin4 = clock(); SelectSort(a4, N); int end4 = clock(); int begin5 = clock(); HeapSort(a5, N); int end5 = clock(); int begin6 = clock(); QuickSort_Hoare(a6,0, N-1); int end6 = clock(); int begin7 = clock(); QuickSort_Hole(a7,0, N-1); int end7 = clock(); int begin8 = clock(); QuickSort_Follower(a8, 0, N - 1); int end8 = clock(); int begin9 = clock(); MergeSort(a9, N); int end9 = clock(); int begin10 = clock(); CountSort(a10, N); int end10 = clock(); int begin11 = clock(); QuickSort_ThreeRoad(a11, 0, N - 1); int end11 = clock(); printf("InsertSort:%d\n", end1 - begin1); printf("ShellSort:%d\n", end2 - begin2); printf("BubbleSort:%d\n", end3 - begin3); printf("SelcetSort:%d\n", end4 - begin4); printf("HeapSort:%d\n", end5 - begin5); printf("QuickSort_Hoare:%d\n", end6 - begin6); printf("QuickSort_Hole:%d\n", end7 - begin7); printf("QuickSort_Follower:%d\n", end8 - begin8); printf("MergeSort:%d\n", end9 - begin9); printf("CountSort:%d\n", end10 - begin10); printf("QuickSort_ThreeRoad:%d\n", end11 - begin11); free(a1); free(a2); free(a3); free(a4); free(a5); free(a7); free(a8); free(a9); free(a10); free(a11); } int main() { //TestInsertSort(); //TestShellSort(); //TestSelectSort(); //TestBubbleSort(); //TestHeapSort(); //TestQuickSort_Hoare(); //TestQuickSort_Hole(); //TestQuickSort_Follower(); //TestMergeSort(); //TestCountSort(); //TestQuickSort_ThreeRoad(); TestOP(); return 0; }
总结
本篇博客到这里就结束了,感谢观看!
...................................................................你就像头也不回任性的流星,怎么体会极光的美丽
————《圣诞星》