常用排序算法总结
1.冒泡排序
要说冒泡应该是非常简单的一种排序了,思路就如其名,数据像是泡泡一样逐个上升。
/* * 冒泡排序 */ void bubbleSort(int *array , int length) { //设置flag来标示是否已经有序,用于优化冒泡排序 int flag; //共执行n-1趟 for(int i = 0 ; i < length - 1; i++) { //默认为已经有序 flag = 0; //从最后一个元素开始判断,因为每一趟都会排好一个元素,所以每一趟都会少比较一次 for(int j = length - 1 ; j > i ; j--) { //比较是否比前面的元素小 if(array[j-1] > array[j]) { //如果小就交换位置并且标示为非有序数组 swip(&array[j-1], &array[j]); flag = 1; } } //如果已经默认有序就不在进行 if(!flag) break; } }
这里说一下标示flag,如果已经有序比如1、2、3、4、5、6数组,但是内层循环依然会执行,只是不交换元素而已。当执行到没有元素交换的时候也就说明该数组已经有序了,这时就可以退出循环。
2.简单选择排序
简单选择排序就是先找当前位置为最小,判断后面是否有比当前元素小的,如果存在就和当前元素交换位置。相比冒泡排序简单选择的比较次数较多而交换最多就n-1次。
/* * 简单选择排序 */ void selectSort(int *array, int length) { //和冒泡排序一样,也是执行n-1次 for(int i = 0 ; i < length-1 ; i++) { //猜测当前位置为最小元素 int min = i; //执行n-i-1次,用于找到最小的元素 for(int j = i+1 ; j < length ;j++) { //如果当前元素比min位置小,那么当前位置为min if(array[min] > array[j]) min = j; } //如果min的位置发生变化,即当前位置不是min,那么交换位置 if(min != i) swip(&array[min], &array[i]); } }
3. 直接插入排序
直接插入排序思路也很简单,就不多说了。
/* * 直接插入排序 */ void insertSort(int *array ,int length) { //有几个元素就执行几次,默认最少两个元素,从第二个元素开始 for(int i = 1 ; i < length ; i++) { //如果当前位置比前一位置元素小 if(array[i] < array[i-1]) { //保存当前元素 int temp = array[i]; int j; //当前元素前面的所有比当前元素大的元素全部后移一位 for(j = i-1 ; array[j] > temp && j >= 0; j--) { array[j+1] = array[j]; } //把空出来的位置赋上保存的元素 array[j+1] = temp; } } }
4.希尔排序
希尔排序就是直接插入排序的一个升级,让序列先相对有序,然后在不断减小间隔重新分组使整个有序。
/* * 希尔排序 */ void shellSort(int *array ,int length) { //设置希尔排序的间隔(每次/2) int increment = length/2; //当间隔变为0时结束 while(increment >= 1) { //从第一个间隔位置开始,到最后,分好组对每一组进行直接插入排序 for(int i = increment ; i < length ; i++) { //和直接插入排序相同,只是间隔从直接插入的1变为了increment if(array[i] < array[i-increment]) { int j; int temp = array[i]; for(j = i - increment ; array[j] > temp && j >= 0 ; j -= increment) { array[j+increment] = array[j]; } array[j+increment] = temp; } } //更新间隔 increment /= 2; } }
5.堆排序
堆排序相对复杂一些,主要思路就是把线性表当做完全二叉树去处理,然后构造大顶堆。
/* * 调整堆结构 */ void heepAdjust(int *array, int loc , int length) { //保存当前位置数据 int temp = array[loc]; //得到左子节点位置,每次都得到子节点 for(int i = loc * 2 ; i <= length ; i *= 2) { //如果当前位置<长度,就说明父节点有两个子节点,如果等于长度就只有一个 //如果有两个,把位置调整到较大的位置 if(i < length && array[i] < array[i+1]) i++; //判断父节点是否比较大的大 if(temp > array[i]) break; //把较大的赋值给父节点 array[loc] = array[i]; //移动父节点位置到当前节点 loc = i; } //最终位置赋值为保存的数据 array[loc] = temp; } /* * 堆排序 * array 中的0号元素空过去,所以length是array长度-1 */ void heepSort(int *array , int length) { //长度/2得到最后一个有子节点的节点位置,循环到根节点 for(int i = length / 2 ; i > 0 ; i--) { //调整堆结构,始终保持大顶堆 heepAdjust(array, i, length); } //从最后一个元素开始,逐个和根节点交换并调整堆结构 for(int i = length ; i > 1 ; i--) { swip(&array[1], &array[i]); heepAdjust(array, 1, i-1); } }
6.归并排序
归并排序应该算是最不好理解的了,归并排序分为拆分递归和合并回朔两个过程,其中在拆分的时候会建立一个数组用于保存下一层的回朔。
递归实现:
/* * 合并排序当前层的数据 */ void Merge(int SR[], int TR[], int l , int m , int r) { //左边的初始位置 int i = l; //右边的初始位置 int j = m+1; //TR的位置 int k = l; //如果比较排序没有完成就循环 while(i <= m && j <= r) { //因为左右相对有序,所以只需要每次找左边和右边较小的,然后让找的位置+1 if(SR[i] < SR[j]) { TR[k] = SR[i]; i++; } else { TR[k] = SR[j]; j++; } //保存的数组位置+1 k++; } //把剩下没有归并的数据全部存入TR if(i <= m) { for(int n = 0 ; n <= m - i ; n++) { TR[k] = SR[i+n]; k++; } } if(j <= r) { for(int n = 0; n <= r - j ; n++) { TR[k] = SR[j+n]; k++; } } } /* * 递归归并过程 */ void MSort(int SR[], int TR1[] ,int l , int r) { //定义一个数组用来存放该层归并的结果 int TR2[20] = {0}; //如果已经不能在拆分,把数据赋值给TR1回朔,注意这里TR1回朔后就是上一层的TR2了 if(l == r) { TR1[l] = SR[l]; } else { //归并的界限计算 int m = (l+r)/2; //拆分左右,TR2是回朔合并用的 MSort(SR, TR2, l, m); MSort(SR, TR2, m+1, r); //把该层的TR2重新排序并赋值给上一层的TR2 Merge(TR2 ,TR1, l, m, r); } } void MergeSort(int *array, int length) { MSort(array, array, 0 ,length - 1); }
非递归实现:Merge方法和递归版的一样
void MergePass(int SR[], int TR[], int k, int length) { int i; //判断是否还有间隔能容纳数据,也就是判断当最后不够两组的时候结束 for(i = 0 ; i < length-2*k+1 ; i+=2*k) { //合并 Merge(SR, TR, i, i+k-1, i+2*k-1); } //判断是不是在1组合2组数据之间 if(i < length - k) { Merge(SR, TR, i, i+k-1, length-1); } //1组数据以下或者间隔超过了数组长度 else { for(int j = i ; j < length ;j++) { TR[j] = SR[j]; } } print(TR,length); } /* * 归并排序(非递归) */ void MergeSort(int *array , int length) { //开辟一个和数据一样大的空间 int *TR = (int *)malloc(sizeof(int) * length); //初始合并间隔为1 int k = 1; //k < length就不断地合并 while (k < length) { //把array合并到TR MergePass(array,TR,k,length); //间隔*2 k *= 2; //把TR合并回来 MergePass(TR, array, k, length); //间隔*2 k *= 2; } }
7.快速排序
快速排序是冒泡排序的一个升级,通过不断调整元素位置来排序,是排序算法中最为高效的
int Partition(int *array , int low , int high) { //取第一个元素作为目标位置 int pivotKey = array[low]; while (low < high) { //如果high比目标小,交换 while (low < high && array[high] > pivotKey) { high--; } swip(&array[low], &array[high]); //如果low比目标大,交换 while (low < high && array[low] < pivotKey) { low++; } swip(&array[low], &array[high]); } return low; } void Qsort(int *array , int low, int high) { //目标值的位置 int pivot; if(low < high) { //返回目标值位置 pivot = Partition(array ,low ,high); //对前面进行快速排序 Qsort(array, low, pivot-1); //对后面进行快速排序 Qsort(array, pivot+1, high); } } void QuickSort(int *array , int length) { Qsort(array,0,length-1); }
总结
插入排序类 | 直接插入排序 | 希尔排序 |
选择排序类 | 简单选择排序 | 堆排序 |
交换排序类 | 冒泡排序 | 快速排序 |
归并排序类 | 归并排序 |
排序算法指标
排序方法 | 平均情况 | 最好情况 | 最坏情况 | 辅助空间 | 稳定性 |
冒泡排序 | O(n2) | O(n) | O(n2) | O(1) | 稳定 |
简单选择排序 | O(n2) | O(n2) | O(n2) | O(1) | 稳定 |
直接插入排序 | O(n2) | O(n2) | O(n2) | O(1) | 稳定 |
希尔排序 | O(nlogn)-o(n2) | O(n1.3) | O(n2) | O(1) | 不稳定 |
堆排序 | O(nlogn) | O(nlogn) | O(nlogn) | O(1) | 不稳定 |
归并排序 | O(nlogn) | O(nlogn) | O(nlogn) | O(n) | 稳定 |
快速排序 | O(nlogn) | O(nllogn) | O(n2) | O(logn)~O(n) | 不稳定 |