冒泡排序:
冒泡排序是一种简单直观的排序算法,其基本思想是多次遍历待排序数组,每次遍历时比较相邻的两个元素,如果它们的顺序不符合排序规则(比如升序),则交换它们的位置,直到整个数组有序为止。
代码解释分为以下几个步骤:
-
外层循环:for (int i = 0; i < n; i++)
这是冒泡排序的主循环,控制总共需要进行多少轮排序。对于长度为n的数组,最多需要n-1轮排序就可以将数组完全排序好。 -
内层循环:for (int j = 0; j < n - i - 1; j++)
内层循环负责相邻元素的比较和交换。每次内层循环都会把当前未排序部分中最大的元素放置到到数组的最后位置,就像气泡一样冒到最上层。 -
比较和交换:if (a[ j ] > a[ j + 1])
在内层循环中,通过比较相邻的两个元素a[ j ]和 a[ j + 1]的大小关系,如果a[ j ]大于 a[ j + 1],则交换它们的位置,确保每次循环结束后,最大的元素都被交换到了当前未排序部分的末尾。 -
Swap函数:Swap(&a[j], &a[j + 1]);
这里调用了一个交换函数Swap
,用于交换数组中两个元素的值并使用了取地址因为形参的改变并不会影响实参,所以需要用到&符号。
随着每轮外层循环的进行,内层循环将大的数据排到数组的末端,直到所有元素都被正确排序。
选择排序:
代码解释分为以下几个步骤:
-
初始化: begin表示当前未排序部分的起始索引,end表示当前未排序部分的末尾索引。
-
查找最大值和最小值:
在每一轮循环中,通过遍历未排序部分(begin到end),找到其中的最大值max和最小值min的索引。 -
特殊情况处理:
如果begin的索引就是找到的max的索引,说明max可能被错误地更新为min,因此需要将max重新赋值为min。 -
交换操作:
将最小值min移动到当前起始位置 begin,将最大值max移动到当前末尾位置end。 -
开始缩小排序范围:
每完成一轮排序begin向右移动一位,end向左移动一位,缩小未排序部分的范围,也就是从两头开始向中间缩。
插入排序:
-
循环结构:外层循环for (int i = 0; i < n-1; i++)控制每次插入的位置。从数组的第一个元素开始,依次将后面的元素插入到已排序区间中。
-
初始化插入位置:在每次外层循环的开始,将end初始化为i。这表示当前待插入元素 a[i+1]将要插入到已排序区间中的合适位置。
-
保存待插入元素:用 temp变量保存当前待插入的元素a[i+1]的值。
-
内层循环:while (end >= 0)是一个内部循环,用来寻找temp 在已排序区间中的正确位置。
如果 a[end] > temp,表示已排序区间的当前元素大于temp,则将该元素后移一位a[end + 1] = a[end],同时end--继续向前比较。 如果 a[end] <= temp,说明已经找到了temp的插入位置,退出循环。 -
插入操作:在内层循环结束后,将temp插入到正确的位置及a[end + 1] = temp。
希尔排序:
代码解释分为以下几个步骤:
-
初始间隔(gap)设置:
希尔排序的特点是使用不同的间隔进行分组插入排序,初始间隔通常取数组长度n。 -
间隔(gap)的缩小:
在每次循环中,通过gap /= 3来减小间隔,这是希尔排序的核心。通常使用gap = gap / 3+1来缩小间隔。这里最后的+1是有必要的,当gap最后为3或小于3时再除3会直接为0。gap需要保证最后一次排序gap为1,完整最终的排序。 -
插入排序的应用:
对于每个间隔,类似上面插入排序的方式将当前元素插入到已排序部分的正确位置。这一步骤是希尔排序的关键,通过大步长的排序预排序,使得数组逐渐趋向部分有序,再通过小步长的排序最终达到完全有序。 -
终止条件:
当间隔gap缩小到1时,此时完成了最后一次以间隔为1的插入排序及直接插入排序,整个数组也就完成了排序。
堆排序:
代码解释分为以下几个步骤:
AdjustDwon函数:
用于向下调整堆,确保以parent为根节点的子树满足大根堆的性质。
根据传入的parent节点,计算其左孩子节点child = parent * 2 + 1。
进入循环,只要child小于n(即数组长度),继续执行向下调整操作。
在循环中,首先判断child+1是否<n,如果不小于则表示只有一个左孩子,保证数组不会越界访问再选择左右子节点中值较大的节点作为child,
如果child节点的值大于parent节点的值,则交换它们,并更新parent 和child 索引,继续向下调整。
如果 parent已经大于等于child,则跳出循环,调整结束。
HeapSort 函数:
实现了堆排序算法,包括建堆和排序两个阶段。
建堆阶段:从最后一个非叶子节点开始,依次向前调用AdjustDwon函数,使得整个数组调整为大顶堆。这里使用(n - 2) / 2计算最后一个非叶子节点的下标(及最后一个叶子节点的父亲节点),确保从第一个非叶子节点开始向下调整。(n-2)/2展开为: (n-1-1)/2。n-1表示从数组最后一个有效数据的下标开始,其父节点的下标为是(n-1-1)/2 = (n-2)/2。
排序阶段:通过循环,每次将堆顶元素(当前最大值)与未排序部分的最后一个元素交换,然后调用AdjustDwon函数重新调整剩余元素,使其继续保持大顶堆的性质。size变量递减,确保每次排序后未排序部分减少一个元素。
快速排序:
hoare法:
代码解释分为以下几个步骤:
PartSort1
函数解释:
-
参数和变量初始化:
a
是一个整型数组, left 和 right 分别表示当前排序部分的左右边界。 keyi是基准元素的下标,默认初始设为 left 。 -
分区过程:
left 和 right是两个指针,它们从左右两端向中间移动,寻找需要交换的元素位置 while (left <= right && a[right] > a[keyi]) --right;
:从右向左找到第一个小于或等于基准元素的值。 while (left <= right && a[left] < a[keyi]) ++left;
:从左向右找到第一个大于或等于基准元素的值。 -
交换操作:
- 如果left <= right,即找到了需要交换的两个元素,则执行交换操作 Swap(&a[left++], &a[right--]);
- left++和 right--确保在交换后继续向中间缩,继续寻找下一对需要交换的元素。
-
基准元素归位:
- 当 left > right时,意味着 left和right指针已经错过,此时right指向的位置就是基准元素最终需要放置的位置。
- 将基准元素a[keyi]与a[right]进行交换,确保基准元素左侧的元素都小于等于它,右侧的元素都大于等于它。
-
返回值:
- 返回 right,即基准元素的最终位置,用于后续递归调用快速排序时排序以keyi为基准的左右区间。
QuickSort
函数解释:
-
递归终止条件:
如果left >= right,表示当前排序的部分只有一个元素或没有元素,无需继续排序,直接返回。 -
分区和递归排序:
key变量存储了PartSort1返回的基准元素的最终位置。接着,递归调用QuickSort函数分别对基准元素左右两侧的子数组进行排序: QuickSort(a, left, key - 1);:排序右侧子数组。 QuickSort(a, key + 1, right);:排序左侧子数组。
挖坑法:
代码解释分为以下几个步骤:
-
基准值选择和初始化:
keyi变量初始化为 a[left],即选择当前分区的最左边元素作为基准值。 hole变量用于保存坑的位置。 -
分区过程:
从右向左找小元素: while (left < right && a[right] >= keyi):从右向左找到第一个小于基准值keyi的元素。 a[hole] = a[right]:将找到的小于基准值的元素移到坑位,即hole
所指向的位置。 hole = right:更新hole的位置为刚刚移动的元素的位置。 从左向右找大元素: while (left < right && a[left] <= keyi):从左向右找到第一个大于基准值keyi的元素。 a[hole] = a[left]:将找到的大于基准值的元素移到之前坑洞的位置,即hole所指向的位置。 hole = left;更新hole的位置为刚刚移动的元素的位置。 -
基准值最终归位:
当left == right时,将基准值keyi放到hole所指示的最终位置上。 -
返回值:
- 返回hole,即基准值的最终位置,下次快速排序算法递归调用hole的左区间与右区间
前后指针法:
代码解释分为以下几个步骤:
-
变量初始化:
prev和cur是两个指针,prev初始值为left,cur初始值为left + 1。这两个指针用于遍历数组并进行元素交换操作。 keyi初始化为 left,表示选择最左边的元素作为基准值的下标位置。 -
分区过程:
- 遍历过程: cur从left + 1开始向右遍历,查找小于基准值a[keyi]的元素。 如果a[cur]小于a[keyi],则先++prev,将cur所指向的元素与prev的元素交换。 这样,prev指向的是当前已经处理好的小于基准值的部分的最后一个元素的位置。
-
最终位置确定:
遍历结束后,将基准值a[keyi]与a[prev]进行交换,确保基准值被放置在正确的位置上。此时,prev所指向的位置就是基准值的最终位置。 -
返回值:
返回prev,即基准值的最终位置,下次快速排序算法递归调用prev的左区间与右区间。
归并排序:
Mergesor函数是归并排序的入口函数,接收一个整型数组a和数组长度n。
在函数内部,首先动态分配了一个与数组a同样大小的临时数组temp,用于在排序过程中存储中间结果。
接着调用了递归函数 Mergesor1,负责实际的归并排序操作。
最后,释放了临时数组temp的内存,避免内存泄漏。
递归函数Mergesort1:
Mergesort1是归并排序的递归函数,负责实际的排序工作。
首先检查区间[left, right]是否为空或者只有一个元素,如果是,则直接返回,不需要再进行排序。
计算中间位置mid,将数组分割为两部分:左半部分[left, mid]和右半部分[mid+1, right]。
递归调用Mergesort1对左右两部分继续递归分割。
接下来是合并操作: 使用begin1和begin2分别指向左右两个区间的起始位置,end1和end2分别指向左右两个区间的结束位置。 比较a[begin1]和a[begin2]的大小,将较小的元素放入临时数组temp中,并将相应的指针向后移动。 如果某一区间还有剩余的元素,直接将剩余的元素复制到临时数组temp中。 最后,通过memcpy将排序好的临时数组temp的内容复制回原数组 a
的对应区间 [left, right]。