排序算法性能对比
【注】
内排序指待排序的数据存放在计算机内存中进行的排序过程;(计算机内存可以一次性全部加载待排序数据)
外排序指排序要对外存储器进行访问的排序过程;(数据过多,内存无法一次性直接排序,放外存分部加载)
选择排序:直接选择排序和堆排序;
插入排序:直接插入排序和希尔排序;
交换排序:冒泡排序和快速排序;
为什么要分稳定排序 和 非稳定排序?
答:当有两个排序关键字的时候,稳定排序可以让第一个关键字排序的结果服务于第二个关键字排序中数值相等的那些数。经典例子就是期中期末考试成绩排序,先有期中的排序,当要实现期末的排序(排序的规则为:从高到低,若成绩相等按期中成绩的排序来排),只需在期中的基础上进行稳定排序,足以保证在期末成绩相等的情况下期中(原来在前的)高的在前面;
直接选择排序
原理:
假设当前元素是最小的(排第一),遍历剩下的所有元素,去寻找更小的,
若找到则将其与之前假设的最小的元素互换。
另一种说法为:先令首元素最小,再剩下的挑比首元素更小的,找的则互换位置。
性能:
时间复杂度为O(n^2),空间复杂度为O(1)的不稳定排序
代码:
void straight_select_sort(vector<int> &array){
for(int i = 0; i < array.size()-1; i++)
{
// 假设当前下标元素为最小数
int min_index = i;
// 在剩下的元素中寻找更小的
for(int j = i + 1; j < array.size(); j++){
if(array[j] < array[min_index] )
min_index = j;
}
// 找到更小的元素,则交换找到更小的数和假设最小数的位置
if(min_index != i){
int tmp = array[min_index];
array[min_index] = array[i];
array[i] = tmp;
}
}
}
直接插入排序
原理:
每插入一个数据都将该数据与之前已经排序好的数据进行重新排序,找到当前数据的合适位置。
性能:
时间复杂度为O(n^2),空间复杂度为O(1)的稳定排序;
代码:
void straight_insert_sort(vector<int> &array){
int current_num;
// 假设第一个数已排序好,故i从1起
for(int i = 1; i < array.size(); i++){
current_num = array[i];
// 将比当前数据大的都向后移
while(i >= 1 && array[i-1] > current_num ){
array[i] = array[i-1];
i--; // 从后往前进行
}
// 将当前数据插入到合适的位置
array[i] = current_num;
}
}
冒泡排序
思想:
两两比较相邻记录的关键字,如果反序则交换,直到没有反序的记录为止。最多比较次数:n(n-1)/2次
也就是说每一趟均会让最大的数据放在合适的位置(假设是从小到大排序);
性能:
时间复杂度为O(n^2),空间复杂度为O(1)的不稳定排序;
代码:
void bubble_sort(int para_array[],int length){
int i,j;
for(i = 0; i < length-1; i++){
for(j = 0; j < length-1 -i; j++){// 减去i使得性能会所略有提升
if(para_array[j] >= para_array[j+1]){//从小到大排序
int tmp = para_array[j];
para_array[j] = para_array[j+1];
para_array[j+1] = tmp;
}
}
}
}
冒泡排序 优化1
void func_1(int arr[], int length){
if(NULL == arr || 0 == length)
return ;
int i = 0, j = 0, tmp = 0;
// 引入已经排序的标志位,,避免整个序列已经有序的情况下进行无意义的循环判断
unsigned int unorder_flag = true;
for(i = 0; i < length; i++){
unorder_flag = false;
for(j = 0; j < length - 1 - i; j++){
// if(arr[j] > arr[j + 1]){ // 升序
if(arr[j] < arr[j + 1]){ // 降序
tmp = arr[j ];
arr[j] = arr[j + 1];
arr[j + 1] = tmp;
unorder_flag = true;
}
}
if(false == unorder_flag) // 内层遍历,不改变标志位则说明整体已经有序
return;
}
}
冒泡排序 优化2
void func_2(int arr[], int length){
if(NULL == arr || 0 == length)
return ;
int i = 0, j = 0, tmp = 0;
// 引入已经排序的标志位,,避免整个序列已经有序的情况下进行无意义的循环判断
unsigned int unorder_flag = true;
int tmp_pos = 0; // 用于记录交换的位置
int in_len = length - 1;
for(i = 0; i < length - 1; i++){
unorder_flag = false;
for(j = 0; j < in_len; j++){
// if(arr[j] > arr[j + 1]){ // 升序
if(arr[j] < arr[j + 1]){ // 降序
tmp = arr[j ];
arr[j] = arr[j + 1];
arr[j + 1] = tmp;
unorder_flag = true;
tmp_pos = j; // 记录交换的位置
}
}
in_len = tmp_pos; // 把最后一次交换的位置给length,来缩减内循环的次数
if(false == unorder_flag) // 内层遍历,不改变标志位则说明整体已经有序
return ;
}
}
快速排序
基本思想是:
通过一趟排序将待排记录分割成独立的两部分,其中一部分记录的关键字均比另一部分记录的关键字小
(前一部分均比关键字小,后一部分均比关键字大),则可分别对这两部分记录继续进行排序,以达到整个
序列有序的目的。也就是说会找一个基准,每一趟均会将把小的和大的分在基准的左右;
性能:
时间复杂度为O(n*log n),空间复杂度为O(log n)的不稳定排序;
代码:
int v_parttation(vector<int> &input, int begin, int end)
{
int low=begin;
int high=end;
int pivot=input[low]; // 选定基准为第一个元素(相当于将input[low]做了一个备份)
while(low < high){
// 对后半部分处理
while(low < high && pivot <= input[high]){high--;}
// 一旦找到小于基准pivot的数,则交换到pivot前面去(input[low]被input[high]覆盖了)
input[low] = input[high];
// 对前半部分处理
while(low < high && pivot >= input[low]){low++;}
// 一旦找到大于基准pivot的数,则交换到pivot后面去(input[high]被input[low]覆盖了)
input[high] = input[low];
}
input[low] = pivot; // 再将input[low]恢复
return low;
}
void v_quick_sort(vector<int> &array, int low, int high)
{
if(low < high){
int base = v_parttation(array, low, high);
v_quick_sort(array, low, base-1);
v_quick_sort(array, base+1, high);
}
}
【注】:当数组已经有序而要将其倒序时为最差情况,此时快速排序的时间复杂度会变成O(n^2) ;
归并排序
归并排序是采用 分治法 的一个典型应用;
核心思想:是利用递归与分治的技术将数据序列划分为越来越小的半子表,再对半子表排序,最后再用递归方
法将排好序的半子表合并成越来越大的有序序列。需要一个与原数组相同长度的数组做辅助。因其空间复杂度
O(n)。。
性能:
时间复杂度:O(n*log n),空间复杂度:O( n )的不稳定排序;
代码:
void merge_sub_arr(int arr[], int start, int mid, int end){
int helper_len = end - start + 1; // 本次的辅助数组长度
int* helper_arr = (int*)malloc(helper_len * sizeof(int)); // 申请辅助数组
int prev_index = start; // 前半段的开始下标
int back_index = mid + 1; // 后半段的开始下标
int helper_index = 0; // 辅助数组的开始下标
// 将 满足排序 条件的放入 helper_arr 中,,也就是说
// 把排序好的存在 辅助数组中,然后将辅助数组去替换 原有数组中的元素
while((prev_index <= mid) && (back_index <= end)){
helper_arr[helper_index++] = (arr[prev_index] <= arr[back_index]) ?
arr[prev_index++] : arr[back_index++];
// if(arr[prev_index] <= arr[back_index]){
// helper_arr[helper_index++] = arr[prev_index++];
// }else{
// helper_arr[helper_index++] = arr[back_index++];
// }
}
// 将前半段或是后半段中 剩下的元素复制到辅助数组中
for(; prev_index <= mid; prev_index++){
helper_arr[helper_index++] = arr[prev_index];
}
for(; back_index <= end; back_index++){
helper_arr[helper_index++] = arr[back_index];
}
// 将合并后的元素(辅助数组中的值)复制到原数组中
int i;
for(i = 0; i < helper_len; i++){
arr[i + start] = helper_arr[i];
}
free(helper_arr);
}
void merge_sort(int arr[], int low, int high){
if(low < high){
int mid = (low + high) / 2;
merge_sort(arr, low, mid); // 将前半段分割成更小的
merge_sort(arr, mid + 1, high); // 将后半个分割成更小的
merge_sub_arr(arr, low, mid, high); // 将所有的小数组全部合并起来
}
}
堆排序
原理:
对简单选择排序的改进,,利用 堆 这种数据结构设计的排序算法,也可以说堆排序说是一种利用堆的概念
来排序的选择排序。
也就是说每一趟定能选择一个最小的或是最大的数放在根节点处;
性能:
时间复杂度:O(n*log n),空间复杂度:O( 1 )的不稳定排序;
代码:
注: algorithm 头文件中存在写好的 堆排序 sort_heap(v.begin, v.end() )
待补充.....
希尔(shell)插入排序
算法思想:
https://baijiahao.baidu.com/s?id=1631531130010150845&wfr=spider&for=pc
它是直接 插入排序的 优化。核心思想是:待排序列有n个元素,先取一个小于n的整数h1作为第一个增量,
把待排序列以间隔h1分成若干子序列,子序列内使用插入排序;然后取第二个增量h2(< h1),重复上述的划
分和排序,直至所取的增量hl = 1 (h1 > h2 > ... > hl)。
性能:
时间复杂度:O(n*log n),空间复杂度:O( 1 )的不稳定排序;
代码:
待补充...
基数排序
基数排序不需要进行记录关键字间的比较,是一种借助于多关键字排序的思想对单逻辑关键字进行排序的方法。
元素的移动次数与关键字的初始排列次序无关。
未完...