没有更完,上完课继续更新,完整代码 (╥_╥)
本文整理了十种排序算法(升序),包括冒泡排序,选择排序,插入排序,快速排序,归并排序,希尔排序,堆排序,计数排序,桶排序,基数排序。
基本的算法思路参考菜鸟教程,算法的改进和具体的问题答案来源于网络(见参考资料)
1 冒泡排序
1.1 介绍
冒泡排序,由于每次将最值与其他元素交换位置,使得最值向一个方向传递,如同泡泡上浮而得名。每次遍历数组,将最值向前传递,遍历n-1次结束。
1.2 算法描述
1、将无序部分(初始为整个数组)首元素定为max,依次遍历无序部分
2、如果遇到更大元素将max该元素交换
3、遍历一次,无序部分最后元素变为max
4、遍历n-1次,得到排列好的数组
1.3 代码
#define MAXSIZE 10000 /*全局变量,需要排序的数组,是初始数组的copy*/ long long int num[MAXSIZE]; /*交换num[i]和num[j]的值*/ void swap(long long int i, long long int j) { long long int tmp = num[i]; num[i] = num[j]; num[j] = tmp; } /*冒泡排序*/ void BubbleSort(long long int len) { for (long long int i = 0; i < len - 1; i++) { int flag = 0; for (long long int j = 0; j < len - 1 - i; j++) { if (num[j] > num[j + 1]) { swap(j, j + 1); flag = 1; } } if (flag == 0) break; } }
1.4 改进
原来的冒泡算法不论数组是什么情况,都会遍历n-1次,改进后添加标志flag,如果在本次遍历中有元素交换,flag=1,继续遍历,否则停止遍历,数组已经有序。
1.5 算法分析
平均时间复杂度为O(n2),最好情况(原数组为升序排列)为O(n),最坏情况(原数组为降序排列)为O(n2)
冒泡排序是稳定的,排序后,大小相同的元素的相对位置不变
一般不常用冒泡排序
2 选择排序
2.1 介绍
选择排序,每次从无序部分选取min,添加到有序部分,遍历n-1次。
2.2 算法概述
1、选取无序部分(初始为原数组)的首元素序号为min,遍历无序部分
2、通过比较元素大小,更新最小值的序号min
3、遍历一遍后,将无需部分首元素与序号为min的元素交换
4、共遍历n-1遍,重复上述过程,结束
2.3 代码
#define MAXSIZE 10000 /*全局变量,需要排序的数组,是初始数组的copy*/ long long int num[MAXSIZE]; /*交换num[i]和num[j]的值*/ void swap(long long int i, long long int j) { long long int tmp = num[i]; num[i] = num[j]; num[j] = tmp; } /*选择排序*/ void SelectionSort(long long int len) { //long long int flag = 0;//num[flag]之前元素已经排序 long long int min_value,min_no; for (long long int i = 0; i < len - 1; i++) { min_value = num[i]; min_no = i; for (long long int j = i + 1; j < len; j++) { if (num[j] < min_value) { min_no = j; min_value = num[j]; } } swap(i, min_no); } }
2.4 算法分析
选择排序的时间复杂度相当稳定,平均、最好、最坏的情况都是O(n2),因为只有完全遍历才能找到最小值,所以数组的有序度不影响操作的次数
选择排序不稳定,例如5,5,3这种情况
与传统的冒泡排序(遍历n-1次)相比,每次比较后,如果交换,冒泡排序需要交换两个元素的值(3步),而选择排序只需要更新序号(1步),所以选择排序更优,但是当冒泡排序改进后(增加flag),两者的优劣就与数组有关系了
3 插入排序
3.1 介绍
从无序部分选择一个元素(一般为首元素),将这个元素按照大小插入到有序部分(涉及到多个元素的后移),重复这个操作直到整体数组有序。
3.2 算法概述
1、从第2个元素开始,将其选择为待插入元素
2、将待插入元素与它之前的元素依次进行比较(传统的插入排序是从后向前比较,我是这样写的,也有从前向后比较,性能上有区别,下面有分析),找到插入的位置
3、将插入位置到待插入元素之前的元素整体后移一个位置,将插入位置空出,将待插入元素插入
4、重复上述过程,直到没有待插入元素,数组有序
3.3 代码
#define MAXSIZE 10000 /*全局变量,需要排序的数组,是初始数组的copy*/ long long int num[MAXSIZE]; /*插入排序*/ void InsertSort(long long int len) { for (long long int i = 1; i < len; i++) { for (long long int j = 0; j < i; j++) { if (num[i] < num[j]) { long long int tmp = num[i]; for (long long int k = i; k > j; k--)//从后向前查找插入位置 num[k] = num[k - 1]; num[j] = tmp; break; } } } }
3.4 改进
插入排序的改进有两种:二分插入排序和希尔排序。前者将每次的查找插入位置进行了优化,将逐个遍历改成二分查找;而后者采用了分治的思想,每个最小子问题还是通过插入排序解决的。(可以算是一种新的排序算法,也可以看做是插入算法的改进)
当然,对于查找插入位置是从后向前遍历还是从前向后遍历都会有不同的性能。从后向前遍历,需要的遍历过的元素后移,而从前向后遍历,需要将有序部分未遍历过的元素后移,这两者之间的性能与数组的关系很大,前者数组越趋向升序排列性能越好,后者性能很稳定,一直是O(n2)。一般,人们都是采用从后向前遍历查找插入位置的方式。
3.5 算法分析
插入排序的时间复杂度与数组的有序度有关,越趋近于升序排列,算法越快。平均复杂度为O(n2),最好情况(数组升序排列)为O(n),最坏情况(数组降序排列)为O(n2)
插入排序是稳定的
插入排序一般用于将小规模数据或者趋近于结果序列的数组,原数组越趋近于结果序列,算法越快。
由于插入单个元素的同时不破坏数组的有序,常被其他排序算法用于插入单个元素,例如基数排序。
由于对于个数很小的数组排序的性能较好,插入排序常被其他算法调用处理分组后的较小的数组,例如归并排序
上述三种排序算法的平均复杂度都是O(n2),所以单纯的排序一般用的较少。
4 快速排序
4.1 介绍
快速排序,是一种分治思想的排序算法。以首元素作为基准值,将整个数组按照小于基准值、基准值、大于基准值的顺序排列,对小于基准值和大于基准值的两部分分别进行上述操作。
4.2 算法概述
1、数组的左端元素序号为left,右端为right
2、以序号为left的元素作为基准值,用less从右侧查找小于基准值的元素,用greater从左侧查找大于基准值的元素,将less和greater标明的元素交换
3、重复交换的过程,直到less和greater中间的元素全部都是大于基准值或小于基准值的,这是将left指向的元素和最后一个less指向的元素交换
4、这样可以将整个数组分成小于基准值和大于基准值的两个小数组,对这两个小数组递归进行上述操作,递归终止条件为每个小数组仅有一个元素
4.3 代码
#define MAXSIZE 10000 /*全局变量,需要排序的数组,是初始数组的copy*/ long long int num[MAXSIZE]; /*交换num[i]和num[j]的值*/ void swap(long long int i, long long int j) { long long int tmp = num[i]; num[i] = num[j]; num[j] = tmp; } /*快速排序*/ void _QuickSort(long long int left, long long int right) { if (left >= right) return; srand(time(NULL)); //随机选取一个元素作为基准,这样可以保证递归的深度在log n左右 swap(rand()%(right-left)+left, left); long long int std = num[left], less=right, greater=left; while (greater < less)//这块的核心代码是我自己写的,下面的if else没见过其他人和我的写法相同,代码量少还好记 { while (greater < less && num[less] >= std) less--; while (greater < less && num[greater] <= std) greater++; if (greater != less)//找到了可交换的两个元素 swap(greater, less); else //greater=less,这时less一定指向数组中最后一个小于等于std的元素 swap(left, less); } _QuickSort(left, less-1); _QuickSort(less+1, right); } /*对所有排序算法的形参做到统一,用于调用递归的快速排序*/ void QuickSort(long long int n) { _QuickSort(0, n - 1); }
4.4 改进
对快速排序的改进可以分为两个方面:减少递归层数(优化基准的选取)和优化每次递归的进行的操作
选取基准的优化方法:随机选取元素作为基准值(上述代码就是这样)和选取左右两端、中间的三个中的大小居中的元素作为基准值。
前者在数组长度足够大的情况下,保证递归深度在log n左右(不考虑重复元素),后者可以保证避免出现某侧的数组元素过少的情况
对每次递归进行的操作进行优化的方法:当数组长度小于某个值时,数组较为有序,采用插入排序;将数组分成小于基准值、等于基准值、大于基准值的三部分,对前后两部分进行递归(这种优化只适合重复元素很多的情况)
实际上上述几种方法都是对数组极端情况的一种应对措施,对于随机产生的数组,上述优化并没有多大作用
4.5 算法分析
快速分析是均摊复杂度最小的比较排序算法,所以被经常使用。
对于传统的快速排序,平均复杂度是O(n*log n),最好情况是O(n*log n),最坏情况(升序或者降序排列或者重复元素)是O(n2)
快速排序算法是不稳定的,例如 5,6(1),6(2),3(1),3(2),会将6(1)与3(2)交换,6(2)与3(1)交换,5与3(1)交换,形成3(1),3(2),5,6(2),6(1)
5 归并排序
5.1 介绍
归并排序是典型的分治算法。将数组分成n个子数组,对每个子数组继续进行分割,直到数组元素个数足够少时,进行比较、排序,最后将这n个有序子数组进行n路归并,得到最终的有序数组。分、治、合的过程很清晰。
5.2 算法概述
1、将数组分成n个子数组,对每一个子数组递归进行分割
2、递归的出口是当数组长度<=1时,返回;当数组长度=2时,比较,swap解决
3、递归完成后,将n个有序的子数组进行n路归并:定义一个空的大数组,每个子数组设置头部指针,指向该数组未合并的头部元素,将n个子数组的头部元素比较大小,最小数并入大数组,头部指针后移,继续比较,最后剩余的一个子数组的剩余元素全部并入大数组
4、将已经排序好的大数组重新赋值给原数组,排序结束
5.3 代码
我用的是二路归并
#define MAXSIZE 10000 /*全局变量,需要排序的数组,是初始数组的copy*/ long long int num[MAXSIZE]; /*交换num[i]和num[j]的值*/ void swap(long long int i, long long int j) { long long int tmp = num[i]; num[i] = num[j]; num[j] = tmp; } /*归并排序:合并两个数组*/ void Merge(long long int first, long long int mid, long long int last) { long long int tmp[MAXSIZE], top1 = first, top2 = mid + 1; for (long long int i = first; i <= last; i++) { if (top1 <= mid && top2 <= last) { if (num[top1] <= num[top2]) tmp[i] = num[top1++]; else tmp[i] = num[top2++]; } else if (top1 > mid) tmp[i] = num[top2++]; else tmp[i] = num[top1++]; } for (long long int i = first; i <= last; i++) num[i] = tmp[i]; } /*归并排序*/ void _MergeSort(long long int first, long long int last) { if (first == last) return; if (last == first + 1) { if (num[last] < num[first]) swap(last, first); return; } _MergeSort(first, (first + last) / 2); _MergeSort((first + last) / 2 + 1, last); Merge(first, (first + last) / 2, last); } /*对所有排序算法的形参做到统一,用于调用递归的归并排序*/ void MergeSort(long long int n) { _MergeSort(0, n - 1); }
5.4 改进
归并排序的改进包括:分组的优化、对每个子数组的操作、归并的优化
分组的优化:由二分改为n分,这样递归的深度会降低,但是复杂度没变化
对每个子数组的操作:当子数组长度小于某个值limit时,用插入排序而非继续递归
归并的优化:跳过有序数组(将子数组按照首元素大小整体进行排序,如果某个子数组的末尾元素小于下一个子数组的首元素,那么不用排序,直接归入大数组)
用最小堆进行n路归并(将各个有序的子数组首元素构成最小堆,每次选择根节点,将根节点所在的子数组中的首元素加入堆,更新最小堆,再次选取根节点,直到子数组所有元素加入最小堆)
用败者树进行n路归并
5.5 算法分析
对于二路归并排序算法,性能优秀,平均、最好、最坏时间复杂度都是O(n*log n)。
由于每次归并都需要开一个能够包含两个子数组的辅助数组,所以空间复杂度为O(n*log n)。也有让原来的
归并排序的时间复杂度达到了比较排序所能做到的最优,但空间复杂度较大,所以一般内排序用的较少,外排序用得较多。
就做题而言,多数题目对空间的要求不高,所以可以多考虑归并排序。
6 希尔排序
6.1 介绍
按照增量将数组分组,对每一组进行插入排序,提高数组有序度,再将小组合成大组,利用插入排序重排,一直合并直到整个数组有序,
6.2 算法概述
6.3 代码
#define MAXSIZE 10000 /*全局变量,需要排序的数组,是初始数组的copy*/ long long int num[MAXSIZE]; /*适应间隔为gap的插入排序,用于希尔排序*/ void insert(long long int len, long long int gap, long long int i) { for (long long int j = i % gap; j < i; j += gap) { if (num[i] < num[j]) { long long int tmp = num[i]; for (long long int k = i; k > j; k -= gap) num[k] = num[k - gap]; num[j] = tmp; break; } } } /*希尔排序*/ void ShellSort(long long int n) { long long int gap = n / 2; while (gap) { for (long long int i = 0; i < n; i++) insert(n, gap, i); gap /= 2; } }
3.4 改进
3.5 算法分析
7 堆排序
3.1 介绍
3.2 算法概述
3.3 代码
3.4 改进
3.5 算法分析
8 计数排序
3.1 介绍
3.2 算法概述
3.3 代码
#define MAXSIZE 10000 /*全局变量,需要排序的数组,是初始数组的copy*/ long long int num[MAXSIZE]; /*计数排序,数据为非负数,且最大值不超过MAXSIZE-1,*/ void CountSort(long long int n) { long long int tmp[MAXSIZE] = { 0 }; for (long long int i = 0; i < n; i++) tmp[num[i]]++; long long int i = 0; for (long long int j = 0; j < MAXSIZE; j++) { while (tmp[j]--) num[i++] = j; } }
3.4 改进
3.5 算法分析
9 桶排序
9.1 介绍
桶排序与计数排序的比较
9.2 算法分析
10 基数排序
3.1 介绍
3.2 算法概述
3.3 代码
#define MAXSIZE 10000 /*全局变量,需要排序的数组,是初始数组的copy*/ long long int num[MAXSIZE]; /*基数排序,存在问题:数组中元素只能是正数*/ void RadixSort(long long int len) { long long int digits = 1,max=num[0]; for (long long int i = 0; i < len; i++)//找到最大值max if (max < num[i]) max = num[i]; for (; max != 0; max /= 10)//统计最大值max的位数digits digits++; for (long long int i = 0; i < digits; i++)//按照第i位比较大小,对num数组进行digits次排序 { long long int tmp[10][MAXSIZE] = { 0 };//存储每次排序的结果 for (long long int j = 0; j < len; j++) { //num[j]求第digits位的数字 int one_digit = num[j] / pow(10, i) - num[j] / pow(10, i + 1) * 10; //将num[j]存储到tmp对应位置 long long int k = 0; while (tmp[one_digit][k] != 0)//这里认为值为0的位置是没有被赋值的,原数组中含有0会被覆盖 k++; tmp[one_digit][k] = num[j]; //对被插入的tmp[one_digit][]进行插入排序 for(long long int m=0;m) if (tmp[one_digit][m] > tmp[one_digit][k]) { long long int x = tmp[one_digit][k]; for (long long int n = k; n > m; n--) tmp[one_digit][n] = tmp[one_digit][n-1]; tmp[one_digit][m] = x; break; } } //将数组tmp[q][y]复制到num[z] long long int q = 0, y = 0, z = 0; while (q < 10) { while (tmp[q][y] != 0)//这里认为值为0的位置是没有被赋值的,所以为0及0之后的位置不会被赋值给num[] num[z++] = tmp[q][y++]; q++; } } }
3.4 改进
3.5 算法分析
参考资料
菜鸟教程 经典排序算法(附带动图演示)
附带C代码的十大排序算法讲解
时间复杂度和空间复杂度的分析(重点递归)
理解选择排序的不稳定性
直接插入排序改为二分插入排序
比较冒泡排序、选择排序、插入排序的优劣
快速排序的常见写法和详解
快速排序的改进
K路归并算法的详解
二路和n路归并排序在外排序的性能比较
归并排序、快速排序、冒泡排序的复杂度比较
归并排序的复杂度分析
希尔排序详解
堆排序详解
堆排序常见写法
桶排序和计数排序的区别(1)
桶排序和计数排序的区别(2)
桶排序、计数排序和基数排序的比较
完整代码
#include#include #include #define MAXSIZE 10000 /*全局变量,需要排序的数组,是初始数组的copy*/ long long int num[MAXSIZE]; /*全局变量,存储输入的数字或随机生成的初始数组*/ long long int copy_num[MAXSIZE]; /*打印num[]数组*/ void PrintArray(char* sort_num, long long int len) { printf("\n%s", sort_num); for (long long int i = 0; i < len; i++) printf("%-6lld ", num[i]); printf("\n"); } /*随机生成长度为n的copy_num[]数组*/ void CreateArray(long long int n) { srand(time(NULL)); for (long long int i = 0; i < n; i++) copy_num[i] = rand() % 10000; } /*将copy_num[]的值复制给num[]*/ void Copy() { for (long long int i = 0; i < MAXSIZE; i++) num[i] = copy_num[i]; } /*交换num[i]和num[j]的值*/ void swap(long long int i, long long int j) { long long int tmp = num[i]; num[i] = num[j]; num[j] = tmp; } /*冒泡排序*/ void BubbleSort(long long int len) { for (long long int i = 0; i < len - 1; i++) { int flag = 0; for (long long int j = 0; j < len - 1 - i; j++) { if (num[j] > num[j + 1]) { swap(j, j + 1); flag = 1; } } if (flag == 0) break; } } /*选择排序*/ void SelectionSort(long long int len) { //long long int flag = 0;//num[flag]之前元素已经排序 long long int min_value,min_no; for (long long int i = 0; i < len - 1; i++) { min_value = num[i]; min_no = i; for (long long int j = i + 1; j < len; j++) { if (num[j] < min_value) { min_no = j; min_value = num[j]; } } swap(i, min_no); } } /*插入排序*/ void InsertSort(long long int len) { for (long long int i = 1; i < len; i++) { for (long long int j = 0; j < i; j++) { if (num[i] < num[j]) { long long int tmp = num[i]; for (long long int k = i; k > j; k--) num[k] = num[k - 1]; num[j] = tmp; break; } } } } /*快速排序*/ void _QuickSort(long long int left, long long int right) { if (left >= right) return; srand(time(NULL)); //随机选取一个元素作为基准,这样可以保证递归的深度在log n左右 swap(rand()%(right-left)+left, left); long long int std = num[left], less=right, greater=left; while (greater < less) { while (greater < less && num[less] >= std) less--; while (greater < less && num[greater] <= std) greater++; if (greater != less)//找到了可交换的两个元素 swap(greater, less); else //greater=less,这时less一定指向数组中最后一个小于等于std的元素 swap(left, less); } _QuickSort(left, less-1); _QuickSort(less+1, right); } /*对所有排序算法的形参做到统一,用于调用递归的快速排序*/ void QuickSort(long long int n) { _QuickSort(0, n - 1); } /*归并排序:合并两个数组*/ void Merge(long long int first, long long int mid, long long int last) { long long int tmp[MAXSIZE], top1 = first, top2 = mid + 1; for (long long int i = first; i <= last; i++) { if (top1 <= mid && top2 <= last) { if (num[top1] <= num[top2]) tmp[i] = num[top1++]; else tmp[i] = num[top2++]; } else if (top1 > mid) tmp[i] = num[top2++]; else tmp[i] = num[top1++]; } for (long long int i = first; i <= last; i++) num[i] = tmp[i]; } /*归并排序*/ void _MergeSort(long long int first, long long int last) { if (first == last) return; if (last == first + 1) { if (num[last] < num[first]) swap(last, first); return; } _MergeSort(first, (first + last) / 2); _MergeSort((first + last) / 2 + 1, last); Merge(first, (first + last) / 2, last); } /*用于调用递归的归并排序*/ void MergeSort(long long int n) { _MergeSort(0, n - 1); } /*计数排序*/ void CountSort(long long int n) { long long int tmp[MAXSIZE] = { 0 }; for (long long int i = 0; i < n; i++) tmp[num[i]]++; long long int i = 0; for (long long int j = 0; j < MAXSIZE; j++) { while (tmp[j]--) num[i++] = j; } } /*桶排序*/ /*基数排序,存在问题:数组中不能含有0*/ void RadixSort(long long int len) { long long int digits = 1,max=num[0]; for (long long int i = 0; i < len; i++)//找到最大值max if (max < num[i]) max = num[i]; for (; max != 0; max /= 10)//统计最大值max的位数digits digits++; for (long long int i = 0; i < digits; i++)//按照第i位比较大小,对num数组进行digits次排序 { long long int tmp[10][MAXSIZE] = { 0 };//存储每次排序的结果 for (long long int j = 0; j < len; j++) { //num[j]求第digits位的数字 int one_digit = num[j] / pow(10, i) - num[j] / pow(10, i + 1) * 10; //将num[j]存储到tmp对应位置 long long int k = 0; while (tmp[one_digit][k] != 0)//这里认为值为0的位置是没有被赋值的,原数组中含有0会被覆盖 k++; tmp[one_digit][k] = num[j]; //对被插入的tmp[one_digit][]进行插入排序 for(long long int m=0;m ) if (tmp[one_digit][m] > tmp[one_digit][k]) { long long int x = tmp[one_digit][k]; for (long long int n = k; n > m; n--) tmp[one_digit][n] = tmp[one_digit][n-1]; tmp[one_digit][m] = x; break; } } //将数组tmp[q][y]复制到num[z] long long int q = 0, y = 0, z = 0; while (q < 10) { while (tmp[q][y] != 0)//这里认为值为0的位置是没有被赋值的,所以为0及0之后的位置不会被赋值给num[] num[z++] = tmp[q][y++]; q++; } } } /*适应间隔为gap的插入排序,用于希尔排序*/ void insert(long long int len, long long int gap, long long int i) { for (long long int j = i % gap; j < i; j += gap) { if (num[i] < num[j]) { long long int tmp = num[i]; for (long long int k = i; k > j; k -= gap) num[k] = num[k - gap]; num[j] = tmp; break; } } } /*希尔排序*/ void ShellSort(long long int n) { long long int gap = n / 2; while (gap) { for (long long int i = 0; i < n; i++) insert(n, gap, i); gap /= 2; } } /*对非叶子结点和它的孩子结点进行调整,用于堆排序*/ void NodeSort(long long int node) { long long int left = node * 2 + 1, right = left + 1; if (num[left] > num[right]) swap(left, right); if (num[node] > num[left]) swap(node, left); } /*堆排序*/ void HeapSort(long long int len) { for (long long int i = len; i > 0; i -= 2) NodeSort(i / 2 - (1 - i % 2)); } int main() { long long int n = 0; scanf("%lld", &n); CreateArray(n); Copy(); PrintArray("原数组: ", n); Copy(); BubbleSort(n); PrintArray("冒泡排序: ", n); Copy(); SelectionSort(n); PrintArray("选择排序: ", n); Copy(); InsertSort(n); PrintArray("插入排序: ", n); Copy(); QuickSort(n); PrintArray("快速排序: ", n); Copy(); MergeSort(n); PrintArray("归并排序: ", n); Copy(); CountSort(n); PrintArray("计数排序: ", n); Copy(); ShellSort(n); PrintArray("希尔排序: ", n); Copy(); RadixSort(n); PrintArray("基数排序: ", n); Copy(); ShellSort(n); PrintArray("堆排序: ", n); Copy(); printf("\n桶排序: 无\n"); system("pause"); return 0; }