经典的八种排序算法
如题目>▽<
直接插入排序
将数组中的所有元素依次跟前面已经排好的元素相比较,如果选择的元素比已排序的元素小,则交换,直到全部元素都比较过。
代码
1 | for(int i=1;i<n;i++){ //从第2个数开始,取出当前数作为待排序数 |
希尔排序
希尔排序的算法思想:将待排序数组按照步长gap进行分组,然后将每组的元素利用直接插入排序的方法进行排序;每次将gap折半减小,循环上述操作;当gap=1时,利用直接插入,完成排序。
同样的:从上面的描述中我们可以发现:希尔排序的总体实现应该由三个循环完成:
第一层循环:将gap依次折半,对序列进行分组,直到gap=1
第二、三层循环:也即直接插入排序所需要的两次循环。具体描述见上
代码
1 | // 由于序列近似有序时插入排序时间复杂度很低,因此可以用增量的方法,使序列基本有序 |
选择排序
简单选择排序的基本思想:比较+交换。
从待排序序列中,找到关键字最小的元素;
如果最小元素不是待排序序列的第一个元素,将其和第一个元素互换;
从余下的 N - 1 个元素中,找出关键字最小的元素,重复(1)、(2)步,直到排序结束。
因此我们可以发现,简单选择排序也是通过两层循环实现。
第一层循环:依次遍历序列当中的每一个元素
第二层循环:将遍历得到的当前元素依次与余下的元素进行比较,符合最小元素的条件,则交换。
代码
1 | for (int i = 0; i < n; i++) { |
堆排序
堆排序,顾名思义,是利用堆的特性来进行排序。本文讲述利用大顶堆来进行非递减排序,非递增排序同理。
调整堆(heapify)
将某一个节点进行heapify操作,使得该节点(父亲节点)的值大于两个儿子节点,我们要保证交换后的子节点仍然是一个堆,所以同样需要以这个子节点为父节点继续进行heapify(调整堆)操作,直到进行到堆的最底部
因为完全二叉树的特性,在知道某一个点的下标的情况下,我们可以轻易地算出其父节点及其子节点的下标
函数代码如下:
1 | // n:堆的大小 i:待调整的元素下标 |
建立堆
对于一个乱序的数组,为了将其创建成一个堆,我们需要从最后一个父节点开始,层层向上进行heapify操作,这样我们就能保证所有的父节点均大于子节点,也就建立成了大顶堆
函数代码如下:
1 | void build_heap(int tree[], int n) { |
进入正戏!堆排序
现在我们已经建立出了大顶堆,我们此时可以将堆顶与堆底的最后一个元素交换。这样,我们就将最大的数排好序了。对于新交换来的点,为了继续维护大顶堆,我们仍需要进行heapify的操作,不过此时的n应该变为n-1(因为我们已经将最后的元素排好序了)。
函数代码如下:
1 | void heap_sort(int tree[], int n) { |
冒泡排序
代码
1 | // 不解释 |
快速排序
从序列当中选择一个基准数(pivot)
在这里我们选择序列当中第一个数最为基准数(教材就是这个基准数)
将序列当中的所有数依次遍历,比基准数大的位于其右侧,比基准数小的位于其左侧
重复步骤1.2,直到所有子集当中只有一个元素为止。
用伪代码描述如下:
- i = L; j = R; 将基准数挖出形成第一个坑a[i]。
- j–由后向前找比它小的数,找到后挖出此数填前一个坑a[i]中。
- i++由前向后找比它大的数,找到后也挖出此数填到前一个坑a[j]中。
- 再重复执行2,3二步,直到i==j,将基准数填入a[i]中
代码
1 | void quick_sort(int low, int high) { |
归并排序
归并排序(MERGE-SORT)是建立在归并操作上的一种有效的排序算法,该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。
所谓归并,就是将两组有序的数据合成为一组有序的数据,例如有如下两个有序数组,将两个有序数组归并为一个有序数组,这就是一次归并操作。
代码
1 | int a[MAX], b[MAX]; |